Skip to content

Instantly share code, notes, and snippets.

@reductionistearthcatalog
Created January 21, 2023 15:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save reductionistearthcatalog/79689486712dbc74891a210b02090792 to your computer and use it in GitHub Desktop.
Save reductionistearthcatalog/79689486712dbc74891a210b02090792 to your computer and use it in GitHub Desktop.
//block 1: start an instance of ttymidi for every connected GRAINS and the Arduino gate-to-midi
(
Pipe.new("sudo systemctl start jack", "r");
Pipe.new("ttymidi -s /dev/ttyUSB0 -n ttyUSB0 &", "r");
Pipe.new("ttymidi -s /dev/ttyUSB1 -n ttyUSB1 &", "r");
Pipe.new("ttymidi -s /dev/ttyUSB2 -n ttyUSB2 &", "r");
Pipe.new("ttymidi -s /dev/ttyUSB3 -n ttyUSB3 &", "r");
)
s.boot;
//block 2:
(
MIDIClient.init;
)
//block 3: set up midiouts, including one for the pisound MIDI interface
(
~midiouts = Array.newClear(4);
MIDIClient.destinations.do({ |item, i|
if (item.name == "MIDI in", {
if (item.device == "ttyUSB0", {
~midiouts[0] = MIDIOut(0, MIDIClient.destinations[i].uid);
~midiouts[0].latency = 0;
});
if (item.device == "ttyUSB1", {
~midiouts[1] = MIDIOut(1, MIDIClient.destinations[i].uid);
~midiouts[1].latency = 0;
});
if (item.device == "ttyUSB2", {
~midiouts[2] = MIDIOut(2, MIDIClient.destinations[i].uid);
~midiouts[2].latency = 0;
});
if (item.device == "ttyUSB3", {
~midiouts[3] = MIDIOut(3, MIDIClient.destinations[i].uid);
~midiouts[3].latency = 0;
});
});
if (item.device == "pisound", {
~m = MIDIOut(0, MIDIClient.destinations[i].uid);
~m.latency = 0;
});
});
)
//block 4: connect all midiins, register a cc responder for the MIDI handshake to distinguish
// different connected grains/arduinos
(
MIDIIn.connectAll;
~respVal = false;
MIDIdef.cc(\ccResp, {
arg ...args;
if (args[1] == 127, {
~respVal = args[0];
});
});
)
//block 5: perform MIDI handshake to give descriptive names to the ttymidi MIDIOuts
// (~fm, ~wg, ~ts, ~gates)
(
{
~respVal = false;
0.1.wait;
4.do({ |chan|
~midiouts.do({
|item, i|
item.control(chan, 3, 60);
//("Channel sent: "++chan.asString).postln;
//("MIDIout sent: "++i.asString).postln;
0.05.wait;
if (~respVal == 4, {
~fm = item;
~respVal = false;
});
if (~respVal == 3, {
~wg = item;
~respVal = false;
});
if (~respVal == 2, {
~ts = item;
~respVal = false;
});
if (~respVal == 1, {
~gates = item;
~respVal = false;
});
0.2.wait;
});
});
}.fork;
)
// set up a MIDI note buffer for the MIDIouts being used, so that the noteOff number which needs
// to be sent can be stored
(
~wg1NoteBuf = 60;
~ts1NoteBuf = 60;
)
// set up MIDI patterns that can be triggered with the gates-to-MIDI module. Here, the notes are
// described based on scale degrees. Normally, this would be changed to MIDI note values
// behind the scenes, but something about retrieving the next event based on an external trigger
// requires me to manually calculate the midinote; see \midinote key for the Pfunc. the note
// buffers are set to be the MIDI note playing so that noteOffs can be sent when the ends of
// gates are detected at the gate-to-MIDI module
(
~wgP1 = Pbind(
\type, \midi,
\midicmd, \noteOn,
\midiout, ~wg,
\chan, 2,
\scale, Scale.hexDorian,
\degree, Pxrand([0,3,4,8],inf),
\octave, 3,
\mtranspose, 0,
\gtranspose, 0,
\stepsPerOctave, 12.0,
\root, 0,
\midinote, Pfunc({ |ev| ~wg1NoteBuf = ((((ev.degree + ev.mtranspose).degreeToKey(ev.scale, ev.stepsPerOctave)) + ev.gtranspose+ev.root) / ev.stepsPerOctave + ev.octave) * 12.0; }),
).asStream;
~tsP1 = Pbind(
\type, \midi,
\midicmd, \noteOn,
\midiout, ~ts,
\chan, 1,
\scale, Scale.hexDorian,
\degree, Pseq([Pseq([0,3,5,6,8],1), Pseq([7,5,4,3,0],1)],inf),
\octave, 5,
\mtranspose, 0,
\gtranspose, 0,
\stepsPerOctave, 12.0,
\root, 0,
\midinote, Pfunc({ |ev| ~ts1NoteBuf = ((((ev.degree + ev.mtranspose).degreeToKey(ev.scale, ev.stepsPerOctave)) + ev.gtranspose+ev.root) / ev.stepsPerOctave + ev.octave) * 12.0; }),
).asStream;
)
// set up a MIDI panic code block to send noteOffs to all hanging notes, just in case
(
//panic
127.do({
|item|
~wg.noteOff(3, item, 0);
~ts.noteOff(3, item, 0);
});
)
// set up MIDI noteOn responders. In this example, noteOns 7 and 6 from the gates-to-MIDI module
// will retrieve the next events from the ~wgP1 and ~tsP1 patterns, respectively, and play them.
// Random portamento times are also calculated for each noteOn. If noteOn 1 is received, the
// patterns will be reset.
(
MIDIdef.noteOn(\noteResp, {
arg ...args;
if (args[1] == 7, {
~wg1ev = ~wgP1.next(());
~wg.control(2,5,10.rand);
~wg1ev.play;
});
if (args[1] == 6, {
~ts1ev = ~tsP1.next(());
~ts.control(2,5,10.rand);
~ts1ev.play;
});
if (args[1] == 1, {
~wgP1.reset;
~tsP1.reset;
});
});
)
// the noteOff responder sends noteOffs to the proper MIDIouts
(
MIDIdef.noteOff(\noteOffResp, {
arg ...args;
if (args[1] == 7, {
~wg.noteOff(2, ~wg1NoteBuf, 0);
});
if (args[1] == 6, {
~ts.noteOff(1, ~ts1NoteBuf, 0);
});
});
)
//to shut it all down:
Pipe.new("sudo killall ttymidi", "r");
MIDIClient.disposeClient;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment