New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Propose a MIDIOut Factory to handle differences between Linux and other OSes #3805

jamshark70 opened this Issue Jun 23, 2018 · 2 comments


None yet
2 participants

jamshark70 commented Jun 23, 2018

A recent sc-users thread pointed out multiple discrepancies between Linux and other operating systems for MIDIOut.

OSX / Windows Linux
Target port is in MIDIClient.destinations Target port is in MIDIClient.sources
MIDIOut(n) --> MIDIClient.destinations[n] MIDIOut(n) --> "SuperCollider:outn" regardless of array index
uid is optional (should match target port) Either a uid or .connect is mandatory; it should be the "real" target, not the "SC:out*" port

I don't believe it's possible to paper over these distinctions within MIDIOut itself. Currently, we have to assume there is code out in the wild for OSX depending on the OSX MIDIOut way, and code for Linux depending on the Linux MIDIOut way. Changing MIDIOut directly would break existing code.

I suggest a wrapper -- perhaps even a simple Factory class -- to provide a unified interface to create a MIDI output object, which always works the same way in every platform.

The sticky bit there is that, AFAIK in OSX/Windows, SC has only one virtual MIDI output in the system, and messages are targeted to a specific destination. In Linux, SC has multiple virtual MIDI outputs which are patched to targets. Cross-platform MIDI-output code needs to take this into account, but OSX/Windows users aren't going to be aware of this. I don't think it will be easy to spec this out properly (but I do think this is the only non-hacky solution).

Related issue: #1006. However, that issue is positioned as a bug fix. I don't believe it's fixable, without breaking code that uses MIDIOut as is. I think it's better to create a higher-level interface that hides the OS-specific details from the user.

@jamshark70 jamshark70 changed the title from MIDIOut: Irreconcilable differences for Linux to Propose a MIDIOut Factory to handle differences between Linux and other OSes Jun 23, 2018


This comment has been minimized.


jamshark70 commented Jun 26, 2018

I thought about this some more. It might be enough to make a couple of adjustments to newByName and update the documentation.

I had complained that newByName gets the device index from destinations and then uses it for an argument which, in Linux, refers to one of the sources (but not at that specific array index). Now I think this is a clever (and basically undocumented) solution to get one-to-one connections "for free."

In OSX, you write MIDIOut.newByName("device", "port") and SC establishes a one-to-one link between this MIDIOut object and the port.

In Linux, it's myMIDIOut --> the ALSA MIDI source "SC:out0" and then the message is relayed to every target connected to SC:out0. If you want a one-to-one connection to the destination, then you need an association where SC:out0 --> only this target. The current implementation does this by presupposing:

SC:out0 --> MIDIClient.destinations[0]
SC:out1 --> MIDIClient.destinations[1]
SC:out2 --> MIDIClient.destinations[2]

So we get the array index from destinations and associate it with the same out number.


  • We assume that "real" MIDI destination ports will come first in MIDIClient.destinations. I think this is safe (because SC will always be the last to register with ALSA MIDI) but I'm not sure it's guaranteed.

  • The user is responsible for initializing as many SC:out ports as she has "real" destinations. If you have 4 "real" destination ports but initialized MIDIClient with only SC:out0 and out1, then the third and fourth destination devices will not be available in newByName. This is not documented clearly.

So my suggestion now is to add comments into newByName to explain the logic, and document carefully in MIDIOut.schelp.



This comment has been minimized.

mzero commented Jun 30, 2018

tl;dr: As a user, I don't really care about indices into OS delivered MIDI port arrays, nor UUIDs. I care about device and port names. If there's going to be a higher level API, base it on that.

longer: with references to running code...

I tackled this problem in my recent library ClockWise - which I use for live performance: I develop on OS X, but perform on Linux (headless RaspberryPi!) - and so had to face these issues head on.

To client code my solution looks like this:

var cw = ClockWise();

// find ports, in and out, named "FaderFox", associate with \ff
cw.midiDevice(\ff, "FaderFox");

// hook up some Synth args to 'control points'
cw.synthArg(\hpSweep, \freq.asSpec);
cw.synthArg(\lpSweep, \freq.asSpec);

// hook to CC control to those same points
cw.midiCC(\hpSweep, \ff, ch=12, cc=114);
cw.midiCC(\lpSweep, \ff, ch=12, cc=115);

This works well for the user (me): I don't really care which port is which, I care what the devices is called. Further, names are often mangled by the OS and/or USB drivers and/or the devices themselves (plug in a Digitakt!), so matching with .conatiansi() is used, and works well.

Ignoring the port name works well for most devices, and in ClockWise, connects both in and out ports under the same device name:

cw.midiDevice(\mm, "MicroMonsta");  // synth
cw.midiDevice(\nk, "nanoKey");      // controller
cw.midiDevice(\ps, "PiSound");      // DIN MIDI interface

For devices with multiple ports, there is the same style matching for the port name:

cw.midiDevice(\lp, "Launchpad", "Standalone");

But this is where this relates to the issue at hand: It doesn't work well for when moving back and forth to Linux:

cw.midiDevice(\lp, "Launchpad",
        \linux,       { "MIDI 2" },
        /* default */ { "Standalone" }

There are two small additions coming to ClockWise to address MIDI device connection issues:

// support unidirectional connections
cw.midiDevice(\nk, "nanoKey", inOnly:true);  // nanKey's out port is used only for programming it

// support the port name variation:
cw.midiDevice(\lp, "Luanchpad", "Standalone", linuxPort:"MIDI 2");
// --or perhaps--
cw.midiDevice(\lp, "Luanchpad", ["Standalone", "MIDI 2"]);

You can find all the details in the .midiDevice() implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment