Skip to content

Commit 727ac5a

Browse files
committed
Fix 1x clip mute rendering across segments
1 parent 27d588d commit 727ac5a

1 file changed

Lines changed: 149 additions & 18 deletions

File tree

crates/editor/src/audio.rs

Lines changed: 149 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -281,21 +281,19 @@ impl AudioRenderer {
281281
samples: self.playhead_to_samples(cursor.segment_time),
282282
};
283283

284+
self.render_segment_chunk(
285+
project,
286+
TimelineSource {
287+
source_time: cursor.segment_time,
288+
segment_index: cursor.segment_index,
289+
segment: cursor.segment,
290+
},
291+
chunk_samples,
292+
written * 2,
293+
&mut ret,
294+
);
284295
if cursor.segment.timescale == 1.0 {
285-
self.render_current_chunk(project, chunk_samples, written * 2, &mut ret);
286296
self.cursor.samples += chunk_samples;
287-
} else {
288-
self.render_speed_audio_chunk(
289-
project,
290-
TimelineSource {
291-
source_time: cursor.segment_time,
292-
segment_index: cursor.segment_index,
293-
segment: cursor.segment,
294-
},
295-
chunk_samples,
296-
written * 2,
297-
&mut ret,
298-
);
299297
}
300298

301299
self.elapsed_samples += chunk_samples;
@@ -2134,14 +2132,109 @@ mod tests {
21342132
#[test]
21352133
fn one_x_audio_bypasses_speed_processing() {
21362134
let (_dir, mut renderer, mut project) = build_renderer_fixture();
2137-
project.timeline.as_mut().unwrap().segments[0].speed_audio_mode =
2138-
Some(ClipSpeedAudioMode::MaintainPitch);
2135+
for mode in [
2136+
None,
2137+
Some(ClipSpeedAudioMode::MaintainPitch),
2138+
Some(ClipSpeedAudioMode::MatchSpeed),
2139+
] {
2140+
project.timeline.as_mut().unwrap().segments[0].speed_audio_mode = mode;
2141+
renderer.set_playhead(0.0, &project);
2142+
let (_, samples) = renderer.render_frame_raw(4_800, &project).unwrap();
21392143

2140-
renderer.set_playhead(0.0, &project);
2141-
let (_, samples) = renderer.render_frame_raw(4_800, &project).unwrap();
2144+
assert!(mean_abs(&samples) > 0.01);
2145+
assert!(renderer.speed_audio_processors.iter().all(Option::is_none));
2146+
}
2147+
}
2148+
2149+
#[test]
2150+
fn one_x_split_clip_mute_is_local_in_playback_and_export() {
2151+
let (_dir, mut renderer, mut project) = single_clip_fixture(
2152+
&[4000, 8000, 12000],
2153+
vec![
2154+
segment(0, 0.0, 1.0, 1.0),
2155+
segment(0, 1.0, 2.0, 1.0),
2156+
segment(0, 2.0, 3.0, 1.0),
2157+
],
2158+
);
2159+
project.timeline.as_mut().unwrap().segments[1].speed_audio_mode =
2160+
Some(ClipSpeedAudioMode::Mute);
21422161

2143-
assert!(mean_abs(&samples) > 0.01);
2162+
let samples_per_second = AudioData::SAMPLE_RATE as usize * 2;
2163+
let export_stream = render_export_audio(&mut renderer, &project, 30, 90);
2164+
assert_eq!(export_stream.len(), samples_per_second * 3);
2165+
assert!((left_at_second(&export_stream, 0) - expected(4000)).abs() < 0.001);
2166+
assert!(
2167+
export_stream[samples_per_second..samples_per_second * 2]
2168+
.iter()
2169+
.all(|sample| *sample == 0.0)
2170+
);
2171+
assert!((left_at_second(&export_stream, 2) - expected(12000)).abs() < 0.001);
21442172
assert!(renderer.speed_audio_processors.iter().all(Option::is_none));
2173+
2174+
for duration_secs in [3.0, 3600.0] {
2175+
let mut playback = PrerenderedAudioBuffer::<f32>::new(
2176+
renderer.data.clone(),
2177+
MusicTracks::new(),
2178+
&project,
2179+
AudioRenderer::info(),
2180+
duration_secs,
2181+
0.0,
2182+
);
2183+
playback.wait_until_fully_rendered();
2184+
let mut playback_stream = vec![0.0; export_stream.len()];
2185+
for block in playback_stream.chunks_mut(1024) {
2186+
playback.fill(block);
2187+
}
2188+
for (index, (playback_sample, export_sample)) in
2189+
playback_stream.iter().zip(&export_stream).enumerate()
2190+
{
2191+
assert!(
2192+
(playback_sample - export_sample).abs() < 0.000_001,
2193+
"duration {duration_secs}, sample {index}: playback {playback_sample}, export {export_sample}"
2194+
);
2195+
}
2196+
2197+
playback.set_playhead(1.5);
2198+
let mut seek_samples = [1.0; 1024];
2199+
playback.fill(&mut seek_samples);
2200+
assert!(seek_samples.iter().all(|sample| *sample == 0.0));
2201+
}
2202+
2203+
project.timeline.as_mut().unwrap().segments[1].speed_audio_mode = None;
2204+
renderer.set_playhead(1.5, &project);
2205+
let (_, samples) = renderer.render_frame_raw(1024, &project).unwrap();
2206+
assert!((samples[0] - expected(8000)).abs() < 0.001);
2207+
}
2208+
2209+
#[test]
2210+
fn one_x_clip_mute_cuts_and_resumes_inside_a_single_request() {
2211+
let (_dir, mut renderer, mut project) = single_clip_fixture(
2212+
&[4000, 8000, 12000],
2213+
vec![
2214+
segment(0, 0.0, 1.0, 1.0),
2215+
segment(0, 1.0, 2.0, 1.0),
2216+
segment(0, 2.0, 3.0, 1.0),
2217+
],
2218+
);
2219+
project.timeline.as_mut().unwrap().segments[1].speed_audio_mode =
2220+
Some(ClipSpeedAudioMode::Mute);
2221+
2222+
for (playhead, before, after) in [(0.99, 4000, 0), (1.99, 0, 12000)] {
2223+
renderer.set_playhead(playhead, &project);
2224+
let (written, samples) = renderer.render_frame_raw(1920, &project).unwrap();
2225+
assert_eq!(written, 1920);
2226+
let boundary = (0.01 * AudioData::SAMPLE_RATE as f64).round() as usize * 2;
2227+
assert!(
2228+
samples[..boundary]
2229+
.iter()
2230+
.all(|sample| (*sample - expected(before)).abs() < 0.001)
2231+
);
2232+
assert!(
2233+
samples[boundary..]
2234+
.iter()
2235+
.all(|sample| (*sample - expected(after)).abs() < 0.001)
2236+
);
2237+
}
21452238
}
21462239

21472240
/// One clip per second `section_values`, on a timeline made of `segments`.
@@ -2321,6 +2414,22 @@ mod tests {
23212414
assert!((midpoint - expected_midpoint).abs() < 0.01);
23222415
}
23232416

2417+
#[test]
2418+
fn one_x_clip_mute_preserves_the_other_side_of_a_transition() {
2419+
for muted_index in [0, 1] {
2420+
let (_dir, mut renderer, mut project) =
2421+
transition_fixture(ClipTransitionType::CrossFade);
2422+
project.timeline.as_mut().unwrap().segments[muted_index].speed_audio_mode =
2423+
Some(ClipSpeedAudioMode::Mute);
2424+
let stream = render_export_audio(&mut renderer, &project, 30, 45);
2425+
let audible_value = if muted_index == 0 { 16000 } else { 8000 };
2426+
let midpoint = expected(audible_value) * std::f32::consts::FRAC_1_SQRT_2;
2427+
assert!((left_at_time(&stream, 0.75) - midpoint).abs() < 0.001);
2428+
let muted_time = if muted_index == 0 { 0.25 } else { 1.25 };
2429+
assert_eq!(left_at_time(&stream, muted_time), 0.0);
2430+
}
2431+
}
2432+
23242433
#[test]
23252434
fn fade_through_black_audio_reaches_silence_at_midpoint() {
23262435
let (_dir, mut renderer, project) =
@@ -2625,6 +2734,28 @@ mod tests {
26252734
assert!((left_at_time(&stream, 0.5) - expected(8000)).abs() < 0.02);
26262735
}
26272736

2737+
#[test]
2738+
fn one_x_clip_mute_keeps_timeline_music_audible() {
2739+
let (dir, mut renderer, mut project) =
2740+
single_clip_fixture(&[16000], vec![segment(0, 0.0, 1.0, 1.0)]);
2741+
let music_path = dir.path().join("music.wav");
2742+
write_step_wav(&music_path, &[4000]);
2743+
let mut music = MusicTracks::new();
2744+
music.insert(
2745+
"music.wav".to_string(),
2746+
Arc::new(AudioData::from_file(&music_path).unwrap()),
2747+
);
2748+
renderer = renderer.with_music(music);
2749+
let timeline = project.timeline.as_mut().unwrap();
2750+
timeline.segments[0].speed_audio_mode = Some(ClipSpeedAudioMode::Mute);
2751+
timeline
2752+
.audio_segments
2753+
.push(music_track_segment("music.wav", 0.0, 1.0, 0.0, 0.0));
2754+
2755+
let stream = render_export_audio(&mut renderer, &project, 30, 30);
2756+
assert!((left_at_time(&stream, 0.5) - expected(4000)).abs() < 0.001);
2757+
}
2758+
26282759
// A timeline-positioned music clip only sounds inside its [start, end) window.
26292760
#[test]
26302761
fn timeline_music_respects_start_offset() {

0 commit comments

Comments
 (0)