Extensive OSC tutorial

Hi,

I’m going to hitchhike on this thread. @robin.newman You say that it’s better to put multiple data in to one message.

But in my case I have 3 devices sending minimum of 4 osc messages (prefereably more but I’m content with 4 if possible) .
With sync I can only sync multiple messages from one device.

How do I sync multiple devices?

What would be the code you wish you could write and what would be its behaviour?

Well every code on every device pulls sensor data and sends it repeatedly with a few seconds of sleep (this can be change to minutes) the data would trigger parameters in the sonic pi code. It would change delay times, sustain time also trigger certain cases of sample or small live_loops.

I would like for example to use

a, b = sync(osc)
c = sync(osc1)
d = sync(osc2)

And it would read al the sync or 1 sync that can handle multiple osc

a, b, c, d = sync(osc1, osc2, osc3)

Does this make sense?

Almost, but not quite yet.

Did you want the following to match all or just one of the incoming OSC messages:

a, b = sync(osc)
c = sync(osc1)
d = sync(osc2)

i.e. should all of osc, osc1 and osc2 come in before waiting for the next batch, or just any of osc, osc1 or osc2 come in before waiting for any of osc osc1 or osc2 again.

Same question for this line:

a, b, c, d = sync(osc1, osc2, osc3)

Here’s an example which uses wild cards to allow one loop to respond to three different addresses
and to extract the data for each one. They can be received in any combination.

#example using one sync loop to determine which of three osc commands is received

use_osc "localhost",4560
use_debug false
use_osc_logging false

#use function based on unpublished get_event routine to resolve wild cards
define :parse_sync_address do |address|
  v= get_event(address).to_s.split(",")[6]
  if v != nil
    return v[3..-2].split("/")
  else
    return ["error"]
  end
end

live_loop :test3 do
  #this routine sends 0 to 3 osc commands each pass
  #commands addresses are com1 com2 and com3
  #each sends different data
  puts "pass",tick
  osc "/com1",1 if one_in 3
  osc "/com2",2,20 if one_in 3
  osc "/com3",3,30,300 if one_in 3
  sleep 2
end

#this loop receives and decode ALL the osc commands sent
live_loop :decode do
  #use a wild card * to match all of com1,com2 and com3
  a= sync "/osc*/com*" #a is a list of the data items received
  res = parse_sync_address("/osc*/com*")
  #puts res #uncommentfor testing
  #res[1] holds com1, com2 or com3 as strings. extract last character and convert to integer
  code =res[1][-1].to_i
  case code #use a case statement to carry out differnt commands for each osc received
  when 1
    puts a[0] #print single data item for com1
    play :c3
  when 2
    puts a[0],a[1] #print the two data items for com2
    play :c4
  when 3
    puts a[0],a[1],a[2] #print all three data tiems for com3
    play :c5
  end
  
end

EDIT sounds quite nice if you reduce the sleep time in teh first loop from 2 to 0.2 Not really the point of the example but quite cool. I had it at 2 to make it easier to fathom what is going on.

@samaaron In my case the second statement would be true. Any osc coming in before waiting any following osc message per osc “channel”.
The timing between them doesn’t matter. In case there is a limit of total incoming messages. I can alter the delay in my Arduino code or use timers to make sure that they don’t all come in at the same moment.

But the main thing is that I need to have alle of the osc messages coming in as for now I only get the first sync data.

@robin.newman

#use function based on unpublished get_event routine to resolve wild cards
define :parse_sync_address do |address|
  v= get_event(address).to_s.split(",")[6]
  if v != nil
return v[3..-2].split("/")
  else
return ["error"]
  end
end

#this loop receives and decode ALL the osc commands sent
live_loop :decode do
  #use a wild card * to match all of com1,com2 and com3
  a= sync "/osc*/com*" #a is a list of the data items received
  res = parse_sync_address("/osc*/com*")
  #puts res #uncommentfor testing
  #res[1] holds com1, com2 or com3 as strings. extract last character and convert to integer
  code =res[1][-1].to_i
  case code #use a case statement to carry out differnt commands for each osc received
  when 1
    puts a[0] #print single data item for com1
    play :c3
  when 2
    puts a[0],a[1] #print the two data items for com2
    play :c4
  when 3
    puts a[0],a[1],a[2] #print all three data tiems for com3
    play :c5
  end

