Setting up Tidal Cycles with SuperDirt MIDI and a multiport midi interface with channel specific namespaces

Author
Nick

I'm getting into Tidal Cycles, which is a live coding midi sequencer platform. Many of the tutorials just describe using a single port midi interface so it can be difficult to understand what is the code and what is a variable if you are just starting out.

After some chatting on the Tidal Cycles Discord with user and live coders GEIKHA and Matthew Kaney I have an example script for initializing SuperCollider for multiport midi interfaces and a way to address synth and synth parts without having to remember the interface port number or midi channel each piece is using. These commands are executed from within SuperCollider.

First, start SuperDirt. There are a few ways to do it, but this is one way that will work. With the cursor on the line, type Command+Enter (Mac) or Control+Enter (Windows, maybe Linux)
SuperDirt.start;

Next, initialise midi.
MIDIClient.init;

Next, get a list of the connected midi interfaces and ports. I have the MidiSport 8x8 USB midi interface from the year 2000 for Windows 98, MidiMan later wrote drivers for Windows XP, 7, 8, 10, 11 and MacOSX 10.0-10.8.5 (that last official driver continues to work for at least 10.13.), and then dropped support for new OSes due to the age. Leigh Smith has graciously written a Apple Silicon driver for so it can work on macOS v10.14 - 13.4. So, when I run this line it spells out a pair of names we will need to use in the next section.
MIDIClient.destinations.do(_.postln)

It returns with:

MIDI Destinations:
MIDIEndPoint("MIDISPORT 8x8/S", "Port 1")
MIDIEndPoint("MIDISPORT 8x8/S", "Port 2")
MIDIEndPoint("MIDISPORT 8x8/S", "Port 3")
MIDIEndPoint("MIDISPORT 8x8/S", "Port 4")
MIDIEndPoint("MIDISPORT 8x8/S", "Port 5")
MIDIEndPoint("MIDISPORT 8x8/S", "Port 6")
MIDIEndPoint("MIDISPORT 8x8/S", "Port 7")
MIDIEndPoint("MIDISPORT 8x8/S", "Port 8")

