Chiptune Theremin

Build Your Own C64-Style Theremin with an ESP32

Ever wanted to wave your hand through the air and summon sweet 8-bit tones like some kind of synth wizard? Well, you’re in luck. This post is all about building a capacitive theremin, using an ESP32, a simple audio DAC, and whatever metal stick you’ve got lying around.

Inspired by the eerie vibe of the original RCA theremin and the crunchy textures of the Commodore 64 SID chip, this little project is compact, cheap, and ridiculously fun.


What’s a Theremin, Really?

The original theremin, invented in 1920 by Léon Theremin, is one of the earliest electronic instruments—and still one of the weirdest. It’s played without physical contact: one hand controls pitch by varying distance to a vertical antenna, and the other controls volume via a horizontal antenna.

It works by detecting changes in capacitance—in essence, how your body influences the surrounding electric field. Classic theremins use analog RF oscillators to do this. But we’ll fake it with digital means:

  • An ESP32 will measure variations in capacitive touch using a metal antenna.
  • It will then map that to a pitch, which is rendered as audio using I²S output.
  • That audio is fed to a simple DAC + amp (MAX98357A) and blasted out to a speaker.

We’re not trying to be 100% faithful to analog theremins—we’re building a C64-style digital clone with some nods to retro tech and chipmusic.


What Are We Building?

  • A theremin-like instrument that reacts to your hand
  • Pitch controlled by capacitive antenna (your hand gets closer → pitch goes lower)
  • Audio generated digitally (no analog oscillators here!)
  • Output via I²S DAC/amplifier (MAX98357A)
  • Waveform: square wave, SID-style. Later we’ll add more.

Parts You’ll Need

Part Purpose
ESP32 DevKit The brains of the synth
MAX98357A Audio DAC + amplifier
Copper wire / metal rod / old antenna The pitch antenna
Speaker (3W / 8Ω) For the glorious sound
USB power Power supply

Wiring It Up

1
2
3
4
5
6
7
[ESP32 DevKit V1]
GPIO 33 -------------------> Capacitive antenna
GPIO 22 (I2S DOUT) --------> DIN of MAX98357A
GPIO 25 (I2S WS) ----------> LRC of MAX98357A
GPIO 26 (I2S BCLK) --------> BCLK of MAX98357A
+---> GND -> GND
+---> VIN -> 5V

Simple. Clean. No magic.


Choosing Your Antenna

Type Pros Cons
Vertical metal rod (30–60 cm) Expressive, easy to find Can pick up noise
Old telescopic radio antenna Retro, adjustable May be rare
Vertical spiral Sensitive, compact More DIY work
Metal plate / CD / foil Stable signal Less theatrical
Insulated wire Just don’t. Not sensitive enough

Pro tip: Can’t solder to aluminum? Don’t. Use a ring terminal and bolt it in. Or use a croc clip.


Step 1: Read the antenna

Let’s test how reactive your antenna is.

1
2
3
4
5
6
7
8
9
10
11
#define ANTENNA_PIN 33

void setup() {
Serial.begin(115200);
}

void loop() {
int val = touchRead(ANTENNA_PIN);
Serial.println(val);
delay(50);
}

You should see the values drop when your hand gets close. If not, your antenna needs more exposure (dédune it, use metal, make it longer, etc.).


Step 2: Make it sing (square wave output)

Let’s generate a SID-style square wave, pitch-controlled by your hand.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include "driver/i2s_std.h"
#include <math.h>

#define SAMPLE_RATE 44100
#define BUFFER_SIZE 256
#define ANTENNA_PIN 33
#define PI 3.14159265

i2s_chan_handle_t tx_chan;
int phase = 0;
float frequency = 440;
float smoothed = 1000;

void setupI2S() {
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
i2s_new_channel(&chan_cfg, &tx_chan, NULL);

i2s_std_config_t std_cfg = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),
.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
.gpio_cfg = {
.mclk = I2S_GPIO_UNUSED,
.bclk = 26,
.ws = 25,
.dout = 22,
.din = I2S_GPIO_UNUSED
}
};

i2s_channel_init_std_mode(tx_chan, &std_cfg);
i2s_channel_enable(tx_chan);
}

void setup() {
Serial.begin(115200);
setupI2S();
}

void loop() {
int raw = touchRead(ANTENNA_PIN);
smoothed = 0.95 * smoothed + 0.05 * raw;
frequency = map(smoothed, 300, 1500, 1200, 100);
frequency = constrain(frequency, 100, 1200);

int16_t buffer[BUFFER_SIZE];
for (int i = 0; i < BUFFER_SIZE; i++) {
float wave = (phase % (SAMPLE_RATE / frequency)) < ((SAMPLE_RATE / frequency) / 2) ? 1.0 : -1.0;
buffer[i] = (int16_t)(wave * 32767);
phase++;
}

size_t bytes_written;
i2s_channel_write(tx_chan, buffer, sizeof(buffer), &bytes_written, portMAX_DELAY);
}

Where to Go From Here

  • Add a second antenna (or capacitive pad) for volume or vibrato
  • Add an OLED display to show pitch
  • Try new waveforms: triangle, noise, whatever feels C64ish
  • Add a bitcrusher effect for real lo-fi grit
  • Add touch buttons to switch octaves or modes

Final thoughts

This thing is weird, fun, and surprisingly playable. If you like DIY synths, 8-bit aesthetics, or just waving your hand in the air like a techno-sorcerer, it’s absolutely worth it.

Let me know if you want a version with effects, MIDI out, or SID-style filters. I’m already working on a polyphonic build…

Stay noisy.