{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreiczuilqk3f6geujsdfrgt45jcul7xqweud2nsbpgfah6da6ksq2py",
"uri": "at://did:plc:nsfgazqhudwd5egnrbrykfca/app.bsky.feed.post/3mobisp2x5qq2"
},
"description": "I designed and implemented a theremin using an Arduino with an ultrasonic sensor.",
"path": "/~twinbfield/posts/ultrasonic-theremin/",
"publishedAt": "2026-06-14T19:21:00.000Z",
"site": "https://tilde.club",
"tags": [
"theremin",
"Theremin World",
"another tutorial",
"Mozzi",
"LM386 amplifier schematic",
"Arduino library",
"CC BY-SA 3.0"
],
"textContent": "There is a wonderfully ethereal instrument called the theremin that is played without touching anything. Built using two antennas (one for volume and one for pitch), it creates a synthesized wave that sounds otherwordly:\n\nYour browser does not support the audio element.\n\nThe original instrument is designed as a large capacitive sensor. As the distance between your hand and a charged conductor changes, it changes the frequency of an oscillating circuit. However, since the 1920s when the theremin was invented, there are many different ways to create a theremin that incorporate modern sensors and microcontrollers. Theremin World has dozens of designs.\n\nIn this next series of posts, I want to explore three theremin designs. First, I will use an Arduino and an ultrasonic sensor to measure distance and produce a pitch-only theremin. In future posts, I hope to create a \"true\" theremin based on capacitive sensing, and finally one using circuits only with no microcontroller (if I am brave enough).\n\n## Materials and design #\n\nFor this project, we need the following:\n\n * Arduino (I used an Uno)\n * HC-SR04 ultrasonic sensor\n * 3W 8Ω speaker\n * LM386 amplifier board (not just the raw LM386 chip)\n * Various jumper cables and a breadboard\n\n\n\nYou could easily substitute the type of speaker or amplifier board. When we wire everything together, we need to make sure that the speaker is connected ONLY to the amplifier so we don't accidentally pull a ton of current on the Arduino. Here's the schematic:\n\n## Testing the ultrasonic sensor #\n\nFirst, I made a quick script to get the distance from the ultrasonic sensor. I'm summarizing info from another tutorial, but the ultrasonic sensor sends out a pulse on the trigger pin, and you can read how long it takes for the pulse to return. Then, the distance from the sensor to your hand is the time multiplied by the speed of sound over two (since the wave traveled there and back).\n\n\n // See https://projecthub.arduino.cc/lucasfernando/ultrasonic-sensor-with-arduino-complete-guide-284faf\n\n const int TRIG_PIN = 9;\n const int ECHO_PIN = 8;\n float timing = 0.0;\n float distance = 0.0;\n char buffer[40];\n\n void setup() {\n pinMode(ECHO_PIN, INPUT);\n pinMode(TRIG_PIN, OUTPUT);\n\n digitalWrite(TRIG_PIN, LOW);\n\n Serial.begin(9600);\n }\n\n void loop() {\n // Send a pulse and measure the timing\n digitalWrite(TRIG_PIN, HIGH);\n delay(10);\n digitalWrite(TRIG_PIN, LOW);\n\n timing = pulseIn(ECHO_PIN, HIGH);\n distance = (timing * 0.034) / 2;\n\n Serial.print(\"Distance: \");\n Serial.println(distance);\n }\n\n## Making the theremin sing #\n\nNow, we can set up the theremin entirely. To synthesize a sound on the speaker, we use Mozzi, which allows us to take advantage of the pulse width modulation available on pin 9.\n\nTo avoid blocking the audio synthesis, we put all of the logic in the `updateControl` function. We also provide a moving average for the pitch and the volume so that there isn't awkward clipping and noise in the output.\n\n\n #define MOZZI_CONTROL_RATE 64\n #include <Mozzi.h>\n #include <Oscil.h>\n #include <tables/sin2048_int8.h>\n #include <RollingAverage.h>\n\n const int TRIG_PIN = 7;\n const int ECHO_PIN = 8;\n const int ECHO_TIMEOUT = 6000;\n\n const float LOW_FREQ = 220; // A3\n const float HIGH_FREQ = 880; // A5\n const float MAX_DIST = 50.0; // cm\n const float MIN_DIST = 5.0; // cm\n\n float timing = 0.0;\n float distance = 0.0;\n float freq = 0.0;\n float freq_avg = 0.0;\n float volume_avg = 0.0;\n float volume = 0.0;\n\n Oscil <SIN2048_NUM_CELLS, MOZZI_AUDIO_RATE> aSin(SIN2048_DATA);\n RollingAverage <int, 16> kAverage_freq; // Average across 16\n RollingAverage <int, 16> kAverage_vol;\n\n void setup() {\n startMozzi();\n aSin.setFreq(LOW_FREQ);\n\n pinMode(ECHO_PIN, INPUT);\n pinMode(TRIG_PIN, OUTPUT);\n\n digitalWrite(TRIG_PIN, LOW);\n\n Serial.begin(115200);\n }\n\n void updateControl(){\n // Send a pulse and measure the timing\n digitalWrite(TRIG_PIN, HIGH);\n delayMicroseconds(10);\n digitalWrite(TRIG_PIN, LOW);\n\n timing = pulseIn(ECHO_PIN, HIGH, ECHO_TIMEOUT);\n distance = (timing * 0.0343) / 2;\n\n // Map onto frequencies\n float volume;\n if (distance <= MIN_DIST) {\n volume = 0.0;\n } else {\n volume = 1.0;\n }\n float freq = ((distance - MIN_DIST) / (MAX_DIST - MIN_DIST)) * (HIGH_FREQ - LOW_FREQ) + LOW_FREQ;\n freq_avg = kAverage_freq.next(freq);\n volume_avg = kAverage_vol.next(volume);\n\n aSin.setFreq(freq_avg);\n\n }\n\n AudioOutput updateAudio(){\n return MonoOutput::from8Bit(aSin.next() * volume_avg);\n }\n\n void loop() {\n audioHook();\n }\n\nLoading this onto our board, we can produce a sound! The potentiometer on the LM386 amplifier can help control the volume.\n\nYour browser does not support the audio element.\n\nVery nice! Now, as a musician, this instrument overall is... okay. I could probably improve the synthesis - it's a bit laggy with the averages, and it's a bit sensitive when the sensor doesn't detect your hand. Stay tuned for the second design, based on capacitive sensing with an antenna!\n\n### License\n\nThe schematic was created with Fritzing. The LM386 amplifier schematic was modified from the Arduino library. Both are released under CC BY-SA 3.0.",
"title": "Theremin 1 - Ultrasonic sensor"
}