Play an ultrasonic theremin

Move your hand above an ultrasonic sensor to play music without touching anything. A button switches between a smooth siren and the notes of a scale.
  • Level:Beginner
  • Duration: 45 min
  • Ages 10+
  • Platform: Arduino

Tools for this lesson

Helpful for this build. Open a tool to see tips and more projects that use it.

Play an ultrasonic theremin – EEMENTOR STEM™ lesson

The theremin is an electronic instrument from 1920 that you play without touching it: you move your hands near two antennas, and the pitch changes. Your version uses the ultrasonic sensor instead of antennas. The closer your hand is, the higher the note. A button switches between a smooth sound that slides like a siren, and a "musical" mode that snaps to the notes of a C major scale, so you can play real tunes.

What you'll learn

  • How to turn a distance into a pitch with map()
  • How to play notes with tone() and stop them with noTone()
  • How to store a musical scale in an array
  • How to smooth jumpy sensor readings

What you need

  • UNO R3 board and USB cable (UNO R3 Starter Kit)
  • Ultrasonic sensor (UNO R3 Starter Kit)
  • Passive buzzer (UNO R3 Starter Kit)
  • 1 push button (UNO R3 Starter Kit)
  • The printed parts from the 3D printing box below (theremin stand), or the cardboard version described there
  • Breadboard and jumper wires (UNO R3 Starter Kit)

Wiring

Breadboard wiring diagram: Play an ultrasonic theremin
Breadboard view. Check each connection against the wiring table below.
From To
Ultrasonic VCC 5V
Ultrasonic Trig UNO pin 12
Ultrasonic Echo UNO pin 11
Ultrasonic GND GND
Passive buzzer + UNO pin 8
Passive buzzer − GND
Push button, one leg UNO pin 2
Push button, the diagonally opposite leg GND

Use the passive buzzer: it plays any note you ask for. The active buzzer can only beep at one pitch.

The sketch

const int TRIG_PIN = 12;
const int ECHO_PIN = 11;
const int BUZZER_PIN = 8;
const int BUTTON_PIN = 2;

const int NEAREST = 5;      // cm: the highest note
const int FARTHEST = 50;    // cm: the lowest note; further away is silence

// C major scale over two octaves, in hertz
const int SCALE[] = {262, 294, 330, 349, 392, 440, 494, 523, 587, 659, 698, 784, 880, 988, 1047};
const int NOTES = sizeof(SCALE) / sizeof(SCALE[0]);

bool musical = true;        // true: notes of the scale, false: smooth siren
bool lastButton = HIGH;
float smooth = 0;

long readCm() {
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);
  long us = pulseIn(ECHO_PIN, HIGH, 6000);   // give up after 6 ms (about 1 m)
  if (us == 0) return -1;                    // nothing in range
  return us / 58;
}

void setup() {
  Serial.begin(9600);
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
}

void loop() {
  bool button = digitalRead(BUTTON_PIN);
  if (button == LOW && lastButton == HIGH) {
    musical = !musical;
    Serial.println(musical ? "Musical mode" : "Siren mode");
    delay(200);
  }
  lastButton = button;

  long cm = readCm();
  if (cm < 0 || cm > FARTHEST) {             // no hand: silence
    noTone(BUZZER_PIN);
    smooth = 0;
    delay(30);
    return;
  }
  cm = constrain(cm, NEAREST, FARTHEST);
  if (smooth == 0) smooth = cm;
  smooth = smooth * 0.7 + cm * 0.3;          // smooth out the jitter

  int freq;
  if (musical) {
    int index = map((long)smooth, FARTHEST, NEAREST, 0, NOTES - 1);
    freq = SCALE[constrain(index, 0, NOTES - 1)];
  } else {
    freq = map((long)smooth, FARTHEST, NEAREST, 150, 1200);
  }
  tone(BUZZER_PIN, freq);
  delay(30);
}

Play it

  1. Upload the sketch and hold your flat hand about 40 cm above or in front of the sensor. You hear a low note.
  2. Move your hand slowly closer. In musical mode the sound steps up through the scale, like a piano.
  3. Press the button for siren mode and move your hand quickly up and down.
  4. Try a tune: "Twinkle, Twinkle, Little Star" starts with the 1st, 1st, 5th, 5th, 6th, 6th and 5th notes of the scale.

How it works

map() turns one range into another. Here it turns 50 … 5 cm into note number 0 … 14. Because the ranges are written "far to near", a closer hand gives a bigger number and a higher note. In siren mode it maps straight to a frequency between 150 and 1200 Hz instead.

Ultrasonic readings jump by a centimetre or two even when your hand is still. The line smooth = smooth * 0.7 + cm * 0.3 mixes each new reading with the old value, so a single odd reading only moves the note a little. That is a simple low-pass filter.

Try this next

  • Change the scale to a pentatonic one (C, D, E, G, A): every combination of notes sounds good.
  • Add the LCD from the LCD lesson and show the name of each note.
  • Add a second button that plays a short tune you recorded by remembering the last notes.

Troubleshooting

  • It plays all the time, even with no hand: something else is within 50 cm, such as a wall or the ceiling. Make FARTHEST smaller, or point the sensor away from it.
  • No sound at all: check that you used the passive buzzer, and that its + leg is on pin 8.
  • The notes jump around: hold your hand flat. Fingers and edges reflect the sound badly.
  • A printed part is too tight or too loose: printers differ by a few tenths of a millimetre. Sand or file a tight hole, and fix a loose part with a drop of hot glue or a strip of tape.

Build it yourself

Everything used in this lesson comes in one kit, with parts tested by our engineers.

UNO R3 Starter Kit | Arduino IDE Compatible | Free Tutorials $56.00

Shop the kit