This is a simple guide to build an electronic musical instrument. The MIDI notes are generated using a Raspberry Pi and a sonar sensor. Although the original Theremin is played using antennas, this one will use sonar technology as pitch control. The idea is to vary the distance between the user hand and the distance sensor, causing the pitch to change accordingly.
How will the measured distance (dist) translate to pitch?
This is done in the get_note() method.
def get_note(dist=0):
""" Compute the note based on the distance measurements, get percentages of each scale and compare """
# Config
# you can play with these settings
minDist = 3 # Distance Scale
maxDist = 21
octaves = 1
minNote = 48 # c4 middle c
maxNote = minNote + 12*octaves
# Percentage formula
fup = (dist - minDist)*(maxNote-minNote)
fdown = (maxDist - minDist)
note = minNote + fup/fdown
""" To-do: calculate trends form historical data to get a smoother transitions """
return int(note)
Like any other musical instrument, this one will translate a user action to sound. In this case the user changes the distance between the HC-SR04 sensor and an object (hand) producing a sound. The idea is to equally distribute the amount of notes through the distance scale, resulting in a playable area.
This is done using proportions. When dist = minDist the resulting note will be minNote. The same way is done on the other end of the scale. This enables the software to define the relation between the distance and the steps for each note in a function form. This function is evaluated every time a distance (dist) is measured.
It is assumed that the proportion of the measured distance (dist) should be equal to the proportion of the generated note (note).
$noteProportion = distProportion$
Each proportion can be calculated using simple fractions.
$distProportion = \frac{dist - minDist}{maxDist - minDist}$
$noteProportion = \frac{note - minNote}{maxNote - minNote}$
The resulting formula to calculate the note.
$note\left ( dist \right ) = minNote + \frac{\left (dist - minDist\right )\times \left (maxNote - minNote\right )}{maxDist - minDist}$
NOTE: This is just one approach to define the interface of this instrument. Add-ons and changes can be made in order to get many different effects in the way this is played.
# Derick DeLeon
# 2014-02
# theremin.py
"""MIDI based theremin using a Raspberry Pi and a sonar HC-SR04 as pitch control"""
"""derickdeleon.com"""
import RPi.GPIO as GPIO
import pygame.midi
import time
# Distance Related Methods
def prepare(GPIO_ECHO, GPIO_TRIGGER):
""" Initialize the Raspberry Pi GPIO """
# Set pins as output and input
GPIO.setup(GPIO_TRIGGER,GPIO.OUT) # Trigger
GPIO.setup(GPIO_ECHO,GPIO.IN) # Echo
# Set trigger to False (Low)
GPIO.output(GPIO_TRIGGER, False)
# Allow module to settle
time.sleep(0.5)
def get_distance(GPIO_ECHO, GPIO_TRIGGER):
""" get the distance from the sensor, echo - the input from sensor """
# Send 10us pulse to trigger
GPIO.output(GPIO_TRIGGER, True)
time.sleep(0.00001)
GPIO.output(GPIO_TRIGGER, False)
start = time.time()
# Taking time
while GPIO.input(GPIO_ECHO)==0:
start = time.time()
while GPIO.input(GPIO_ECHO)==1:
stop = time.time()
# Calculate pulse length
elapsed = stop-start
# Distance pulse travelled in that time is time
# multiplied by the speed of sound (cm/s)
distance = elapsed * 34300
# That was the distance there and back so halve the value
distance = distance / 2
return distance
# Audio and MIDI Related Methods
def conf_midi():
""" Initialize MIDI component """
instrument = 79 # Whistle
pygame.init()
pygame.midi.init()
port = 2
global midiOutput # It is used in other methods
midiOutput = pygame.midi.Output(port, 1)
midiOutput.set_instrument(instrument)
def play_midi(note, b4note, volume):
""" Play a new MIDI note and turn off the last one """
# use the last note to compare
if (note != b4note):
""" To-Do: smoother transitions between notes, use pitch bend. """
midiOutput.note_off(b4note,volume)
midiOutput.note_on(note,volume)
# help to not consume all resources
time.sleep(.15)
def get_note(dist=0):
""" Compute the note based on the distance measurements, get percentages of each scale and compare """
# Config
# you can play with these settings
minDist = 3 # Distance Scale
maxDist = 21
octaves = 1
minNote = 48 # c4 middle c
maxNote = minNote + 12*octaves
# Percentage formula
fup = (dist - minDist)*(maxNote-minNote)
fdown = (maxDist - minDist)
note = minNote + fup/fdown
""" To-do: calculate trends form historical data to get a smoother transitions """
return int(note)
# MAIN
GPIO.setwarnings(False)
# The pin number is the actual pin number
GPIO.setmode(GPIO.BCM)
# Set up the GPIO channels
trigger = 17
echo = 27
prepare(echo, trigger)
note = 0
conf_midi()
volume = 127
try:
while True:
b4note = note
# get distance
d = get_distance(echo, trigger)
# calculate note
note = get_note(d)
# to-do, take a number of sample notes and average them, or any other statistical function. eg. Play just minor notes, ponderate average, etc.
# play the note
play_midi(note, b4note, volume)
except KeyboardInterrupt:
GPIO.cleanup()
del midiOutput
pygame.midi.quit()
GPIO.cleanup()
pygame.midi.quit()
Here's a sound sample of the MIDI Theremin in action or download from here:
Please see my previous posts for detailed information on wiring and operation.
MIDI and the Raspberry Pi
Distance sensor working on a Raspberry Pi
Please visit and thanks to:
Please see my previous posts for detailed information on wiring and operation.
MIDI and the Raspberry Pi
Distance sensor working on a Raspberry Pi
Please visit and thanks to:
A nice story about theremins in pop culture: The Real Instrument Behind The Sound In 'Good Vibrations'
Thanks for your great tutorial. I'm getting the following errors when running the script:
ReplyDeleteFile "theremin.py", line 91, in
conf_midi()
File "theremin.py", line 53, in conf_midi
midiOutput = pygame.midi.Output(port, 1)
File "/usr/lib/python2.7/dist-packages/pygame/midi.py", line 414, in __init__
raise MidiException("Device id invalid, out of range.")
pygame.midi.MidiException: 'Device id invalid, out of range.'
Any suggestions?
Thanks for your comment, I'm glad you liked my tutorial.
DeleteIt looks to me that the port number is wrong. In my setup the port number that worked was port number 2, please try other integers (1,3,etc).
See https://www.pygame.org/docs/ref/midi.html#pygame.midi.Output for more info on this function.
Please let me know if that works for you.
D
Thanks for pointing me in the right direction. I no longer get that error after setting my port to 0 and I can hear the "hiss" of sound coming through the speakers, but I can't get any actual tones.
DeleteThe script found here works and gives me a reliable distance, so I know that I've got everything set up right.
http://www.modmypi.com/blog/hc-sr04-ultrasonic-range-sensor-on-the-raspberry-pi
Just can't get it to play any tones.
Any other suggestions? I appreciate all of your help so far!
Have you tried my post on MIDI and Rapsberry Pi?
Deletehttp://www.derickdeleon.com/2014/02/midi-and-raspberry-pi.html
I think that the problem is on the MIDI side. Please take a look to that post and let me know if you can hear something using that script.
Kind Regards,
D
Thanks for your reply and for pointing me to your other post. It does seem like the problem is on the midi side. Can't hear anything with your other script either. Is there anything else I need to do to get midi up and running on the pi? I'm using a B+. I couldn't find much via google. Thanks again for all of your help. Hoping I can get this running to impress my students.
DeleteCan you please try this code?
Deletefor id in range(pygame.midi.get_count()):
print pygame.midi.get_device_info(id)
Let's see the output please.
D
Here is a nice explanation on the pygame.midi module
Deletehttp://tinyurl.com/norxpud
Hope it helps, please let me know.
D
Thanks for the links they were very helpful.
ReplyDeleteI tried the code and got the following output:
('ALSA', 'Midi Through Port-0', 0, 1, 0)
('ALSA', 'Midi Through Port-0', 1, 0, 0)
Then I tried changing the port to 0 on your midi tutorial (midihelloworld.py) and the script ran without error but no sound.
I will try to reproduce the error as soon as I get home from work.
DeleteIn the meantime, are you listening form the headphones jack or the HDMI audio?
D
I got it to work. I had to first run timidity as a server and then point your script to it for output. Thanks so much for your help the last few days.
ReplyDeleteHere's the command I used:
timidity -iA -B2,8 -Os -EFreverb=0
AWESOME!!! I am glad it finally worked.
DeletePlease feel free to ask any question any time.
D
What exactly do you mean with " point your script to it for output" ? How do you do that ?
DeleteI guess @grainybazzles ment to chanhe the script in a way that the output sound would get out using timidity server instead of the ALSA one. I personally haven't tried this, but I guess is a good fix.
DeleteAre you having issues?
D
Hi how do you do "run timidity as a server"?
DeleteOnce it is downloaded, what do you do?
I hope this post helps you. https://vsr.informatik.tu-chemnitz.de/~jan/nted/doc/ch01s51.html
DeleteLet me know how it went.
D
hmm not quite sure what to do after typing timidity -iA -B2,8 -Os1l -s 44100
Deletehow do I get the code tp play through timidity?
Thanks,
Mizu
DUDE help me
ReplyDeleteTraceback (most recent call last):
File "theremin.py", line 91, in
conf_midi()
File "theremin.py", line 53, in conf_midi
midiOutput = pygame.midi.Output(port, 1)
File "/usr/lib/python2.7/dist-packages/pygame/midi.py", line 414, in __init__
raise MidiException("Device id invalid, out of range.")
pygame.midi.MidiException: 'Device id invalid, out of range.'
___________________________________________________________________
İ connect sensor like this
http://www.raspberrypi-spy.co.uk/2012/12/ultrasonic-distance-measurement-using-python-part-1/
It looks like your port is invalid. Please check my MIDI post about how to get MIDI and RasPi work.
DeletePlease let me know how it went.
D
http://www.derickdeleon.com/2014/02/midi-and-raspberry-pi.html
ReplyDelete# This port number seems the only one to work
port = 2
latency = 1
i try port "0"
no error but no sound
check rpi sound out "raspi-config" its ok
check C-SR04-its ok
DUDE help me
ReplyDeleteI 'm just a simple music teacher from east
HC-SR04_example.py work good but ;
python theremin.py not work no error but no sound .any idea ?
maybe u upload raspberry pi image wetransfer*com or other share site , I need it for my poor students
I will upload an image as soon as I can (I guess it is not going to be fast). In the meantime, the RasPi can output audio through the HDMI connection, have you tried to connect a TV with speakers?
DeleteHave you tried the MIDI solo exercise in this blog? If you did, did you got any errors?
Please let me know how it went.
D
yes i try hdmı and anlolog out no sound
DeleteI have problem making this code work on my project , tried to adapt it to my build and I still get the following error:
ReplyDeleteFile "Theremin.py", line 56, in conf_midi()
File "Theremin.py", line 31, in conf_midi
midiOutut = pygame.midi.Output(port, 1)
File "/usr/lib/python2.7/dist-package/pygame/midi.py", line 414, in __init__pygame.midi.MidiException("Device id invalid, out of range."
you can open another terminal, and start the timidity server...
ReplyDelete> timidity -iA
that will get it running in interactive mode, so go back to the original terminal session and run your python script. If you want timidity to always run on bootup, edit cron and add the command there
> crontab -e
then add
sudo timidity -iA
Hello Derick, I am using your program with disabled children and it goes perfectly but after a time between 5 and 10 minutes it stays hung up. What can be the reason?
ReplyDeleteThank you
Hi I am having trouble where I cannot hear any sound. Can you kindly document the sequence of command execution between the Timidity server and the python code please. I really want to get this working for my son.
ReplyDeleteHi, what version of hardware (Raspberry Pi) are you using? This was done so long ago that I'm pretty sure all software is different. Please share details on your setup, I will replicate and post steps for you.
Delete