Skip to content

Instantly share code, notes, and snippets.

@rbnpi
Last active November 18, 2019 10:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rbnpi/12e05e0d401cfe2d4db710c75be7f178 to your computer and use it in GitHub Desktop.
Save rbnpi/12e05e0d401cfe2d4db710c75be7f178 to your computer and use it in GitHub Desktop.
JukeBox script to run programs in Sonic Pi using a TouchOSC frontend, and associated scripts to start Sonic Pi headless. See comment to load index.html file Article at https://rbnrpi.wordpress.com/a-touchosc-jukebox-for-sonicpi/ video at https://youtu.be/G8DOquunrFc
@reboot /home/pi/Desktop/startsp.sh
<?xml version="1.0" encoding="UTF-8"?><layout version="15" mode="0" orientation="horizontal"><tabpage name="amI=" scalef="0.0" scalet="1.0" ><control name="ZjI=" x="13" y="134" w="290" h="18" color="gray" scalef="0.0" scalet="1.0" type="push" local_off="false" ></control><control name="ZjE=" x="13" y="100" w="290" h="18" color="gray" scalef="0.0" scalet="1.0" type="push" local_off="false" ></control><control name="ZjEw" x="15" y="410" w="290" h="18" color="gray" scalef="0.0" scalet="1.0" type="push" local_off="false" ></control><control name="Zjk=" x="15" y="375" w="290" h="18" color="gray" scalef="0.0" scalet="1.0" type="push" local_off="false" ></control><control name="Zjg=" x="15" y="341" w="290" h="18" color="gray" scalef="0.0" scalet="1.0" type="push" local_off="false" ></control><control name="Zjc=" x="14" y="306" w="290" h="18" color="gray" scalef="0.0" scalet="1.0" type="push" local_off="false" ></control><control name="ZjQ=" x="17" y="203" w="290" h="18" color="gray" scalef="0.0" scalet="1.0" type="push" local_off="false" ></control><control name="ZjU=" x="15" y="237" w="290" h="18" color="gray" scalef="0.0" scalet="1.0" type="push" local_off="false" ></control><control name="ZjM=" x="15" y="168" w="290" h="18" color="gray" scalef="0.0" scalet="1.0" type="push" local_off="false" ></control><control name="ZjY=" x="15" y="272" w="290" h="18" color="gray" scalef="0.0" scalet="1.0" type="push" local_off="false" ></control><control name="bjE=" x="13" y="100" w="290" h="18" color="yellow" type="labelh" text="" size="14" background="false" outline="true" ></control><control name="bjI=" x="13" y="133" w="290" h="20" color="yellow" type="labelh" text="" size="14" background="false" outline="true" ></control><control name="bjM=" x="16" y="168" w="290" h="20" color="yellow" type="labelh" text="" size="14" background="false" outline="true" ></control><control name="bjQ=" x="16" y="202" w="290" h="20" color="yellow" type="labelh" text="" size="14" background="false" outline="true" ></control><control name="bjU=" x="15" y="236" w="290" h="20" color="yellow" type="labelh" text="" size="14" background="false" outline="true" ></control><control name="bjc=" x="15" y="305" w="290" h="20" color="yellow" type="labelh" text="" size="14" background="false" outline="true" ></control><control name="bjg=" x="15" y="340" w="290" h="20" color="yellow" type="labelh" text="" size="14" background="false" outline="true" ></control><control name="bjk=" x="15" y="374" w="290" h="20" color="yellow" type="labelh" text="" size="14" background="false" outline="true" ></control><control name="bjEw" x="15" y="409" w="290" h="20" color="yellow" type="labelh" text="" size="14" background="false" outline="true" ></control><control name="bjY=" x="15" y="271" w="290" h="20" color="yellow" type="labelh" text="" size="14" background="false" outline="true" ></control><control name="bGFiZWw0" x="83" y="62" w="20" h="25" color="red" type="labelh" text="LQ==" size="14" background="true" outline="false" ></control><control name="cHJldg==" x="12" y="59" w="33" h="33" color="blue" scalef="0.0" scalet="1.0" type="push" local_off="false" ></control><control name="c3RvcA==" x="144" y="59" w="33" h="33" color="red" scalef="0.0" scalet="1.0" type="push" local_off="false" ></control><control name="bmV4dA==" x="269" y="59" w="33" h="33" color="green" scalef="0.0" scalet="1.0" type="push" local_off="false" ></control><control name="dGl0bGU=" x="48" y="10" w="224" h="20" color="yellow" type="labelh" text="U29uaWMgUGkgSnVrZWJveCBJbnRlcmZhY2U=" size="18" background="true" outline="false" ></control><control name="c3RhcnQ=" x="49" y="64" w="39" h="24" color="red" type="labelh" text="" size="14" background="false" outline="true" ></control><control name="ZmluaXNo" x="98" y="64" w="39" h="24" color="red" type="labelh" text="" size="14" background="false" outline="true" ></control><control name="dG90YWw=" x="226" y="64" w="39" h="24" color="red" type="labelh" text="" size="14" background="false" outline="true" ></control><control name="bGFiZWw1" x="179" y="63" w="46" h="25" color="red" type="labelh" text="dG90YWw=" size="14" background="true" outline="false" ></control><control name="bGVkMQ==" x="305" y="101" w="15" h="15" color="red" scalef="0.0" scalet="1.0" type="led" ></control><control name="bGVkMg==" x="305" y="135" w="15" h="15" color="red" scalef="0.0" scalet="1.0" type="led" ></control><control name="bGVkMw==" x="305" y="169" w="15" h="15" color="red" scalef="0.0" scalet="1.0" type="led" ></control><control name="bGVkNA==" x="305" y="204" w="15" h="15" color="red" scalef="0.0" scalet="1.0" type="led" ></control><control name="bGVkNQ==" x="305" y="238" w="15" h="15" color="red" scalef="0.0" scalet="1.0" type="led" ></control><control name="bGVkNg==" x="305" y="273" w="15" h="15" color="red" scalef="0.0" scalet="1.0" type="led" ></control><control name="bGVkNw==" x="305" y="307" w="15" h="15" color="red" scalef="0.0" scalet="1.0" type="led" ></control><control name="bGVkOA==" x="305" y="342" w="15" h="15" color="red" scalef="0.0" scalet="1.0" type="led" ></control><control name="bGVkOQ==" x="305" y="376" w="15" h="15" color="red" scalef="0.0" scalet="1.0" type="led" ></control><control name="bGVkMTA=" x="305" y="411" w="15" h="15" color="red" scalef="0.0" scalet="1.0" type="led" ></control><control name="bGFiZWw2" x="11" y="35" w="94" h="20" color="purple" type="labelh" text="RW5hYmxlIEF1ZGlvIElu" size="12" background="true" outline="false" ></control><control name="YXVkaW8taW4=" x="14" y="7" w="25" h="25" color="purple" scalef="0.0" scalet="1.0" type="toggle" local_off="false" ></control><control name="Y2xlYXI=" x="277" y="7" w="25" h="25" color="purple" scalef="0.0" scalet="1.0" type="push" local_off="false" ></control><control name="bGFiZWw3" x="213" y="35" w="89" h="20" color="purple" type="labelh" text="Q2xlYXIgU2FtcGxlcw==" size="12" background="true" outline="false" ></control></tabpage></layout>
# Sonic Pi init file
# Code in here will be evaluated on launch.
system('/home/pi/Desktop/jb.py --ip 172.24.1.89 --tip 172.24.1.127 >/dev/null 2>&1 &')
play 72,sustain: 2
#!/usr/bin/env python3
#TouchOSC jukebox interface for Sonic Pi by Robin
#Newman, Aug 2017
##################USER CONFIGURATION#########################
#specify full folder path containing playable SP3 files
SPfolder="/home/pi/Documents/SPfromXML/" #with trailing /
#specify full run path for sonic_pi Command Line Interface
c="/usr/local/bin/sonic_pi "
#################END OF USER CONFIGURATION############
from pythonosc import osc_message_builder
from pythonosc import udp_client
from pythonosc import dispatcher
from pythonosc import osc_server
import os,time
from os.path import isfile,join
import argparse
import sys
#optional addition to getserver IP addres automatically
import socket
import signal
AF_INET=2
SOCK_DGRAM=2
def my_ip():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('172.24.1.1',1027))
return s.getsockname()[0]
#Read file names from selected folder, ignorgin subfolders and hidden files
list=sorted([f for f in os.listdir(SPfolder) if not f.startswith('.') if isfile(join(SPfolder, f))], key=lambda f: f.lower())
print("\nTouchOSC controlled Jukebox server for Sonic Pi")
print("Written by Robin Newman, August 2017\n")
print("Folder is at ",SPfolder)
print("Nunber of files in folder ",len(list))
smax=len(list) #max setting for start
start=0;finish=9 #initialise start/finish pointers
audioFlag=0
def getargs(): #do getargs as a function which can be called from this namespace and from __main___
try:
#first set up and deal with input args when program starts
parser = argparse.ArgumentParser()
#This arg gets the server IP address to use. 127.0.0.1 or
#The local IP address of the PI, required when using external TouchOSC
parser.add_argument("--ip",
default="127.0.0.1", help="The ip to listen on")
#This is the port on which the server listens. Usually 8000 is OK
#but you can specify a different one
parser.add_argument("--sport",
type=int, default=8000, help="The port the server listens on")
#This is the IP address of the machine running TouchOSC if remote
#or you can omit if using TouchOSC on the local Pi (very unlikely!!).
parser.add_argument("--tip",
default="127.0.0.1", help="The ip TouchOSC is on")
#This is the port that TouchOSC is listening on. Usually 9000 is OK
#but you can specify a differnt one
parser.add_argument("--tport",
type=int, default=9000, help="The port TouchOSC listens on")
args = parser.parse_args()
#return args #return without further processing if called
if args.ip=="127.0.0.1" and args.tip !="127.0.0.1":
#You must specify the local IP address of the Pi if trying to use
#the program with a remote TouchOSC on an external computer
raise AttributeError("--ip arg must specify actual local machine ip if using remote TouchOSC, not 127.0.0.1")
#Provide feed back to the user on the setup being used
if args.tip == "127.0.0.1":
touchip=args.tip
print("local machine used for TouchOSC: unlikely this is what you want",touchip)
else:
touchip=args.tip
#print("remote_host for TouchOSC is",args.tip)
touchport=args.tport
#print("remote TouchOSC listent on port",touchport)
#first two values needed by server, last two by sender
return args#(args.ip,args.sport,touchip,tport)
#Used the AttributeError to specify problems with the local ip address
except AttributeError as err:
print(err.args[0])
sys.exit(0)
vals=getargs() #call getargs here to get touchip and touchport values for sender
touchip=(vals.tip)
#optional adjust if using auto select for server IP address
#you can uncomment and amend next four lines to suit your situation
##if my_ip()=='172.24.1.10':
## touchip='172.24.1.130' #adjust for my iPhone client
##elif my_ip()=='172.24.1.89:
## touchip='172.24.1.127' #adjust for my iPad client
touchport=int(vals.tport)
print("Sending to TouchOSC on ('{}', {})".format(touchip,touchport))
sender=udp_client.SimpleUDPClient(touchip,touchport) #to send data to TouchOSC
sender.send_message('/jb/audio-in',0)
def update(): #updates display after one of the buttons is pushed
i=1
for n in range(start,start+10):
#print('/jb/f'+str(i),[list[n]])
if n<=smax-1:
sender.send_message('/jb/n'+str(i),[list[n]]) #print valid file name
else:
sender.send_message('/jb/n'+str(i)," ") #print blank filename
i +=1
time.sleep(0.01)
sender.send_message('/jb/start',start+1) #update numeric values
sender.send_message('/jb/finish',finish+1)
sender.send_message('/jb/total',smax)
def updateleds(n): #update leds to reflect which file is playing
for x in range(1,11):
if x==n:
sender.send_message('/jb/led'+str(x),1)
else:
sender.send_message('/jb/led'+str(x),0)
time.sleep(1)
update()
updateleds(0)
def handle_next(unused_addr,args,n): #deal with next button pushed
global start,finish
if n == 1: #only act on push, not release
if start < smax-11: #inc start if more than 10 left
start +=10
finish = start+9 #update finish
finish = min(finish,smax-1) #check if reached last file
print("next",n,start,finish) #print updated values on terminal
update() #update display
updateleds(0) #clear all leds
def handle_prev(unused_addr,args,p): #deal with previous button pushed
global start,finish
if p ==1: #only react to push, not release
start -= 10 #set new start value
start = max(start,0) #check not back at the beginning of list
finish=start+9 #set new finish value
finish = min(finish,smax-1)
print("prev",p,start,finish) #print new values on terminal
update() #update display
updateleds(0) #clear all leds
def handle_stop(unused_addr,args,s): # send stop signal to SP
if s ==1: #only on press, not release
os.system(c+"stop")
os.system(c+"midi_sound_off")#in case of any oprphan midi notes
updateleds(0) #clear all leds
print("stopped") #on terminal window
def handle_f1(unused_addr,args,n): #deal with button f1 (under first filename) pushed
global audioFlag
if n == 1: #only push not release
if start <= smax: #check there is a file there
if audioFlag==0:
os.system(c+"stop") #stop previous file (if any)
updateleds(1) #set led for position 1
time.sleep(0.2)
if audioFlag==1:
os.system(c+"\'run_code \"with_fx :compressor,amp: 2 do;live_audio :sin;end\"\'")
f=SPfolder+list[start] #get full filename
os.system(c+"\'"+"run_file"+"\""+f+"\"'") #send run_file command via sonic_pi cli
def handle_f2(unused_addr,args,n): # as per other buttons adjust for position 2
global audioFlag
if n == 1:
if start+1 <= smax: #is it valid?
if audioFlag==0:
os.system(c+"stop") #stop previous file (if any)
updateleds(2)
time.sleep(0.2)
if audioFlag==1:
os.system(c+"\'run_code \"with_fx :compressor,amp: 2 do;live_audio :sin;end\"\'")
f=SPfolder+list[start+1]
os.system(c+"\'"+"run_file"+"\""+f+"\"'")
def handle_f3(unused_addr,args,n):
global audioFlag
if n == 1:
if start+2 <= smax:
if audioFlag==0:
os.system(c+"stop") #stop previous file (if any)
updateleds(3)
time.sleep(0.2)
if audioFlag==1:
os.system(c+"\'run_code \"with_fx :compressor,amp: 2 do;live_audio :sin;end\"\'")
f=SPfolder+list[start+2]
os.system(c+"\'"+"run_file"+"\""+f+"\"'")
def handle_f4(unused_addr,args,n):
global audioFlag
if n == 1:
if start+3 <= smax:
if audioFlag==0:
os.system(c+"stop") #stop previous file (if any)
updateleds(4)
time.sleep(0.2)
if audioFlag==1:
os.system(c+"\'run_code \"with_fx :compressor,amp: 2 do;live_audio :sin;end\"\'")
f=SPfolder+list[start+3]
os.system(c+"\'"+"run_file"+"\""+f+"\"'")
def handle_f5(unused_addr,args,n):
global audioFlag
if n == 1:
if start+4 <= smax:
if audioFlag==0:
os.system(c+"stop") #stop previous file (if any)
updateleds(5)
time.sleep(0.2)
if audioFlag==1:
os.system(c+"\'run_code \"with_fx :compressor,amp: 2 do;live_audio :sin;end\"\'")
f=SPfolder+list[start+4]
os.system(c+"\'"+"run_file"+"\""+f+"\"'")
def handle_f6(unused_addr,args,n):
global audioFlag
if n == 1:
if start+5 <= smax:
if audioFlag==0:
os.system(c+"stop") #stop previous file (if any)
updateleds(6)
time.sleep(0.2)
if audioFlag==1:
os.system(c+"\'run_code \"with_fx :compressor,amp: 2 do;live_audio :sin;end\"\'")
f=SPfolder+list[start+5]
os.system(c+"\'"+"run_file"+"\""+f+"\"'")
def handle_f7(unused_addr,args,n):
global audioFlag
if n == 1:
if start+6 <= smax:
if audioFlag==0:
os.system(c+"stop") #stop previous file (if any)
updateleds(7)
time.sleep(0.2)
if audioFlag==1:
os.system(c+"\'run_code \"with_fx :compressor,amp: 2 do;live_audio :sin;end\"\'")
f=SPfolder+list[start+6]
os.system(c+"\'"+"run_file"+"\""+f+"\"'")
def handle_f8(unused_addr,args,n):
global audioFlag
if n == 1:
if start+7 <= smax:
if audioFlag==0:
os.system(c+"stop") #stop previous file (if any)
updateleds(8)
time.sleep(0.2)
if audioFlag==1:
os.system(c+"\'run_code \"with_fx :compressor,amp: 2 do;live_audio :sin;end\"\'")
f=SPfolder+list[start+7]
os.system(c+"\'"+"run_file"+"\""+f+"\"'")
def handle_f9(unused_addr,args,n):
global audioFlag
if n == 1:
if start+8 <= smax:
if audioFlag==0:
os.system(c+"stop") #stop previous file (if any)
updateleds(9)
time.sleep(0.2)
if audioFlag==1:
os.system(c+"\'run_code \"with_fx :compressor,amp: 2 do;live_audio :sin;end\"\'")
f=SPfolder+list[start+8]
os.system(c+"\'"+"run_file"+"\""+f+"\"'")
def handle_f10(unused_addr,args,n):
global audioFlag
if n == 1:
if start+9 <= smax:
if audioFlag==0:
os.system(c+"stop") #stop previous file (if any)
updateleds(10)
time.sleep(0.2)
if audioFlag==1:
os.system(c+"\'run_code \"with_fx :compressor,amp: 2 do;live_audio :sin;end\"\'")
f=SPfolder+list[start+9]
os.system(c+"\'"+"run_file"+"\""+f+"\"'")
def handle_audioIn(unused_addr,args,a):
global audioFlag
if a == 1:
audioFlag=1
print("Audio Flag is ",audioFlag)
if a == 0:
audioFlag=0
print("Audio Flag is ",audioFlag)
os.system(c+"\'run_code \"live_audio :sin,:stop\"\'")
def handle_clear(unused_addr,args,cl):
if cl == 1: #only on press, not release
print("Clearing..")
com="clear;sample_free_all"
os.system(c+"\'"+"run_code"+"\""+com+"\"'")
#The main routine called when the program starts up follows
if __name__ == "__main__":
try: #use try...except to handle possible errors
args=getargs() #call args parsing to get ip and port for server
sip=args.ip;sport=int(args.sport) #extract required itens
#dispatcher reacts to incoming OSC messages and then allocates
#different handler routines to deal with them
dispatcher = dispatcher.Dispatcher()
#set up the handler calls
dispatcher.map("/jb/prev",handle_prev,"p")
dispatcher.map("/jb/next",handle_next,"n")
dispatcher.map("/jb/stop",handle_stop,"s")
dispatcher.map("/jb/f1",handle_f1,"n")
dispatcher.map("/jb/f2",handle_f2,"n")
dispatcher.map("/jb/f3",handle_f3,"n")
dispatcher.map("/jb/f4",handle_f4,"n")
dispatcher.map("/jb/f5",handle_f5,"n")
dispatcher.map("/jb/f6",handle_f6,"n")
dispatcher.map("/jb/f7",handle_f7,"n")
dispatcher.map("/jb/f8",handle_f8,"n")
dispatcher.map("/jb/f9",handle_f9,"n")
dispatcher.map("/jb/f10",handle_f10,"n")
dispatcher.map("/jb/audio-in",handle_audioIn,"a")
dispatcher.map("/jb/clear",handle_clear,"cl")
#The following handler responds to the OSC message /testprint
#and prints it plus any arguments (data) sent with the message
#can be used for testing without doing anything
dispatcher.map("/testprint",print)
#Now set up and run the OSC server
#optionally determine IP address of server: uncomment next line
##sip=my_ip() #overwrites sip value obtained from input args
server = osc_server.ThreadingOSCUDPServer(
(sip, sport), dispatcher)
print("Serving messages from TouchOSC on {}".format(server.server_address))
#run the server "forever" (till stopped by pressing ctrl-C)
server.serve_forever()
#deal with some error events
except KeyboardInterrupt:
print("\nServer stopped") #stop program with ctrl+C
#handle errors generated by the server
except OSError as err:
print("OSC server error", err.args)
#anything else falls through
#!/bin/sh
Xvfb :1 &amp; xvfb-run /usr/bin/sonic-pi 2 &gt;/dev/null &amp;
@rbnpi
Copy link
Author

rbnpi commented Aug 25, 2017

The file index.xml needs to be converted to a format which is used by TouchOSC. To do this, download the raw file and save it as index.html Then turn it into a zip file. On the Mac do this by right clicking the index.xml file in Finder, and choosing the compress option. This will produce a file named index.xml.zip Now rename this file to jukebox.touchosc (NB touchosc is the file extension). This file can then be loaded into the TouchOSC editor which comes with TouchOSC and downloaded to the TouchOSC app running on an iPad/iPhone/Android device in the usual way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment