How can I find a MIDI port name programmatically

I have lots of MIDI in- and outputs connected to my computer. As may be seen e.g. in MIDI-OX, the devices get some kind of “sequence numbers” by the OS (Win10)/drivers. One device may appear as e.g. “37) Moog Matriarch”. These sequence numbers may change now and then, depending on instruments crashes, sequence of connecting them/turning them on, plugging in new ones after the PC is started etc. Very often the instrument mentioned is reported as “36) Moog Matriarch”. When trying to connect to my Matriarch, I have found no other way in Sonic Pi than connecting to a port named e.g. “moog_matriarch_37” to get in contact with it. It seems like Sonic Pi does some string magics like lower casing, swapping blanks for underscores, appending the sequence number etc. So each time I start my PC music day, I have to change my port naming configurations. That is very frustrating and sometimes time consuming. I have been looking for ways to name the port generically, like e.g. “*atriarch*”, but with no luck. It seems like Sonci Pi requires naming it exactly e.g. “moog_matriarch_37”.

Have I overseen something?

I could have made my own method to generically name my port if I had had some function giving me a list of all connected MIDI devices so that I could programmatically pick the one I am looking for. I have found no such function.

Have I overseen something?

(I know the editor offers me suggestions when writing code, but it gives me of course only the ad hoc name, which may not be reused the next time I start my PC. And that is not how I always work, I write “libraries” that I use when playing live/online. These libraries will then have to be changed all the time.)


Does this do it ?

No, I am able to find the port name manually. But it may often change, it’s unreliable. My problem could be solvable if SP offered a function that returned a list of all connected MIDI ports. Then I would be able to write my own function that always returned the correct port name for my Moog out port. (e.g.)

EDIT This solution for Raspberry pi or other Linux based versions of Sonic Pi
I’VE ADDED VERSIONS FOR WINDOWS AND MAC BELOW
I’ve spent some time thinking about this. First I looked at the code in Sonic Pi, but I couldn’t see an easy way of extracting the required information, although I think it should be possible with some work to come up with something. The required info is available in the guid for example it’s shown in the io menu, but the info is not readily available in the ruby environment.
I then looked instead at extracting the information from the Alsa utility connect Using acconect -i and aconnect -o you can get printed info on midi ports available, and this can readily be brought to the Sonic Pi editor. Once there, a fair amount of string and list manipulation is required, but after some playing around I was able to produce midi connection strings in the required format, simply using a unique part of the midi device name eg oxygen for my Oxygen 32 keyboard, or steinberg for my Steinberg UR22mkII midi interface and Yoshimi for the Yoshimi software synth which I use on my Pi4. Together with either the parameter “input” or “output” I could produce the required string.
The strings that are required are quite complex because of the use of rtMidi to interface with the devices, controlled by the sp_midi code in Sonic Pi, and this results in rather unwieldy strings. Also, they are dynamically generated and will change depending upon the order in which devices are detected and plugged into the Linux computer. One way that can help to keep them stable is plug in devices before starting Sonic Pi, using the same usb sockets for each device each time. That way they should hopefully get the same numbers associated with the port strings. However, with the function that I have developed, it is not so much a problem, as this can ascertain the current value each time the function is used, and thus ensure that the correct string is used.

The first image below shows the output of aconnect for both input and output on my Pi4, with the three midi devices mentioned connected. Notice also the many rtMidi ports which are dynamically generated and added to or removed as extra devices are plugged in or removed.
You can also see the connection panel in Qjackctl which allows the rtMidi ports associated with Sonic Pi to connect to the external ports. One aside. Use Alsa ports rather than ones produced in the jack midi tab.

The second image shows the program with the getPort function in use. You can see three calls to the function.
the first one p = getPort(“Yoshimi”,“output”) gets the port string to send (ie output to) the Yoshimi synth
the second one pout=getPort(“stein berg”,“output”) produces the port to connect to the stenberg midi output, which was driving a Korg X5DR module, and the third
pin = getPort(“oxygen”,“input”) gives the port string to receive midi input from my Oxygen 32 keyboard.

You can see the full strings produced in each case in the log window for the first wo and the cues log window for the keyboard. Note the keyboard port is a little tricker to use as it has to form part of a longer string containing the initial midi command and the channel. Of Cours if you don’t care which device you are receiving midi FROM which is often the case you can use /midi*? which means that it doesn’t care about the port or the channel, or you could use /midi*:1 to match channel 1 input from ANY connected input device.

