Dual Sensor Theremin for Sonic Pi

A word of caution. When this article was written the current version of Sonic PI on Raspbian Stretch was 3.0.1. Since then the Pi4 has come out, and the current Raspbian Buster now has a version of Sonic Pi named 3.1 Unfortunately this is not a fully functional version as on the Mac or Windows versions, and it will not support OSC calls necessary for this project to work. Sonic Pi is just about to be upgraded to version 3.2, but currently there is no package for the Raspberry Pi, although the build process has been refined, and it will not be too difficult to build your own. (I currently run it on my Pi4 and performance is very good).
So currently if you want to use this project you must use Raspbian Stretch on a Pi2, or Pi3 with Sonic Pi 3.0.1 (but NOT a Pi4 which will not work with stretch, or build your own version to run on a Pi4, with support for OSC and midi.

Previously I built a theremin based on a modified design from The Magpi magazine, which is now also the subject of a project on the raspberrypi.org site here

Alexandra Stepanoff playing a Theremin in 1930. (from Wikipedia Theremin article)

While this is a great little project, I was not very satisfied with the end result which didn’t sound very similar to a “real” theremin. The original theremin was an analogue instrument in which the audio frequency was produced by the interaction between two radio frequency oscillators at very nearly the same frequency which were controlled by the proximity of a musicians hand to a metal rod aerial. A third oscillator controlled by the proximity to a metal loop of the musicians other hand was used to control the volume. this gave a sound which was continuous when started by the proximity of a hand, whose frequency and volume could be varied smoothly. You can see an example in the picture above (from the Wikipedia article on Theremin)

The first thing I resolved to do was to use two ultrasonic sensors instead of one. I used the same circuit as the one in the raspberrypi.org article but built it twice on the same breadboard. The first circuit used pins 4 and 17 as in the original, and the second one, used pins 23 and 24 for the trigger and echo signals instead, otherwise being identical. I built the circuit on a RasPiO ProHat board which gives convenient access to all the GPIO pins, as shown in the photographs below.

To read the inputs from the two ultrasonic sensors a python script is used. The starting point for the python script is to extract data from the two ultrasonic sensors using the excellent gpiozero library, included by default on the latest Raspbian Stretch image. This makes it very easy to get a series of readings from the two sensors.
In order to send the data to Sonic Pi an OSC message is used, using the python-osc library. You can install this using:

sudo pip3 install python-osc

The complete code for my python3 script is shown below:

#!/usr/bin/env python3
#program sets up two distance sensors and exports readings using OSC to host port 4559
#using gpiozero and python-osc libraries
#written by Robin Newman, May 2018
from gpiozero import DistanceSensor
from time import sleep
from pythonosc import osc_message_builder
from pythonosc import udp_client
import argparse
import sys

def control(spip):
    sensor = DistanceSensor(echo=17, trigger=4,threshold_distance=0.5) 
    sensor2 = DistanceSensor(echo=23,  trigger=24,threshold_distance=0.5)
    sender = udp_client.SimpleUDPClient(spip,4559)

    while True:
            r1 = (sensor.distance* 100) #send numbers in range 0->100
            r2 = (sensor2.distance* 100)
            sender.send_message('/play_this',[r1,r2]) #sends list of 2 numbers
        except KeyboardInterrupt:

if __name__=="__main__":
    parser = argparse.ArgumentParser()
    default="", help="The ip onic Pi listens on")
    args = parser.parse_args()
    print("Sonic Pi on ip",spip)

You can type this in using text editor from the Pi Gui Accessories Menu, or using the nano editor from a terminal window. Alternatively you can download it from my gist site (link at the end of of the post). To make it easy to start, when you have it installed on your Raspberry Pi set it to be executable by the pi user by typing (in a terminal window):

chmod u+x theremin.py

I have recently received my Kickstarter Pi_Juice board after sdveral years wait :-) and I decided to try it out with this project, as it would mean that the sensor setup: RasPiO board, Pi-Juice board and Raspberry Pi were completely self contained. That is why I added the optional input parameter, so that I could send the OSC messages to an external computer on the local network, instead of just using it on the local Raspberry Pi. Of course if you don;t have a second suitable computer on which to run Sonic Pi, you can do all of this using the built in Sonic Pi on Raspbian Stretch (version 3.0.1), although you will need to have a screen and keyboard and speaker attached to your Pi to do so. (It IS possible to run Sonic Pi on a headless Raspberry Pi using xdrp, which I have done on other projects, but I don’t want to complicate this post by giving details here). If you are going to use a separate computer for Sonic Pi, then first make sure that your Raspberry Pi is set up to connect automatically to your network via WiFi. (I used a Pi3 with built in Wireless). Then you can use the Raspiconfig utility on the gui preferences menu to set your Pi to boot to the command line (logged in). You should also enable SSH using the same utility. When you do so, you should change the Raspberry Pi password for user pi from the default raspberry to something else you can remember. Again the same utility will let you do that. Note the ip address of your Raspberry Pi on your wireless network, so that you can connect to it remotely. If you don’t want to do this, you can of course leave the Pi connected to screen and keyboard and just start the theremin.py script directly from a terminal window.