This would be all I need right to get several devices their osc messages into 1 single SonicPi code.

Yes this would work with several devices,as they all send to the same port, and you amks out the first bit with differnt ip addresses etc. For convenience I used very similar osc addresses /com1 /com2 /com3, but you could use more complex ones although it would make the extrication of the different addresses a little more involved. For convenince in the example I just sent osc calls from the local machine.

Is there any reason why you can’t use multiple live loops? One live loop per osc message you want to match and sync on?

Actually not, I’m trying it out like you said. I think I’m getting there with that setup.

@samaaron Thanks it works!

1 Like

@samaaron I cheered to soon.

uncomment do
live_loop :test do
delayosc = sync “/osc*/MHZ19/”
with_fx :gverb do
with_fx :octaver do
live_loop :input0 do
synth :dark_ambience, sustain: 0.70, attack: 0.50, release: delayosc
sleep 1
end
end
end
end
end

I want the loop to run continously and the data should change whenever an osc message comes in.
And I want multiple of these live_loops.

But I’m not getting the continously sound. It’s late so maybe I’m overlooking something?? :sweat_smile:

Hi @dewildequinten,

it’s pretty unusual to see nested live loops. However, from what you’ve written, what I would imagine woudl happen is that the :test live loop will wait for an incoming OSC message, and when one occurs, it will set the delayosc variable before creating two new FX and then an internal live loop that uses the delayosc variable for the release duration of the :dark_ambience synth.

On the next incoming OSC message, it will create a new local delayosc variable, create two new FX synths (which will immediately be discarded because the internal live loop already exists and therefore will not generate a new thread for them to wrap). The input0 live loop will be updated to use the new delayosc value next time it spins round.

Is this not what you’re observing? Could you explain what you’re getting and what you expected it to do?

This is indeed what I’m observing.

But I thought and need the loops to start playing from the start continuously.
And only when an OSC message comes in it should change parameters of the variables. The issue is with multiple different OSC messages that if they are declared “globally” it only picks the first.

I need to put the OSC message in a separate live loop as you said above. So I thought that was away.

5 different OSC messages from 3 OSC devices should come in and alter already playing music. I’m not sure if I’m making it clear to you :sweat_smile:

I know what I want but I’m not sure how it would work.

@samaaron

I actually can’t find any info on multiple different machine incoming osc messages in the Ruby language. Do I have to rethink how to get multiple osc messages from different devices?

Buy a different wifi module instead of the wemos D1 mini

when you receive an osc message from a particular device it looks like
/osc:192.168.1.56:4560/cutoff for example if it is sent from a device with ip address 192.168.1.56 Normally you are not interested in the ip address of the sending device, and you can use a statement like
data = sync "/osc*/cutoff" to receive and extract the data. If however you want to differentiate between different devices you can sync on the entire input and use
data = sync "/osc:192.168.1.56:4560/cutoff"
This will ONLY sync to an osc command sent from that particular ip address. As Sam suggested earlier you could have a separate live_loop for each of your required OSC messages. maybe even 15 in total with 5 for each separate device.
Once you have the data, how you use it depends on what you want to control.
I’ll add a further post with some examples of controlling fx parameters with osc inputs tomorrow.

I’ll wait but from my knowledge so far (and that’s not much :sweat_smile:)
The sound of a live_loop won’t play without the OSC message coming in.

And I need the sound to be constantly playing from the moment I start it. And the OSC should only change the parameters.

Again I want to show my appreciation of your help @samaaron too, because every time it gets a little easier to understand how to work with the code and makes me keep coming back to realize my projects!

Here as promised is one example in which three osc commands independently control three parameters of sounding notes in a live loop. You could alternatively have one very long (even an hour) note using a fixed synth and control the parameters of that note such as pitch, volume, pan value using osc inputs. Let me know if you would like to see such an example. Wasn’t sure what you meant by continous sound.

