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)