So to the program. The important bit is the definition of the getPort function near the beginning, after the audio_in section I was using.
I have put quite a number of comments in to help you follow what is going on, although I haven’t broken down in detail how the various string manipulations are achieving what is required. You can add extra puts statements if you want to investigate further.

#function by Robin Newman to easily generate Sonic Pi midi port strings on Linux
#this utilises the alsa aconnect program which can be used with -i or -o parameters
#to produce a list of input and output port data.
#The function iterates through this data to find a match for the required device name
#and then extracts and formats data to produce the required string
#The example program shows its use to connect Sonic Pi on a Raspberry Pi to the Yoshimi synth
#and to accept midi input from my Oxygen keyboard

#audio in section from the external synths
with_fx :compressor,amp: 4 do
  with_fx :panslicer  do
    live_audio :ain,stereo: true
  end
end

#port find function
define :getPort do |name,type| #name=string with midi device name, type ="input" or "output"
  if type=="input"
    connectData = `aconnect -i` #use alsa aconnect utility to get data (note the back ticks)
  elsif type=="output"
    connectData = `aconnect -o`
  else
    return "invalid type"
    exit
  end
  connectData = connectData.downcase #so you can use any case or combination
  data = connectData.split("\n") #get a list of data items
  found=false;key=-1 #set variables to find required data match
  data.each_with_index do |i,t| #search for first match of device name
    #puts t,i #uncomment to see all data
    if (not found) and (i.include? name.downcase)
      found=true #prevent further matches
      key=t #record match data key
    end
  end
  if key>=0 #positive find, otherwise key still -1
    values for entries key AND from key+1 are required to extract all the data required
    #puts "matched #{key} #{data[key]}" #uncomment for debugging
    #now manipulate info in data[key] and data[key+1] to extract required portname
    ps= data[key].split("'")[1]+" "+data[key+1].split("'")[1]+" "+(data[key][7..-1].to_i).to_s+" "+(data[key+1].to_i).to_s
    ps= ps.gsub(/\s+/, ' ') #reduce multiple spaces (if any) to single ones
    p= ps.gsub(" ","_") #replace spaces with underscore
    return p #calculated portname
  else
    return "not found"
  end
end
#usually you will want output ports when you are sending midi and can use this
#in the midi command as a port: opt, or in use_midi_defaults port: setting

#to select a specific midi input port you need to insert the port in the sync for
#the midi cue eg:
#n,v = sync "/midi:"+portnameString+":1/note_on" here 1 is the channel which can be changed or a * used


p= getPort("yoshimi","output")
puts p
live_loop :mout do
  use_real_time
  midi scale(:c3,:minor_pentatonic,num_octaves: 2).choose,sustain: 0.2,port: p
  sleep 0.25
end
poutf=getPort("steinberg","output")
live_loop :mout2 do
  use_real_time
  midi scale(:c4,:minor_pentatonic,num_octaves: 2).choose,sustain: 0.2,port: poutf
  sleep 0.5
end

pin = getPort("oxygen","input")
puts pin
use_synth :tb303
live_loop :test do
  use_real_time
  n,v = sync  "/midi:"+pin+"*/note_on"
  play n, amp: 0.5*v/127.0,release: 1,cutoff: 80
  puts n,v
end

Hope this all makes some sense. It works nicely on my Pi4, and (although I haven’t tested it yet) should be fine on other linux s Lon as you have the aconnect utility installed. (should be in package alsa-utils)

Thanks for putting so much effort into my problem! I am on Win10. Is the utility working and available there as well? (I will not be able to test it right now, as I am deep into the woods spending my Easter ski holidays. I will give it a try sometimes next week.)

Oops no. It won’t work on windows as is. I spotted a raspberry pi Sybil on a screenshot above and assumed that was what you were using but I see now it was in one of the replies. However I’ll look further for windows…

Hi Øyvind
I have now done further research and I am happy to say I now have working solutions for Windows, Mac and Linux. The ideal would of course to have an inbuilt function in Sonic Pi and I think this achievable, but would need a bit of work. I looked at the Erlang/elixir setup and there are calls set up to produce the required lists, as you can seen in the tau.log file. However although I was able to produce the required data from a build of the sp_midi.erl file in a terminal window I could see it would be more involved to get the data back to a user function in Sonic Pi via the tau server.
Instead I have used the same approach I used for linux/raspberry pi by using two utilities I found written by Geert Bevin which are available from GitHub at
GitHub - gbevin/SendMIDI: Multi-platform command-line tool to send out MIDI messages and
GitHub - gbevin/ReceiveMIDI: Multi-platform command-line tool to monitor and receive MIDI messages
In each case I downloaded binary files for Windows (or Mac) and installed them. The Windows .exe files can be run from wherever you wish, the Mac noes install in /usr/local/bin/ The function ;getPort is defined to match a supplied partial device name and will print the midi port name in full The windows and Mac functions are identical apart from the lines accessing the send and receive midi executable files.

The utilities are actually quite powerful and a useful addition generally for midi work.
On my Windows PC in particular the first call to the function is very slow, but thereafter subsequent calls are much quicker. I hope that this function does what you want.

WINDOWS version below

#windows version
define :getPort do |name,type| #name=string with midi device name, type ="input" or "output"
  if type=="input"
    connectData = `c:/Users/tasoc/receivemidi list` #use receivemidi utility to get data (note the back ticks)
  elsif type=="output"
    connectData = `c:/Users/tasoc/receivemidi list` #use sendmidi utiltiy to get data
  else
    return "invalid type"
    exit
  end
  connectData = connectData.downcase #so you can use any case or combination
  data = connectData.split("\n") #get a list of data items
  found=false;key=-1 #set variables to find required data match
  data.each_with_index do |i,t| #search for first match of device name
    puts t,i #uncomment to see all data
    if (not found) and (i.include? name.downcase)
      found=true #prevent further matches
      key=t #record match data key
    end
  end
  if key>=0 #positive find, otherwise key still -1
    puts " #{key} #{data[key]}" #uncomment for debugging
    ps = data[key]
    ps= ps.gsub(/\s+/, ' ') #reduce multiple spaces (if any) to single ones
    p= ps.gsub(" ","_") #replace spaces with underscore
    return p
  else
    return  "not found"
  end
end
puts getPort "brid","input"

This matched “touchosc_bridge”
MAC VERSION below

#mac version
define :getPort do |name,type| #name=string with midi device name, type ="input" or "output"
  if type=="input"
    connectData = `/usr/local/bin/receivemidi list` #use receivemidi utility to get data (note the back ticks)
  elsif type=="output"
    connectData = `/usr/local/bin/sendmidi list` #use sendmidi utiltiy to get data
  else
    return "invalid type"
    exit
  end
  connectData = connectData.downcase #so you can use any case or combination
  data = connectData.split("\n") #get a list of data items
  found=false;key=-1 #set variables to find required data match
  data.each_with_index do |i,t| #search for first match of device name
    puts t,i #uncomment to see all data
    if (not found) and (i.include? name.downcase)
      found=true #prevent further matches
      key=t #record match data key
    end
  end
  if key>=0 #positive find, otherwise key still -1
    puts " #{key} #{data[key]}" #uncomment for debugging
    ps = data[key]
    ps= ps.gsub(/\s+/, ' ') #reduce multiple spaces (if any) to single ones
    p= ps.gsub(" ","_") #replace spaces with underscore
    return p
  else
    return  "not found"
  end
end
puts getPort "bus","input"

This matched “iac_driver_bus”

In both versions the function produces only the first match,. Thus in the Mac case searching for " iac" would match “iac_driver_sonicpi” but would NOT find “iac_driver_bus” so adjust searches accordingly.
You can leave the earlier line puts t,i #uncomment to see all data uncommented and the function will print all the devices as it enumerates them.

A good try. But it does not solve the issue that a kind of “sequence number” is appended. If I do a

puts getPort "matriarch","output"