use_osc "localhost",4560

#three osc messages. Normally sent from your hardware device
osc "/amp",0.4
osc "/cutoff",120
osc "/synth","saw" #you could use numbers and a lookup list to get the name of the synth instead

#three live loops to read the osc messages and get their data
#data is stored using set commands. The values are retireve dusing get commands in the test liveloop
live_loop :rx1 do
  use_real_time
  val = sync "/osc:127.0.0.1:4560/cutoff"
  puts val[0]
  set :lp,val[0]
end

live_loop :rx2 do
  use_real_time
  val = sync "/osc:127.0.0.1:4560/amp"
  puts val[0]
  set :vol,val[0]
end

live_loop :rx3 do
  use_real_time
  val= sync  "/osc:127.0.0.1:4560/synth"
  set :synthname,val[0]
end

#test live loop that plays a stream of ntoes with synth, volume and synth set by the osc commands
live_loop :test do
  cutoff=get(:lp)
  vol=get(:vol)
  synthname=get(:synthname)
  synth synthname,note: rrand_i(50,100),release: 0.2,cutoff: cutoff,amp: vol
  sleep 0.2
end

The osc commands could be sent from different ip addresses, in which case the rx liveloops would contain the relevant ip address as part of the sync string. They will always have to be port 4560 to work as Sonic Pi cues. You can extend the number of osc inputs and use several ip addresses together. If you dont care or need to separate which address they come from you can use sync “/osc*/cutoff” etc.

In the example above, you can change the values sent by the osc commands and re-run whenever you want. Your hardware may use rotary knobs or sliders to change values sent, in which case you may need to scale them.

2 Likes

Very good example @robin.newman. Thanks for your help !

One thing that may be useful is that wherever you use sync you can also use get.

So, instead of needing multiple live loops (:rx1, :rx2, :rx3) that are explicitly syncing on an incoming OSC message and then setting another time state entry such as (:lp, :vol, :synthname) with the incoming values, you can just call get["/osc:127.0.0.1:4560/synth"] directly and just need one live loop:

live_loop :test do
  co = get("/osc:127.0.0.1:4560/cutoff")[0]
  vol = get("/osc:127.0.0.1:4560/amp")[0]
  syn = get("/osc:127.0.0.1:4560/synth")[0]
  synth syn, note: rrand_i(50,100), release: 0.2, cutoff: co, amp: vol
  sleep 0.2
end
3 Likes

Hi @robin.newman

I’m using this.

use_osc "localhost",4560

#three osc messages. Normally sent from your hardware device
#osc "/mq4"
#osc "/cutoff",120
#osc "/synth","saw" #you could use numbers and a lookup list to get the name of the synth instead

#three live loops to read the osc messages and get their data
#data is stored using set commands. The values are retireve dusing get commands in the test liveloop
live_loop :rx1 do
  use_real_time
  val = sync "/osc:127.0.0.1:4560/mq4"
  puts val[0]
  set :lp,val[0]
end

live_loop :rx2 do
  use_real_time
  val = sync "/osc:127.0.0.1:4560/mq8"
  puts val[0]
  set :vol,val[0]
end

live_loop :rx3 do
  use_real_time
  val= sync  "/osc:127.0.0.1:4560/mq4"
  set :synthname,val[0]
end

#test live loop that plays a stream of ntoes with synth, volume and synth set by the osc commands
live_loop :test do
  cutoff=get(:lp)
  vol=get(:vol)
  synthname=get(:synthname)
  synth synthname,note: rrand_i(50,100),release: 0.2,cutoff: cutoff,amp: vol
  sleep 0.2
end 

And I get osc messages in sonic pi but I don’t hear or see anything altering due to the incoming messages…

What I’m I still doing wrong?

Looks like you are sending from an external device. change the osc sync lines to
from
val = sync "/osc:127.0.0.1:4560/mq4"
to
val= = sync "/osc*/mq4"
and simlarly for the other one.
val = sync "/osc*/mq8
This means they will respond to input from any ip address sending a message to /mq4