Last active
October 15, 2024 18:10
-
-
Save catfact/acf36bda257f05d0f5a41258491e365a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//---------------------------------------------------- | |
//--- state variables and parameters | |
n = 8; // number of "beats" (really 1/8 notes i guesss) | |
d = 6; // ticks per beat. should be LCM of all desired possible divisions | |
//-- main sequence data, an array of beats | |
q = Array.fill(n, {( | |
activeDown: false, // active on "downbeat"? | |
activeOff: false, // active on "offbeat"? | |
denom: 2, // how to internally divide the beat. should be divisor of `d` above | |
div: 1, // how many internal divisions to play | |
bias: 0, // late or early swing | |
// array for ticks within this beat | |
ticks: Array.fill(d, {0}); | |
)}); | |
// tempo expressed as tick duration | |
t = 0.005; | |
~set_bpm = { arg bpm; | |
t = (bpm/120) / d; | |
postln("t = " ++ t); | |
}; | |
~set_bpm.value(90); | |
// midi output port | |
MIDIClient.init; | |
m = MIDIOut(0); | |
postln("connected to MIDI port: " ++ MIDIClient.destinations[0]); | |
// MIDI note number for this sequencer voice | |
~nn = 60; | |
//---------------------------------------------------- | |
//--- helpers | |
// call this to (re-)construct the ticks array for a given beat data | |
~build_beat = { arg b; | |
d.do({ arg i; b.ticks[i] = 0; }); | |
if (b.activeDown, { | |
b.ticks[0] = 1; | |
}); | |
if (b.activeOff, { | |
switch(b.denom, | |
{2}, { | |
b.ticks[3] = 1; | |
}, | |
{3}, { | |
switch(b.div, | |
{1}, { // "swing" | |
switch(b.bias, | |
{0}, { // "early" | |
b.ticks[2] = 1; | |
}, | |
{1}, { // "late" | |
b.ticks[4] = 1; | |
} | |
); | |
}, | |
{2}, { // play both internal divisions | |
b.ticks[2] = 1; | |
b.ticks[4] = 1; | |
} | |
); | |
} | |
); | |
}); | |
}; | |
q.do({ arg beat; ~buid_beat.value(beat); }); | |
//---------------------------------------------------- | |
//--- runtime sequencer logic | |
r = Routine { | |
s.sync; | |
inf.do { | |
q.do({ arg beatData, beatIndex; | |
{ ~show_position.value(beatIndex); }.defer; | |
d.do({ arg tickIndex; | |
~play_tick.value(beatData, tickIndex); | |
t.wait; | |
}); | |
}); | |
} | |
}; | |
~didNoteOn = false; | |
SynthDef.new(\poof, { | |
Out.ar(\out.kr(0), (EnvGen.ar(Env.perc(\attackTime.kr(0,001), \releaseTime.kr(0.08)), doneAction:2) * LPF.ar(PinkNoise.ar, 1000) * \amp.kr(0.2)).dup); | |
}).send(s); | |
~play_tick= { arg b, i; | |
// postln('t: ' ++ b.ticks[i]); | |
if (~didNoteOn, { | |
// if we played a note last tick, release it now | |
m.noteOff(~nn); | |
}); | |
if (b.ticks[i] > 0, { | |
// postln('poof'); | |
Synth.new(\poof); | |
m.noteOn(~nn); | |
}); | |
}; | |
//---------------------------------------------------- | |
//--- UI | |
u = (); | |
w = Window("divvy", Rect(100, 100, 600, 300)).background_(Color.white); | |
l = FlowLayout(w.bounds, 2@4); | |
w.view.decorator = l; | |
StaticText(w, 120@10).string_("pos: "); | |
u['pos'] = Array.fill(n, { | |
Button(w, 40@40).states_([ | |
["", Color.red, Color.white], ["", Color.white, Color.red] | |
]); | |
}); | |
l.nextLine; | |
StaticText(w, 120@10).string_("active downbeat: "); | |
u['activeDown'] = Array.fill(n, { arg i; | |
Button(w, 40@40).states_([ | |
["", Color.blue, Color.white], ["", Color.white, Color.blue] | |
]).action_({ arg but; | |
q[i].activeDown = but.value > 0; | |
~build_beat.value(q[i]); | |
}); | |
}); | |
l.nextLine; | |
StaticText(w, 120@10).string_("active offbeat: "); | |
u['activeOff'] = Array.fill(n, { arg i; | |
Button(w, 40@40).states_([ | |
["", Color.green, Color.white], ["", Color.white, Color.green] | |
]).action_({ arg but; | |
q[i].activeOff = but.value > 0; | |
~build_beat.value(q[i]); | |
}); | |
}); | |
l.nextLine; | |
StaticText(w, 120@10).string_("denominator: "); | |
u['denom'] = Array.fill(n, { arg i; | |
Button(w, 40@40).states_([ | |
["2", Color.yellow, Color.white], ["3", Color.white, Color.yellow] | |
]).action_({ arg but; | |
q[i].denom = if(but.value > 0, {3}, {2}); | |
~build_beat.value(q[i]); | |
}); | |
}); | |
l.nextLine; | |
StaticText(w, 120@10).string_("divisions: "); | |
u['div'] = Array.fill(n, { arg i; | |
Button(w, 40@40).states_([ | |
["1", Color.cyan, Color.white], ["2", Color.white, Color.cyan] | |
]).action_({ arg but; | |
q[i].div = if(but.value > 0, {2}, {1}); | |
~build_beat.value(q[i]); | |
}); | |
}); | |
l.nextLine; | |
StaticText(w, 120@10).string_("bias: "); | |
u['bias'] = Array.fill(n, { arg i; | |
Button(w, 40@40).states_([ | |
["early", Color.magenta, Color.white], ["late", Color.white, Color.magenta] | |
]).action_({ arg but; | |
q[i].bias = but.value; | |
~build_beat.value(q[i]); | |
}); | |
}); | |
l.nextLine; | |
w.front; | |
~show_position = { arg pos; | |
u.pos.do({ arg but, i; | |
if (pos == i, { | |
but.value = 1; | |
}, { | |
but.value = 0; | |
}); | |
}); | |
}; | |
//--------------- | |
//--- go! | |
r.play; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment