Skip to content

Instantly share code, notes, and snippets.

@catfact
Last active October 15, 2024 18:10
Show Gist options
  • Save catfact/acf36bda257f05d0f5a41258491e365a to your computer and use it in GitHub Desktop.
Save catfact/acf36bda257f05d0f5a41258491e365a to your computer and use it in GitHub Desktop.
//----------------------------------------------------
//--- 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