Approaches to recording generated midi in DAW?

Hi all,

I’m looking to record midi generated in supercollider in a DAW. As a workaround, I now use the SimpleMIDIFile class to render patterns to midi file, then import the midi file into the DAW, but it quickly gets tedious.

I’m on linux, so I think if supercollider would support jack_midi that’d be the solution to my problem, but as far as I can tell supercollider doesn’t support jack_midi at the moment (and I’m not sure what it would take to add it).

Therefore this question: does anyone have a good process for recording generated MIDI into a DAW (instead of sending it to an external synth)?

You can run a2jmidid -e to create a bridge that shows alsa midi i/o to jack midi.
I don’t know if you’re using Ardour, but here there’s a page from their manual explaining midi setup, including a2jmidid:
https://manual.ardour.org/setting-up-your-system/setting-up-midi/midi-on-linux/

I use ardour and I’ve experimented with alsa to jack bridge but I haven’t found what to connect to what to actually be able to record something.

If I don’t connect any hardware synthesizer, MIDIClient.init results in

MIDI Sources:
	MIDIEndPoint("System", "Timer")
	MIDIEndPoint("System", "Announce")
	MIDIEndPoint("Midi Through", "Midi Through Port-0")
	MIDIEndPoint("SuperCollider", "out0")
MIDI Destinations:
	MIDIEndPoint("Midi Through", "Midi Through Port-0")
	MIDIEndPoint("SuperCollider", "in0")
	MIDIEndPoint("SuperCollider", "in1")
	MIDIEndPoint("SuperCollider", "in2")

No matter what I try to connect to, I cannot record any midi in ardour.

Trying to connect to out0 results in a rather weird error

MIDI Sources:
	MIDIEndPoint("System", "Timer")
	MIDIEndPoint("System", "Announce")
	MIDIEndPoint("Midi Through", "Midi Through Port-0")
	MIDIEndPoint("SuperCollider", "out0")
MIDI Destinations:
	MIDIEndPoint("Midi Through", "Midi Through Port-0")
	MIDIEndPoint("SuperCollider", "in0")
	MIDIEndPoint("SuperCollider", "in1")
	MIDIEndPoint("SuperCollider", "in2")
ERROR: Failed to find MIDIOut port  SuperCollider out0

Trying to connect to Midi Through and playing something sends sclang into some kind of infinite loop using 100% cpu.

I should probably add that I can record incoming midi from the external synth without problems. It’s just midi generated by supercollider that I cannot record.

This is it! Make sure you are aware of the catches about MIDIOut in Linux
https://doc.sccode.org/Classes/MIDIOut.html#Linux%20specific:%20Connecting%20and%20disconnecting%20ports

After you run a2jmidid -e, ardour will be able to see supercollider’s out0, in the MIDI connections pane, so that you can connect it to a MIDI track. Alternatively, you can make the connection from qjackctl, in the MIDI tab. Supercollider’s out0 will be a port inside the a2j “readable client”, which you can connect to your MIDI track’s input inside ardour.

I always use newByName, which according to the documentation should work on all operating systems.
Trying to connect to out0 gives a weird error:

^^ The preceding error dump is for ERROR: Failed to find MIDIOut port  SuperCollider out0

I’m officially lost.

Try with just: m = MIDIOut(0); and then connect to ardour with qjackctl or ardour itself

This sends sclang into an infinite loop with 100% CPU utilization.

I’m also quite confused about what a2jmidid -e displays:

port created: SuperCollider [130] (playback): in0
port created: SuperCollider [130] (playback): in1
port created: SuperCollider [130] (playback): in2
port created: SuperCollider [130] (capture): out0


is in0 playback and out0 capture?
Should midiOut connect to in0 instead of out0 ?

Out0 is sc’s out, so m=MIDIOut(0) uses that. Then m should connect to ardour. If you want to send midi from ardour to sc, connect ardour to sc’s in0.
a2jmidid forwards (kind of duplicates) midi ports from alsa to jack, so that jack midi clients can use them.
So in the end, ardour connected to a2jmidid’s port 0 will receive data that you send from sc to out0.
And to send to out0 is sufficient to create a MIDIOut(0)

Ok, important progress (for me at least). The following works:

supercollider:

(
var pat, player;
MIDIClient.init;
 // per the docs the following is what linux users should use instead of just MIDIOut(0)
m = MIDIOut(0, MIDIClient.destinations[0].uid);

pat = Pbind(
	\type, \midi,
	\midicmd, \noteOn,
	\chan, 0,
	\midiout, m,
	\midinote, Pseq([60], inf),
	\dur, 1
);
player = pat.play;
)

in ardour:

connect "system" to my midi track 

(none of a2j, supercollider "out0", "out1", "out2", 
or whatever else appears in ardour works - only "system")

Surprise:

If I kill the a2jmidid -e program, it still works. Seems like the alsa bridge is not needed/used at all in this
case. I cannot claim it makes any kind of sense to me, but here we are.

MIDIClient.destinations[0] btw corresponds to MIDIEndPoint(“Midi Through”, “Midi Through Port-0”)

If I now try to use newByName instead, it works too:

(
var pat, player;
MIDIClient.init;
m = MIDIOut.newByName("Midi Through", "Midi Through Port-0");

pat = Pbind(
	\type, \midi,
	\midicmd, \noteOn,
	\chan, 0,
	\midiout, m,
	\midinote, Pseq([60], inf),
	\dur, 1
);
player = pat.play;
)

