331 lines
18 KiB
HTML
331 lines
18 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en" data-theme="colorful">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>MIDI Breadboard Instrument (Desk Rave & 16-Track Symphony) — Projects — Ertunç Özbay</title>
|
||
<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.">
|
||
<link rel="icon" type="image/svg+xml" href="../assets/images/favicon.svg">
|
||
<link rel="stylesheet" href="../assets/css/style.css?v=2">
|
||
</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
|
||
<span class="brand-subtitle">embedded // hardware // open-source</span>
|
||
</span>
|
||
</a>
|
||
|
||
<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>
|
||
</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">
|
||
<span class="badge badge-experimental">Status: Active Breadboard Prototype</span>
|
||
<span>Project 02 // ESP32 Dual-Core • ESP-IDF C99 • FreeRTOS • Python RLE</span>
|
||
</div>
|
||
<h1><span class="gradient-title">MIDI Breadboard Instrument — Desk Rave & 16-Track Symphony</span></h1>
|
||
<p class="hero-lead">
|
||
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.
|
||
</p>
|
||
<p class="reading-width">
|
||
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.
|
||
</p>
|
||
<div class="button-row">
|
||
<a class="btn btn-primary" href="https://github.com/ertunc-ozbay/midi-breadboard-instrument" rel="noopener noreferrer">💻 View GitHub Repo</a>
|
||
<a class="btn btn-emerald" href="https://github.com/ertunc-ozbay/midi-breadboard-instrument/archive/refs/heads/main.zip">⬇ Download Project (.zip)</a>
|
||
<a class="btn btn-cyan" href="https://raw.githubusercontent.com/ertunc-ozbay/midi-breadboard-instrument/main/scripts/midi_to_c.py" download="midi_to_c.py">🐍 Download midi_to_c.py</a>
|
||
<a class="btn btn-youtube" href="#poc-video">🎬 Watch PoC Video Slot</a>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- 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>
|
||
|
||
<figure class="schematic-frame" aria-label="MIDI Breadboard Instrument Component Diagram">
|
||
<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">
|
||
<figcaption class="schematic-caption">
|
||
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.
|
||
</figcaption>
|
||
</figure>
|
||
|
||
<section class="section-block" aria-labelledby="p3-gallery">
|
||
<span class="section-label">01 // Hardware Build Gallery</span>
|
||
<h2 id="p3-gallery">Breadboard Prototype Photos (Click to Zoom)</h2>
|
||
<div class="card-grid">
|
||
<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;">
|
||
<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>
|
||
<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;">
|
||
<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>
|
||
<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;">
|
||
<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>
|
||
|
||
<!-- 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:
|
||
</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>
|
||
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
|
||
git clone https://github.com/ertunc-ozbay/midi-breadboard-instrument.git
|
||
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>
|
||
</section>
|
||
|
||
<section class="section-block reading-width" aria-labelledby="p3-components">
|
||
<span class="section-label">03 // Confirmed Hardware & Pinout</span>
|
||
<h2 id="p3-components">ESP32 Pinout & Architecture Specifications</h2>
|
||
<table class="spec-table">
|
||
<thead>
|
||
<tr>
|
||
<th scope="col">Component / Peripheral</th>
|
||
<th scope="col">ESP32 Pin / Interface</th>
|
||
<th scope="col">Function</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><strong>Microcontroller</strong></td>
|
||
<td>ESP32 (Dual-Core Xtensa @ 160 MHz)</td>
|
||
<td>ESP-IDF v5.2.2 + FreeRTOS dual-core execution</td>
|
||
</tr>
|
||
<tr>
|
||
<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>
|
||
</tr>
|
||
<tr>
|
||
<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>
|
||
</tr>
|
||
<tr>
|
||
<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>
|
||
</tr>
|
||
<tr>
|
||
<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>
|
||
</tr>
|
||
<tr>
|
||
<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>
|
||
</tr>
|
||
<tr>
|
||
<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>
|
||
</tr>
|
||
<tr>
|
||
<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>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</section>
|
||
|
||
<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>
|
||
</article>
|
||
|
||
</div>
|
||
</main>
|
||
|
||
<footer class="site-footer">
|
||
<div class="container footer-inner">
|
||
<div>
|
||
<strong>Ertunç Özbay</strong> — Electrical & Electronics Engineering Student • <a href="mailto:ertunc.ozbay@outlook.com">ertunc.ozbay@outlook.com</a>
|
||
</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>
|
||
<li><a href="https://github.com/ertunc-ozbay" rel="noopener noreferrer">GitHub</a></li>
|
||
<li><a href="https://www.linkedin.com/in/ertunc-ozbay" rel="noopener noreferrer">LinkedIn</a></li>
|
||
</ul>
|
||
</div>
|
||
</footer>
|
||
|
||
<script src="../assets/js/main.js?v=2"></script>
|
||
</body>
|
||
</html>
|