Skip to content

Commit 42d6dac

Browse files
committed
Improve sequencer tests
Fixed the looping issue with unfinished notes by checking in each render call to see if events happen outside the loop window and scheduling them to the end of frames
1 parent c33dac0 commit 42d6dac

3 files changed

Lines changed: 32 additions & 23 deletions

File tree

Sources/CAudioKitEX/Sequencing/SequencerEngine.mm

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,6 @@ void process(AUAudioFrameCount frameCount) {
174174
for (auto& event : events) {
175175
// go through every event
176176
int triggerTime = beatToSamples(event.beat);
177-
178177
if (currentEndSample > lengthInSamples() && data->settings.loopEnabled) {
179178
// this buffer extends beyond the length of the loop and looping is on
180179
int loopRestartInBuffer = (int)(lengthInSamples() - currentStartSample);
@@ -188,14 +187,18 @@ void process(AUAudioFrameCount frameCount) {
188187
offset, event.beat);
189188
}
190189
} else if (currentStartSample == 0 && triggerTime == lengthInSamples() && data->settings.loopEnabled) {
191-
// this event handles the case of skipped last note
190+
// this event handles the case of skipped last note
192191
sendMidiData(event.status, event.data1, event.data2,
193192
0, event.beat);
194193
} else if (currentStartSample <= triggerTime && triggerTime < currentEndSample) {
195194
// this event is supposed to trigger between currentStartSample and currentEndSample
196195
int offset = (int)(triggerTime - currentStartSample);
197196
sendMidiData(event.status, event.data1, event.data2,
198197
offset, event.beat);
198+
} else if (currentEndSample >= lengthInSamples() && triggerTime > lengthInSamples() && data->settings.loopEnabled) {
199+
// event is happens outside loop window, schedule it at the end of frames
200+
sendMidiData(event.status, event.data1, event.data2,
201+
frameCount, event.beat);
199202
}
200203

201204
}

Tests/AudioKitEXTests/SequenceTests.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ class NoteEventSequenceTests: XCTestCase {
2525
newNote.noteOff.data2 = 127
2626
newNote.noteOff.beat = 2.0
2727

28-
XCTAssertEqual(seq, NoteEventSequence(notes: [newNote], events: [], totalDuration: 1.0))
28+
XCTAssertEqual(seq, NoteEventSequence(notes: [newNote], events: [], totalDuration: 2.0))
29+
// Even though note duration is 1.0, there is space at beginning of track since note position also 1.0.
30+
// Total duration should be 2.0
2931
}
3032

3133
func testRemoveNote() {

Tests/AudioKitEXTests/SequencerEngineTests.swift

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ class SequencerEngineTests: XCTestCase {
226226
}
227227

228228
// events that start late in the loop are stopped after the engine is destroyed
229+
// Or at the end of the loop before new note events occur
229230
func testShortNotesAcrossLoop() {
230231

231232
var seq = NoteEventSequence()
@@ -239,26 +240,29 @@ class SequencerEngineTests: XCTestCase {
239240

240241
/// 6 render calls at 120bpm, 44100 buffersize is 12 beats, default loop is 4 beats
241242
let events = observerTest(sequence: seq, renderCallCount: 6)
242-
XCTAssertEqual(events.count, 30)
243-
244-
XCTAssertEqual(events.map { $0.noteNumber! }, [60, 62, 65, 60, 62, 65,
245-
60, 64, 67, 60, 62, 65, 60, 62, 65,
246-
60, 64, 67, 60, 62, 65, 60, 62, 65,
247-
60, 64, 67,
248-
67, 64, 60]) // engine destroyed
249-
250-
XCTAssertEqual(events.compactMap { $0.status!.type }, [.noteOn, .noteOn, .noteOn, .noteOff, .noteOff, .noteOff,
251-
.noteOn, .noteOn, .noteOn, .noteOn, .noteOn, .noteOn,
252-
.noteOff, .noteOff, .noteOff, .noteOn, .noteOn, .noteOn,
253-
.noteOn, .noteOn, .noteOn, .noteOff, .noteOff, .noteOff,
254-
.noteOn, .noteOn, .noteOn,
255-
.noteOff, .noteOff, .noteOff]) // engine destroyed
256-
XCTAssertEqual(events.map { $0.timeStamp }, [0, 0, 0, 0, 0, 0,
257-
43658, 43658, 43658, 0, 0, 0,
258-
0, 0, 0, 43658, 43658, 43658,
259-
0, 0, 0, 0, 0, 0,
260-
43658, 43658, 43658,
261-
1, 1, 1]) // engine destroyed
243+
XCTAssertEqual(events.count, 36)
244+
245+
XCTAssertEqual(events.map { $0.noteNumber! }, [60, 62, 65, 60, 62, 65, // First 3 notes on and off
246+
60, 64, 67, 60, 64, 67, // Second 3 notes on and off
247+
60, 62, 65, 60, 62, 65, // Loop #2 first 3 notes on/off
248+
60, 64, 67, 60, 64, 67, // Loop #2 second 3 on/off
249+
60, 62, 65, 60, 62, 65, // Loop #3 first 3 on/off
250+
60, 64, 67, 60, 64, 67]) // Loop #3 second 3 on/off
251+
// Engine cleans up remaining active note events, but there shouldn't be any since they're all handled before loop ends
252+
253+
XCTAssertEqual(events.compactMap { $0.status!.type }, [.noteOn, .noteOn, .noteOn, .noteOff, .noteOff, .noteOff, // First 3
254+
.noteOn, .noteOn, .noteOn, .noteOff, .noteOff, .noteOff, // Second 3
255+
.noteOn, .noteOn, .noteOn, .noteOff, .noteOff, .noteOff, // First 3
256+
.noteOn, .noteOn, .noteOn, .noteOff, .noteOff, .noteOff, // Second 3
257+
.noteOn, .noteOn, .noteOn, .noteOff, .noteOff, .noteOff, // First 3
258+
.noteOn, .noteOn, .noteOn, .noteOff, .noteOff, .noteOff]) // Second 3
259+
// Engine cleans up remaining active note events, but there shouldn't be any since they're all handled before loop ends
260+
XCTAssertEqual(events.map { $0.timeStamp }, [0, 0, 0, 0, 0, 0, // First render call
261+
43658, 43658, 43658, 44100, 44100, 44100, // Second
262+
0, 0, 0, 0, 0, 0, // Third
263+
43658, 43658, 43658, 44100, 44100, 44100, // Fourth
264+
0, 0, 0, 0, 0, 0, // Fifth
265+
43658, 43658, 43658, 44100, 44100, 44100,]) // Sixth
262266
}
263267
}
264268
#endif

0 commit comments

Comments
 (0)