Skip to content

Instantly share code, notes, and snippets.

@rbnpi
Last active March 14, 2017 16:27
Show Gist options
  • Save rbnpi/0545a509d96066ae5c6b311ebe98a5b3 to your computer and use it in GitHub Desktop.
Save rbnpi/0545a509d96066ae5c6b311ebe98a5b3 to your computer and use it in GitHub Desktop.
Code for remote controlled SP with processing GUI controller
These files accompany a video at https://youtu.be/K1KQSFiHg6I which explores the use of a GUI to control a Sonic Pi program
running code to drive an external midi player. (I used Logic Pro) It currently works with SP2.12.0-midi-alpha1 on a Mac,
but may probably break on future versions. It is intended to show some concepts, particularly the use of a GUI written
in processing which can control SP by means of OSC messages. The source code of the processing sketch is included.
It also illustrates a technique to allow a Sonic Pi piece to be started at any bar, by calculating the start note for
each of the parts concerned. It can handle a tied note already playing at the start of the selected bar.
The final technique utilised is to use an external script utilising calls to the Sonic Pi cli interface gem to stop
Sonic Pi running and then to restart the program using a run_file command. In this way, Sonic Pi can respond to
a stop singal from the GUI and then restart playing when a subsequent Play cue is received via an OSC message.
There are some further bits you will ahve to sort out. EG setting up the audio midi program so that Sonic PI can
communicate with your midi player. See a picture in the video which shows this.
FILES: BWV588.rb is run in a SP buffer. BWV588auto.rb is accessed from a specified location by BWV588.rb
Processing 3 should be installed and then the StartBarSelector.txt is pasted into a new sketch, which is then run to create
the GUI. You need to speficy the address of the machine running SP in the script, (or leave if the local machine)
#Bach Canzona in D minor BWV588 converted from midi file via MuseScore and processing script
s=[] #holds cue received parameters
bs=1#start bar initial value
bpb=4 #beats per bar
globalvol=100
until s[0]==1 #loop until play signal arrived
s=sync '/transport'
puts s
end
bs=s[1] #extract startbar number from cue info
puts "BS selected is "+bs.to_s
in_thread do #start thread waiting for stop cue
s=[]
until s[0]==-1
s=sync '/transport'
puts s
end
puts"stopping"
puts "running sonic pi cli script to restart"
#run external sonic_cli script to stop and rerun
system("/Users/rbn/Documents/SPfromXML/BWV588Auto.rb &")
end
#define routine to play midi notes/durations lists
#s parameter allows list to start in the middle
define :mplay do |notes,durs,channel,s=0,vel=globalvol|
for x in s..notes.length-1
#puts x #for debugging
if notes[x].respond_to?(:each)
notes[x].each do |n|
midi n+current_transpose,dur: durs[x],channel: channel,vel: vel
end
else
midi notes[x]+current_transpose,dur: durs[x],channel: channel,vel: vel #if notes[x] !=:r
end
sleep durs[x]
end
end
#efine routine to work out start position from bar number
define :getmatchd do|bn,bpb,durations|
matchbeat=(bn-1)*bpb
l=0.0;x=0
until l>=matchbeat || (l-matchbeat).abs < 0.125
l+=durations[x]
x+=1
end
return [x ,l-matchbeat] #start pointer plus sleep time
end
a1=[]
b1=[]
a1[0]=[:r,:r,:r,:r,:r,:r,:r,:r,:r,:r,:r,:r,:r,:r,:r,:r,:r,:r,:r,:r,:r,:r,:A4,:D5,:D5,:F5,:E5,:D5,:C5,:D5,:E5,:D5,:C5,:B4,:Cs5,:D5,:A4,:D5,:C5,:B4,:C5,:B4,:A4,:B4,:A4,:Af4,:A4,:Af4,:A4,:B4,:Af4,:A4,:A4,:B4,:Cs5,:B4,:Cs5,:D5,:C5,:Bf4,:A4,:G4,:A4,:Bf4,:A4,:Bf4,:C5,:F5,:E5,:D5,:G5,:F5,:E5,:C5,:F5,:Ef5,:D5,:C5,:Bf4,:A4,:r,:r,:r,:D5,:Cs5,:C5,:B4,:Bf4,:A4,:G4,:A4,:D5,:G4,:A4,:G4,:Fs4,:E4,:D4,:G4,:Fs4,:E4,:Fs4,:G4,:r,:F5,:E5,:A5,:G5,:F5,:E5,:D5,:Bf5,:A5,:G5,:C6,:C6,:Bf5,:A5,:Bf5,:Bf5,:A5,:G5,:A5,:A4,:B4,:Cs5,:D5,:E5,:F5,:E5,:D5,:G5,:F5,:E5,:F5,:A5,:G5,:F5,:G5,:F5,:E5,:D5,:C5,:D5,:E5,:D5,:C5,:B4,:C5,:D5,:Cs5,:D5,:E5,:A4,:B4,:C5,:B4,:C5,:D5,:Af4,:Fs4,:E4,:A4,:G4,:Fs4,:Af4,:A4,:Af4,:D5,:C5,:B4,:C5,:D5,:E5,:F5,:E5,:D5,:E5,:D5,:Cs5,:D5,:Cs5,:C5,:B4,:Bf4,:A4,:C5,:A4,:D5,:F5,:D5,:G5,:A5,:Bf5,:D5,:D5,:D5,:E5,:F5,:G5,:Cs5,:D5,:D5]
b1[0]=[4.0,4.0,4.0,4.0,4.0,4.0,4.0,4.0,4.0,4.0,4.0,4.0,4.0,4.0,4.0,4.0,4.0,4.0,4.0,4.0,4.0,2.0,2.0,3.0,1.0,1.0,0.5,0.5,1.0,1.0,1.0,0.5,0.5,1.0,1.0,1.0,1.0,3.0,0.5,0.5,3.0,0.5,0.5,3.0,0.5,0.5,2.5,0.5,0.5,0.5,1.5,0.5,1.0,1.0,1.0,0.5,0.5,2.0,2.0,2.0,2.0,1.0,1.0,1.0,0.5,0.5,1.0,0.5,0.5,1.0,0.5,0.5,0.5,0.5,2.0,1.0,2.0,2.0,4.0,2.0,2.0,4.0,2.0,2.0,2.0,2.0,2.0,1.0,0.5,0.5,3.0,1.0,3.0,0.5,0.5,1.0,0.5,0.5,3.0,0.5,0.5,2.0,2.0,1.0,1.0,2.0,0.5,0.5,0.5,0.5,2.5,0.5,0.5,0.5,2.5,0.5,0.5,0.5,2.0,0.5,0.5,1.0,0.5,0.5,0.5,0.5,0.25,0.25,0.5,0.5,0.5,3.0,0.5,0.5,1.0,0.5,0.5,0.5,0.5,0.5,0.5,1.0,0.5,0.5,1.0,0.5,0.5,1.5,0.5,0.5,0.5,0.5,0.5,1.0,0.5,0.5,1.0,0.5,0.5,1.0,0.5,0.5,1.5,0.5,1.0,0.5,0.5,1.0,2.0,0.5,0.5,1.0,2.0,1.0,3.0,0.5,0.5,3.0,0.5,0.5,2.0,2.0,2.0,2.0,2.0,3.0,0.5,0.5,1.0,0.5,0.5,1.0,0.5,0.5,1.5,0.5,1.0,0.5,0.25,0.25,1.5,0.5,2.0]
c1=[120]
sv1= getmatchd(bs,bpb,b1[0])
puts "1: "+sv1.to_s
in_thread do
use_bpm c1[0]
sleep sv1[1] #sleep if tied note found over start barline
for i in 0..a1.length-1
use_bpm c1[i]
mplay(a1[i],b1[i],1,sv1[0])
sv1=[0,0] #reset in case of tempo change
end
end
a2=[]
b2=[]
a2[0]=[:r,:r,:r,:r,:r,:r,:r,:r,:r,:r,:r,:r,:r,:r,:r,:G4,:D5,:D5,:Ef5,:D5,:C5,:Bf4,:C5,:D5,:C5,:Bf4,:A4,:Bf4,:C5,:G4,:C5,:Bf4,:A4,:Bf4,:A4,:G4,:A4,:D4,:G4,:A4,:Bf4,:A4,:Bf4,:C5,:D5,:D5,:Cs5,:C5,:B4,:Bf4,:A4,:G4,:A4,:D5,:Bf4,:A4,:A4,:Af4,:A4,:G4,:Fs4,:G4,:A4,:G4,:A4,:B4,:A4,:B4,:C5,:Bf4,:A4,:Bf4,:C5,:Bf4,:C5,:D5,:C5,:Bf4,:Ef5,:D5,:C5,:F5,:Ef5,:D5,:C5,:Bf4,:A4,:Bf4,:C5,:Fs4,:G4,:A4,:Bf4,:A4,:B4,:C5,:B4,:C5,:C5,:Bf4,:A4,:G4,:r,:r,:r,:r,:r,:r,:G4,:D5,:D5,:Ef5,:D5,:C5,:Bf4,:C5,:D5,:C5,:Bf4,:A4,:Bf4,:C5,:G4,:C5,:Bf4,:A4,:Bf4,:A4,:G4,:A4,:D4,:G4,:Fs4,:E4,:Fs4,:G4,:r,:r,:D5,:Cs5,:C5,:B4,:Bf4,:A4,:D4,:G4,:A4,:B4,:C5,:D5,:D5,:C5,:D5,:Ef5,:A4,:r,:r,:D5,:C5,:Bf4,:A4,:G4,:A4,:Bf4,:A4,:Cs5,:Cs5,:Cs5,:A4,:G4,:A4,:Bf4]
b2[0]=[4.0,4.0,4.0,4.0,4.0,4.0,4.0,4.0,4.0,4.0,4.0,4.0,4.0,4.0,2.0,2.0,3.0,1.0,1.0,0.5,0.5,1.0,1.0,1.0,0.5,0.5,1.0,1.0,1.0,1.0,3.0,0.5,0.5,3.0,0.5,0.5,3.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,2.0,2.0,2.0,2.0,2.0,1.0,0.5,0.5,3.0,1.0,2.0,2.0,3.0,1.0,3.0,1.0,1.0,1.0,2.0,1.0,1.0,1.0,0.5,0.5,2.0,2.0,1.0,1.0,1.0,0.5,0.5,1.0,1.0,1.0,2.0,1.0,1.0,2.0,2.0,2.0,0.5,0.5,1.0,0.5,0.5,2.0,1.0,0.5,0.5,1.0,0.5,0.5,1.5,0.5,1.0,0.5,0.5,1.0,1.0,4.0,4.0,4.0,4.0,2.0,2.0,3.0,1.0,1.0,0.5,0.5,1.0,1.0,1.0,0.5,0.5,1.0,1.0,1.0,1.0,3.0,0.5,0.5,3.0,0.5,0.5,3.0,1.0,3.0,0.5,0.5,2.0,2.0,2.0,2.0,2.0,2.0,2.0,2.0,2.0,3.0,1.0,4.0,3.0,1.0,2.0,2.0,4.0,3.0,0.5,0.5,1.0,1.0,1.0,1.0,1.0,0.5,0.5,1.0,1.0,6.0,2.0,1.5,0.5,1.0,0.5,0.5,2.0,2.0]
c2=[120]
sv2= getmatchd(bs,bpb,b2[0])
puts "2: "+sv2.to_s
in_thread do
use_bpm c2[0]
sleep sv2[1]
use_transpose -5 #this part was written for a transposed instrument
for i in 0..a2.length-1
use_bpm c2[i]
mplay(a2[i],b2[i],2,sv2[0])
sv2=[0,0]
end
end
a3=[]
b3=[]
a3[0]=[:r,:r,:r,:r,:r,:r,:r,:r,:A3,:D4,:D4,:F4,:E4,:D4,:C4,:D4,:E4,:D4,:C4,:B3,:C4,:D4,:A3,:D4,:C4,:B3,:C4,:B3,:A3,:B3,:A3,:B3,:Cs4,:D4,:r,:E4,:F4,:r,:D4,:Cs4,:C4,:B3,:Bf3,:A3,:G3,:A3,:D4,:G3,:A3,:G3,:F3,:G3,:A3,:Bf3,:A3,:G3,:F3,:G3,:A3,:B3,:A3,:G3,:A3,:B3,:Af3,:A3,:F4,:E4,:D4,:C4,:B3,:C4,:B3,:A3,:B3,:A3,:r,:r,:r,:r,:r,:r,:r,:r,:D3,:A3,:A3,:Bf3,:A3,:G3,:F3,:G3,:A3,:G3,:F3,:E3,:Fs3,:G3,:D3,:G3,:F3,:E3,:F3,:Ef3,:D3,:Ef3,:D3,:Ef3,:D3,:C3,:D3,:D4,:Cs4,:C4,:B3,:Bf3,:A3,:D4,:G3,:F3,:Bf3,:E3,:A3,:r,:r,:r,:r,:r,:r,:D3,:A3,:A3,:Bf3,:A3,:G3,:F3,:G3,:A3,:G3,:F3,:E3,:F3,:G3,:D3,:G3,:Fs3,:E3,:Fs3,:G3,:A3,:Bf3,:C4,:D4,:F4,:F4,:F4,:E4,[:A3,:D4]]
b3[0]=[4.0,4.0,4.0,4.0,4.0,4.0,4.0,2.0,2.0,3.0,1.0,1.0,0.5,0.5,1.0,1.0,1.0,0.5,0.5,1.0,1.0,1.0,1.0,3.0,0.5,0.5,3.0,0.5,0.5,2.0,1.0,0.5,0.5,2.0,1.0,1.0,2.0,2.0,2.0,2.0,2.0,2.0,1.0,0.5,0.5,3.0,1.0,3.0,0.5,0.5,3.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,2.0,3.0,0.5,0.5,2.0,3.0,1.0,1.0,0.5,0.5,3.0,0.5,0.5,3.0,0.5,0.5,4.0,4.0,4.0,4.0,4.0,4.0,4.0,4.0,4.0,2.0,2.0,3.0,1.0,1.0,0.5,0.5,1.0,1.0,1.0,0.5,0.5,1.0,1.0,1.0,1.0,3.0,0.5,0.5,3.0,0.5,0.5,2.0,3.0,0.5,0.5,2.0,4.0,2.0,2.0,2.0,2.0,2.0,3.0,1.0,4.0,3.0,1.0,2.0,4.0,2.0,4.0,4.0,4.0,4.0,2.0,2.0,3.0,1.0,1.0,0.5,0.5,1.0,1.0,1.0,0.5,0.5,1.0,1.0,1.0,1.0,3.0,0.5,0.5,2.0,1.0,1.0,1.0,1.0,8.0,1.5,0.5,2.0,2.0,2.0]
c3=[120]
sv3= getmatchd(bs,bpb,b3[0])
puts "3: "+sv3.to_s
in_thread do
use_bpm c3[0]
sleep sv3[1]
for i in 0..a3.length-1
use_bpm c3[i]
mplay(a3[i],b3[i],3,sv3[0])
sv3=[0,0]
end
end
a=[]
b=[]
a[0]=[:r,:D3,:A3,:A3,:Bf3,:A3,:G3,:F3,:G3,:A3,:G3,:F3,:E3,:F3,:G3,:D3,:G3,:F3,:E3,:F3,:E3,:D3,:E3,:D3,:E3,:F3,:E3,:F3,:G3,:A3,:A3,:Af3,:G3,:Fs3,:F3,:E3,:D3,:E3,:A3,:D3,:C3,:D3,:E3,:F3,:E3,:D3,:Cs3,:D3,:G2,:A2,:Bf2,:A2,:A3,:G3,:E3,:Cs3,:D3,:B2,:Cs3,:D3,:E3,:F3,:E3,:D3,:C3,:B2,:A3,:Af3,:E3,:A3,:A3,:Af3,:A3,:Fs3,:E3,:A2,:A3,:Bf3,:A3,:G3,:A3,:Bf3,:A3,:Bf3,:C4,:Bf3,:A3,:Bf3,:C4,:r,:C3,:D3,:E3,:Fs3,:E3,:Fs3,:G3,:A3,:G3,:F3,:E3,:D3,:Cs3,:D3,:G2,:A2,:Bf2,:A2,:G2,:E3,:Cs3,:A2,:D3,:C3,:B2,:C3,:Bf2,:A2,:D3,:G2,:r,:r,:r,:r,:r,:r,:r,:r,:A2,:D3,:D3,:F3,:E3,:D3,:C3,:D3,:E3,:D3,:C3,:B2,:Cs3,:D3,:A2,:D3,:C3,:B2,:C3,:B2,:A2,:B2,:A2,:D3,:G2,:D3,:Cs3,:C3,:B2,:Bf2,:A2,:D3,:G2,:G3,:F3,:A3,:F3,:Bf3,:B3,:B3,:B3,:A3,:D3]
b[0]=[2.0,2.0,3.0,1.0,1.0,0.5,0.5,1.0,1.0,1.0,0.5,0.5,1.0,1.0,1.0,1.0,3.0,0.5,0.5,3.0,0.5,0.5,2.0,3.0,1.0,1.0,1.0,1.0,1.0,2.0,2.0,2.0,2.0,2.0,1.0,0.5,0.5,3.0,1.0,1.0,1.0,1.0,1.0,3.0,0.5,0.5,2.0,2.0,1.0,1.0,2.0,2.0,2.0,2.0,2.0,2.0,2.0,2.0,2.0,9.0,1.0,2.0,4.0,1.0,1.0,1.0,1.0,1.0,1.0,2.0,2.0,2.0,2.0,2.0,4.0,2.0,2.0,2.0,2.0,1.0,1.0,1.0,0.5,0.5,2.0,2.0,2.0,2.0,3.0,0.5,0.5,1.5,0.5,1.0,0.5,0.5,5.0,0.5,0.5,1.0,0.5,0.5,2.0,2.0,1.0,1.0,2.0,4.0,3.0,1.0,1.0,1.0,1.5,0.5,2.0,4.0,2.0,2.0,2.0,2.0,2.0,4.0,4.0,4.0,4.0,4.0,2.0,1.0,1.0,3.0,1.0,1.0,0.5,0.5,1.0,1.0,1.0,0.5,0.5,1.0,1.0,1.0,1.0,3.0,0.5,0.5,3.0,0.5,0.5,2.0,3.0,1.0,2.0,2.0,2.0,2.0,2.0,2.0,2.0,2.0,2.0,2.0,1.0,0.5,0.5,6.0,1.5,0.5,2.0,2.0,2.0]
c=[120]
sv0= getmatchd(bs,4,b[0])
puts "0: "+sv0.to_s
in_thread do
use_bpm c[0]
sleep sv0[1]
for i in 0..a.length-1
use_bpm c[i]
mplay(a[i],b[i],4,sv0[0])
sv0=[0,0]
end
end
#!/usr/bin/ruby
`/usr/local/bin/sonic_pi stop`
`/usr/local/bin/sonic_pi "run_file '/Users/rbn/Documents/SPfromXML/BWV588Midi.rb'"`
#This file stops SP and then runs the BWV588Midi.rb file again using a run_file command
#it utilises the sonic-pi-cli gem
#adjust path for BWV588Midi.rb to suit your installtion
#install sonic-pi-cli for system ruby in /usr/bin/ruby
#use sudo gem install sonic-pi-cli
#This should give binary /usr/local/bin/sonic_pi
#If you use rvm, you can install the gem for your current ruby, but you need to use a wrapper to run it
#using the system ruby supplied by apple is easier here
import oscP5.*;
import netP5.*;
OscP5 oscP5;
NetAddress sonicPi;
float mx,my;
int bs = 1;
int tr = 0;
PFont f;
void setup(){
background(204);
frameRate(60);
size(100,120);
oscP5 = new OscP5(this, 8000);
sonicPi = new NetAddress("127.0.0.1",4559);//////INSERT ADDRESS REQUIRED//////////////
// add click rectangles
rect(45,15,10,10);//bs+
rect(45,55,10,10);//bs=1
rect(45,90,10,10);//bs-
rect(10,55,10,10);//stop
rect(80,55,10,10);//play
// Create the font
f = createFont("ArialNarrow-16.vlw", 12);
textFont(f);
textAlign(CENTER, CENTER);
fill(0);
text("stop",15,40);//add screen captions
text("play",85,40);
text("bs+",50,4);
text("bs-",50,105);
text("bs1",50,72);
text("1",48,40); //print initialbs value
}
void sendOscData(int tr,int bs) {
OscMessage toSend = new OscMessage("/transport");
toSend.add(tr);
toSend.add(bs);
oscP5.send(toSend,sonicPi);
//println(toSend);//for debugging
}
void draw() {
mx=mouseX;
my=mouseY;
if (mousePressed) {
if ((mx > 44) && (mx <56 )&&(my > 89) && (my < 101)){//bs- clicked
bs-=1;
if (bs<1){
bs=1;
}
//println(bs);//for debugging
}
if ((mx > 44) && (mx < 56)&&(my >14) && (my<26)) {//bs+ clicked
bs+=1;
//println(bs);//for debugging
}
if ((mx >44)&&(mx<56)&&(my>54) && (my<66)){//bs=1 clicked
bs=1;
//println(bs);//for debugging
}
if ((mx >79)&&(mx<91)&&(my>54) && (my<66)){//play clicked
tr=1;
sendOscData(tr,bs);
//println(tr); //for debugging
//println(bs);
}
if ((mx >9)&&(mx<21)&&(my>54) && (my<66)) {//stop clicked
tr=-1;
sendOscData(tr,bs);
//println(tr); //for debugging
}
fill(204); //update bs number on the screen
noStroke(); //clear last entry with a background rectangle
rect(30,32,35,16);
stroke(255); //change stroke colour to black
fill(0); //set fill to black
text(bs,48,40); //print bs value
}
delay(125); //loop delay
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment