Oscy perc and notes EDITED FOR VERSION 2

Spent so long recently working on building packages of Sonic Pi I thought it was about time I used it!. Had a great afternoon playing with OSC commo ands controlling some live loops playing percussion instruments and notes. Found it was a great way to control things, syncing loops together, passing parameters to them, and even using a backdoor OSC call to stop the program at the end.

Hear it on Sound Cloud

Code is here (FOR VERSION I which has a potential problem:
(version 2 follows after. Preferable to use)

#an experimental piece entirely controlled by OSC messages that Sonic Pi
#sends to itself. This gives a great way of controlling running live_loops
#letting you synchronise then and pass paramters to control their operation
#written by Robin Newman April 2020
use_osc "localhost",4560 #send OSC commands to Sonic Pi port 4560
use_osc_logging false
use_cue_logging false

define :pvalue do #this functions reads from server-output.log
  #the dynamically allocated port used for incoming commands to sonic-pi server.
  value='' #initialise variable
  #read server-output.log
  File.open(ENV['HOME']+'/.sonic-pi/log/server-output.log','r') do |f1|
    while l = f1.gets
      if l.include?"Listen port:" #find line containing Listen port:
        value = l.split(" ").last.to_i #extract port
        break
      end
    end
    f1.close #close file
  end
  return value #return found port value
end

define :x do #functions gives x 1->4
  dice(4)
end

live_loop :tempo do #live loop gives variable bpm
  bpm= range(120,160,inclusive: true,step: 5).mirror.tick
  osc "/bpm",bpm
  use_bpm bpm
  sleep 4
end

#this live loop controls operations of the other loops below
live_loop :control,delay: 0.04 do #delay to make sure bpm value is set
  use_bpm get("/osc*/bpm")[0]
  #list of pattern timings
  plist=[[1,1,1,1],[1,0.5,1.5,1],[0.5,1.5,1.5,0.5],\
         [1,1.5,0.5,1],[2.5,0.5,0.5,0.5],[0.5,0.5,0.5,2.5]]
  n=rrand_i(0,5) #choose pattern to use
  pat=plist[n]
  #trigger notes and four sample live_loops
  osc "/one",x,2
  #notes parameters: synth and case value t 1 or 2
  osc "/notes",(["beep","saw","fm","tb303"].tick(:syn)),dice(2)
  sleep pat.tick #slepp times from current pattern
  osc "/two",x,1
  sleep pat.tick
  osc "/three",x,2
  sleep pat.tick
  osc "/four",x,0.3
  sleep pat.tick
end

#playing section below, wrapped in fx reverb and fx level
with_fx :reverb,room: 0.8,amp: 1.2,mix: 0.7 do
  with_fx :level,amp: 0.1 do |vol| #adjust the level as the tempo changes
    
    set :vol,vol #save control reference
    live_loop :controlVol,delay: 0.04 do #wait to make sure bpm value is set
      use_bpm get("/osc*/bpm")[0]
      #puts current_bpm
      v=range(0.1,1.0,inclusive: true,step: 0.1).mirror.tick #get next vol level
      tick(:end) if v==0.1 #bump every time vol is at lowest
      control get(:vol),amp: v,amp_slide: 4 #change vol with control slide
      sleep 4 #wait for end of current period
      puts "end tick #{look(:end)}"
      #choose tick(:end) value to finish below (I chose 7)
      #next osc message stops all jobs using dynamic port variable
      #note special port, and also needs a "guid" (can be anything)
      osc_send "localhost",pvalue,"/stop-all-jobs","rbnguid" if look(:end)==7
    end
    
    live_loop :notes do
      use_real_time
      s,t=sync "/osc*/notes" #trigger and get synth and t value
      use_synth s.to_sym #convert s from string to symbol
      use_bpm get("/osc*/bpm")[0] #get current bpm
      case t #select order for playing notes
      when 1
        8.times do
          play scale(:c4,:minor_pentatonic,num_octaves:2).choose,release: 0.1,pan: -0.7
          sleep 0.25
        end
        play :c5,release: 0.1
        #note dont need sleep here as synced by nect osc message
      when 2
        play :c5,release: 0.1
        sleep 2
        7.times do #only 7 notes so finished before next osc message
          play scale(:c4,:minor_pentatonic,num_octaves:2).reverse.tick,release: 0.1,pan: 0.7
          sleep 0.25
        end
      end
    end
    
    #You can try adding a variety of the modifiers for each sample
    #of the form:   if c <condition> some examples shown
    live_loop :one do
      use_real_time
      c,v = sync "/osc*/one" #parameters are c (1-4) and volume v
      sample :bd_haus,amp: v if c >1 #optional variety using c
    end
    
    live_loop :two do
      use_real_time
      c,v = sync "/osc*/two"
      sample :drum_cymbal_closed,amp: v  #if c <2 #optional
    end
    
    live_loop :three do
      use_real_time
      c,v = sync "/osc*/three"
      sample :drum_cymbal_closed,amp: v #if c != 3 #optional
    end
    
    live_loop :four do
      use_real_time
      c,v = sync "/osc*/four"
      sample :perc_bell,amp: v if c !=4 #optional
    end
  end #level
end#reverb

VERSION 2 CODE (eliminates get "/osc*… statements which could fail in their timing:

#an experimental piece entirely controlled by OSC messages that Sonic Pi
#sends to itself. This gives a great way of controlling running live_loops
#letting you synchronise then and pass paramters to control their operation
#written by Robin Newman April 2020
#VERSION 2 avoiding get("/osc*... statements
use_osc "localhost",4560 #send OSC commands to Sonic Pi port 4560
use_osc_logging false
use_cue_logging false

define :pvalue do #this functions reads from server-output.log
  #the dynamically allocated port used for incoming commands to sonic-pi server.
  value='' #initialise variable
  #read server-output.log
  File.open(ENV['HOME']+'/.sonic-pi/log/server-output.log','r') do |f1|
    while l = f1.gets
      if l.include?"Listen port:" #find line containing Listen port:
        value = l.split(" ").last.to_i #extract port
        break
      end
    end
    f1.close #close file
  end
  return value #return found port value
end

define :x do #functions gives x 1->4
  dice(4)
end

live_loop :tempo do #live loop gives variable bpm
  bpm= range(120,160,inclusive: true,step: 5).mirror.tick
  osc "/bpm",bpm
  use_bpm bpm
  sleep 4
end

#this live loop controls operations of the other loops below
live_loop :control do
  bpm = sync "/osc*/bpm"
  use_bpm bpm[0]
  #list of pattern timings
  plist=[[1,1,1,1],[1,0.5,1.5,1],[0.5,1.5,1.5,0.5],\
         [1,1.5,0.5,1],[2.5,0.5,0.5,0.5],[0.5,0.5,0.5,2.5]]
  n=rrand_i(0,5) #choose pattern to use
  pat=plist[n]
  #trigger notes and four sample live_loops
  osc "/one",x,2
  #notes parameters: synth and case value t 1 or 2
  osc "/notes",(["beep","saw","fm","tb303"].tick(:syn)),dice(2)
  sleep pat.tick #slepp times from current pattern
  osc "/two",x,1
  sleep pat.tick
  osc "/three",x,2
  sleep pat.tick
  osc "/four",x,0.3
  tick #to keep tick counter at correct value since ignoring 4th entry
  #sleep pat.tick #this sleep value provided by sync at start of next pass
end

#playing section below, wrapped in fx reverb and fx level
with_fx :reverb,room: 0.8,amp: 1.2,mix: 0.7 do
  with_fx :level,amp: 0.1 do |vol| #adjust the level as the tempo changes
    
    set :vol,vol #save control reference
    live_loop :cVol do #loop controls leve amp setting
      bpm = sync ("/osc*/bpm")
      use_bpm bpm[0]
      puts current_bpm
      v=range(0.1,1.0,inclusive: true,step: 0.1).mirror.tick #get next vol level
      tick(:end) if v==0.1 #bump every time vol is at lowest
      puts "end tick #{look(:end)}"
      control get(:vol),amp: v,amp_slide: 4 #change vol with control slide
      #choose tick(:end) value to finish below (I chose 7)
      if look(:end)==7 #check for finish
        sleep 4
        #next osc message stops all jobs using dynamic port variable
        #note special port, and also needs a "guid" (can be anything)
        osc_send "localhost",pvalue,"/stop-all-jobs","rbnguid"
      end
    end
    
    live_loop :notes do
      use_real_time
      s,t=sync "/osc*/notes" #trigger and get synth and t value
      use_synth s.to_sym #convert s from string to symbol
      bpm= get("/osc*/bpm") #get current bpm
      use_bpm bpm[0]
      case t #select order for playing notes
      when 1
        8.times do
          play scale(:c4,:minor_pentatonic,num_octaves:2).choose,release: 0.1,pan: -0.7
          sleep 0.25
        end
        play :c5,release: 0.1
        #note dont need sleep here as synced by nect osc message
      when 2
        play :c5,release: 0.1
        sleep 2
        7.times do #only 7 notes so finished before next osc message
          play scale(:c4,:minor_pentatonic,num_octaves:2).reverse.tick,release: 0.1,pan: 0.7
          sleep 0.25
        end
      end
    end
    
    #You can try adding a variety of the modifiers for each sample
    #of the form:   if c <condition> some examples shown
    live_loop :one do
      use_real_time
      c,v = sync "/osc*/one" #parameters are c (1-4) and volume v
      sample :bd_haus,amp: v if c >1 #optional variety using c
    end
    
    live_loop :two do
      use_real_time
      c,v = sync "/osc*/two"
      sample :drum_cymbal_closed,amp: v  #if c <2 #optional
    end
    
    live_loop :three do
      use_real_time
      c,v = sync "/osc*/three"
      sample :drum_cymbal_closed,amp: v #if c != 3 #optional
    end
    
    live_loop :four do
      use_real_time
      c,v = sync "/osc*/four"
      sample :perc_bell,amp: v if c !=4 #optional
    end
  end #level
end#reverb
3 Likes

Good morning @robin.newman,

For a sunday morning, it’s a serious piece of code full of good ideas as you always do ! Thanks to share with the community.

So i copy / paste into my spi 3.2.2 on ubuntu 18.04.04 and i get this error blocking the script


by the way, where can i find the output error displayed into the small window into a log file ? didn’t find.
Cheers

This occurs because the second line of that live loop :controlVol was checking for the last set value of bpm in the line
use_bpm get("/osc*/bpm")[0]
and it did so too early. I set a delay of 0.04 in this loop. Try increasing it to 0.1

Same should be changed for the delay in the live_loop :control

Often you find it works the second time you run the program, as the time to set up the live loops is only required once.
I may try and rework this to use sync statements here which would remove the problem.

In the following post, your free Mode 239 i not found may be related to this trying to get a value before it has been set. In general node errors can be ignored. Sonic Pi deals with them internally.

Thanks ! it works as expected

I suspect that the file handling of the Peale function also affects the first run timings. It was a late addition to the code. I’ll definitely look at eliminating the error. I think it may well occur frequently on first runs.

I have now added version 2 which eliminates the get "/osc * … statements.
I am using sync = "/osc*… instead in the two live_loops that used get before. In order to keep the synchromisation I alter the timing of the last element in each loop by removing it, and letting the loop wait for the next sync instead which achieves the same effect. In the case of the patterms in the :control live_loop I use a tick at the end of the loop to keep the pattern in sync, but ignore the associated sleep time for the last element of each 4.