Skip to content

Instantly share code, notes, and snippets.

@rbnpi
Last active December 19, 2019 08:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rbnpi/1bb941ee211c0a6ee071f91eb67e2e90 to your computer and use it in GitHub Desktop.
Save rbnpi/1bb941ee211c0a6ee071f91eb67e2e90 to your computer and use it in GitHub Desktop.
ThePiHut RGBXmasTree controlled by Sonic Pi

The python server runs on the Raspberry Pi with the Xmas tree attached. Set the treeServer.py file executable using chmod+x treeServer.py start it running with ./treeServer.py -ip 192.168.1.34 -sp 192.168.1.129 where 192.168.1.34 is the ip address of the Raspberry Pi, and 192.168.1.129 is the address of the computer running Sonic Pi. Set the correct IP address in line 7 of the Sonic Pi program, and use port 8000 as shown.

EDIT You need to have installed the tree.py file for the RGBXmasTree in the same folder as the treeServer.py file. This can be found at https://github.com/ThePiHut/rgbxmastree#rgbxmastree colorzero should already be installed in the latest Raspbian Buster distribution. If you need it use

sudo apt update

sudo apt install python-colorzero python3-colorzero

#CoventryCarol.rb
#controlling ThePiHut RGBXmasTree via OSC commands
#these are sent from Sonic Pi to a python OSC server running on the Pi
#which then controls the leds on the tree
#written by Robin Newman, Decemeber 2019
use_real_time
use_osc"192.168.1.34",8000
use_bpm 120
##| osc"/setAll",'black'
##| stop
#colour names
cn= ["darkred", "maroon", "red", "purple", "fuchsia", "green", "lime", "olive", "yellow", "navy", "blue", "brown", "aqua","orange","darkgreen","violet","indigo", "maroon", "red", "purple", "turquoise", "green", "lime","blue","purple","black"]
#lookup table for colour index in cn
lu={55=>0, 57=>1, 60=>2, 62=>3, 64=>4, 65=>5, 66=>6, 67=>7, 68=>8,
69=>9, 71=>10, 72=>11, 73=>12, 74=>13, 76=>14, 77=>15, 79=>16,
80=>17, 81=>18, 83=>19, 84=>20, 86=>21, 88=>22, 92=>23, 93=>24,0=>25}
#set brightness
osc"/brightness",0.05,1 #adjust brightness as desired. (Here set low for video: can be set up to 0.8 instead of 0.05)
sync"/osc*/brightnessdone1"
##| stop
2.times do #play complete carol specified number of times
set :kill,false #kills tree loop
with_fx :reverb,room: 0.8,mix: 0.6 do
use_synth :tri
a1=[:A5,:A5,:Gs5,:A5,:C6,:B5,:A5,:Gs5,:A5,:B5,:C6,:D6,:B5,:A5,:A5,:E6,:D6,:C6,:B5,:C6,:B5,:A5,:Gs5,:A5,:Gs5,:A5,:D6,:B5,:A5,:A5,:r,:r,:r,:r,:r,:r,:r,:r,:r,:r,:r,:r,:r,:A5,:Gs5,:A5,:D6,:B5,:A5,:A5,:r,:A5,:A5,:Gs5,:A5,:C6,:B5,:A5,:Gs5,:A5,:B5,:C6,:D6,:B5,:A5,:A5,:B5,:C6,:D6,:E6,:D6,:C6,:B5,:C6,:B5,:A5,:Gs5,:A5,:B5,:C6,:D6,:E6,:Gs6,:A6,:A5]
b1=[1.0,1.0,1.0,2.0,1.0,2.0,1.0,3.0,1.0,1.0,1.0,1.0,2.0,3.0,2.0,1.0,2.0,1.0,2.0,1.0,2.0,1.0,3.0,1.0,1.0,1.0,1.0,2.0,3.0,2.0,1.0,3.0,3.0,3.0,3.0,3.0,3.0,3.0,3.0,3.0,3.0,3.0,3.0,1.0,1.0,1.0,1.0,2.0,3.0,2.0,1.0,1.0,1.0,1.0,2.0,1.0,2.0,1.0,3.0,1.0,1.0,1.0,2.0,1.0,3.0,0.5,0.5,0.5,0.5,1.0,2.0,1.0,2.0,1.0,2.0,1.0,3.0,1.0,1.0,1.0,1.0,1.0,1.0,3.0,3.0]
in_thread do
for i in 0..a1.length-1
set :n1,note(a1[i]) #store current note value
play a1[i],sustain: b1[i]*0.9,release: b1[i]*0.1,pan: -0.6
sleep b1[i]
end
set :n1,nil #reset note to turn off leds on next pass
end
a2=[:E5,:E5,:A5,:G5,:E5,:E5,:E5,:G5,:G5,:A5,:Gs5,:A5,:A5,:G5,:B5,:A5,:G5,:E5,:G5,:D5,:E5,:E5,:E5,:E5,:A5,:Gs5,:A5,:A5,:r,:r,:r,:r,:r,:r,:r,:r,:A4,:C5,:E5,:D5,:C5,:B4,:C5,:B4,:A4,:Gs4,:E5,:E5,:E5,:A5,:Gs5,:E5,:E5,:r,:E5,:E5,:E5,:E5,:C5,:D5,:C5,:E5,:E5,:F5,:E5,:D5,:C5,:B4,:C5,:D5,:E5,:G5,:A5,:Gs5,:E5,:C5,:D5,:E5,:F5,:G5,:B5,:A5,:B5,:A5,:G5,:F5,:E5,:G5,:D5,:B4,:A4,:B4,:C5,:D5,:E5,:G5,:G5,:F5,:A5,:Gs5,:A5,:A4]
b2=[3.0,2.0,1.0,2.0,1.0,3.0,1.0,1.0,1.0,2.0,1.0,3.0,2.0,1.0,2.0,1.0,2.0,1.0,2.0,1.0,3.0,1.0,1.0,1.0,2.0,1.0,3.0,2.0,1.0,3.0,3.0,3.0,3.0,3.0,3.0,3.0,1.0,1.0,1.0,2.0,1.0,2.0,1.0,2.0,1.0,3.0,1.0,1.0,1.0,2.0,1.0,3.0,2.0,1.0,2.0,1.0,1.0,1.0,1.0,2.0,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,1.0,1.0,0.5,0.5,2.0,1.0,3.0,0.5,0.5,0.5,0.5,1.0,2.0,1.0,0.5,0.5,0.5,0.5,1.0,2.0,1.0,1.0,0.5,0.5,0.5,0.5,1.0,1.0,1.0,1.0,1.0,1.0,3.0,3.0]
in_thread do
for i in 0..a2.length-1
set :n2,note(a2[i])
play a2[i],sustain: b2[i]*0.9,release: b2[i]*0.1,pan: -0.3
sleep b2[i]
end
set :n2,nil
end
a3=[:C5,:B4,:C5,:F5,:D5,:C5,:B4,:C5,:D5,:E5,:F5,:E5,:E5,:E5,:E5,:G5,:E5,:E5,:C5,:D5,:A4,:B4,:C5,:B4,:C5,:F5,:E5,:E5,:E5,:r,:r,:r,:r,:r,:A4,:B4,:C5,:D5,:B4,:A4,:C4,:E4,:G4,:B4,:A4,:B4,:A4,:G4,:F4,:E4,:G4,:D5,:B4,:A4,:B4,:C5,:D5,:C5,:B4,:C5,:F5,:E5,:C5,:C5,:r,:C5,:B4,:C5,:E5,:A4,:G4,:C5,:E5,:D5,:C5,:B4,:A4,:Gs4,:A4,:B4,:C5,:E5,:A4,:A4,:B4,:C5,:D5,:C5,:C5,:E5,:G5,:E5,:G5,:F5,:E5,:D5,:C5,:D5,:A4,:E4,:F4,:Gs4,:A4,:B4,:C5,:D5,:C5,:A4,:E5,:Cs5,:Cs5]
b3=[2.0,1.0,2.0,1.0,2.0,1.0,3.0,1.0,1.0,1.0,1.0,2.0,3.0,2.0,1.0,2.0,1.0,2.0,1.0,2.0,1.0,3.0,1.0,1.0,1.0,1.0,2.0,3.0,2.0,1.0,3.0,3.0,3.0,3.0,1.0,1.0,1.0,2.0,1.0,3.0,1.0,1.0,1.0,2.0,1.0,0.5,0.5,0.5,0.5,1.0,2.0,1.0,1.0,0.5,0.5,0.5,0.5,1.0,1.0,1.0,1.0,2.0,3.0,2.0,1.0,2.0,1.0,1.0,1.0,1.0,2.0,1.0,0.5,0.5,0.5,0.5,0.5,0.5,1.0,1.0,0.5,0.5,1.0,0.5,0.5,0.5,0.5,3.0,2.0,1.0,2.0,1.0,0.5,0.5,0.5,0.5,1.0,2.0,1.0,1.0,0.5,0.5,0.5,0.5,1.0,1.0,1.0,1.0,2.0,3.0,3.0]
in_thread do
for i in 0..a3.length-1
set :n3,note(a3[i])
play a3[i],sustain: b3[i]*0.9,release: b3[i]*0.1,pan: 0.3
sleep b3[i]
end
set :n3,nil
end
a4=[:A4,:A4,:E4,:A3,:F4,:G4,:A4,:r,:E4,:A4,:G4,:E4,:D4,:E4,:A4,:A4,:A4,:C5,:G4,:A4,:E4,:A4,:G4,:Fs4,:E4,:Fs4,:Gs4,:A4,:E4,:A4,:D4,:E4,:A4,:A4,:r,:A4,:A4,:Gs4,:A4,:C5,:B4,:A4,:Gs4,:A3,:G4,:E4,:D4,:E4,:A4,:A3,:E4,:A4,:C5,:G4,:A4,:E4,:A4,:G4,:Fs4,:E4,:Fs4,:Gs4,:A4,:E4,:A4,:D4,:E4,:A4,:A4,:r,:A3,:C4,:E4,:A4,:G4,:F4,:G3,:D4,:G4,:A4,:E4,:A3,:G4,:E4,:D4,:E4,:A4,:A3,:E4,:A4,:C5,:r,:B4,:A4,:r,:G4,:A4,:r,:G4,:Fs4,:E4,:A4,:G4,:E4,:D4,:E4,:A4,:A4]
b4=[1.0,1.0,1.0,2.0,1.0,2.0,1.0,1.0,2.0,1.0,1.0,1.0,1.0,2.0,1.0,2.0,2.0,1.0,2.0,1.0,2.0,1.0,2.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,2.0,3.0,2.0,1.0,1.0,1.0,1.0,2.0,1.0,2.0,1.0,3.0,1.0,1.0,1.0,1.0,2.0,1.0,1.0,1.0,2.0,1.0,2.0,1.0,2.0,1.0,2.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,2.0,3.0,2.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,0.5,0.5,1.0,1.0,3.0,1.0,1.0,1.0,1.0,2.0,1.0,1.0,1.0,2.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,3.0,1.0,1.0,1.0,1.0,2.0,3.0,3.0]
in_thread do
for i in 0..a4.length-1
set :n4,note(a4[i])
play a4[i],sustain: b4[i]*0.9,release: b4[i]*0.1,pan: 0.6
sleep b4[i]
end
set :n4,nil
set :top,'black' #clear top at end of carol
sleep 0.5 #allow one more pass t reset
set :kill,true #then kill tree loop
cue :done #cue end of carol ready for loop to start again
end
set :top,'white' #set colour for top led
live_loop :tree do
stop if get(:kill) #stop loop at the end of each play
n1=get(:n1)
n1=0 if n1==nil #deal with rests => nill
n2=get(:n2)
n2=0 if n2==nil
n3=get(:n3)
n3=0 if n3==nil
n4=get(:n4)
n4=0 if n4==nil
top=get(:top)
#print selected colourts for each leaf
puts cn[lu[n1]],cn[lu[n2]],cn[lu[n3]],cn[lu[n4]]
#send info to tree server
osc "/leafCn",top,cn[lu[n1]],cn[lu[n2]],cn[lu[n3]],cn[lu[n4]]
sleep 0.2
end
end #reverb
sync :done #cued when carol finished
osc"/randColName",100,25,1 #sparkling display
sync"/osc*/randcolnamedone1"
osc"/setAll",'black',1 #all leds off
sync"/osc*/setalldone1"
sleep 2 #pause
end
#!/usr/bin/env python3
#this script allows OSC control of ThePiHut RGBXmasTree
#it is intended for use with Sonic Pi 3
#written by Robin Newman, December 2019
#it utilises the python-osc library and also the colorzero library
#this latter is used because of its support for gradient commands
#and a large list of named colours.
#see API doc at https://colorzero.readthedocs.io/en/release-1.1/api_color.html
#Sonic Pi can be on same computer if using Pi3, but if using pizero must be on separate computer
from tree import RGBXmasTree
import colorzero as cz
from pythonosc import osc_message_builder
from pythonosc import udp_client
from pythonosc import osc_server
from pythonosc import dispatcher
import argparse
import time
import numpy as np
from random import choice
tree = RGBXmasTree()
cn= ["silver", "maroon", "red", "purple", "fuchsia", "green", "lime", "olive", "yellow", "navy", "blue", "teal", "aqua"]
def randColName():
return [cz.Color(choice(cn)) for i in range(25)]
######## osc hander routines called by the dispatcher
def oscSetAll(unused_addr,args,cname='black',id=-1):
t=cz.Color(cname).rgb
tree.color=(t[0],t[1],t[2])
if id > -1: sender.send_message('/setalldone'+str(id),"ok")
def oscBrightness(unused_address,args,b,id=-1):
tree.brightness=max(min(b,0.8),0.0) #check in range 0 to 0.8
if id>-1: sender.send_message('/brightnessdone'+str(id),"ok")
def oscLeafCn(unused_args,args,cn1,cn2,cn3,cn4,cn5,id=-1):
cv=[[0,0,0]]*25
r=[[3],[19,20,21,22,23,24],[0,1,2,7,8,9],[13,14,15,16,17,18],[4,5,6,10,11,12]]
for i in r[0]: cv[i]=cz.Color(cn1)
for i in r[1]: cv[i]=cz.Color(cn2)
for i in r[2]: cv[i]=cz.Color(cn3)
for i in r[3]: cv[i]=cz.Color(cn4)
for i in r[4]: cv[i]=cz.Color(cn5)
cv2=np.array(cv).reshape((-1,3))
tree.value=[tuple(row) for row in cv2]
if id>-1: sender.send_message('/leafcndone'+str(id),"ok")
def oscRandColName(unused_args,args,n=20,wait_ms=20,id=-1):
for i in range(n):
tree.value=randColName()
time.sleep(wait_ms/1000.0)
if id>-1: sender.send_message('/randcolnamedone'+str(id),"ok")
if __name__ == "__main__":
try:
parser = argparse.ArgumentParser()
parser.add_argument("-ip",
default = '127.0.0.1', help="The ip of this computer")
parser.add_argument("-sp",
default = '127.0.0.1', help="The ip of the computer running Sonic Pi")
#This is the port on which the server listens. Usually 8000 is OK
#but you can specify a different one
parser.add_argument("--port",
type=int, default=8000, help="The port to listen on")
args=parser.parse_args()
spip=args.sp
#######dispatchers which handle incoming osc calls. They pass the data received on to
####### the routine following the OSC address. eg "/setAll" calls oscSetAll with data cname and id
####### All routines can send optional message back to Sonic Pi if id param is > -1 (default)
####### steps, wait_ms and id all have default values if omitted
dispatcher = dispatcher.Dispatcher()
dispatcher.map("/setAll",oscSetAll,"cname","id")
dispatcher.map("/brightness",oscBrightness,"b","id")
dispatcher.map("/leafCn",oscLeafCn,"cn1","cn2","cn3","cn4","cn5","id")
dispatcher.map("/randColName",oscRandColName,"n","wait_ms","id")
#now setup sender to return OSC messages to Sonic Pi
print("Sonic Pi on ip",spip)
sender=udp_client.SimpleUDPClient(spip,4559) #sender set up for specified IP
#Now set up and run the OSC server
server = osc_server.ThreadingOSCUDPServer(
(args.ip, args.port), dispatcher)
print("Serving on {}".format(server.server_address))
#run the server "forever" (till stopped by pressing ctrl-C)
server.serve_forever()
except KeyboardInterrupt:
tree.color=(0.0, 0.0, 0.0) #clear display then exit
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment