Ultrasonic theremin
Play music by waving your hand over an ultrasonic sensor. Closer is higher. Open the bench to watch distance and pitch chart live.
Wiring
| From | To board | Note |
|---|---|---|
| HC-SR04 VCC | 5V | |
| HC-SR04 GND | GND | |
| HC-SR04 TRIG | GPIO 5 | |
| HC-SR04 ECHO, through 1 kΩ | GPIO 18 | Add 2 kΩ from GPIO 18 to GND to drop the 5 V echo to 3.3 V |
| Buzzer + | GPIO 25 | Buzzer - to GND |
Tested on this board
Not run on this board yet. Treat the wiring and code as a first draft.
Firmware, Arduino sketch for ESP32 Dev Module
// Ultrasonic theremin: hand distance sets the pitch. Closer is higher.
const int TRIG = 5;
const int ECHO = 18;
const int BUZZER = 25;
const float NEAR_CM = 5; // highest note here
const float FAR_CM = 60; // lowest note here; further away is silence
const float LOW_HZ = 220; // A3
const int OCTAVES = 3; // up to A6
const bool SNAP = true; // round to the nearest semitone, so it plays in tune
float readCm() {
digitalWrite(TRIG, LOW);
delayMicroseconds(2);
digitalWrite(TRIG, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG, LOW);
unsigned long us = pulseIn(ECHO, HIGH, 25000); // about 4 m round trip
return us == 0 ? -1 : us * 0.0343f / 2;
}
void setup() {
Serial.begin(115200);
pinMode(TRIG, OUTPUT);
pinMode(ECHO, INPUT);
Serial.println("Theremin ready. Wave a hand 5 to 60 cm above the sensor.");
}
void loop() {
float cm = readCm();
int hz = 0;
if (cm >= NEAR_CM && cm <= FAR_CM) {
float f = LOW_HZ * powf(2.0f, (FAR_CM - cm) / (FAR_CM - NEAR_CM) * OCTAVES);
if (SNAP) f = 440.0f * powf(2.0f, roundf(12 * log2f(f / 440.0f)) / 12.0f);
hz = (int)(f + 0.5f);
tone(BUZZER, hz);
} else {
noTone(BUZZER);
}
Serial.printf("distance_cm=%.1f, freq=%d\n", cm < 0 ? 0.0f : cm, hz);
delay(60);
}Wiring
| From | To board | Note |
|---|---|---|
| HC-SR04 VCC | 5V | |
| HC-SR04 GND | GND | |
| HC-SR04 TRIG | GPIO 4 | |
| HC-SR04 ECHO, through 1 kΩ | GPIO 5 | Add 2 kΩ from GPIO 5 to GND to drop the 5 V echo to 3.3 V |
| Buzzer + | GPIO 6 | Buzzer - to GND |
Tested on this board
Not run on this board yet. Treat the wiring and code as a first draft.
Firmware, Arduino sketch for ESP32S3 Dev Module
// Ultrasonic theremin: hand distance sets the pitch. Closer is higher.
const int TRIG = 4;
const int ECHO = 5;
const int BUZZER = 6;
const float NEAR_CM = 5; // highest note here
const float FAR_CM = 60; // lowest note here; further away is silence
const float LOW_HZ = 220; // A3
const int OCTAVES = 3; // up to A6
const bool SNAP = true; // round to the nearest semitone, so it plays in tune
float readCm() {
digitalWrite(TRIG, LOW);
delayMicroseconds(2);
digitalWrite(TRIG, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG, LOW);
unsigned long us = pulseIn(ECHO, HIGH, 25000); // about 4 m round trip
return us == 0 ? -1 : us * 0.0343f / 2;
}
void setup() {
Serial.begin(115200);
pinMode(TRIG, OUTPUT);
pinMode(ECHO, INPUT);
Serial.println("Theremin ready. Wave a hand 5 to 60 cm above the sensor.");
}
void loop() {
float cm = readCm();
int hz = 0;
if (cm >= NEAR_CM && cm <= FAR_CM) {
float f = LOW_HZ * powf(2.0f, (FAR_CM - cm) / (FAR_CM - NEAR_CM) * OCTAVES);
if (SNAP) f = 440.0f * powf(2.0f, roundf(12 * log2f(f / 440.0f)) / 12.0f);
hz = (int)(f + 0.5f);
tone(BUZZER, hz);
} else {
noTone(BUZZER);
}
Serial.printf("distance_cm=%.1f, freq=%d\n", cm < 0 ? 0.0f : cm, hz);
delay(60);
}Wiring
| From | To board | Note |
|---|---|---|
| HC-SR04 VCC | 5V | |
| HC-SR04 GND | GND | |
| HC-SR04 TRIG | GPIO 4 | |
| HC-SR04 ECHO, through 1 kΩ | GPIO 5 | Add 2 kΩ from GPIO 5 to GND to drop the 5 V echo to 3.3 V |
| Buzzer + | GPIO 10 | Buzzer - to GND |
Tested on this board
Not run on this board yet. Treat the wiring and code as a first draft.
Firmware, Arduino sketch for ESP32C3 Dev Module
// Ultrasonic theremin: hand distance sets the pitch. Closer is higher.
const int TRIG = 4;
const int ECHO = 5;
const int BUZZER = 10;
const float NEAR_CM = 5; // highest note here
const float FAR_CM = 60; // lowest note here; further away is silence
const float LOW_HZ = 220; // A3
const int OCTAVES = 3; // up to A6
const bool SNAP = true; // round to the nearest semitone, so it plays in tune
float readCm() {
digitalWrite(TRIG, LOW);
delayMicroseconds(2);
digitalWrite(TRIG, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG, LOW);
unsigned long us = pulseIn(ECHO, HIGH, 25000); // about 4 m round trip
return us == 0 ? -1 : us * 0.0343f / 2;
}
void setup() {
Serial.begin(115200);
pinMode(TRIG, OUTPUT);
pinMode(ECHO, INPUT);
Serial.println("Theremin ready. Wave a hand 5 to 60 cm above the sensor.");
}
void loop() {
float cm = readCm();
int hz = 0;
if (cm >= NEAR_CM && cm <= FAR_CM) {
float f = LOW_HZ * powf(2.0f, (FAR_CM - cm) / (FAR_CM - NEAR_CM) * OCTAVES);
if (SNAP) f = 440.0f * powf(2.0f, roundf(12 * log2f(f / 440.0f)) / 12.0f);
hz = (int)(f + 0.5f);
tone(BUZZER, hz);
} else {
noTone(BUZZER);
}
Serial.printf("distance_cm=%.1f, freq=%d\n", cm < 0 ? 0.0f : cm, hz);
delay(60);
}Parts
| Quantity | Part | Where to buy |
|---|---|---|
| 1× | ESP32 dev board | Buy at Amazon |
| 1× | HC-SR04 ultrasonic sensor | |
| 1× | 1 kΩ resistor | |
| 1× | 2 kΩ resistor | |
| 1× | Passive buzzer or piezo | Buy at Adafruit |
| 7× | Jumper wires |
Amazon buy links may include an Associates tag, and this site may earn a commission from qualifying Amazon purchases at no extra cost to you. Adafruit links are direct.
Libraries
None beyond the ESP32 Arduino core.
Load it
- In the Arduino IDE, install the esp32 board package and pick the board named above the sketch.
- Paste the sketch, choose the board's port and upload. Hold BOOT if the upload stalls at Connecting.
- Close the IDE's serial monitor, then open the bench here to watch the output.
Field notes
- The HC-SR04's echo pin is 5 V. The 1 kΩ and 2 kΩ divider brings it down to about 3.3 V, the same as the HC-SR04 distance project.
- Set SNAP to false for a smooth, swooping theremin sound instead of notes.
- A flat hand works better than fingers: more surface sends back a stronger echo.
- Open the bench at 115200 baud: the plotter charts distance_cm and freq side by side as you play.
Not tested yet
- Not tested on hardware yet. Compiles for every board listed with Arduino-ESP32 core 3.3.12.
Built it? Send a build report with your board and what the serial port printed. Working reports become the "Tested on this board" entries above.