Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

generatePatternSeqs features #21

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions wslib-classes/Main Features/SimpleMIDIFile/TestSimpleMidiFile.sc
Original file line number Diff line number Diff line change
@@ -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]
]);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3-tuple when velocity flag is set.


}

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]
]);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the default / previous behavior.


}

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]
]);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default / previous behavior.

}
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]
]);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pads the start of the pattern with a rest, because the first note is a beat in.

}
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]
]);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pads the end of the pattern with a rest, because the last note ends at 3.0 and the argument is 4.0


}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)});
Expand All @@ -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]
)
Expand All @@ -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;
Expand Down Expand Up @@ -282,4 +336,4 @@ Example:
}


}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.