What started as making a passive piezo buzzer squeak evolved into a full 16-track embedded music box with live kick/hi-hat drum transients, hardware PCNT quadrature rotary controls for real-time tempo and volume scaling, and dual-core FreeRTOS task isolation.
<imgsrc="../assets/images/midi-breadboard-layout.svg"alt="Block diagram of the MIDI Breadboard Instrument showing the MIDI conversion script producing C code for the breadboard system, connected to two active buzzers, one passive buzzer, a 1.3-inch SPI OLED screen, one rotary encoder, two buttons, and two LEDs."width="760"height="320">
<imgsrc="../assets/images/MIDI_FIRST.jpg"alt="Full dual-breadboard overview of the ESP32 MIDI Breadboard Instrument"style="width:100%; height:auto; border-radius:6px; margin-bottom:0.75rem;">
<figcaption>
<strong>Full Breadboard Layout:</strong> Dual-breadboard build featuring the ESP32 DevKit, 1.3" 7-pin SPI OLED display, KY-040 rotary encoder, tactile buttons, passive melody piezo, and dual active drum buzzers.
</figcaption>
</figure>
<figureclass="card"style="margin:0;">
<imgsrc="../assets/images/MIDI_SECOND.jpg"alt="Active 1.3-inch SPI OLED visualizer running at 30 FPS on the breadboard"style="width:100%; height:auto; border-radius:6px; margin-bottom:0.75rem;">
<figcaption>
<strong>30 FPS Live OLED Visualizer:</strong> Custom 5×7 font renderer displaying track code (<code>WHISP</code>), rotary mode (<code>SPD</code> / <code>VOL</code>), kick/hi-hat drum boxes, and dynamic frequency bars.
</figcaption>
</figure>
<figureclass="card"style="margin:0;">
<imgsrc="../assets/images/MIDI_THIRD.jpg"alt="Close-up of the rotary encoder, tactile buttons, LEDs, and three buzzers"style="width:100%; height:auto; border-radius:6px; margin-bottom:0.75rem;">
<figcaption>
<strong>Controls & Acoustic Stage:</strong> Close-up of the rotary encoder, Play/Pause and Next Track buttons, green/red drum LEDs, and the 3-buzzer audio stage.
<li><strong>The Strobe-Light Bug & RLE Compression (<code>midi_to_c.py</code>):</strong> Raw MIDI files often slice sustained notes into microscopic fragments, causing the LEDs and OLED UI to strobe at 30+ Hz while wasting flash memory. We built a custom Python Run-Length Encoding (RLE) compressor using <code>mido</code> that merges contiguous identical pitch/drum states into solid <code>SequenceStep</code> blocks and routes bass/percussion channels to dedicated drum triggers.</li>
<li><strong>Acoustic Hardware Limits (+2 Octave Transposition):</strong> Passive piezo buzzers physically struggle to resonate at low bass frequencies. Tracks with deep basslines like <em>Seven Nation Army</em> were transposed up two full octaves so the piezo disc resonates clearly while the active buzzers carry the rhythmic punch.</li>
<li><strong>Dual-Core FreeRTOS Lockless IPC:</strong> Core 0 runs <code>oled_visualizer_task</code> (30 FPS SPI DMA framebuffer engine) and <code>button_task</code> (debounced input polling), while Core 1 exclusively runs <code>buzzer_sequence_task</code> for microsecond-accurate LEDC PWM pitch generation and hardware PCNT rotary encoder scaling.</li>
All 16 tracks are stored in ESP32 flash memory as <code>SequenceStep</code> arrays featuring <code>frequency</code>, <code>duration</code>, <code>kick_drum</code>, and <code>hi_hat</code> triggers:
</p>
<ol>
<li><strong>Careless Whisper</strong> (Extended Club Edit)</li>
<li><strong>Take On Me</strong> (EDM Build-up Edition)</li>
<li><strong>Never Gonna Give You Up / Rickroll</strong> (Double Chorus)</li>
<li><strong>Can't Help Falling In Love</strong> (Wedding Mix)</li>
The firmware is rock-solid on the breadboard: the rotary encoder dynamically scales volume and tempo via lockless IPC variables, all 16 tracks play from flash memory, and the 1.3" SPI OLED renders real-time frequency bars and drum hits at 30 FPS.
Migrating the dual-breadboard circuit onto a soldered perfboard or custom KiCad PCB, and optionally re-integrating a WS2812B addressable RGB strip when expanding the enclosure.