/ 52 weeks of pi

Creating Music with Sonic Pi on the Raspberry Pi

Back in May, I was fortunate to attend the Stir Trek conference in Columbus OH (it sold out in under a minute). There were a lot of great presentations, but one that really stood out for me was one on Sonic Pi.

Scott Fradkin live-coded Sonic Pi for nearly an hour, not only explaining what it was capable of, but showing us too. He kept building it up as the session went on, and everyone in the theatre had a chance to see and hear what he was creating.

By the end of the session, he had a good beat going…

What is Sonic Pi?

Sonic Pi is an environment that lets you actually program sounds and create music. You can learn about music and coding at the same time, and it’s entirely free too!

Sonic Pi encourages you to learn about both computing and music through play and experimentation. The most important thing is that you’re having fun, and before you know it you’ll have accidentally learned how to code, compose and perform.

– Sam Aaron, “Welcome to Sonic Pi” tutorial

Installation

If you want a minimal amount of fuss, do your work directly on the Pi. The Jessie version of Raspbian comes preinstalled with Sonic Pi, so you don’t need to do anything else.

However, even though “Pi” is in its name, you can run Sonic Pi on Mac, Windows, different *nix environments and more, so you can create music without being on the Raspberry Pi. Sam has helpfully provided builds for all the major OS’s. I didn’t want to develop everything on the Pi itself (slower, smaller screen size, etc), so I used the Mac OS X installer, which went smoothly.

Hello World!

When you start Sonic Pi for the first time, it opens a tutorial in the lower-left corner. I’d highly recommend following the first section, “Welcome to Sonic Pi”, and probably the second section too, “Synths”.

Here’s the equivalent of “Hello World” in Sonic Pi, taken from the tutorial. It just plays a steady beat like a metronome, in an infinite loop until you stop it.

live_loop :flibble do
  sample :bd_haus, rate: 1
  sleep 0.5
end

What is OSC?

Assuming you’ve got Sonic Pi installed, and it’s working correctly, there’s another technology we need to cover briefly too.

Sonic Pi can record the music you create, allowing you to save it as a wav file. So we could just create some wav files and play them from our Python code. But where’s the fun in that? If we want to change individual notes, we have to record the entire clip again, and that’s just tedious.

I wanted something more dynamic, a way to communicate with Sonic Pi from within the code and send it notes to play. And that way is called OSC.

OpenSound Control (OSC) is “a protocol for communication among computers, sound synthesizers, and other multimedia devices that is optimized for modern networking technology.” – OpenSound Control User Guide

There’s a lot more on that page about it, but all you need to know for now is that it’s a protocol, and we can use it to communicate with Sonic Pi.

Try it on the command line

Sonic Pi was designed with OSC in mind, and is configured to listen for OSC messages on localhost port 4557, so that’s where we need to send our messages. Lucky for us, we don’t need to reinvent the wheel. Nick Johnstone created a ruby gem called sonic-pi-cli, which allows us to send messages via the command line.

Assuming you already have Ruby installed (it should be there if you’re doing this on the Pi, otherwise it’s an easy installation), run this from the command line to install the gem:

sudo gem install sonic-pi-cli

Now make sure the Sonic Pi app is running, then go back to the command line and send it a note to play:

sonic_pi play :E4

For those interested, here’s the source code where Sonic Pi is listening on port 4557 (according to Sam, Sonic Pi only listens on localhost). Likewise, sonic-pi-cli is sending your commands to localhost:4557.  If you don’t have Sonic Pi running, sonic-pi-cli won’t be able to communicate, you won’t hear any sounds, and instead you’ll get a little reminder to start it up:

ERROR: Sonic Pi is not listening on 4557 – is it running?

Try it in a Python script

Assuming that worked, we still need to find a way to communicate with Sonic Pi from Python. There are packages written for Python that do just that, including python-osc, pyOSC and pyliblo, but we’re just going to build on top of what we’ve already got.

Raspbian has Ruby and Python installed by default, so we can just run commands from Python, to be intercepted by the Ruby gem, and forwarded on to Sonic Pi.

(This is how development often works. Find or design the functionality you’re looking for, such as sonic-pi-cli, and once it’s stable build the next layer on top of that. Once you get it running, you can always improve it later on, or try substituting one of those Python packages for the Ruby gem.)

Either place the following two lines in a script and run them, or type “python” in the terminal to open the python shell and paste them in one at a time.

from subprocess import call
 
call(["sonic_pi", "play :E5"])

Big Ben Chimes

The idea for this project started when I was reading through the Sonic Pi tutorial, and somehow found myself on the wikipedia page for the Westminster Quarters. That’s a melody used by some churches, grandfather clocks, Big Ben, etc., on each quarter hour.

I figured it might make a fun project that tied in the Pi, Python and Sonic Pi, in a unique way. I had to do *something *with LEDs because… reasons. Visual feedback is nice.

The Circuit

