ertuncozbay.dev/projects/midi-breadboard-instrument.html

332 lines
18 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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 &amp; 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 &amp; 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="http://34.57.151.250:3000/ertunc-ozbay/midi-breadboard-instrument" rel="noopener noreferrer">💻 View GitHub Repo</a>
<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>
<a class="btn btn-secondary" href="/assets/downloads/midi-breadboard-instrument.zip">📦 Repo (.zip)</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 &amp; 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 &amp; 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 &amp; C99 Firmware</span>
<h2 id="p3-code">Build, Flash &amp; 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 &amp; 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 http://34.57.151.250:3000/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 &gt; 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 (`&lt; 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 &amp; 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 &gt; 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 &amp; drum states
if freq == rle_freq and trigger_kick == rle_kick and trigger_hat == rle_hat:
rle_dur += duration_ms
else:
if rle_dur &gt; 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 &amp; 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 &amp; 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 &amp; 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 &amp; 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 &amp; Pinout</span>
<h2 id="p3-components">ESP32 Pinout &amp; 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 &amp; 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 &amp; 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 &amp; 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 &amp; 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="http://34.57.151.250:3000/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>