diff --git a/wslib-classes/Main Features/SimpleMIDIFile/TestSimpleMidiFile.sc b/wslib-classes/Main Features/SimpleMIDIFile/TestSimpleMidiFile.sc new file mode 100644 index 0000000..7d3c112 --- /dev/null +++ b/wslib-classes/Main Features/SimpleMIDIFile/TestSimpleMidiFile.sc @@ -0,0 +1,96 @@ +TestSimpleMIDIFile : UnitTest { + *getFixturesPath { + ^( + this.class.filenameSymbol.asString.dirname + +/+ "test_fixtures" + ); + } + + test_generatePatternSeqs_withVelocity { + var m, pat; + m = SimpleMIDIFile.new( + this.class.getFixturesPath() +/+ "two-notes-w-velocity.mid" + ); + + m.read(); + + pat = m.generatePatternSeqs(true)[0]; + + this.assertEquals(pat, [ + [60, 1.0, 75/127.0], + [\rest, 1.0, 0.0], + [60, 1.0, 100/127.0] + ]); + + } + + test_generatePatternSeqs_noPaddingBeginningStart { + var m, pat; + m = SimpleMIDIFile.new( + this.class.getFixturesPath() +/+ "two-notes-beginning-start.mid" + ); + + m.read(); + + pat = m.generatePatternSeqs()[0]; + + this.assertEquals(pat, [ + [60, 1.0], + [\rest, 1.0], + [60, 1.0] + ]); + + } + + test_generatePatternSeqs_noPaddingOffsetStart { + var m, pat; + m = SimpleMIDIFile.new( + this.class.getFixturesPath() +/+ "two-notes-offset-start.mid" + ); + + m.read(); + + pat = m.generatePatternSeqs()[0]; + + this.assertEquals(pat, [ + [60, 1.0], + [\rest, 1.0], + [60, 1.0] + ]); + } + test_generatePatternSeqs_padStart { + var m, pat; + m = SimpleMIDIFile.new( + this.class.getFixturesPath() +/+ "two-notes-offset-start.mid" + ); + + m.read(); + + pat = m.generatePatternSeqs(false, true)[0]; + + this.assertEquals(pat, [ + [\rest, 1.0], + [60, 1.0], + [\rest, 1.0], + [60, 1.0] + ]); + } + test_generatePatternSeqs_padEnd { + var m, pat; + m = SimpleMIDIFile.new( + this.class.getFixturesPath() +/+ "two-notes-beginning-start.mid" + ); + + m.read(); + + pat = m.generatePatternSeqs(false, true, 4.0)[0]; + + this.assertEquals(pat, [ + [60, 1.0], + [\rest, 1.0], + [60, 1.0], + [\rest, 1.0] + ]); + + } +} diff --git a/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc b/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc index 8c3a953..e9d46e0 100644 --- a/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc +++ b/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc @@ -149,8 +149,35 @@ Pbind( [\midinote, \dur], Pseq(t[1], 1)).play; Note that the first track in a SimpleMIDIFile often contains no note events if imported from an external midi file (since it's used for metadata), so that the first track of interest is usually the one in index 1 of the getSeqs array.Ê I decided to leave the first blank track in so preserve the mapping from midi track # to getSeqs array #. +Parameters: + + * withVelocity: Instead of a 2-element tuple (as above), returns a 3-element with a velocity value [0.0 - 1.0]. Velocity is 0.0 for rests. + * padStart: If the MIDI file starts with a gap, inserts a rest into the pattern filling the gap from the start of the file until the first note. + * totalDurationForPadEnd: In order to create perfect looping patterns, a duration can be specified here. If there is a gap between the end of the last note event and this duration, a rest will be inserted at the end of the pattern to ensure the pattern is this duration in total. + */ - var trackSeqs; + arg withVelocity = false, padStart = false, totalDurationForPadEnd = false; + var trackSeqs, durationSum, addTrackEventToSeq, addRestEventToSeq; + + // Helper method to add a note to the seq. + addTrackEventToSeq = { + arg seq, event; + if (withVelocity, { + seq.add([event.note, event.dur, event.vel]); + }, { + seq.add([event.note, event.dur]); + }); + }; + + // Helper method to add a rest event to the seq. + addRestEventToSeq = { + arg seq, dur; + if (withVelocity, { + seq.add([\rest, dur, 0.0]); + }, { + seq.add([\rest, dur]); + }); + }; this.timeMode_('ticks'); trackSeqs = Array.fill(tracks, {List.new(0)}); @@ -161,13 +188,14 @@ Note that the first track in a SimpleMIDIFile often contains no note events if i }); trackSeqs = trackSeqs.collect({|track| - var trackEvents, seq; + var trackEvents, seq, seqAsDur; seq = List.new(0); trackEvents = track.clump(2).collect({|pair| ( 'dur': pair[1][1] - pair[0][1], 'note': pair[0][4], + 'vel': pair[0][5] / 127.0, 'startPos': pair[0][1], 'endPos': pair[1][1] ) @@ -177,23 +205,49 @@ Note that the first track in a SimpleMIDIFile often contains no note events if i var diff; if (i==0,Ê { - seq.add([event.note, event.dur]); + // If first note in MIDI file is not at beginning of file, add a + // rest at the beginning of the pattern to fill the empty space. + if (padStart.and(event.startPos != 0), { + addRestEventToSeq.value(seq, event.startPos); + }); + addTrackEventToSeq.value(seq, event); }, { diff = event.startPos - trackEvents[i-1].endPos; if (diff > 0, { - seq.add([\rest, diff]); - seq.add([event.note, event.dur]); + addRestEventToSeq.value(seq, diff); + addTrackEventToSeq.value(seq, event); }, { - seq.add([event.note, event.dur]); + addTrackEventToSeq.value(seq, event); } ) } ); }); - seq.collect({|pair| [pair[0], pair[1] / division]}); + if (withVelocity, { + seqAsDur = seq.collect({|e| [e[0], e[1] / division, e[2]]}); + }, { + seqAsDur = seq.collect({|pair| [pair[0], pair[1] / division]}); + }); + + // Appends a rest at the end of the notes list if `totalDurationForPadEnd` + // is set. + if (totalDurationForPadEnd != false, { + // Sums all durations + durationSum = 0; + seqAsDur.do({ + arg midiEvent; + durationSum = durationSum + midiEvent[1]; + }); + // Adds rest to fill remaining time + if (totalDurationForPadEnd > durationSum, { + addRestEventToSeq.value(seqAsDur, totalDurationForPadEnd - durationSum); + }); + }); + + seqAsDur; }); ^trackSeqs; @@ -282,4 +336,4 @@ Example: } - } \ No newline at end of file + } diff --git a/wslib-classes/Main Features/SimpleMIDIFile/test_fixtures/two-notes-beginning-start.mid b/wslib-classes/Main Features/SimpleMIDIFile/test_fixtures/two-notes-beginning-start.mid new file mode 100644 index 0000000..1e1f2f9 Binary files /dev/null and b/wslib-classes/Main Features/SimpleMIDIFile/test_fixtures/two-notes-beginning-start.mid differ diff --git a/wslib-classes/Main Features/SimpleMIDIFile/test_fixtures/two-notes-offset-start.mid b/wslib-classes/Main Features/SimpleMIDIFile/test_fixtures/two-notes-offset-start.mid new file mode 100644 index 0000000..31de1c8 Binary files /dev/null and b/wslib-classes/Main Features/SimpleMIDIFile/test_fixtures/two-notes-offset-start.mid differ diff --git a/wslib-classes/Main Features/SimpleMIDIFile/test_fixtures/two-notes-w-velocity.mid b/wslib-classes/Main Features/SimpleMIDIFile/test_fixtures/two-notes-w-velocity.mid new file mode 100644 index 0000000..847603d Binary files /dev/null and b/wslib-classes/Main Features/SimpleMIDIFile/test_fixtures/two-notes-w-velocity.mid differ