First, here’s the circuit I designed. It’s really basic, just connecting a series of LEDs to board pins 12, 16, 22, 36 and 40, which can be turned on or off in sequence with the notes being played.

Grandfather Clock Sonic Pi_bb

The Script

Make sure Sonic Pi is running, and then check out and run the script, or just copy the code below.

Note: You may want to adjust the minutes in the monitor function. Change “00” to whatever the current minute is, so you can see feedback immediately when you run the script. Or just wait until the nearest quarter-hour!

from datetime import datetime
from subprocess import call
import threading
import time
import RPi.GPIO as GPIO
# import GPIOmock as GPIO
 
 
PAUSE_BETWEEN_NOTES = 0.75
CHECK_TIME_INTERVAL = 10
 
# LEDs to blink with each 15 min
QUARTER_LED_PINS = [12, 16, 22, 36]
CHIME_LED_PIN = 40
 
 
# Sets of notes for Westminster Quarters
# https://en.wikipedia.org/wiki/Westminster_Quarters
NOTE_SET_1 = ["Gs4", "Fs4", "E4", "B3"]
NOTE_SET_2 = ["E4", "Gs4", "Fs4", "B3"]
NOTE_SET_3 = ["E4", "Fs4", "Gs4", "E4"]
NOTE_SET_4 = ["Gs4", "E4", "Fs4", "B3"]
NOTE_SET_5 = ["B3", "Fs4", "Gs4", "E4"]
 
NOTE_PERM_1 = [NOTE_SET_1]
NOTE_PERM_2 = [NOTE_SET_2, NOTE_SET_3]
NOTE_PERM_3 = [NOTE_SET_4, NOTE_SET_5, NOTE_SET_1]
NOTE_PERM_4 = [NOTE_SET_2, NOTE_SET_3, NOTE_SET_4, NOTE_SET_5]
 
 
def play_note(note):
    call(["sonic_pi", "play :" + note])
 
 
def play_perm(note_perm):
    set_num = 0
    for note_set in note_perm:
        GPIO.output(QUARTER_LED_PINS[set_num], GPIO.HIGH)
        for num in range(4):
            play_note(note_set[num])
            time.sleep(PAUSE_BETWEEN_NOTES)
        GPIO.output(QUARTER_LED_PINS[set_num], GPIO.LOW)
        time.sleep(PAUSE_BETWEEN_NOTES)
        set_num += 1
 
 
def play_hour_chimes(hour):
    for num in range(hour if 0 < hour < 13 else abs(hour - 12)):
        call(["sonic_pi", "play :E3"])
        GPIO.output(CHIME_LED_PIN, GPIO.HIGH)
        time.sleep(PAUSE_BETWEEN_NOTES + 0.25)
        GPIO.output(CHIME_LED_PIN, GPIO.LOW)
        time.sleep(PAUSE_BETWEEN_NOTES)
 
 
def sleep_to_next_minute():
    time.sleep(60)
 
 
def monitor():
    while True:
        curr_time = datetime.now().time()
        curr_minute = curr_time.minute
        if curr_minute == 15:
            play_perm(NOTE_PERM_1)
            sleep_to_next_minute()
        elif curr_minute == 30:
            play_perm(NOTE_PERM_2)
            sleep_to_next_minute()
        elif curr_minute == 45:
            play_perm(NOTE_PERM_3)
            sleep_to_next_minute()
        elif curr_minute == 00:
            play_perm(NOTE_PERM_4)
            play_hour_chimes(curr_time.hour)
            sleep_to_next_minute()
        else:
            time.sleep(CHECK_TIME_INTERVAL)
 
 
def start_monitor():
    t = threading.Thread(target=monitor)
    t.daemon = True
    t.start()
 
 
def initialize_gpio():
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(QUARTER_LED_PINS, GPIO.OUT, initial=GPIO.LOW)
    GPIO.setup(CHIME_LED_PIN, GPIO.OUT, initial=GPIO.LOW)
 
 
def main():
    try:
        initialize_gpio()
        start_monitor()
        raw_input("\nPress any key to exit.\n")
    finally:
        GPIO.cleanup()
 
 
if __name__ == '__main__':
    main()

Here’s a 12-minute video of me talking a bit about it, and showing it in action.

Learn More

If you want to check out what Scott did at Stir Trek, you can find it on GitHub account.

Also, the Stir Trek organizers recorded the sessions this year, and you can watch his below.

Resources

If you’re interested in learning more, here are some links to Sonic Pi (and related) resources.

Thoughts

I also tried Ubuntu 16.04 but ran into multiple startup issues and problems with sound not working (in Sonic Pi only). Debian Jessie is an involved setup that I didn’t feel like doing, but Debian Stretch (its successor, still under development) should allow for a simple one-line install from the command line.

Questions? Comments? Feel free to leave a message below!st


Grant Winney

Grant Winney

I write when I've got something to share - a personal project, a solution to a difficult problem, or just an idea. We learn by doing and sharing. We've all got something to contribute.

Read More