MIDI Theremin
2023As a part of the final project for NYU’s 4 week ITP camp, I created a contemporary MIDI Theremin using an Arduino Nano IoT33 and laser cut acrylic fabrication.
Field > Electronics, C++, Laser Cutting
The theremin is an early electronic musical instrument invented by Russian physicist Leon Theremin in 1920. Unlike conventional instruments, the theremin is unique in that it requires no physical contact to play. It consists of a box equipped with two antennas; one controlling pitch, typically a vertical rod, and the other controlling volume, generally a horizontal loop. The theremin operates on the principle of heterodyning or beat frequency oscillation, whereby the player’s hand movements interact with the electromagnetic fields surrounding the antennas, thereby altering the instrument's pitch and volume.
A MIDI theremin is a modern adaptation of the traditional theremin that leverages MIDI (Musical Instrument Digital Interface) technology for enhanced versatility and integration with other electronic devices. Similar to a conventional theremin, it produces sound based on the player's interaction with its electromagnetic field, but rather than creating its own sound, a MIDI theremin generates MIDI data corresponding to the player's movements. This data can be sent to a synthesizer, a computer, or any other MIDI-compatible device to trigger sounds, thus expanding the sonic possibilities far beyond those of a traditional theremin. The MIDI theremin maintains the distinctive playability of its predecessor while offering an extensive range of sonic capabilities, catering to the needs of contemporary electronic music production and performance.
I embarked on an intriguing project of constructing a theremin using an Arduino microcontroller board, with a Time-of-Flight (ToF) sensor serving as a pivotal component. The Arduino served as the brain of my theremin, processing the data gathered from the sensor and converting it into sound. The ToF sensor, capable of accurately measuring the time it takes for a light pulse to travel to an object and back, became the non-contact interface for my device. I programmed it such that the distance of my hand from the sensor corresponded to changes in pitch and volume, thus mimicking the operation of a traditional theremin. To generate sound, I used the Pulse Width Modulation (PWM) capability of the Arduino, turning digital signals into analog sound waves. The result was a homemade, Arduino-powered theremin that recreated the distinct, eerie sounds of the classic instrument, demonstrating the power and versatility of Arduino in creating musical instruments.
Below was the code I developed:
#include <VL53L0X.h>#include <MIDIUSB.h>
#define XSHUT_PIN_SENSOR_VOLUME A1#define XSHUT_PIN_SENSOR_PITCH A2
VL53L0X sensorVolume;VL53L0X sensorPitch;
// Define variables to store the current pitch and volumeuint8_t current_pitch = 0;uint8_t current_volume = 0;bool hand_detected = false;
void setup() { Serial.begin(115200); Wire.begin();
pinMode(XSHUT_PIN_SENSOR_VOLUME, OUTPUT); pinMode(XSHUT_PIN_SENSOR_PITCH, OUTPUT); digitalWrite(XSHUT_PIN_SENSOR_VOLUME, LOW); digitalWrite(XSHUT_PIN_SENSOR_PITCH, LOW); delay(10); digitalWrite(XSHUT_PIN_SENSOR_VOLUME, HIGH); delay(10); sensorVolume.init(true); delay(10); sensorVolume.setAddress(0x30);
digitalWrite(XSHUT_PIN_SENSOR_PITCH, HIGH); delay(10); sensorPitch.init(true); delay(10); sensorPitch.setAddress(0x31);
sensorVolume.setTimeout(500); sensorPitch.setTimeout(500);
Serial.println("Both sensors initialized!");}
void loop() { uint16_t volume_distance = sensorVolume.readRangeSingleMillimeters(); if (!sensorVolume.timeoutOccurred() && volume_distance <= 200) { // Ignore readings beyond 20cm uint8_t volume = map(volume_distance, 0, 200, 127, 0); // Map volume reversed so volume is highest when hand is closest // Send MIDI Control Change message for volume midiEventPacket_t volume_msg = {0x0B, 0xB0, 7, volume}; // CC message on channel 1, CC#7 (volume), value = volume MidiUSB.sendMIDI(volume_msg);
// Print volume sensor reading to the serial monitor Serial.print("Volume Sensor: "); Serial.println(volume_distance); }
uint16_t pitch_distance = sensorPitch.readRangeSingleMillimeters(); if (!sensorPitch.timeoutOccurred() && pitch_distance <= 200) { // Ignore readings beyond 20cm // Update the pitch value uint8_t pitch = map(pitch_distance, 0, 200, 0, 127); // Map pitch to full MIDI note range, but only within 20cm
if (!hand_detected) { // If hand was not detected before, send Note On message midiEventPacket_t note_on_msg = {0x09, 0x90, pitch, 127}; // Note on message on channel 1, note = pitch, velocity = 127 MidiUSB.sendMIDI(note_on_msg); hand_detected = true; } else if (pitch != current_pitch) { // If hand was detected and pitch has changed, send Note Off for the previous pitch and Note On for the new pitch midiEventPacket_t note_off_msg = {0x08, 0x80, current_pitch, 0}; // Note off message on channel 1, note = current_pitch, velocity = 0 MidiUSB.sendMIDI(note_off_msg);
midiEventPacket_t note_on_msg = {0x09, 0x90, pitch, 127}; // Note on message on channel 1, note = pitch, velocity = 127 MidiUSB.sendMIDI(note_on_msg); }
// Update the current pitch value current_pitch = pitch;
// Print pitch sensor reading to the serial monitor Serial.print("Pitch Sensor: "); Serial.println(pitch_distance); } else { if (hand_detected) { // If hand was detected before and is now not detected, send Note Off for the current pitch midiEventPacket_t note_off_msg = {0x08, 0x80, current_pitch, 0}; // Note off message on channel 1, note = current_pitch, velocity = 0 MidiUSB.sendMIDI(note_off_msg); hand_detected = false; } }
MidiUSB.flush(); delay(100);}