2026-10-14 21:42:24 +00:00
<!DOCTYPE html>
2026-10-18 00:38:48 +00:00
< html lang = "en" data-theme = "colorful" >
2026-10-14 21:42:24 +00:00
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
2026-10-17 23:05:45 +00:00
< title > MIDI Breadboard Instrument (Desk Rave & 16-Track Symphony) — Projects — Ertunç Özbay< / title >
2026-10-18 00:38:48 +00:00
< meta name = "description" content = "Project documentation, Python RLE MIDI-to-C compiler code, ESP-IDF C99 firmware, and downloads for the 16-Track MIDI Breadboard Instrument by Ertunç Özbay." >
2026-10-14 21:42:24 +00:00
< link rel = "icon" type = "image/svg+xml" href = "../assets/images/favicon.svg" >
2026-10-18 00:38:48 +00:00
< link rel = "stylesheet" href = "../assets/css/style.css?v=2" >
2026-10-14 21:42:24 +00:00
< / head >
< body >
< a class = "skip-link" href = "#main-content" > Skip to main content< / a >
< header class = "site-header" >
< div class = "container header-inner" >
< a class = "site-brand" href = "../index.html" >
< span class = "brand-mark" aria-hidden = "true" > EÖ< / span >
< span >
Ertunç Özbay
2026-10-18 00:19:21 +00:00
< span class = "brand-subtitle" > embedded // hardware // open-source< / span >
2026-10-14 21:42:24 +00:00
< / span >
< / a >
2026-10-18 00:38:48 +00:00
< div class = "header-controls" >
< div class = "theme-switcher" role = "group" aria-label = "Select UI Theme" >
< button type = "button" class = "theme-btn" data-set-theme = "colorful" aria-pressed = "true" > 🎨 Colorful< / button >
< button type = "button" class = "theme-btn" data-set-theme = "neon" aria-pressed = "false" > 🌈 Neon< / button >
< button type = "button" class = "theme-btn" data-set-theme = "blueprint" aria-pressed = "false" > 📐 Blueprint< / button >
< / div >
< button class = "nav-toggle" type = "button" data-nav-toggle aria-expanded = "false" aria-controls = "primary-navigation" >
< span aria-hidden = "true" > ☰< / span >
< span data-nav-label > Menu< / span >
< / button >
< nav id = "primary-navigation" class = "site-nav" data-site-nav aria-label = "Main navigation" >
< ul class = "nav-list" >
< li > < a class = "nav-link" href = "../index.html" > Home< / a > < / li >
< li > < a class = "nav-link" href = "../about.html" > About< / a > < / li >
< li > < a class = "nav-link" href = "../projects/index.html" aria-current = "page" > Projects< / a > < / li >
< li > < a class = "nav-link" href = "../blog/index.html" > Blog< / a > < / li >
< li > < a class = "nav-link" href = "../lab.html" > Lab< / a > < / li >
< li > < a class = "nav-link" href = "../contact.html" > Contact< / a > < / li >
< / ul >
< / nav >
< / div >
2026-10-14 21:42:24 +00:00
< / div >
< / header >
< main id = "main-content" >
< div class = "container" >
< nav class = "breadcrumb" aria-label = "Breadcrumb" >
< a href = "index.html" > ← Back to Projects< / a >
< / nav >
< article >
< header class = "workshop-hero" >
< div class = "card-meta" >
2026-10-17 23:05:45 +00:00
< span class = "badge badge-experimental" > Status: Active Breadboard Prototype< / span >
2026-10-18 00:19:21 +00:00
< span > Project 02 // ESP32 Dual-Core • ESP-IDF C99 • FreeRTOS • Python RLE< / span >
2026-10-14 21:42:24 +00:00
< / div >
2026-10-18 00:19:21 +00:00
< h1 > < span class = "gradient-title" > MIDI Breadboard Instrument — Desk Rave & 16-Track Symphony< / span > < / h1 >
2026-10-14 21:42:24 +00:00
< p class = "hero-lead" >
2026-10-17 23:05:45 +00:00
A standalone, dual-core ESP32 MIDI synthesizer, drum sequencer, and 30 FPS SPI OLED visualizer written in ESP-IDF v5.2.2 (C99 / FreeRTOS), powered by a custom Run-Length Encoding (RLE) Python MIDI-to-C compiler.
2026-10-14 21:42:24 +00:00
< / p >
< p class = "reading-width" >
2026-10-17 23:05:45 +00:00
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.
2026-10-14 21:42:24 +00:00
< / p >
< div class = "button-row" >
2026-10-21 16:42:52 +00:00
< a class = "btn btn-primary" href = "http://34.57.151.250:3000/ertunc-ozbay/midi-breadboard-instrument" rel = "noopener noreferrer" > 💻 View GitHub Repo< / a >
2026-10-18 00:40:30 +00:00
< a class = "btn btn-emerald" href = "../assets/downloads/desk_rave_16track_esp32.c" download = "mmcontroller.c" > ⬇ Download 16-Track C Firmware (.c)< / a >
< a class = "btn btn-cyan" href = "../assets/downloads/midi_to_c.py" download = "midi_to_c.py" > 🐍 Download midi_to_c.py< / a >
2026-10-21 16:42:52 +00:00
< a class = "btn btn-secondary" href = "/assets/downloads/midi-breadboard-instrument.zip" > 📦 Repo (.zip)< / a >
2026-10-18 00:19:21 +00:00
< a class = "btn btn-youtube" href = "#poc-video" > 🎬 Watch PoC Video Slot< / a >
2026-10-14 21:42:24 +00:00
< / div >
< / header >
2026-10-18 00:19:21 +00:00
<!-- YouTube Proof - of - Concept Video Placeholder -->
< section id = "poc-video" class = "poc-video-section" aria-labelledby = "midi-video-heading" >
< div class = "poc-video-card" >
< span class = "section-label" > 🎬 Proof of Concept // YouTube Video Showcase< / span >
< h2 id = "midi-video-heading" > YouTube Proof-of-Concept Demonstration< / h2 >
< div class = "poc-video-screen" role = "region" aria-label = "YouTube Proof of Concept Video Placeholder" >
< div class = "poc-play-badge" aria-hidden = "true" >
< div class = "poc-play-triangle" > < / div >
< / div >
< span class = "poc-status-pill" > 🎥 YouTube PoC Video — Recording / Coming Soon< / span >
< div class = "poc-video-title" > Desk Rave 16-Track ESP32 MIDI Synthesizer — Live Audio & 30 FPS OLED Demo< / div >
< p class = "poc-video-desc" >
Upcoming YouTube video showcasing live playback across the 16-track flash playlist (Careless Whisper, Rondo Alla Turca, Seven Nation Army +2 octave transpose), real-time rotary speed/volume tweaking, and synchronized kick/hi-hat buzzers + LEDs.
< / p >
< / div >
< / div >
< / section >
2026-10-14 21:42:24 +00:00
< figure class = "schematic-frame" aria-label = "MIDI Breadboard Instrument Component Diagram" >
2026-10-18 00:19:21 +00:00
< img class = "zoomable-img" src = "../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" width = "760" height = "320" >
2026-10-14 21:42:24 +00:00
< figcaption class = "schematic-caption" >
2026-10-17 23:05:45 +00:00
Software-to-hardware pipeline: Python RLE MIDI compiler (< code > midi_to_c.py< / code > ) generating flash-resident < code > SequenceStep< / code > struct arrays for the dual-core ESP32 breadboard synthesizer.
2026-10-14 21:42:24 +00:00
< / figcaption >
< / figure >
2026-10-17 23:05:45 +00:00
< section class = "section-block" aria-labelledby = "p3-gallery" >
< span class = "section-label" > 01 // Hardware Build Gallery< / span >
2026-10-18 00:19:21 +00:00
< h2 id = "p3-gallery" > Breadboard Prototype Photos (Click to Zoom)< / h2 >
2026-10-17 23:05:45 +00:00
< div class = "card-grid" >
2026-10-18 00:19:21 +00:00
< figure class = "card card-synth" style = "margin:0;" >
< img class = "zoomable-img" src = "../assets/images/MIDI_FIRST.jpg" alt = "Full dual-breadboard overview of the ESP32 MIDI Breadboard Instrument" style = "width:100%; height:auto; border-radius:8px; margin-bottom:0.75rem;" >
2026-10-17 23:05:45 +00:00
< 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 >
2026-10-18 00:19:21 +00:00
< figure class = "card card-synth" style = "margin:0;" >
< img class = "zoomable-img" src = "../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:8px; margin-bottom:0.75rem;" >
2026-10-17 23:05:45 +00:00
< 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 >
2026-10-18 00:19:21 +00:00
< figure class = "card card-synth" style = "margin:0;" >
< img class = "zoomable-img" src = "../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:8px; margin-bottom:0.75rem;" >
2026-10-17 23:05:45 +00:00
< 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.
< / figcaption >
< / figure >
< / div >
< / section >
2026-10-18 00:38:48 +00:00
<!-- Terminal Commands & Source Code Section -->
< section class = "section-block" aria-labelledby = "p3-code" >
< span class = "section-label" > 02 // Terminal Commands, Python RLE Compiler & C99 Firmware< / span >
< h2 id = "p3-code" > Build, Flash & MIDI Conversion Commands< / h2 >
< p class = "reading-width" >
Clone the repository, convert any `.mid` file into an RLE-compressed C struct array using `scripts/midi_to_c.py`, and flash the 16-track firmware to an ESP32:
2026-10-14 21:42:24 +00:00
< / p >
2026-10-18 00:38:48 +00:00
< div class = "code-window" >
< div class = "code-window-header" >
< span class = "code-window-title" >
< span class = "code-dots" > < span class = "code-dot red" > < / span > < span class = "code-dot amber" > < / span > < span class = "code-dot green" > < / span > < / span >
bash — Convert MIDI to C & Flash ESP32 Firmware (ESP-IDF v5.2.2)
< / span >
< button type = "button" class = "copy-code-btn" data-copy-code > 📋 Copy Commands< / button >
< / div >
< pre > < code > # 1. Clone the repository
2026-10-21 16:42:52 +00:00
git clone http://34.57.151.250:3000/ertunc-ozbay/midi-breadboard-instrument.git
2026-10-18 00:38:48 +00:00
cd midi-breadboard-instrument
# 2. (Optional) Convert a custom .mid file into an RLE-compressed C struct array
pip install mido
python3 scripts/midi_to_c.py scripts/midillis/Alla_Turca/Alla_Turca.mid > my_track.h
# 3. Build and flash the 16-track firmware to ESP32
source ~/esp/esp-idf/export.sh
idf.py build
idf.py -p /dev/ttyUSB0 flash monitor< / code > < / pre >
< / div >
< h3 > Python RLE MIDI-to-C Compiler (< code > scripts/midi_to_c.py< / code > )< / h3 >
< p class = "reading-width" >
To prevent fragmented MIDI delta-times from strobing the LEDs/OLED at 30+ Hz and exhausting flash memory, < code > midi_to_c.py< / code > merges contiguous identical states via Run-Length Encoding and routes Channel 10 + bass notes (`< MIDI 50`) to hardware percussion buzzers:
< / p >
< div class = "code-window" >
< div class = "code-window-header" >
< span class = "code-window-title" >
< span class = "code-dots" > < span class = "code-dot red" > < / span > < span class = "code-dot amber" > < / span > < span class = "code-dot green" > < / span > < / span >
python3 — scripts/midi_to_c.py (RLE Compression & Drum Routing Engine)
< / span >
< button type = "button" class = "copy-code-btn" data-copy-code > 📋 Copy Code< / button >
< / div >
< pre > < code > #!/usr/bin/env python3
import sys, mido
def convert_midi_to_c(midi_filename):
mid = mido.MidiFile(midi_filename)
safe_name = midi_filename.split('/')[-1].replace('.mid', '').replace('-', '_')
print(f"const SequenceStep midi_{safe_name}[] = {{")
active_melody_notes = set()
time_accum = 0.0
trigger_kick = trigger_hat = False
rle_freq, rle_dur, rle_kick, rle_hat = -1, 0, False, False
for msg in mid:
time_accum += msg.time
if msg.type in ['note_on', 'note_off'] and time_accum > 0.01:
duration_ms = int(time_accum * 1000)
freq = int(440.0 * (2.0 ** ((max(active_melody_notes) - 69) / 12.0))) if active_melody_notes else 0
# RLE Engine: coalesce contiguous identical frequency & drum states
if freq == rle_freq and trigger_kick == rle_kick and trigger_hat == rle_hat:
rle_dur += duration_ms
else:
if rle_dur > 0:
print(f" {{{rle_freq}, {rle_dur}, {str(rle_kick).lower()}, {str(rle_hat).lower()}}},")
rle_freq, rle_dur, rle_kick, rle_hat = freq, duration_ms, trigger_kick, trigger_hat
time_accum, trigger_kick, trigger_hat = 0.0, False, False< / code > < / pre >
< / div >
< h3 > Dual-Core FreeRTOS Music & Visualizer Architecture (< code > main/mmcontroller.c< / code > )< / h3 >
< div class = "code-window" >
< div class = "code-window-header" >
< span class = "code-window-title" >
< span class = "code-dots" > < span class = "code-dot red" > < / span > < span class = "code-dot amber" > < / span > < span class = "code-dot green" > < / span > < / span >
c99 — main/mmcontroller.c (SequenceStep Struct & Dual-Core Task Pinning)
< / span >
< button type = "button" class = "copy-code-btn" data-copy-code > 📋 Copy Code< / button >
< / div >
< pre > < code > typedef struct {
uint16_t frequency; // Melody pitch in Hz (driven by 13-bit LEDC PWM on GPIO 25)
uint16_t duration; // Step duration in ms (scaled live by PCNT rotary speed)
bool kick_drum; // Triggers Active Buzzer 1 (GPIO 26) + Kick LED (GPIO 21)
bool hi_hat; // Triggers Active Buzzer 2 (GPIO 27) + Hat LED (GPIO 22)
} SequenceStep;
void app_main(void) {
printf("Desk Rave: 16-Track Symphony Edition Initializing...\n");
init_hardware();
// Core 0: 30 FPS SPI OLED Framebuffer Visualizer & 50ms Button Polling
xTaskCreatePinnedToCore(oled_visualizer_task, "oled_task", 4096, NULL, 4, NULL, 0);
xTaskCreatePinnedToCore(button_task, "btn_task", 4096, NULL, 5, NULL, 0);
// Core 1: Dedicated Real-Time Audio Synthesizer & PCNT Rotary Scaling
xTaskCreatePinnedToCore(buzzer_sequence_task, "music_task", 4096, NULL, 5, NULL, 1);
}< / code > < / pre >
< / div >
2026-10-14 21:42:24 +00:00
< / section >
< section class = "section-block reading-width" aria-labelledby = "p3-components" >
2026-10-17 23:05:45 +00:00
< span class = "section-label" > 03 // Confirmed Hardware & Pinout< / span >
< h2 id = "p3-components" > ESP32 Pinout & Architecture Specifications< / h2 >
2026-10-14 21:42:24 +00:00
< table class = "spec-table" >
< thead >
< tr >
2026-10-17 23:05:45 +00:00
< th scope = "col" > Component / Peripheral< / th >
< th scope = "col" > ESP32 Pin / Interface< / th >
< th scope = "col" > Function< / th >
2026-10-14 21:42:24 +00:00
< / tr >
< / thead >
< tbody >
< tr >
2026-10-17 23:05:45 +00:00
< td > < strong > Microcontroller< / strong > < / td >
< td > ESP32 (Dual-Core Xtensa @ 160 MHz)< / td >
< td > ESP-IDF v5.2.2 + FreeRTOS dual-core execution< / td >
2026-10-14 21:42:24 +00:00
< / tr >
< tr >
2026-10-17 23:05:45 +00:00
< td > < strong > Rotary Encoder CLK / DT< / strong > < / td >
< td > < code > GPIO 32< / code > / < code > GPIO 33< / code > < / td >
< td > Hardware PCNT quadrature tracking (1000 ns glitch filter)< / td >
2026-10-14 21:42:24 +00:00
< / tr >
< tr >
2026-10-17 23:05:45 +00:00
< td > < strong > Rotary Encoder SW< / strong > < / td >
< td > < code > GPIO 16< / code > (Active Low)< / td >
< td > Toggles knob & OLED HUD between Speed (< code > 0.2x– 3.0x< / code > ) and Volume (< code > 0%– 100%< / code > )< / td >
2026-10-14 21:42:24 +00:00
< / tr >
< tr >
2026-10-17 23:05:45 +00:00
< td > < strong > Pause / Next Buttons< / strong > < / td >
< td > < code > GPIO 13< / code > / < code > GPIO 14< / code > (Active Low)< / td >
< td > Play/Pause toggle and 16-track playlist cycling< / td >
2026-10-14 21:42:24 +00:00
< / tr >
< tr >
2026-10-17 23:05:45 +00:00
< td > < strong > Passive Buzzer (Melody)< / strong > < / td >
< td > < code > GPIO 25< / code > (LEDC 13-bit PWM)< / td >
< td > Main melody synthesizer driven by hardware LEDC Timer 0< / td >
2026-10-14 21:42:24 +00:00
< / tr >
< tr >
2026-10-17 23:05:45 +00:00
< td > < strong > Active Buzzer 1 & 2 (Drums)< / strong > < / td >
< td > < code > GPIO 26< / code > (Kick) / < code > GPIO 27< / code > (Hi-Hat)< / td >
< td > Physical percussion transients on drum steps< / td >
2026-10-14 21:42:24 +00:00
< / tr >
< tr >
2026-10-17 23:05:45 +00:00
< td > < strong > Kick & Hi-Hat LEDs< / strong > < / td >
< td > < code > GPIO 21< / code > (Kick) / < code > GPIO 22< / code > (Hi-Hat)< / td >
< td > Visual beat indicators synchronized to drum hits< / td >
2026-10-14 21:42:24 +00:00
< / tr >
< tr >
2026-10-17 23:05:45 +00:00
< td > < strong > 1.3" 7-Pin SPI OLED< / strong > < / td >
< td > < code > CS: 5, DC: 2, RES: 4, MOSI: 23, SCK: 18< / code > < / td >
< td > 10 MHz SPI2 display running custom 30 FPS visualizer on Core 0< / td >
2026-10-14 21:42:24 +00:00
< / tr >
< / tbody >
< / table >
< / section >
2026-10-17 23:05:45 +00:00
< section class = "section-block reading-width" aria-labelledby = "p3-playlist" >
< span class = "section-label" > 04 // Flash Memory Playlist< / span >
< h2 id = "p3-playlist" > The 16-Track Symphony< / h2 >
< 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 >
< li > < strong > Gimme! Gimme! Gimme!< / strong > (Midnight Rave)< / li >
< li > < strong > Für Elise< / strong > (Beethoven Trance)< / li >
< li > < strong > Symphony No. 40< / strong > (Mozart Intense Strings)< / li >
< li > < strong > Super Mario Bros.< / strong > (Main Theme)< / li >
< li > < strong > Pirates of the Caribbean 1< / strong > (He's a Pirate)< / li >
< li > < strong > Pirates of the Caribbean 2< / strong > (The Black Pearl)< / li >
< li > < strong > Rondo Alla Turca< / strong > (Mozart)< / li >
< li > < strong > Tetris< / strong > (Korobeiniki)< / li >
< li > < strong > Sonic the Hedgehog< / strong > (Green Hill Zone)< / li >
< li > < strong > Castlevania< / strong > (Vampire Killer)< / li >
< li > < strong > Seven Nation Army< / strong > (Transposed +2 Octaves for piezo resonance)< / li >
< li > < strong > Mega Man 2< / strong > (Dr. Wily Stage)< / li >
< / ol >
< / section >
2026-10-14 21:42:24 +00:00
< / article >
< / div >
< / main >
< footer class = "site-footer" >
< div class = "container footer-inner" >
< div >
2026-10-18 00:38:48 +00:00
< strong > Ertunç Özbay< / strong > — Electrical & Electronics Engineering Student • < a href = "mailto:ertunc.ozbay@outlook.com" > ertunc.ozbay@outlook.com< / a >
2026-10-14 21:42:24 +00:00
< / div >
< ul class = "footer-links" aria-label = "Footer links" >
< li > < a href = "../projects/index.html" > Projects< / a > < / li >
< li > < a href = "../blog/index.html" > Blog< / a > < / li >
< li > < a href = "../lab.html" > Lab< / a > < / li >
< li > < a href = "../contact.html" > Contact< / a > < / li >
2026-10-21 16:42:52 +00:00
< li > < a href = "http://34.57.151.250:3000/ertunc-ozbay" rel = "noopener noreferrer" > GitHub< / a > < / li >
2026-10-18 00:19:21 +00:00
< li > < a href = "https://www.linkedin.com/in/ertunc-ozbay" rel = "noopener noreferrer" > LinkedIn< / a > < / li >
2026-10-14 21:42:24 +00:00
< / ul >
< / div >
< / footer >
2026-10-18 00:38:48 +00:00
< script src = "../assets/js/main.js?v=2" > < / script >
2026-10-14 21:42:24 +00:00
< / body >
< / html >