So the trick here appears to not use any alsa midi bridge at all, but just connect to the “system” midi in in ardour, and use midi through as destination in supercollider.

It’s great that I now can directly record midi from supercollider.
It’s not so great that I have no idea what I just did and why it works…

On which audio backend are you running ardour? The bare fact that you see “system” makes me think you are running it on ALSA.
I actually don’t know how that (ardour on alsa + midi from sc) works, as I have never used it. I tried it a bit now, but I can’t figure it out.

Anyway, if you run ardour on JACK, then this process works (on my machine at least)

MIDIClient.init
m = MIDIOut(0)

// sc can't see ardour, can't connect from here
MIDIClient.destinations

// run a2jmidid -e. You can also do this from another terminal
"a2jmidid -e".unixCmd

// one precisation:
// - ardour is running on JACK, not on alsa
// - create a new MIDI track in ardour
// - from MIDI Connections panel, connect the track to SuperCollider:out0 (in sources > others)

// - enable recording on that track
// - start recording in ardour

m.noteOn(60);
m.noteOff(60);
// a midi note appeared in ardour's track

Pbind(\type,\midi,\midiout,m,\note,Pxrand((0..12),12),\dur,0.05).play
// a very fast random 12-tone row appeared in ardour's track

EXTRA: writing to MIDI Through will likely create a problem if you also listen for midi and use MIDIIn.connectAll. At that point sc will receive (and thus trigger MIDIdef and co. for) its own messages.

If you dont want to render a MIDI file I guess you want to have SC sending “real time” midi to other softwares, right?

If this is the case, I tend not to use \midicmd because the patterns data structure tend to handle midi note events natively. On Mac and Windows I generally do:

s.boot;

MIDIClient.init; //Initialize MIDI client

//instantiate the MIDI bus
~midibus0 = MIDIOut(0);

//Send the pattern to your DAW or Virtual Instrument
(
Pbind(
	\type, \midi, 
	\midiout, ~midibus0,
	\degree, Pwhite(1,6),
	\octave, 6,
	\legato, 1.2,
	\scale, Scale.minor,
	\dur, 1/8,
	\db, -6,
).play;
)

But the main problem that I see in this usage is to sync SC MIDI with the DAW clock. I am really lost about how to solve this issue, but I guess the SC dev community is working on Ableton Link to manage this problem.

Ableton Link support in SC has been released for a few months now.

It may or may not help with Ardour (which doesn’t, and won’t, support Link).

There’s a Jack app to forward Jack transport status to Link apps: jack_link (<-- edit). I tried it once; it was straightforward to build. With that, you can play in Ardour and SC will be reasonably synced with it. As always, the devil is in the details, though, might take some fiddling to make it work really well.

hjh

1 Like

I use ardour with jack backend for sure, but I also have pulseaudio routing into jack so I can use supercollider and watch youtube at the same time :slight_smile: - maybe that’s where system comes from?

Hi, would not using \midicmd have any advantage over using it? I’m not sure I understand.

In my point of view is to do less tipying and get a cleaner code.

SuperCollider:out0 never appears in ardour for me, but “a2j” does. The first time I tried to exactly follow your instructions nothing was recorded at all (consistent with my earlier experience). After restarting all of supercollider, ardour, jack, a2jmidid I tried once more and against all expectations it started to record the midi events. I’m not sure what was any difference between my two attempts other than the phase of the moon.

I guess I’ll just have to experiment a bit more until I find a reliable sequence of operations (once it works, it keeps working - so there’s at least that :wink: )

Some things:

  • Is it important that the alsa bridge is started after supercollider connects to MIDIOut(0)? Maybe because it doesn’t know about supercollider and cannot create a bridge? Or should it automatically pick up any newly appearing alsa midi devices and expose them?

  • Are the docs are wrong about MIDIOut(0) not working correctly on linux and having to use MIDIOut(0, MIDIClient.destinations[0].uid) instead?

  • Could it be that doing something wrong (connecting to a wrong destination e.g.) messes up some internal state that later causes “correct” attempts to also fail? Because I’m fairly sure I have tried the exact sequence you proposed many times before (but probably never as “first attempt”), and it never worked for me until my second attempt today.

Anyway, thanks for all the help! There’s light at the end of the tunnel.

1 Like
  1. a2jmidid picks up new alsa midi devices and removes old ones, while running. So it shouldn’t be necessary to start it after MIDIOut(0)
  2. I agree the docs can be a little confusing… the right informations are there though:
  • on Linux you connect MIDIOut to an out port (e.g MIDIOut(0) connects to out0)
  • connecting to an out port doesn’t connect to a destination, that’s why you can use the second argument as in MIDIOut(0, MIDIClient.destinations[index].uid).

Catches: MIDIClient.destinations lists all ALSA midi destinations. It doesn’t list ardour because it’s a JACK midi client. So there’s no way to connect to ardour from there, and you might as well not connect to any destination from SC (but you still need to connect to the out port), hence MIDIClient(0). *newByName doesn’t work for the same reason.
They both work though if the destination is in ALSA.
Agreed that in that help section there could be a little part about JACK midi.

  1. I honestly don’t know