Those of you who have read my blog regularly will know that I am very attracted to projects which involve using OSC messaging to either control a device from Sonic Pi, or allow a device to send messages to Sonic Pi which can be used to affect what it does. When 4tronix recently announced its new Cube:Bit I was immediately attracted to it as a potential device which Sonic Pi could be used to control. I have previously used Sonic Pi to control the RasPiO Inspiring with 24 rgb leds on it, but this cube was available in three sizes 3 x 3, 4×4 and 5 x 5 and the125 neo-pixels in the 5 x 5 cube looked like an interesting challenge. Gareth Davies at 4tronix also supples an optional base for the cube, which is very worth while getting, as it conveniently brings out the connections to the cube, and enables easy connection to a variety of driving sources. The name implies that the main one is for a microbit, and there is a socket into which such a board can be plugged. However, it also has a socket into which a Raspberry Pizero can be plugged, allowing power to be supplied via the 5v input socket on the base, and, connecting pin 18 on the Pizero (which can be used for Pulse Width Modulation to the data input of the cube). I used a 3A 5v supply from Pimoroni to drive both the cube and the Pizero, which was fine, as I restricted the maximum brightness of the neo-pixels
At first release, 4tronix only have software support for the microbit, and so I had to set things up myself for the Pizero. Fortunately others have gone before, and there is a python library which will drive the neo-pixels available and I downloaded and installed one from Pimoroni The library is easily installed using
sudo pip3 install rpi_ws281x
Note I installed it for python3 as I wanted to use the python-osc library as well which required this. Also on the github site was an example python script by Tony DiCola named strandtest.py which showed how to access the primitives used to drive the neo-pixels and included some animation examples. I amde this the starting point for the fairly extensive script I developed to allow Sonic Pi to control the cube via OSC messages, the resultant script expanding from the 107 lines in strandtest.py up to around 637 in my osc-cube3.py script
I don’t intend her to give a complete line by line description of this script, but the overall structure is shown below:
- import libraries
- set details of cube eg size
- set up colour lookup table name <=>hex code
- housekeeping routines eg (r,g,b)=hex converter
- routines to drive cube (viewed as a long change of pixels
set pixel n to color c
pMap x,y,z => n pixel position
clear all pixels
set all pixels to a colour
colour wipe pixels one by one with delay
set a hollow cube of pixels
- 21 routines (names starting with osc) to handle incoming osc messages connected by dispatchers.
They pass commands on to the primitives above, and (optionally) print info on the terminal screen
(also they send optional return cues to Sonic Pi when process finished if the id parameter is altered from default)
- handles program startup and extracts args from command line eg -ip xx.xx.xx.xx -c -sp xx.xx.xx.xx -pr
- setup osc sender to return info to Sonic Pi
- 21 dispatcher calls, each of which reacts to a specific OSC address and passes on args to the osc routines above
- start the osc server in a forever loop. (terminated by ctrl C)
- flash a layer of neo-pixels to show everything is working
logically the cube is connected up as a continuous line of neo-pixlels. Each row loops round at the end giving essentially a double S shape for the 25 neo-pixels on the 5×5 layer. Each layer is then connected to the layer above via the brass pillars which double as electrical connections. However, as each successive layer is flipped over and rotated, the “snake” for the second layer up runs backwards and forwards rather than side to side as in the first layer. This leads to an interesting problem if you want to address the pixels via a three coordinate x,y,z location rather than their numerical position n, along the line of the 125 neo-pixels. Fortunately 4tronix had already done the hard work in the code supplied to use a microbit to drive the cube, and I was able to look at the logic in that program and translate it into code to work within python. Similarly there was a function to turn on all the neo-pixels in a vertical or horizontal plane and I was able to use the logic for this as well, although I did make it more flexible in being able to address sections of a plane defined by length and depth parameters.
Altogether I developed 18 different functions that could be used to alter the cube display, plus 3 more functions that could be used to send information to Sonic Pi, eg giving the colour of a given pixel either defined by x,y,z or numerical position n in the chain of neo-pixels.
In order to allow Sonic Pi to trigger these function calls, I added support for an OSC (Open Sound Control) server to the script, and also an OSC message sender, for returning information to Sonic Pi. These utilised the python-osc script which could be installed using
sudo pip3 install python-osc
The calls required could then be added to the program using
from pythonosc import dispatcher from pythonosc import osc_server from pythonosc import osc_message_builder from pythonosc import udp_client
The way that the osc support works is that an oscserver is set up to run continuously listening for osc messages addressed to the ip address of the Pizero on a given port (8000). When a message is received, its address is checked by a list of dispatcher calls. If an address eg “/colorAll” is received, then if there is a dispatcher matching that it will be invoked and will calla routine (here oscColorAll, sending it the data associated with the OSC message as a list of parameters. In this case there will be three parameters cv, flash=0, id=-1 cv is the colour to be set (0-> 0xFFFFFF in hex or 0 to 16777215 in decimal), flash (which is 0 by default) will turn the pixels off again after flash seconds, and id (by default -1) will send an osc message back to Sonic Pi creating a cue “/osc/coloralldoneN” where N is the value of id, if id is >-1. This can be used when a sequence of OSC calls are sent by Sonic Pi so that they can be setup up to follow each other rather than overlap, although at times this can be desirable to give some interesting effects. A list of the OSC calls available is shown a little further down.
OSC calls for osc-cube3.py.
Requires ip of cubebit pizero and ip of sp computer.
start the script with:
sudo python3 -c -ip xx.xx.xx.xx -sp xx.xx.xx.xx -pr
-pr can be omitted. It allows verbose output from the script, which can slow its response.
(I connect to the headless pizero via ssh from the computer running Sonic Pi in order to start the script)
ctrl-C to quit
"/clearAll",oscClear,id=-1 "/pixelN",oscpixelN,n,cv,flash=0,wait_ms=50 "/pixelXYZ",oscpixelXYZ,"x","y","z","cv","flash","wait_ms" "/line",oscLine,x,y,z,cv,flash=0,id=-1 #axis set by x,y,z value < 0 "/hollowCube",oscHollowCube,x,y,z,side,cv,flash=0,id=-1 "/solidCube",oscSolidCube,x,y,z,side,cv,flash=0,id=-1 "/slantTriangle",oscSlantTriangle,x,y,z,side,cv,flash=0,id=-1 "/setPlane",oscSetPlane,plane,axis,cv,flash=0,l=5,d=5,rev=0,id=-1 "/colorAll",oscColorAll,cv,flash=0,id=-1 "/colorWipe",oscColorWipe,cv,delay=50,rev=0,id=-1 "/rainbow",oscRainbow, delay,iterations,clear=0,id=-1 "/rainbowCycle",oscRainbowCycle, delay,iterations,clear=0,id=-1 "/theaterChase",oscTheaterChase, cv, delay, iterations, clear=0,id=-1 "/theaterChaseRainbow",oscTheaterChaseRainbow, delay,clear=0,id=-1 "/fadeInAndOut",oscFadeInAndOut, cv,iterations=10,delay=4,direction=0,start=0,finish=cubeSide3,id=-1 "/setBrightness",oscSetBrightness, brightness "/pingBack",oscPingBack,code "/getColorN",oscGetColorN,n "/getColorXYZ",oscGetColorXYZ,x,y,z "/sandwichCube",oscSandwichCube,axis,cv,id=-1 "/triCube",oscTriCube,axis,cv1,cv2,id=-1 id default -1 no cue NUM >-1 give cue /osc/lowercase<name>doneNUM flash 0 leaves on, flash n leaves on for n seconds delay in seconds unless wait_ms
More detail is given in the github repository readme file. To make operation a bit easier I have included a colour name lookup hash so that colours can be displayed by name on the printed output of the program, or else in hex if the name is not in the hash.
The Sonic Pi end
Now we can look at what happens in Sonic Pi. The latest versions from 3 onwards support sending and receiving OSC messages. to Send a message you first need to supply the address of the computer it is to be sent to, and the port to be used. This is done using the command
So then to activate all the neopixels in the cube to a colour blue we could use the command:
255 is the colour number for blue, which could also be sent as 0xff or, using a colour lookup hash I add to the program as cv(‘blue’) giving a command:
Here I give values to the two parameters which will otherwise have default values, flash and id, and the effect would be to turn the cube off again after 2 seconds, and also to send an osc messages which would be received as
back to Sonic Pi once the process had completed. Thus if we had a program like the one below, then the following puts statement would be delayed until this message was received.
use_osc "ip.address",8000 osc "/colorAll",cv('blue'),2,20 sync "/osc/coloralldone20" puts "Got here"
Sonic Pi listens for OSC messages on port 4559 from any ip address on the local network (provided that you enable a flag Receive remote OSC messages in the IO preferences for the application. One other “gotcha” is that Sonic Pi prepends “/osc” onto any osc message received. This is to distinguish then from incoming messages received from say a midi source, of internally from cues sent from a running Sonic Pi program. You can see that in the example above, where the oscColorAll function in the python script returns the OSC message “/colouralldone20″,”ok” but this is received as “/osc/colorall20″,”ok” by Sonic Pi.
Here is an example script that utilises these features. Because it doesn’t include the colour table I set the colours directly using hex numbers.
use_real_time use_osc "192.168.1.37",8000 #ip address of my pizero osc "/colorAll",0xff,2,18 #set cube blue sync "/osc/coloralldone18" #wait for done cue (note the 18 match) osc "/colorAll",0xff00,2,19 #set the cube green sync "/osc/coloralldone19" #wait for done cue (note the 19 match) osc "/colorAll",0xff0000,2,20 #set the cube red sync "/osc/coloralldone20" #wait for done cue (note 20 match) puts "got here" sleep 1 osc "/setPlane",0,"xy",0xff #set bottom horizontal plane blue osc "/setPlane",1,"xy",0xff00 #set second horizontal plane green osc "/setPlane",2,"xy",0xff0000 #set middle horizntal plane rd osc "/setPlane",3,"xy",0xffff00 #set 2nd top plane yellow #in next command notice default values for missing parameters supplied #to allow 1 for id on the end. This generates the return cue /osc/setplanedone1 osc "/setPlane",4,"xy",0xff00ff,0,5,5,0,1 #set top plane majenta sync"/osc/setplanedone1" #get done cue from the last setPlane sleep 1 osc "/clearAll",1 sync "/sc/clearalldone1" #wait for done cue from clearAll command puts "cube cleared"
Really your imagination is now the limiting factor. You can build up Sonic Pi programs to produce intricate patterns on the cube, and you can incorporate Sonic Pi music commands as well to get a “son-et-lumière” effect.
I include in the links at the end of the article to various Sonic Pi programs which can control the cube and provide musical accompaniment
But there is more…
Having got a working system with which I could program sequences on the cube, I wanted to make it even more flexible. To do this I added a further device, which was to use the app TouchOSC running on my iPad (also available for Android devices). This app lets you create a series of virtual controllers, switches push buttons, sliders etc which can send OSC messages to Sonic Pi. Thus then can be used to control for example the red, green and blue components of a colour, to initiate a sequence of stored commands in Sonic Pi. The layout I came up with is shown below:
I developed a supporting program in Sonic Pi, using the two together could produce some interesting effects on the cube by overlaying functions so that they ran at the same time, interleaving their effects. The four sliders control the R,G,and B components of a colour which can be applied to draw a cube, withe the overall brightness also being modifiable. You can only modify the brightness of ALL of the neo-pixels together, however, you can achieve a colour dimming effect by reducing the colour component a colour, and you can see this effect by reducing the red slider from full to 0 for example. You can turn on or off the slider audio button and Sonic Pi will play a three notes whose pitches varies with the slider positions of the three colours. There is also an arpeggio button which move the sliders in steps which are programmed to produce arpeggios over a 4 octave range. A button marked striped cube produces a cube where the three inner layers are lit by the r, g and b components, and the outer two layers give the composite colour. To add to the fun the cube axes are also rotated. You can see this by setting the rgb sliders to max then switching on the striped button. Best to have the Mute Slider Audio button on for this!
Both the template for the TouchOSC application and the Sonic Pi program (which is split into two parts ) can be obtained from the github repository linked below.
So this project was great fun to do. It got my rather rusty mathematics going in trying to work out coordinates in the program, and it illustrated once more how flexible and useful Sonic Pi is a as a program where OSC control is useful, in this case conversing with two completely different programs at once. There is a lot of meat to digest in both the osc-cube3.py script and in the Sonic Pi driver programs for my two major examples, but it worth the effort to see how they work, and I include some simpler Sonic Pi programs illustrating how the osc-cube3 routines can be called and used. I suggest trying the “tester” programs in the repository first.
All of the software is in a github repository here
I have made some videos of the completed system, but it is extremely difficult to video the cube in operation because of the rapidly changing brightness, and the videos do not really do justice to what you can see with a pair of eyes.