@@ -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