Controlling Pimoroni Unicorn Hat Mini with Sonic Pi

Recently I purchased a Pimoroni Unicorn Hat Mini. Having used various visual led displays in the past such as The PiHut’s Christmas Trees, and Piborg’s Tropi board and 4Tronix cubeit, I thought it would be fun to see if I could interface it to Sonic PI, and in the event it turned out to be a whole lot of fun. The key to doing this is to use a python OSC server, running on the Raspberry Pi which is hosting the Pimoroni board. The latest version of Sonic Pi for the Raspberry Pi version 3.2.2 (available for download here for the Pi) fully supports both OSC and Midi signals both in and out, or you can use Sonic Pi on a separate computer which can communicate with the python OSC server running on the pi hosting the Pimoroni board via WiFi. The diagram  below  shows  a schematic  of the system, which you can click for a larger copy.

The Unicorn board is supported by two Python libraries. The unicorn hat mini library from Pimoroni and the gpiozero library which supports the function of the four buttons. I n addition subsets of the python-osc library are used to support the communication via OSC messages. Various other libraries are loaded to support colour, math and time  and various system functions. Most of the software is contained within the osc-uhm.py script (uhm short for unicorn hat midi). This connects to the primitive functions within the Pimoroni library which support such actions as changing the colour of any specified led (using its x y coordinates and r,g,b colour values in the range 0-255. In addition it supports global changes of the led brightness setting and, with the help of the gpiozero library, detecting pushes on the four buttons on the board. The primitives are supported by a series of dispatcher function routines which I have developed, Some of these call the uhm primitive functions directly, others use code to control selections of these leds, eg to draw a rectangle or a horizontal or vertical line. Support from the colorzero library enable fading from one colour to another by single or groups of LED. I have also included slightly amended routines from the demo.py script and the rainbow.py script supplied with the Pimoroni Library so that they may be activated from Sonic Pi, via OSC messages.

Each of the dispatcher function scripts, which control the uhm board has a corresponding dispatcher  which is triggered by an incoming OSC call. If the message matches that stored in a dispatcher , then it is triggered, and passes on the parameters associated with the OSC messages to the corresponding dispatcher function routine. One of the potential drawbacks of the uhm board is that it essentially works via serial messages using i2c communications. It is generally not a good idea to send a command to the board until the previous one has finished, In order to signal to Sonic Pi that a previous call has finished, I have arranged that each of the dispatcher functions routines has an option to send a completion OSC message back to Sonic Pi so that it knows that the board is available to receive another command. This is controlled by the id parameter of the incoming OSC message from Sonic Pi. If this is missing, then a default of id=-1 is used, and no completion message is sent. If id is 0 or greater, then a return OSC message is composed of the original message address (converted to lowercase with the word “done” appended and the id value as a string appended to that. Thus an original message of “/setPixelCol”,3,2,’red’,1 would trigger a dispatcher, which would in turn call the dispatcher function oscSetPixelCol with x,y parameters 3 and 2 and colour name ‘red’. When this function had completed it would send back the osc message “/setpixelcoldone1” to Sonic Pi, where it can be detected and used as a cue to start the next command from Sonic Pi.

The gpiozero library maintains a polling routine which will trigger an event when one of the four buttons is pressed. These then call an appropriate function p1( ); p2( ):….p(4): which sends an OSC message to Sonic Pi of the form “/button”,1…….”/button”,4 where it can be detected and acted upon.

The listing of the osc-uhm.py script is included in a zip file together with the Sonic Pi software, which can be downloded at the end of the article.

In order to start it, you use one of the two following commands from a terminal, depending upon whether the Sonic Pi instance you are using is running on the same Raspberry Pi as the Pimoroni card, or whether it is running on a different computer.
In the first case you type:

python3 osc-uhm.py

In this case the server responds with the message

Sonic Pi on ip 127.0.0.1
Serving on ('127.0.0.1', 8000)

here both Sonic Pi and osc-uhm.py communicate via the address localhost 127.0.0.1 Sonic Pi receivesOSC messages on port 4560 (its default) and osc-uhm.py receives messages on port 8000. Alternatively if using different computer use:

python3 osc-uhm.py -ip 192.168.1.37 -sp 192.168.1.129

In this case the server responds with

Sonic Pi on ip 192.168.1.129
Serving on ('192.168.1.37', 8000)

In this case the osc-uhm.py script is serving on address 192.168.1.37 and Sonic Pi is running on ip address 192.168.1.129 Obviously you substitute the actual addresses you are using in your own setup. You quit the server using ctrl+C.

The Sonic Pi end of the system
Turning to the other end of the system, there are some essentials that need to be included to set up the communications, but the beauty of the setup is that it is very easy to modify exactly what both Sonic and (as a result) the Pimoroni board do. The demo program included with this plays an intro using a fanfare in Sonic Pi, and a scrolling text message on the Pimoroni board, This uses another of the primitives in the board library, which lets it display PIL images, and scroll them. I used a graphics program to write some text in a file 7 pixels tall and 32o pixel long! After that has finished, Sonic Pi activates the button inputs and will run four different sections of code according to which button is pressed.

At its simplest just two commands are necessary for the system to function

use_osc "192.168.1.37",8000

which tells Sonic Pi where to send OSC messages (substitute the address of the computer running the Pimoroni card, or localhost if running on the same computer) and in order to send an osc message (for example)

osc "/setBright",0.5

which would adjust the global brightness setting of the board LEDs (in this case without a return function saying that it was done. I developed a little function that would automatically invoke and respond to such a return message, with the code below

define :om do |msg,*args|
  osc "/"+msg,*args,1
  sync "/osc*/"+msg.downcase+"done1"
end

Here is an example of just how simple programs can be (simpleExampleProgram.rb):

use_real_time
use_osc "192.168.1.37",8000 #incoming port from python osc server
#om automatically sends osc call and waits for corresponding sync callback id 1
define :om do |msg,*args|
  osc "/"+msg,*args,1
  sync "/osc*/"+msg.downcase+"done1"
end

om "setBright",0.5
om "diag",'red',0,0
om "diag",'blue',0,1
sleep 2

live_loop :lines do
  om "setColumnCol",tick%17,'green',50
end

Looking at this in detail, after the first line which set up the link destination for OSC calls, a function om is defined. This basically takes a string (which will from the OSC address) plus an arbitrary number of other arguments and assembles them into an OSC message. It adds a final argument 1 which will become the id value, then sends the OSC message. It then waits for an incoming OSC message addressed to the original string address, set to lowercase and with “done1” appended on the end. When this cue is received and synced to the function exits. The /osc* at the beginning of the incoming cue is added by Sonic Pi. In fact it adds further details, but these are matched by the * which acts as a wildcard.
The first command sent by the program to the OSC server is sent by the line
om “setBright”, 0.5 which sets the global brightness
the following two commands each produce two diagonal lines starting at the position 0 (for x) in ‘red” for the first one, and at position 0 (for x) for the second one in ‘blue’ which is inverted by( the second parameter which is now 1.
After a 2 second sleep a live loop is started called :lines. This repeatedly uses the “setColumnCol” OSC command which draws vertical columns in green with the x position varying from 0 to 16 (produced by the tick%17 which gives the remainder when tick {an increasing counter} is divided by 17). After the time delay 50 (ms) specified as the third parameter the line is reset to ‘black” by the relevant dispatcher function. No sleep line is required in the live_loop as the om call automatically provides a “sleep” until the returned /osc*/setcolumncoldone1″ message is received back.
You can see the various osc messages sent in the main log pane, and the cues received back in the cues log pane, assuming you haven;’t disabled them. Note too the use_real_time command to get the fastest response times from Sonic Pi.

The demo program (fullDemoControllProgram.rb) which responds to the four buttons, and plays various sequences is a bit more complex, but if you look at the various parts you should be able to follow what is happening. There are some comments included. The separate fanfare.rb file is also available which plays the Fanfare in the introduction. It should be stored in a suitable location specified in line 5 of the demo program.

A playlist on youtube has video output of the 5 sections of the demo program, consisting of the intro, and the code played by each of the four push buttons.
In addition a 6th video shows the Pimoroni Card connected to a Sonic Pi program which uses a synth controlled by a midi keyboard. Each note in a 2.5 octave range also triggers a small vertical line on the Pimoroni board giving a visual representation of the sound. The program also uses a midi controller knob on my keyboard to adjust the cutoff of the notes, which is also linked to adjusting the brightness of the LED on the board. I include this program (complexMidiControl.rb), but it is more complex, and comes without much explanation, and will require tweaking to match different setups. It is based on a program I published here which has been modified a bit for Sonic Pi 3.2.2, and has two calls to osc “/setHalfColumnCol”  which are used to switch on and off the relevant LED on the board. Finally a simpler version of this program (simpleMidiControl.rb) with non sustained notes is also included, which will be easier to understand and set up.

All of the relevant software is included in a zip file which can be downloaded here

Setting up. You will need to install the pimoroni unicorn hat mini library on your Pi as detailed here. Also sudo pip3 install osc-python to install the oscpython library that is required.

You need to have Sonic Pi installed. If using a Pi then use the latest version 3.2.2 from here or for Ubuntu 20.4 from here For Mac or Windows PC download from sonic-pi.net