Spurred on by recent questions on this thread, I decided to look again at getting an improved mechanism for syncing Sonic Pi computers together so that they can play in an orchestra or band together. I looked at the possibility of using broadcast OSC messages, but Sonic Pi cannot produce these without some hacking of the built in osc code which is quite finely tuned and critical to the operation of Sonic Pi, both internally and externally. Instead I hit on the idea of building a separate python script which could receive an OSC message from Sonic Pi but then resend it immediately as a broadcast OSC message which could be picked up by any other instance of Sonic Pi running on the same local network.
After playing around with this for most of yesterday I came up with a script which seems to work nicely, and today I have modified various pieces of music to make use of the system.
Basically it is best to run it on your main Sonic Pi computer and send an OSC message addressed to ā/triggerBroadcastā to port 8000 (can be changed) on ālocalhostā with up to 6 (arbitrary choice) parameters in the message. The python script script then immediately generates another OSC message with address ā/broadcastā containing the same data, which it sends to the local network broadcast address (in my case 192.168.1.255) with destination port 4559. This can them be received by any Sonic Pi computer on the local network.
I have used four different computers, a Macbook, an iMac, a Raspberry Pi3 and a very ancient old iMac converted to running Sonic Pi 3.2dev on Ubuntu. The master computer (my Macbook) sent messages to the python script for each part and the first parameter in the message consisted of the part number. Each receiving Sonic Pi has an identical live_loop which can then filter out separate parts according to this part number, and so different machines can play different parts which are all synchronised together.
This is demonstrated in the video below
In this example there are 9 separate parts to the music. I assigned parts 1 and 2 to channel 1, parts 3 and 4 to channel 2 parts 5 and 6 to channel 3 and parts 7 and 8 to channel 4, each of these channels being serviced by a live_loop on the four available computers. In addition I left the base part on channel 9, and duplicated the receiving live_loop on the fourth computer assigning it to process broadcast data on channel 9, so this computer in fact processed 3 parts all together. However by doing this I could adjust its volume for part 9 separately.
A second method of operation can be used when playing a round on several computers. In this case, each computer produces its own sounds, and the broadcast signal is merely used to synchronise the start of each tune, with one computer taking on the additional role of conductor.
by default the python script runs without the need for any input data (using defaults of localhost port 8000 for input and the network broadcast address 192.168.1.255 with port 4559 (the cue port for Sonic Pi) as output. However these can be over-ridden with arguments on startup. If the actual ip address of the host computer is used instead of localhost then other computers on the network can send a directed ā/triggerBroadcastā to the script and this can in turn be broadcast to all the others. I used this to let the last computer in the round sequence (which I called the āfinisherā) send a broadcast to all the other computers causing their programs to be stopped.
The script is on my gist site at
I will add later some example Sonic Pi programs illustrating in more detail how to utilise it, but basically all you need to know is shown in the example below:
On the master Sonic Pi machine, run the script in a terminal window using python3 broadcastOSC.py
From Sonic Pi on that computer communicate with it using
use_osc "localhost",8000
sp=1 #this computer "number". Others will be sp-2, sp=3 etc
bpm=120
use_bpm bpm
live_loop :testSend do
osc "/triggerBroadcast",1,note(:c4),1,bpm,"tri",0.5
sleep 1
osc "/triggerBroadcast",2,note(:e4),1,bpm,"saw",0
sleep 1
osc "/triggerBroadcast",3,note(:g4),1,bpm,"pulse",-0.5
sleep 1
end
live_loop :pl do
use_real_time
b= sync "/osc*/broadcast"
if b[0]==1 #check if the right channel
use_synth b[4]
use_bpm b[3] #set bpm
#rests come up as "nil": ignore them, i.e. play nothing
play b[1],sustain: b[2]*0.9,release: b[2]*0.1,amp: 0.5,pan: b[5] if b[1]!="nil" #allows for rests
end
end
Other machines just need the live_loop :pl and will have different sp values set to play parts 2 and 3.