All being well if you reboot your Raspberry Pi (you can do it with the screen and keyboard attached first time if you like to check that it is working) you should be able to connect to it from your second computer running Sonic Pi. I used a Macbook Pro, with ip address and my Raspberry PI was on, so I typed:

ssh pi@

on my Mac terminal window, followed by the password for user pi on my Raspberry Pi. This connected me, and since I had saved the theremin.py script in my pi home directory I then just had to type this to get the script running (remember it was set to be executable):

./theremin.py --sp

This started the script running, and it reported in the terminal window.

Sonic Pi on

Then if I started moving my hands back and foward near the ultrasound sensors I could see streams of numbers beneath, shown that the readings were geing generated. e.g.

r1: 6.69 r2: 6.45
r1: 6.69 r2: 6.45
r1: 9.28 r2: 6.45
r1: 9.28 r2: 6.45
r1: 9.26 r2: 6.49
r1: 9.26 r2: 6.53

That completes the setup for the Raspberry Pi. Moving on to Sonic Pi, this should be started on your second computer (in my case my Macbook Pro). I was using version 3.1 the latest release on this platform. In an empty buffer screen (selected using the tabs at the bottom of the main screen, you should add the following program, either by typing it out or by downloading it from the gist link at the end of this post.

#Theremin plus, controlling pitch and ixi_techno phase by Robin Newman May 2018
#I control a continously playing note (length 2000)
use_debug false

define :putsPretty do |n,p|
  return num

define :scalev do |v,l,h|
  return (l+v).to_f*(h-l)/100

with_fx :reverb,room: 0.8,mix: 0.8 do
  with_fx :ixi_techno,phase: 4,phase_offset: 1,mix: 0.8 do |p|
    set :p, p
    use_synth :subpulse
    k=play octs(0,2),sustain: 10000,amp: 0
    set :k,k
    live_loop :theremin do
      b = sync "/osc/play_this"
      puts putsPretty(r1,2),putsPretty(r2,2)
      if r1  <  60 then #adjust note pitch, and restore volume to 0.7
        control get(:k),note: octs(r1+12,2),note_slide: 0.06 ,amp: 0.7,amp_slide: 0.2
      else #set output vol to 0
        control get(:k),amp: 0,amp_slide: 0.2
      if r2 < 0.8 then #adjust phase modulation rate, and restore mix to 0.8
        control get(:p),phase: r2,phase_slide: 0.06,mix: 0.8,mix_slide: 0.2
      else #switch off phase modulation by setting mix to zero
        control get(:p),mix: 0,mix_slide: 0.2

Looking at this you will notice that I use the effect :ixi_techno, and further down the program you will see that the :phase value, or the rate at which this effect modulates sounding notes inside the effect are controlled by the value of r2, the second output sent from the two ultrasound sensors. In my initial description of the “real” theremin I said that the two available controls adjusted the pitch or frequency of the notes played and the volume of those notes. Initially I tried adjusting the volume of the notes, and it can be done, but I didn’t find it very effective. Instead I opted to add this optional modulation to the playing note, but the nice thing about Sonic Pi is that it is fairly easy to adjust one of a variety of characteristics using the second number, and I hope to try out some others, and may even find a more satisfying result for volume adjustment in the end.

The main technique I use in the program is to start a very long note playing with zero volume, and then to use Sonic Pi’s ability to control the parameters of the note while it is playing. By adding sliding values to the parameters which change I can get a smoothly varying pitch for the note unlike the more simple theremin in the raspberrypi.org example. There are two functions defined at the start of the program. The first one putsPretty is used to print the numbers to just 2 decimal places. The second scalev  is used to scale the number (which arrive in the range 0->100 to whatever I need in the program. r1 is adjusted to range 30->100 and r2 to the range 0.1->1 Some reverb is applied to all of the output, and then the :ixi_techno effect is applied The |p| is a variable which gets a reference to the running effect. I store this in the time_state as :p so that it can be referenced and used to control the parameters of the effect further down the program. Similarly the long note is started and a reference to it is stored in the variable k in the line:

k=play octs(0,2),sustain: 10000,amp: 0

This is then subsequently stored in the time-state as :k so that it can be retrieved and used to alter the pitch of the note according to the value of r1. Notice the use of the octs function to play two notes an octave apart. Initially the pitch is 0 and the volume 0 as well, but this is altered by the control later on.

The meat of the program takes place in the live_loop :theremin This waits for an incoming OSC messages addressed to /play/this in the sending program (Sonic PI adds the initial /osc to show the source. This contains the data of the two numbers r1, and r2 in a list. They are retrieved, printed on the screen using the putsPretty function and then processed to control the note. a cutoff value of 60 is taken for the scaled  r1 (equivalent to 60cm). For values above this the volume of the note is set to 0 (with a slide time from any previous value of 0.02), and no note sounds. For values below 60 the pitch of the note is adjusted, and the volume restored to 0.7. Again slide times are used, 0.06 for the note adjustment (the time between successive numbers arriving) and a more sedate 0.2 for the volume adjustment. For(scaled)  r2 values < 0.8 the phase rate of the modulation is adjusted, and the mix value is set to 0.8. For values > 0.8 the mix value is set to 0, effectively disabling the effect. Again suitable slide times are chosen.

The one final “gotcha” to be aware of is that for Sonic Pi to receive OSC messages from an external source you need to enable this in the preferences IO tab. (see below)

When you do this, if the theremin.py script is still running, you should see lots of osc messages in the cues section of the Sonic Pi screen like this:

I you now run the Sonic Pi program, then you should be able to control what you hear by moving your hands in front of the two sensors.

I have made a film of the project in action which you can see here
Details of the circuit can be seen on the raspberrypi.org site here
You can download the software here


10 thoughts on “Dual Sensor Theremin for Sonic Pi

    • You should use the resistors. There are two for each ultrasound sensor. The sensors use a 5V supply and the gpio input pins should not receive such a high value. The two resistors reduce the input pulse from the echo pin to an acceptable range. Without using them you could cause some damage.

  1. Hi Robin,
    Thanks a lot for this great project !!
    I succeeded in making it and it works :-)
    If I may, I would propose some corrections/evolutions/warnings on the code and on the article :
    – I had an error running the Python program like :”
    /usr/lib/python3/dist-packages/gpiozero/input_devices.py:996: DistanceSensorNoEcho: no echo received
    warnings.warn(DistanceSensorNoEcho(‘no echo received’))
    You said “The first circuit used pins 4 and 17 as in the original, and the second one, used pins 23 and 24 for the trigger and echo signals instead”. But in the Python program, you used pin 23 for trigger and 24 for echo which is the opposite => sensor2 = DistanceSensor(echo=23, trigger=24,threshold_distance=0.5). When I tried it, I checked all my wiring 10 times and I even replace my 2nd sensor thinking it would be broken. At the end I rewrite the line so : sensor2 = DistanceSensor(echo=24, trigger=23,threshold_distance=0.5).
    – There were no echo in Sonic Pi even with the python program working live. Maybe it is because I use Sonic Pi 3.2.2 on Windows but the default OSC port is 4560 instead of 4559 in the python program : sender = udp_client.SimpleUDPClient(spip,4559). I changed it in the Python program.
    – For the same reason maybe, the Ruby program for Sonic Pi was not working for me. After searching (adding “puts” at every line because I’m not very familiar with Python ;-) I found that it was blocked at this line : b = sync “/osc/play_this”. Reading Sonic Pi doc (https://sonic-pi.net/tutorial.html#section-12-1) I found an exemple with a “*” just after “osc”. And it started working by replacing the line by : b = sync “/osc*/play_this”

    At the end, I found this Theremin fun and very open because of Sonic Pi. Clever idea to be able to use Sonic Pi on another computer. But too much laggy to be usable as a real instrument and play music with. Maybe it is because I use an old Raspberry 2 (a spare I already owned). I may also try with Sonic Pi on my Rpi instead of with another machine in my network. Or maybe it is the sensors ? On your video, I noticed this lag problem already but maybe I’m wrong ?

    Thanks again and Happy RPiing !

    • Thanks for your comments. Sorry you had some difficulties. This is quite an old article and the syntax for receiving OSC calls has indeed changed with new versions of Sonic Pi since it was written. Whereas you used to use b = sync “/osc/play_this” you now need to use b = sync “/osc*/play_this” s the full osc message received included IP address and port info in it which is matched by the wildcard * when it is received. Also in the latest Sonic Pi the port used is now 4560 instead of 4559 and this has to be altered in the python script.
      I you liked this project, may I recommend the later similar project using a ToF (time of flight) laser sensor. It is much more responsive and accurate.

  2. Hi – I had a lot of fun over a couple of lockdown days putting together my project using your ideas. I really appreciate your writing this up, maintaining the code and making it available.
    My project uses a breadboard with the sensors linked to a headless Pi 3B+
    Output is from HDMI through an adapter and into my stereo via RCA connectors, which sounds much better than output from the Pi’s own headphone socket.
    I use a desktop on the Pi, installed from the regular OS so I can control things with the built-in VNC (switched on from the start button/preferences/configuration menu)
    On the way through I’ve learnt a lot about Python, the Theremin, and how Sonic Pi works. Next is to start fiddling with your parameters that the sensors control and see what sort of fun sounds I can get out of it!
    Thanks once more

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s