OSC messages are very useful for passing data in Sonic Pi, particularly if you want to to communicate with external programs, but they can also be used within a single buffer program.
Although complex OSC messages can be created, Sonic Pi restricts things to three data types: integers, floating point numbers and strings. If you send anything else it is is placed within two " " and sent as a string.I wanted to be able to send lists of notes (with symbolic names) and lists of numbers (the durations of these notes) as well as individual synths names as symbols.
After some playing around I came up with some decoding functions than enabled me to do so.
Thus I could have an osc command like:
osc "/list" 1 ,1.5 , [1,2,3] , [1,2.5,0.5] , :c4 , [:c4,:d4,:e4] , "Finish!"
and I could receive this list of data and extract it into the same component parts: an integer, a floating point number, a list of integers, a list of floating point numbers, a note symbol, a list of note symbols and finally a string.
I achieved this with the program below:
#developing list and symbol decode for data sent as OSC data
use_osc_logging false
use_debug false
use_cue_logging false
use_osc "localhost",4560
#define decoding functions for various data type
define :decodeSYML do |nl| #symbol list
nl[1..-2].split(",").map {|str| str.strip[1..-1].to_sym}
end
define :decodeFNL do |dl| #floating point number list
(dl[1..-2].split(",")).map {|str| str.to_f}
end
define :decodeINL do |dl| #intenger number list
(dl[1..-2].split(",")).map {|str| str.to_i}
end
define :decodeSYM do |synr| #single symbol
synr.strip[1..-1].to_sym
end
puts "data to send:",1 ,1.5 , [1,2,3] , [1,2.5,0.5] , :c4 , [:c4,:d4,:e4] , "Finish!"
live_loop :playIt do
use_real_time
#get the data from the incoming OSC message
i,f,il,fl,sym,syml,s =sync "/osc*/list"
puts "raw data received:", i,f,il,fl,sym,syml,s
puts "decoded data:", i,f,decodeINL(il),decodeFNL(fl),decodeSYM(sym),decodeSYML(syml),s
puts
puts "example stage by stage for floating point number list"
puts "starting point", fl
puts "stage1: strip []:",fl[1..-2]
puts "stage2: convert to string list:",fl[1..-2].split(",")
puts "stage3: map stripped strings to floating point numbers:",(fl[1..-2].split(",")).map {|str| str.to_f}
end
#send the osc message to be received and decoded
osc "/list",1, 1.5, [1,2,3], [1,2.5,0.5], :c4, [:c4,:d4,:e4], "Finish!"
The decode functions look fairly fearsome, and use various Ruby constructs beyond the defined Sonic Pi language set. If you take them stage by stage you can follow how they work. I have printed out the various stages for one of them in the program so you can follow how it works.
Finally, I developed these whilst working on a collaborative program between two different computers both running Sonic Pi. I went back to an old favourite often used in showing the use of Sonic Pi, Frère Jaques. Each line of the tune repeats, and I started with one machine playing the first part of the line and the other answering with the repeat. Having got this working, I them moved on the using it as a four part round, each with a different synth voice, but using the same answering between the two machines. Finally I added tempo changes as well! In the process, I had various sync problems to work out, but eventually I got a working system.