(Note: the Midiman drivers for MacOSX 10.0-10.14 will use a lower case s while Leigh Smith's macOS 10.14 - 13.4 driver uses uppercase S. So, always copy and past exactly what Super Collider returns with MIDIClient.destinations.do(_.postln).)

You can also have two of the same single I/O midi interfaces and they will appear like this:
MIDIEndPoint("YAMAHA UX16", "Port1")
MIDIEndPoint("YAMAHA UX16 2", "Port1")

.

SuperDirt calls a midi output a "MIDIEndPoint". A few other 8 port midi interfaces this should also work with are Emagic Unitor 8, MOTU MIDI Express 128 or MIDI Express XT, or iConnectivity mioXL or mio10, or Alyseum AL-88C, ESI M8U or MU8XL, or any other multiport midi interface, and it will display their names as the system and interface drivers are seeing them, so take note.

From there we can assign a name that you will use in the text editor to run the Tidal Cycles code. I picked ms and then the port number. I found starting with a number is not allowed by SuperCollider and will generate an error.

~ms1 = MIDIOut.newByName("MIDISPORT 8x8/S", "Port 1");
~dirt.soundLibrary.addMIDI(\ms1, ~ms1);

~ms2 = MIDIOut.newByName("MIDISPORT 8x8/S", "Port 2");
~dirt.soundLibrary.addMIDI(\ms2, ~ms2);

~ms3 = MIDIOut.newByName("MIDISPORT 8x8/S", "Port 3");
~dirt.soundLibrary.addMIDI(\ms3, ~ms3);

~ms4 = MIDIOut.newByName("MIDISPORT 8x8/S", "Port 4");
~dirt.soundLibrary.addMIDI(\ms4, ~ms4);

~ms5 = MIDIOut.newByName("MIDISPORT 8x8/S", "Port 5");
~dirt.soundLibrary.addMIDI(\ms5, ~ms5);

~ms6 = MIDIOut.newByName("MIDISPORT 8x8/S", "Port 6");
~dirt.soundLibrary.addMIDI(\ms6, ~ms6);

~ms7 = MIDIOut.newByName("MIDISPORT 8x8/S", "Port 7");
~dirt.soundLibrary.addMIDI(\ms7, ~ms7);

~ms8 = MIDIOut.newByName("MIDISPORT 8x8/S", "Port 8");
~dirt.soundLibrary.addMIDI(\ms8, ~ms8);

Place the typing cursor on each line and Command+Enter (Mac) or Control+Enter (Windows) on each line, one after another. If you know of a faster way to do this let me know, I'll be looking for a way to do this next.

The first set of lines creates a MIDIOut object from it so we can interact with it with SuperCollider. Assign this to a global variable called ms1 so it can be refered to by that name in the Tidal Cycles editor.

The second set of lines tells SuperDirt's sound library to add a midi type element to the library, give it the unique name, and make it interact with the MIDIOut of the same name. Now that the "sound" is in the library, when SuperDirt receives a "sound" or s message that has the same name, it will know to send a midi message, instead of looking for it in your sample folders, which is the default behavior, and how most Tidal Cycle live coders use Tidal Cycles. You could say using midi is sort of a specialized use case.

No, go to your editor (in my case Atom or Pulsar) and you can start addressing port and channel numbers as each SuperDirt instrument.

d1 $ n "0 2 4 7" # "ms1" # midichan "0"

d6 $ n "0 2 4 7 6" # s "ms2" # midichan "1"

d7 $ n "0 2 4 7 6 8" # s "ms3" # midichan "5"

d8 $ n "0 2 4 7 6 8 0" # s "ms3" # midichan "2"

d9 $ n "0 2 4 0" # s "ms4" # midichan "4"

Midi channel number offset hack: 1~16 instead of 0~15

In SuperCollider MIDI the midi channel numbers start at zero which is a little annoying. This might remind you of the way some manufacturers call their patch numbers 0-127 vs 1-128. But with this line I have named the "midi channel offset hack" you can you can offset the channel number so 1 is 1, 2 is 2, etc but only if you address it a specific way (more on this below).
~channel = { arg chan; (\midichan: chan - 1)}

Addressing hardware synths by name by declaring a midi channel in SuperCollider

Let's say you want to name a hardware synth instead of addressing midi interface outputs numbers. It is possible. Let's say I have a DX7 on channel 1, a D550 on channel 2, and a a Matrix 1000 on channel 3 all on the midi interface Port 1 going to a thru box or with the midi thru-to-in daisy chain. You will want to just type dx7, d550, and m1000 in the live code app instead of trying to to remember the midi channel and port numbers, just like we used to do back in the OMS days. And note this code is specific to using the midi channel offset mentioned above. In SC:

~dx7 = MIDIOut.newByName("MIDISPORT 8x8/S", "Port 1");
~dirt.soundLibrary.addMIDI(\dx7, ~dx7, ~channel.value(1));

~d550 = MIDIOut.newByName("MIDISPORT 8x8/S", "Port 1");
~dirt.soundLibrary.addMIDI(\d550, ~d550, ~channel.value(2));

~m1000 = MIDIOut.newByName("MIDISPORT 8x8/S", "Port 1");
~dirt.soundLibrary.addMIDI(\m1000, ~m1000, ~channel.value(3));

Now in the Tidal Cycle text editor you can omit the channel declaration and just use the instrument name.

d1 $ n "48 50 64 42" # s "dx7"

d2 $ n "23 78 35 78" # s "d550"

d3 $ n "45 67 23 90 101" # "m1000"


If you opt to NOT use the channel offset hack, It is a little different. You would run this in SC to set up the names:

~dx7 = MIDIOut.newByName("MIDISPORT 8x8/S, "Port 1");
~dirt.soundLibrary.addMIDI(\dx7, ~dx7, (\midichan: 0));

~d550 = MIDIOut.newByName("MIDISPORT 8x8/S, "Port 1");
~dirt.soundLibrary.addMIDI(\d550, ~d550, (\midichan: 1));

~m1000 = MIDIOut.newByName("MIDISPORT 8x8/S", "Port 1");
~dirt.soundLibrary.addMIDI(\m1000, ~m1000, (\midichan: 2));

Multitimbral synth part naming

Now let's set up the naming for a TX81Z on port 2 set with 2 voices per part for 4 part multitimbral mode on channels 1, 2, 3 and 4. If you want to add the channel part to the name an underscore is an allowable character. This is with the channel offset hack:
~tx81z_1 = MIDIOut.newByName("MIDISPORT 8x8/S", "Port 2");
~dirt.soundLibrary.addMIDI(\tx81z_1, ~tx81z_1, ~channel.value(1));

~tx81z_2 = MIDIOut.newByName("MIDISPORT 8x8/S", "Port 2");
~dirt.soundLibrary.addMIDI(\tx81z_2, ~tx81z_2, ~channel.value(2));

~tx81z_3 = MIDIOut.newByName("MIDISPORT 8x8/S", "Port 2");
~dirt.soundLibrary.addMIDI(\tx81z_3, ~tx81z_3, ~channel.value(3));

~tx81z_4 = MIDIOut.newByName("MIDISPORT 8x8/S", "Port 2");
~dirt.soundLibrary.addMIDI(\tx81z_4, ~tx81z_4, ~channel.value(4));