@@ -85,6 +85,53 @@ final class AudioMixerTests: XCTestCase {
8585 XCTAssertTrue ( samples. allSatisfy { $0 == 0.0 } )
8686 }
8787
88+ func testMuteMaskLargeMicDelayDoesNotCrash( ) {
89+ // micDelay larger than timeline timestamps → range.end < range.start
90+ var samples = [ Float] ( repeating: 1.0 , count: 100 )
91+ let timeline = [
92+ MuteTransition ( timestamp: 10.0 , isMuted: true ) ,
93+ MuteTransition ( timestamp: 10.5 , isMuted: false ) ,
94+ ]
95+
96+ AudioMixer . applyMuteMask (
97+ samples: & samples,
98+ timeline: timeline,
99+ sampleRate: 100 ,
100+ micDelay: 20.0 ,
101+ recordingStart: 10.0
102+ )
103+
104+ // All samples should remain untouched (mute range is entirely negative)
105+ XCTAssertTrue ( samples. allSatisfy { $0 == 1.0 } )
106+ }
107+
108+ func testMuteMaskPartiallyNegativeRange( ) {
109+ // Mute starts before recording but ends during it
110+ var samples = [ Float] ( repeating: 1.0 , count: 100 )
111+ let timeline = [
112+ MuteTransition ( timestamp: 5.0 , isMuted: true ) ,
113+ MuteTransition ( timestamp: 5.8 , isMuted: false ) ,
114+ ]
115+
116+ AudioMixer . applyMuteMask (
117+ samples: & samples,
118+ timeline: timeline,
119+ sampleRate: 100 ,
120+ micDelay: 0.5 ,
121+ recordingStart: 5.0
122+ )
123+
124+ // Mute range: (5.0 - 5.0 - 0.5) to (5.8 - 5.0 - 0.5) = -0.5s to 0.3s
125+ // Clamped start to 0; end ≈ sample 29-30 (floating point)
126+ // Verify most of the range is muted and tail is untouched
127+ for i in 0 ..< 29 {
128+ XCTAssertEqual ( samples [ i] , 0.0 , " Sample \( i) should be muted " )
129+ }
130+ for i in 31 ..< 100 {
131+ XCTAssertEqual ( samples [ i] , 1.0 , " Sample \( i) should be untouched " )
132+ }
133+ }
134+
88135 func testMuteMaskEmptyTimeline( ) {
89136 var samples : [ Float ] = [ 1.0 , 2.0 , 3.0 ]
90137 AudioMixer . applyMuteMask (
0 commit comments