An interactive 8-bit instrument built on Arduino that transforms physical gestures into real-time
melodic control of the Super Mario Bros. Theme.
This project is an interactive, self-contained digital instrument that uses an Arduino to synthesize 8-bit melodies. By mapping physical gestures to a custom playback engine, the instrument allows a performer to manipulate the Super Mario Bros. Theme in real-time, shifting pitch and altering tempo through a 2-axis joystick.
Build a simple interactive instrument using physical controls
Map joystick movement to sound (pitch and timbre) in real time
Create a responsive system that feels expressive, not static
Use an RGB LED to visually reflect changes in the sound
Connect Arduino input with digital sound synthesis in PlugData
Hardware
Arduino board, joystick, passive buzzer, LED, breadboard wiring diagram
Arduino Code
Data Structures: Custom-built melody[] and durations[] arrays transcribed from flute sheet music.
Frequency Engine: Use of the tone() function and the logarithmic MIDI-to-Hz formula.
User Interface Logic
State-machine logic using a playing boolean to toggle the instrument on/off.
Real-time mapping of analog input to melodic speed (80ms - 300ms delay range)
Joystick Input → Arduino Processing (Pitch/Tempo Mapping) → Passive Buzzer Output
*Feedback Loop*
Note Index → LED Toggle for Visual Synchronization
Arduino Code Logic
The instrument’s logic is driven by a Real-Time Processing Loop that bridges raw analog data with musical synthesis. The core engine utilizes two arrays, melody[] for pitch and durations[] for rhythm. The song is stored within these. Within the main loop, the Arduino continuously maps
Joystick X-axis input to a pitch shift and Y-axis input to a dynamic tempo delay.
A Boolean State-Machine handles the user interface, ensuring the audio remains inactive until the integrated joystick button is triggered. When active, the system calculates the required Hertz for each note and executes a Synchronized LED Flash on pin D9.
This provides a rhythmic visual pulse that matches the 8-bit output, while the noTone() function and digitalWrite(LOW) ensure clean silences during musical rests
/* * Project: Super Mario Bros. (Bars 1-10)
* Hardware: Passive Buzzer (Pin 8), Red LED (Pin 9)
* Joystick: X-Pitch (A0), Y-Tempo (A1), BTN-Toggle (Pin 2)
*/
// MIDI Notes from your Sheet Music (0 = Rest)
int melody[] = {
// Bars 1-4 (Intro)
76, 76, 0, 76, 0, 72, 76, 0, 79, 0, 67, 0,
// Bars 5-7 (Main Theme Start)
72, 0, 67, 0, 64, 0, 69, 71, 70, 69,
// Bars 8-10 (The "Jump" and Triplets)
67, 76, 79, 81, 77, 79, 76, 0, 72, 74, 71
};
// Durations based on sheet music notation
float durations[] = {
// Bars 1-4
0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
// Bars 5-7
1.0, 0.5, 1.0, 0.5, 1.0, 1.0, 0.5, 0.5, 0.5, 0.5,
// Bars 8-10
0.66, 0.66, 0.66, 0.5, 0.5, 0.5, 1.0, 0.5, 0.5, 0.5, 1.0
};
int noteIndex = 0;
const int totalNotes = 33; // Exact count for Bars 1-10
// Hardware Pins
const int buzzerPin = 8;
const int ledPin = 9;
const int joyX = A0;
const int joyY = A1;
const int btnPin = 2;
bool playing = false;
bool lastBtnState = HIGH;
void setup() {
Serial.begin(115200);
pinMode(buzzerPin, OUTPUT);
pinMode(ledPin, OUTPUT);
pinMode(btnPin, INPUT_PULLUP);
}
void loop() {
// 1. Button Logic
bool currentBtnState = digitalRead(btnPin);
if (currentBtnState == LOW && lastBtnState == HIGH) {
playing = !playing;
delay(150);
}
lastBtnState = currentBtnState;
if (playing) {
// 2. Joystick Control
int xVal = analogRead(joyX);
int yVal = analogRead(joyY);
// Map X to Pitch Shift (Center is 0)
int pitchShift = map(xVal, 0, 1023, -12, 12);
// Map Y to Tempo (Mario at 100bpm is roughly 150-500ms range)
int tempo = map(yVal, 0, 1023, 80, 300);
int currentMIDI = melody[noteIndex];
if (currentMIDI != 0) {
float freq = 440.0 * pow(2.0, ((currentMIDI + pitchShift) - 69) / 12.0);
digitalWrite(ledPin, HIGH);
tone(buzzerPin, (int)freq);
Serial.print("Mario Note: "); Serial.println(currentMIDI + pitchShift);
delay(120); // The "snap" of the note
noTone(buzzerPin);
digitalWrite(ledPin, LOW);
}
// 3. Rhythm Delay
delay(tempo * durations[noteIndex]);
// 4. Loop Logic
noteIndex++;
if (noteIndex >= totalNotes) {
noteIndex = 0;
delay(1000); // 1 second pause before repeating the loop
}
} else {
noTone(buzzerPin);
digitalWrite(ledPin, LOW);
}
}
The instrument was constructed by interfacing high-resolution analog inputs to a digital synthesis engine.
Control Input: An Elegoo R3 was connected to a 2-axis joystick using Female-to-Male (F/M) jumper wires. The axes were mapped to pins A0 (Pitch) and A1 (Tempo), with the integrated button on D2
Audio Synthesis: A passive piezoelectric buzzer was installed in a dual-rail configuration to maximize acoustic resonance for the 8-bit frequencies
Visual Feedback: An LED array was wired to pin D9. Each circuit includes a 220Ω resistor for current regulation, with the LED programmed to alternate in sync with the musical rhythm
Key Detail:
musical frequencies had to be calculated in real-time within the Arduino sketch using the formula:
f = 440 x 2^((n-69)/12)
Rhythmic Transcription
Goal: Translate the Super Mario Bros. "Ground Theme" from sheet music into a data format the Arduino can read
Action: Analyzed the flute sheet music (Bars 1-10) to identify MIDI note numbers and rhythmic durations
Key Detail: Had to manually account for triplets (mapped as 0.66 beats) and rests (mapped as 0) to ensure the 8-bit output didn't sound "robotic" and maintained its iconic "swing."
We subtract 69 from any MIDI key number (to find the pitch difference in semitones from A 440) and use that number divided by 12 as the exponent of the base 2, multiplied by the base frequency of 440.
For example, Bb above middle C (MIDI 70) has a frequency of 440 times 2 to the (70-69)/12 power
Serial Latency to
Embedded Timing
The Problem: Initially, trying to send data to PlugData at lower baud rates caused a noticeable delay (latency) between moving the joystick and hearing the sound change.
The Solution: Switched to an embedded approach. By generating the sound directly on the Arduino at 115200 baud, the latency was eliminated. The joystick now controls internal variables for pitchShift and tempo instantly.
Breadboard Power
Rail Continuity
The Problem: As seen in my wiring photos, the 5V pin was occupied by the joystick. Attempting to power LEDs and the buzzer resulted in some components not working (specifically the Y-axis and the button).
The Solution: Traced the issue to a split power rail on the breadboard. Systematically tested each component via the Serial Monitor to ensure the ground was common across the entire board. This stabilized the analog readings for the joystick, allowing for smooth tempo manipulation. Also used female to male jumper wires
A Closer Look at the Passive Buzzer
Arduino CC. (2026). Language Reference: tone(). Retrieved from https://www.arduino.cc/reference/en/language/functions/advanced-io/tone/
Esposito, A. (2023). Understanding the Joystick Module: VRx, VRy, and SW. Arduino Project Hub.
Elegoo. (2026). The Most Complete Starter Kit for UNO R3: User Manual. * Usage: Reference for pin voltage limits and passive buzzer wiring (Rows 25/28).
Kondō, K. (1985). Super Mario Bros. Theme [Sheet Music]. Transcribed by FluteTunes.com. Retrieved from https://www.flutetunes.com/
Physclips. (2026). Note-Frequency Table: MIDI to Hertz. University of New South Wales.
MUMT 306 Course Materials. (2026). Arduino2Midi Reference Patch and FUDI Protocol. *
things were dire, stuck on schulich 5th