I get:

 ├─ 0 "komplete kontrol - 1"
 ├─ 1 "komplete kontrol ext - 1"
 ├─ 2 "m8u ex 1"
 ├─ 3 "m8u ex 2"
 ├─ 4 "m8u ex 3"
 ├─ 5 "m8u ex 4"
 ├─ 6 "m8u ex 5"
 ├─ 7 "m8u ex 6"
 ├─ 8 "m8u ex 7"
 ├─ 9 "m8u ex 8"
 ├─ 10 "m8u ex 9"
 ├─ 11 "m8u ex 10"
 ├─ 12 "m8u ex 11"
 ├─ 13 "m8u ex 12"
 ├─ 14 "m8u ex 13"
 ├─ 15 "m8u ex 14"
 ├─ 16 "m8u ex 15"
 ├─ 17 "m8u ex 16"
 ├─ 18 "moog matriarch"
 ├─ 19 "2- m8u ex 10"
 ├─ 20 "2- m8u ex 11"
 ├─ 21 "2- m8u ex 12"
 ├─ 22 "2- m8u ex 13"
 ├─ 23 "2- m8u ex 14"
 ├─ 24 "2- m8u ex 15"
 ├─ 25 "2- m8u ex 16"
 ├─ 26 "2- m8u ex 1"
 ├─ 27 "2- m8u ex 2"
 ├─ 28 "2- m8u ex 3"
 ├─ 29 "2- m8u ex 4"
 ├─ 30 "2- m8u ex 5"
 ├─ 31 "2- m8u ex 6"
 ├─ 32 "2- m8u ex 7"
 ├─ 33 "2- m8u ex 8"
 ├─ 34 "2- m8u ex 9"
 ├─ 35 "vienna instruments midi"
 ├─ 36 "komplete kontrol daw - 1"
 ├─ 37 "keystep pro midi in"
 ├─ 38 "2- focusrite usb midi"
 ├─ " 18 moog matriarch"
 └─ "moog_matriarch"

and the anwer suggested hence:

moog_matriarch

which is wrong. SP only works when I use the following name:

moog_matriarch_19

I can see that in your array, my Matriarch is the 19th element (index 18). I will try appending the array index + 1 for a while, and see whether it works consistently. It feels a bit shaky, however, that the elements are accidentally (?) listed this way each time using different kinds of utilities. Also, when finding the out port of my KronosX which is connected to my second ESI m8u MIDI hub port 11, I have to do the following search:

getPort "2- m8u ex 11","output"

getting the answer

 "2-_m8u_ex_11"

which also gets it correct when appending the array index + 1 (20 + 1 = 21):

"2-_m8u_ex_11_21"

It feels a bit like a hack, but as long as it works, I will stick to this solution for now.
On the other side, the SP IDE gets the names correct from some place (preferences | IO and code completion). Isn’t the SP IDE MIDI information available to the SP code? E.g. does it get the information from SuperCollider? In that case, it should be possible to present an SP function returning this information which I consistently could use in my code. I have not yet had time to scrutinize how the different parts of the SP play together, so I leave it here for the moment. Anyhow, thank you for the effort @robin.newman!

P.S.
The “receivemidi” utility documents its “list” parameter as:

"Lists the MIDI input ports"

but indeed, it lists all ports, outs and ins. :slightly_smiling_face:

@robin.newman I’ve run it several times now, and I am sorry I have to tell you that I cannot get consistent results. In this moment it returns my Matriarch at array index 19, which indicates that the port name should resolve to

"moog_matriarch_20"

But Matriarch is reached at port

"moog_matriarch_19"

P.S.
BTW, your code calls receivemidi independently of it being called with “output” or “input”. I changed it to sendmidi in the output case, but however I do it, the array index is 19 giving an appendage of 20.

@oor This certainly seems a bit of a problem for you. My Windows hack relies entirely on the output produced by the receive midi and send midi utilities. The index I printed was just obtained from the order if items in the array produced, and can of course be state from 0 or 1 as desired. Otherwise my approach is entirely dependent on what information the two utilities provide with the list command. If what you want is in their output then it should be possible to manipulate the data to get your connection port. If not a different approach would be required: either trying different utilities if you can find one, or developing something from the inbuilt Sonic Pi functionality.

Sonic Pi used to use two small utilities m2o and o2m get get and interact with ports, but in the later versions has moved over to using NIFS (Native Implemented Functions written in C) which interact with Erlang to handle midi comms. The code exists to produce lists as appear in the IO preferences screen and the IO menu of current midi in and out ports, and these are updated every few seconds if devices change, but these exist on the GUI interface and are not immediately and readily available in the ruby environment for the end user. In the ruby environment there is a tau-api.rb file which with the aid of tau_comms.rb communicates with the tau-server, and the processes are started by the daemon.rb file on boot.The run_time.rb file utilises the tau_api to communicate with the external midi environment and send and receive information from it.

I myself can’t do further work on this in the coming week as I won’t have access to my windows machine, and also I think access to your Moog Matriarch is really needed for testing.

You could put in a request to @samaaron to look at the possibility of producing a Sonic Pi function for the user to output a list of current midi_ins and midi_outs ports.

I understand well. Anyhow, thank you so far! (As to needing a Moog Matriarch: No, it is not needed. The same problem pertains to the environment naming of all my MIDI in and out ports.)