From 72191159c537a5802aead36316297c8df434e7c1 Mon Sep 17 00:00:00 2001 From: Colin Sullivan Date: Fri, 11 Sep 2020 12:05:42 -0700 Subject: [PATCH 1/7] Adds rest at beginning of pattern. --- .../SimpleMIDIFile/extSimpleMIDIFile-patterns.sc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc b/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc index 8c3a953..98a0a31 100644 --- a/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc +++ b/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc @@ -177,6 +177,11 @@ Note that the first track in a SimpleMIDIFile often contains no note events if i var diff; if (i==0,Ê { + // 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 (event.startPos != 0, { + seq.add([\rest, event.startPos]); + }); seq.add([event.note, event.dur]); }, { @@ -282,4 +287,4 @@ Example: } - } \ No newline at end of file + } From 0ed1169d8664f6d5d01db924e6730613a16a4b29 Mon Sep 17 00:00:00 2001 From: Colin Sullivan Date: Fri, 11 Sep 2020 12:50:30 -0700 Subject: [PATCH 2/7] Start of unit tests for beginning and end padding. --- .../SimpleMIDIFile/TestSimpleMidiFile.sc | 22 ++++++++++++++++++ .../extSimpleMIDIFile-patterns.sc | 3 ++- .../two-notes-beginning-start.mid | Bin 0 -> 88 bytes .../test_fixtures/two-notes-offset-start.mid | Bin 0 -> 85 bytes 4 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 wslib-classes/Main Features/SimpleMIDIFile/TestSimpleMidiFile.sc create mode 100644 wslib-classes/Main Features/SimpleMIDIFile/test_fixtures/two-notes-beginning-start.mid create mode 100644 wslib-classes/Main Features/SimpleMIDIFile/test_fixtures/two-notes-offset-start.mid diff --git a/wslib-classes/Main Features/SimpleMIDIFile/TestSimpleMidiFile.sc b/wslib-classes/Main Features/SimpleMIDIFile/TestSimpleMidiFile.sc new file mode 100644 index 0000000..9a429d5 --- /dev/null +++ b/wslib-classes/Main Features/SimpleMIDIFile/TestSimpleMidiFile.sc @@ -0,0 +1,22 @@ +TestSimpleMIDIFile : UnitTest { + *getFixturesPath { + ^( + this.class.filenameSymbol.asString.dirname + +/+ "test_fixtures" + ); + } + test_generatePatternSeqs_noPadding { + var m = SimpleMIDIFile.new( + this.class.getFixturesPath() +/+ "two-notes-beginning-start.mid" + ); + + var pat = m.generatePatternSeqs(); + + pat.postln(); + } + test_generatePatternSeqs_padStart { + var m = SimpleMIDIFile.new(); + + this.class.getFixturesPath().postln(); + } +} diff --git a/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc b/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc index 98a0a31..7d19603 100644 --- a/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc +++ b/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc @@ -150,6 +150,7 @@ 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 #. */ + arg padStart = false, totalDurationForPadEnd = false; var trackSeqs; this.timeMode_('ticks'); @@ -179,7 +180,7 @@ Note that the first track in a SimpleMIDIFile often contains no note events if i { // 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 (event.startPos != 0, { + if (padStart.and(event.startPos != 0), { seq.add([\rest, event.startPos]); }); seq.add([event.note, event.dur]); 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 0000000000000000000000000000000000000000..1e1f2f9b3b97d8723d8b897193473f1746f9dd4a GIT binary patch literal 88 zcmeYb$w*;fU|<7cMur66kfLlL%ZcGXvs6iWzHVNANouifQfhi;US4Kix^8hvVo?bL c!~X~t7A6%AI6c88C85E_ApuG={MTmy0P+nOS^xk5 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..31de1c84a26773e43892c807058171c1a0575d48 GIT binary patch literal 85 zcmeYb$w*;fU|<7cMur66kfLlL%bwvsvv^5)zHVNANouifep*^_YKd-fNn%k61H=Ca Z78WKI4j7# Date: Fri, 11 Sep 2020 13:19:57 -0700 Subject: [PATCH 3/7] Tests pass. --- .../SimpleMIDIFile/TestSimpleMidiFile.sc | 71 ++++++++++++++++--- .../extSimpleMIDIFile-patterns.sc | 23 +++++- 2 files changed, 83 insertions(+), 11 deletions(-) diff --git a/wslib-classes/Main Features/SimpleMIDIFile/TestSimpleMidiFile.sc b/wslib-classes/Main Features/SimpleMIDIFile/TestSimpleMidiFile.sc index 9a429d5..8f0921f 100644 --- a/wslib-classes/Main Features/SimpleMIDIFile/TestSimpleMidiFile.sc +++ b/wslib-classes/Main Features/SimpleMIDIFile/TestSimpleMidiFile.sc @@ -5,18 +5,73 @@ TestSimpleMIDIFile : UnitTest { +/+ "test_fixtures" ); } - test_generatePatternSeqs_noPadding { - var m = SimpleMIDIFile.new( + test_generatePatternSeqs_noPaddingBeginningStart { + var m, pat; + m = SimpleMIDIFile.new( this.class.getFixturesPath() +/+ "two-notes-beginning-start.mid" ); - var pat = m.generatePatternSeqs(); + m.read(); - pat.postln(); - } - test_generatePatternSeqs_padStart { - var m = SimpleMIDIFile.new(); + pat = m.generatePatternSeqs()[0]; - this.class.getFixturesPath().postln(); + 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(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(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 7d19603..2708ac8 100644 --- a/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc +++ b/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc @@ -151,7 +151,7 @@ Note that the first track in a SimpleMIDIFile often contains no note events if i */ arg padStart = false, totalDurationForPadEnd = false; - var trackSeqs; + var trackSeqs, durationSum; this.timeMode_('ticks'); trackSeqs = Array.fill(tracks, {List.new(0)}); @@ -162,7 +162,7 @@ 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| @@ -199,7 +199,24 @@ Note that the first track in a SimpleMIDIFile often contains no note events if i } ); }); - seq.collect({|pair| [pair[0], pair[1] / division]}); + 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, { + seqAsDur.add([\rest, totalDurationForPadEnd - durationSum]); + }); + }); + + seqAsDur; }); ^trackSeqs; From 0dd6d5e27bb1fbaceb270d9481b9a37c1f337044 Mon Sep 17 00:00:00 2001 From: Colin Sullivan Date: Fri, 11 Sep 2020 13:43:33 -0700 Subject: [PATCH 4/7] Added flag for parsing velocity too. --- .../SimpleMIDIFile/TestSimpleMidiFile.sc | 23 +++++- .../extSimpleMIDIFile-patterns.sc | 77 ++++++++++++------ .../test_fixtures/two-notes-w-velocity.mid | Bin 0 -> 83 bytes 3 files changed, 75 insertions(+), 25 deletions(-) create mode 100644 wslib-classes/Main Features/SimpleMIDIFile/test_fixtures/two-notes-w-velocity.mid diff --git a/wslib-classes/Main Features/SimpleMIDIFile/TestSimpleMidiFile.sc b/wslib-classes/Main Features/SimpleMIDIFile/TestSimpleMidiFile.sc index 8f0921f..7d3c112 100644 --- a/wslib-classes/Main Features/SimpleMIDIFile/TestSimpleMidiFile.sc +++ b/wslib-classes/Main Features/SimpleMIDIFile/TestSimpleMidiFile.sc @@ -5,6 +5,25 @@ TestSimpleMIDIFile : UnitTest { +/+ "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( @@ -47,7 +66,7 @@ TestSimpleMIDIFile : UnitTest { m.read(); - pat = m.generatePatternSeqs(true)[0]; + pat = m.generatePatternSeqs(false, true)[0]; this.assertEquals(pat, [ [\rest, 1.0], @@ -64,7 +83,7 @@ TestSimpleMIDIFile : UnitTest { m.read(); - pat = m.generatePatternSeqs(true, 4.0)[0]; + pat = m.generatePatternSeqs(false, true, 4.0)[0]; this.assertEquals(pat, [ [60, 1.0], diff --git a/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc b/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc index 2708ac8..4ef631a 100644 --- a/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc +++ b/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc @@ -135,6 +135,7 @@ // ------------------------------------------------------------ //// the following methods were kindly added by Jascha Narveson + generatePatternSeqs { /* @@ -149,9 +150,34 @@ 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. + */ - arg padStart = false, totalDurationForPadEnd = false; - var trackSeqs, durationSum; + 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)}); @@ -169,6 +195,7 @@ Note that the first track in a SimpleMIDIFile often contains no note events if i ( '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] ) @@ -181,42 +208,46 @@ Note that the first track in a SimpleMIDIFile often contains no note events if i // 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), { - seq.add([\rest, event.startPos]); + addRestEventToSeq.value(seq, event.startPos); }); - seq.add([event.note, event.dur]); + 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); } ) } ); }); - seqAsDur = 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, { - seqAsDur.add([\rest, totalDurationForPadEnd - durationSum]); - }); - }); + // 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; + seqAsDur; }); ^trackSeqs; 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 0000000000000000000000000000000000000000..847603de1a5a7776dd96b9318c89267d12975e44 GIT binary patch literal 83 zcmeYb$w*;fU|<7cMur66kfLlL%a-9kvuH_qzHVNANouifxo%l%PJVJ`NhJfr{|FWq aCKV1iJ;BC1p~1!>VS-Hxh-Ub&&j0{l*A_ki literal 0 HcmV?d00001 From 0d1f5c92cc11645244e318f00c76a1fc1c2ac758 Mon Sep 17 00:00:00 2001 From: Colin Sullivan Date: Fri, 11 Sep 2020 13:45:34 -0700 Subject: [PATCH 5/7] comments --- .../SimpleMIDIFile/extSimpleMIDIFile-patterns.sc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc b/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc index 4ef631a..c5b5107 100644 --- a/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc +++ b/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc @@ -152,8 +152,9 @@ Note that the first track in a SimpleMIDIFile often contains no note events if i 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. + * 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. */ arg withVelocity = false, padStart = false, totalDurationForPadEnd = false; From 513a888b4031ab9c8c35499f7b622ce37e9037ee Mon Sep 17 00:00:00 2001 From: Colin Sullivan Date: Fri, 11 Sep 2020 13:53:01 -0700 Subject: [PATCH 6/7] cleanup --- .../Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc b/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc index c5b5107..222ae68 100644 --- a/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc +++ b/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc @@ -135,7 +135,6 @@ // ------------------------------------------------------------ //// the following methods were kindly added by Jascha Narveson - generatePatternSeqs { /* @@ -152,7 +151,7 @@ Note that the first track in a SimpleMIDIFile often contains no note events if i 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. + * 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. From 11ede42b8a78da93d6deb80884bfb5e14538545d Mon Sep 17 00:00:00 2001 From: Colin Sullivan Date: Fri, 11 Sep 2020 13:54:16 -0700 Subject: [PATCH 7/7] cleanup --- .../SimpleMIDIFile/extSimpleMIDIFile-patterns.sc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc b/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc index 222ae68..e9d46e0 100644 --- a/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc +++ b/wslib-classes/Main Features/SimpleMIDIFile/extSimpleMIDIFile-patterns.sc @@ -163,7 +163,7 @@ Parameters: addTrackEventToSeq = { arg seq, event; if (withVelocity, { - seq.add([event.note, event.dur, event.vel]); + seq.add([event.note, event.dur, event.vel]); }, { seq.add([event.note, event.dur]); }); @@ -173,7 +173,7 @@ Parameters: addRestEventToSeq = { arg seq, dur; if (withVelocity, { - seq.add([\rest, dur, 0.0]); + seq.add([\rest, dur, 0.0]); }, { seq.add([\rest, dur]); });