DIY Musical Instrument
Arduino Theremin
Jurjan Wouda Kuipers & Lewis Yu
Design Blog
As our project objective is to make an instrument that is similar to a violin, we have decided to go with a theremin type instrument. The violin is an instrument which can play continuous frequencies, meaning that any frequency in a range can be played. This is different from a piano for example which is discrete, as there are only certain notes that can be played, and not all the sounds in between. A theremin is also a continuous instrument, so any frequency in a range can be played which is similar to a violin, so we decided to be inspired by this.
To make our design more interesting and add more musical functionality, we were inspired by the way that organs work. On an organ you have the possibility to add notes over the current note you are playing. If you are playing middle C for example, which is the 60th note on a keyboard, you can add a note 7 intervals above this, so that when you play C, note 60, it adds the sound of a G, note 67. This adds more layers to the sound and makes it more musically interesting and expressive. When playing a different note, the interval remains the same, so when playing and E, which is note 64, it would add note 71, which is a B. This means that while only playing one note with your hands, a second note is also produced by the instrument.
This is done on an organ by choosing how loud this second note would be, and we wanted to add this functionality to our instrument as well. Using 3 potentiometers, each one would correspond to different intervals, one being 7 above, one 12 above and the last one 19 above. Depending on how you set each potentiometer, it is decided how loudly each interval is played.
This way of layering sounds over each other is very common in music, and it gives certain instruments its unique timbre and sound, which is why it would be exciting to add to our musical instrument as well.
After thinking about these main elements we wanted to add to produce the sound itself, we wanted to add more functionality and interactivity using buttons. As a theremin, the instrument we are basing our project on, is relatively hard to play and it is easy to play a wrong note, we wanted to add something to easily improve the playing for the user. We wanted to achieve this with three buttons which would affect which specific notes which the instrument would output.
When we look at the picture above, these are all the notes in one piano octave. There are 12 possible notes, and this setup is what is repeated multiple times to make a piano. When playing any song, we are in a certain key, so that only 7 of these 12 notes are being used. In the key of C for example, only the white keys are played. Using buttons we wanted to add functionality so that the user could set it up so that the instrument would be in the key of C, and only these white keys would be played and nothing in between.
As the intervals needed to find all these notes in a key are always the same we can easily calculate which notes the user wants. So to play only the white keys for example, we start at C and then skip ahead 2 notes, 2 notes, then 1 note, then 2, 2 and 2 again. This way we skip all the black notes. To find all the notes in the key of E we use the same numbers, so 2, 2, 1, 2, 2 and 2.
Using this, when the user presses the button, it takes the last note that was played, and then finds all the notes in that key. These notes are then added to an array. To then only play those notes, the input from the arduino is mapped to the range of the array, which is then used for the musical output.
From a musical perspective this is great, as it is then impossible to play a wrong sounding note, greatly improving the way the instrument can be played adding a useful interactive feature.
Other than this first array of notes which we just calculated which is called the major scale, we also added two more buttons which do a similar thing but produce a different array of notes. Depending on the genre of music that is played, the user can choose which button to press to produce the best sounding music.
To produce this instrument we therefore need several parts. To make the theremin aspect of our project work, we are using two ultrasound sensors which measure distance. One of them will affect the frequency of the sound, and the other will affect how loud the sound is being played. These ultrasound sensors will be placed at opposite ends of the instrument, so that the user needs to use both of their hands to play.
We will also be using 3 potentiometers to produce the secondary notes above the main note at certain volumes.
The third thing we are using are the buttons which will be set up to tune the arduino instrument to a certain key and scale to improve functionality.
Lastly, we are using LEDs to indicate which button has been pressed, which provides more user feedback.
We made the following diagram to indicate what the final product would look like.
After making this image, we started working out how we were going to execute it in real life using the arduino, so we made a number of breadboard diagrams to get the layout to be correct with all of our components in the right place. The image below shows our final breadboard layout.
One of the main issues we faced was how we were going to actually produce the sound. There were a number of options we had. First of all, we could connect an amplifier to the arduino board, and output sound using the tone() function just like with the Piezo. This had many downsides though, such as the quality of the sound being limited to 8 bits, and the possibility of only playing one tone at once, so we could not layer multiple notes as we were planning.
Our second idea was to convert the user inputs into midi values. Midi is what connects instrument inputs with computers, so that we could convert the values that the ultrasound sensors used into sound for example. The way that midi works is that it takes one value indicating which note should be played, and one other value indicating at what volume said not should be played. These midi values are then put into a Digital Audio Workstation (DAW), where the numbers are used to output sound. The user can then choose themselves what it should sound like, for example a digital piano or guitar. Using midi also made it possible for us to layer multiple notes like we wanted, as we could simply send two midi notes to be played at once.
The way the midi setup worked was that the input values from the arduino were sent to the serial monitor. We then used a program called Hairless Midi, which was recommended to us by the demonstrators, to read these serial monitor values and convert them to the specific midi notes they referred to. To then get these notes to appear in the Digital Audio Workstation, we used a program called LoopMIDI which created a virtual midi port which the DAW could read from. This means that the values from the serial monitor were read by Hairless Midi, which sent the midi notes to LoopMIDI, which could then be read by the DAW which then outputs the sound.
While this sounds complicated, from a musical standpoint this is by far the best way to do this, as sending the Midi data to the DAW gives the user a lot of customization and expressive features that would not be possible if the sound were simply output to an amplifier on the board.
Project Task List
To complete this project there were a number of tasks that had to be carried out. Firstly was figuring out which parts to order, building the circuit using the components, writing the code and testing the code. As we are currently not in the same country, Lewis was in charge of actually making the circuit, and Jurjan helped with ordering the parts and writing the code.
We did much of the coding together online so that we both knew how it worked and we could come up with improvements together to make it work better. A lot of the testing was done while adding components, so we would add a component and then test how it would work, until it was complete.
Due to the way we split up the work it was very evenly divided between us. In the first week we mainly researched how we were going to carry out making the project, and in the weeks that followed we actually put the circuit together, did testing and wrote the code for it.
Maker Manual
Parts list:
- Arduino Uno
- Breadboard 60 pins in length
- 2x Ultrasound sensors
- 3x Push buttons
- 3x Potentiometers
- Green LED
- Red LED
- 2x 560 ohm Resistors
- 3x 10k ohm Resistors
- Assortment of wires of varying length
Build:
-
- Place the 2 ultrasound sensors on pins 1-4 on the breadboard facing outwards
- Place the 3 buttons on pins 16-26 with a 1 pin gap in between
- Place the 10k ohm resistors on pins 16, 20 and 24 connecting them to the positive pins on the breadboard
- place the 3 potentiometers on pins 28-38
- place the LEDs on pins 44-47
- place the 560 ohm resistors on the negative end of the LED connecting it to the negative pins on the breadboard
- connect the trigger pins on the ultrasound sensors to analog inputs A4 and A5 on the arduino
- connect the echo pins to pins 3 and 5 on the arduino
- connect VCC to the positive pins on the breadboard
- connect GND to the negative pins on the breadboard
-
- on the buttons, connect one side of the wires to the pins next to the resistors and connect the other end to pins 8, 9 and 10 on the arduino
- connect the other side of the button to the negative pins on the arduino
- connect positive and negative pins on the potentiometer to the positive and negative pins on the breadboard
- connect the swiper pins to analog inputs A0, A1, A2 on the arduino
- connect the positive on the LEDs to pins 3 and 5 on the arduino
Circuit Diagram
Code
#include <MIDI.h>
// Create and bind the MIDI interface to the default hardware Serial port
MIDI_CREATE_DEFAULT_INSTANCE();
//3R, 5G
int rPin = 3;
int gPin = 5;
int cm1 = 0;
int cm2 = 0;
int midiDistance1;
int midiDistance2;
int buttonPins[] = {8,9,10};
int buttonVal[3];
boolean pressed = false; //checks if the button has been pressed or not
int key[35]; //fills array with midi notes for certain key
int keyVal; //sets the current value corresponding to the key
boolean pressed2 = false; //checks if the button has been pressed or not
int key2[35]; //fills array with midi notes for certain key
int key2Val; //sets the current value corresponding to the key
boolean pressed3 = false; //checks if the button has been pressed or not
int key3[35]; //fills array with midi notes for certain key
int key3Val; //sets the current value corresponding to the key
int sensorPins[] = {0,1,2};
int sensorVal[3];
long readUltrasonicDistance(int triggerPin, int echoPin)
{
pinMode(triggerPin, OUTPUT); // Clear the trigger
digitalWrite(triggerPin, LOW);
delayMicroseconds(2);
// Sets the trigger pin to HIGH state for 10 microseconds
digitalWrite(triggerPin, HIGH);
delayMicroseconds(10);
digitalWrite(triggerPin, LOW);
pinMode(echoPin, INPUT);
// Reads the echo pin, and returns the sound wave travel time in microseconds
return pulseIn(echoPin, HIGH);
}
void setup(){
Serial.begin(115200);
for (int i=0; i<3; i++) {
pinMode(buttonPins[i], INPUT);
}
pinMode(rPin, OUTPUT);
pinMode(gPin, OUTPUT);
}
void loop()
{
// measure the ping time in cm
cm1 = 0.01723 * readUltrasonicDistance(A5, 2);
cm2 = 0.01723 * readUltrasonicDistance(A4, 4);
for (int i=0; i<3; i++) {
buttonVal[i] = digitalRead(buttonPins[i]);
}
for (int i=0; i<3; i++) {
sensorVal[i] = map(analogRead(sensorPins[i]), 0, 1023, 0, 127);
}
if (cm1 >= 0 && cm1 <= 140) {
midiDistance1 = map(round(cm1), 0, 50, 24, 83);
}
if (cm2 >= 0 && cm2 <= 140) {
midiDistance2 = map(round(cm2), 0, 50, 127, 0);
}
//uses button in 0 position in array
if (buttonVal[0] == 0 && pressed) {
delay(1000);
pressed = false;
buttonVal[0] = 1;
digitalWrite(gPin, HIGH);
digitalWrite(rPin, LOW);
}
if (buttonVal[0] == 0 && !pressed) {
digitalWrite(rPin, HIGH);
digitalWrite(gPin, LOW);
while (midiDistance1 > 24) {
midiDistance1 -= 24;
}
for (int i=1; i<6; i++){
key[7*i-7] = midiDistance1 + ((i*12) – 12);
key[7*i-6] = midiDistance1 + 2 + ((i*12) – 12);
key[7*i-5] = midiDistance1 + 4 + ((i*12) – 12);
key[7*i-4] = midiDistance1 + 5 + ((i*12) – 12);
key[7*i-3] = midiDistance1 + 7 + ((i*12) – 12);
key[7*i-2] = midiDistance1 + 9 + ((i*12) – 12);
key[7*i-1] = midiDistance1 + 11 + ((i*12) – 12);
}
delay(1000);
pressed = true;
pressed2 = false;
pressed3 = false;
}
//uses button in 1 position in array
if (buttonVal[1] == 0 && pressed2) {
delay(1000);
pressed2 = false;
buttonVal[1] = 1;
digitalWrite(gPin, HIGH);
digitalWrite(rPin, LOW);
}
if (buttonVal[1] == 0 && !pressed2) {
digitalWrite(rPin, HIGH);
digitalWrite(gPin, LOW);
while (midiDistance1 > 24) {
midiDistance1 -= 24;
}
for (int i=1; i<6; i++){
key2[7*i-7] = midiDistance1 + ((i*12) – 12);
key2[7*i-6] = midiDistance1 + 2 + ((i*12) – 12);
key2[7*i-5] = midiDistance1 + 3 + ((i*12) – 12);
key2[7*i-4] = midiDistance1 + 5 + ((i*12) – 12);
key2[7*i-3] = midiDistance1 + 7 + ((i*12) – 12);
key2[7*i-2] = midiDistance1 + 8 + ((i*12) – 12);
key2[7*i-1] = midiDistance1 + 10 + ((i*12) – 12);
}
delay(1000);
pressed = false;
pressed2 = true;
pressed3 = false;
}
//uses button in 2 position in array
if (buttonVal[2] == 0 && pressed3) {
delay(1000);
pressed3 = false;
buttonVal[2] = 1;
digitalWrite(gPin, HIGH);
digitalWrite(rPin, LOW);
}
if (buttonVal[2] == 0 && !pressed3) {
digitalWrite(rPin, HIGH);
digitalWrite(gPin, LOW);
while (midiDistance1 > 24) {
midiDistance1 -= 24;
}
for (int i=1; i<6; i++){
key3[7*i-7] = midiDistance1 + ((i*12) – 12);
key3[7*i-6] = midiDistance1 + 3 + ((i*12) – 12);
key3[7*i-5] = midiDistance1 + 5 + ((i*12) – 12);
key3[7*i-4] = midiDistance1 + 6 + ((i*12) – 12);
key3[7*i-3] = midiDistance1 + 7 + ((i*12) – 12);
key3[7*i-2] = midiDistance1 + 10 + ((i*12) – 12);
key3[7*i-1] = midiDistance1 + 12 + ((i*12) – 12);
}
delay(1000);
pressed = false;
pressed2 = false;
pressed3 = true;
}
if (pressed) {
keyVal = key[map(midiDistance1, 0, 127, 0, 34)];
MIDI.sendNoteOn(keyVal, midiDistance2, 1);
MIDI.sendNoteOn(keyVal + 7, sensorVal[0], 1); //takes potentiometer value and uses it to play note 1 fifth higher
MIDI.sendNoteOn(keyVal + 12, sensorVal[1], 1);
MIDI.sendNoteOn(keyVal + 19, sensorVal[2], 1);
delay(500);
MIDI.sendNoteOff(keyVal, midiDistance2, 1);
MIDI.sendNoteOff(keyVal + 7, sensorVal[0], 1); //takes potentiometer value and uses it to play note 1 fifth higher
MIDI.sendNoteOff(keyVal + 12, sensorVal[1], 1);
MIDI.sendNoteOff(keyVal + 19, sensorVal[2], 1);
delay(10);
}
if (pressed2) {
key2Val = key2[map(midiDistance1, 0, 127, 0, 34)];
MIDI.sendNoteOn(key2Val, midiDistance2, 1);
MIDI.sendNoteOn(key2Val + 7, sensorVal[0], 1); //takes potentiometer value and uses it to play note 1 fifth higher
MIDI.sendNoteOn(key2Val + 12, sensorVal[1], 1);
MIDI.sendNoteOn(key2Val + 19, sensorVal[2], 1);
delay(500);
MIDI.sendNoteOff(key2Val, midiDistance2, 1);
MIDI.sendNoteOff(key2Val + 7, sensorVal[0], 1); //takes potentiometer value and uses it to play note 1 fifth higher
MIDI.sendNoteOff(key2Val + 12, sensorVal[1], 1);
MIDI.sendNoteOff(key2Val + 19, sensorVal[2], 1);
delay(10);
}
if (pressed3) {
key3Val = key3[map(midiDistance1, 0, 127, 0, 34)];
MIDI.sendNoteOn(key3Val, midiDistance2, 1);
MIDI.sendNoteOn(key3Val + 7, sensorVal[0], 1); //takes potentiometer value and uses it to play note 1 fifth higher
MIDI.sendNoteOn(key3Val + 12, sensorVal[1], 1);
MIDI.sendNoteOn(key3Val + 19, sensorVal[2], 1);
delay(500);
MIDI.sendNoteOff(key3Val, midiDistance2, 1);
MIDI.sendNoteOff(key3Val + 7, sensorVal[0], 1); //takes potentiometer value and uses it to play note 1 fifth higher
MIDI.sendNoteOff(key3Val + 12, sensorVal[1], 1);
MIDI.sendNoteOff(key3Val + 19, sensorVal[2], 1);
delay(10);
}
if (!pressed && !pressed2 && !pressed3) {
MIDI.sendNoteOn(midiDistance1, midiDistance2, 1);
MIDI.sendNoteOn(midiDistance1 + 7, sensorVal[0], 1); //takes potentiometer value and uses it to play note 1 fifth higher
MIDI.sendNoteOn(midiDistance1 + 12, sensorVal[1], 1);
MIDI.sendNoteOn(midiDistance1 + 19, sensorVal[2], 1);
delay(500);
MIDI.sendNoteOff(midiDistance1, midiDistance2, 1);
MIDI.sendNoteOff(midiDistance1 + 7, sensorVal[0], 1); //takes potentiometer value and uses it to play note 1 fifth higher
MIDI.sendNoteOff(midiDistance1 + 12, sensorVal[1], 1);
MIDI.sendNoteOff(midiDistance1 + 19, sensorVal[2], 1);
delay(10);
}
}
Testing
We tested the instrument by using it as intended. We tested its minimum and maximum values, all its functions and its ability to play. We liked how it can play sounds through a DAW (Digital Audio Workstation) but it did not play as smoothly as we intended. After listening to it a couple of times, we changed some values of how long notes were held for so that it sounded more appealing to the user.
Shortcomings
Due to COVID we found it hard to work on the project efficiently. We had to make a lot of compromises such as using a potentiometer in place of an ultrasound sensor because one of us was in London, UK whilst another was in Hamburg, Germany.
Because this instrument is very experimental, it can be challenging to use. It also meant it is very hard to record anything with it. But it is something we can get away with as we only built a prototype.