From 62cc1184247a29fafe30ae22aa6921d3f4fd1c4b Mon Sep 17 00:00:00 2001 From: ertunc-ozbay Date: Wed, 21 Oct 2026 20:19:18 +0300 Subject: [PATCH] Clean up README formatting --- README.md | 192 +++++++----------------------------------------------- 1 file changed, 23 insertions(+), 169 deletions(-) diff --git a/README.md b/README.md index b95c161..548e04b 100644 --- a/README.md +++ b/README.md @@ -1,184 +1,38 @@ -# πŸŽ›οΈ MIDI Breadboard Instrument β€” Desk Rave & 16-Track Symphony Controller +# ESP32 Multi-Track MIDI Breadboard Instrument -[![ESP-IDF](https://img.shields.io/badge/ESP--IDF-v5.2.2-E7352C?logo=espressif&logoColor=white)](https://docs.espressif.com/projects/esp-idf/en/v5.2.2/esp32/) -[![MCU](https://img.shields.io/badge/MCU-ESP32%20Dual--Core%20Xtensa-blue)](https://www.espressif.com/en/products/socs/esp32) -[![Language](https://img.shields.io/badge/Language-ISO%20C99%20%2B%20Python%203-00599C)](main/mmcontroller.c) -[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE) +An embedded polyphonic buzzer synthesizer and OLED visualizer running on a dual-core ESP32 (`ESP-IDF v5.2.2`, FreeRTOS). Melodies are parsed from standard `.mid` files using a Python run-length encoding script (`scripts/midi_to_c.py`) into C struct arrays stored in flash. -A standalone, dual-core **ESP32 MIDI Synthesizer, Drum Sequencer, and 30 FPS OLED Visualizer** built from scratch in **ESP-IDF v5.2.2 (C99 / FreeRTOS)**, paired with a custom **Run-Length Encoding (RLE) Python MIDI-to-C compiler** (`scripts/midi_to_c.py`). +## Hardware Pinout ---- +| Component | ESP32 GPIO | Notes | +| :--- | :--- | :--- | +| Passive Buzzer (Melody) | `GPIO 25` | Driven by hardware `LEDC` PWM timer | +| Active Buzzer 1 (Kick) | `GPIO 26` | Percussion trigger output | +| Active Buzzer 2 (Hi-Hat) | `GPIO 27` | Percussion trigger output | +| LED 1 (Kick Indicator) | `GPIO 21` | Active on kick trigger | +| LED 2 (Hi-Hat Indicator) | `GPIO 22` | Active on hi-hat trigger | +| Rotary Encoder CLK / DT / SW | `GPIO 32 / 33 / 16` | Hardware `PCNT` quadrature decoding; button toggles volume/tempo mode | +| Play / Pause Button | `GPIO 13` | Active-low input with internal pull-up | +| Next Track Button | `GPIO 14` | Cycles through the 16-track array | +| 1.3" SPI OLED (`SH1106`) | `CS: 5, DC: 2, RES: 4, MOSI: 23, SCK: 18` | 128x64 display rendered from Core 0 | -## πŸ“Έ Breadboard Hardware Gallery +## Software Structure -| Full Breadboard Overview | Active OLED Visualizer & Controls | Multi-Buzzer & Rotary Wiring | -| :---: | :---: | :---: | -| ![Breadboard Overview](images/MIDI_FIRST.jpg) | ![Active Visualizer](images/MIDI_SECOND.jpg) | ![Wiring Detail](images/MIDI_THIRD.jpg) | -| *Dual-breadboard layout featuring ESP32, 1.3" SPI OLED, rotary encoder, tactile buttons, passive melody piezo, and dual active drum buzzers.* | *Live 30 FPS OLED HUD showing track title (`WHISP`), playback state, `SPD`/`VOL` multiplier, kick/hi-hat drum boxes, and frequency bars.* | *Close-up of the KY-040 rotary encoder, tactile control buttons, kick/hi-hat LED indicators, and triple-buzzer acoustic stage.* | +- `main/mmcontroller.c` β€” FreeRTOS application splitting UI rendering (`oled_visualizer_task` on Core 0) and audio timing (`buzzer_sequence_task` on Core 1). +- `scripts/midi_to_c.py` β€” Python utility (`mido`) that converts MIDI note events into C `SequenceStep` arrays (`{frequency, duration, kick_drum, hi_hat}`). +- `images/` β€” Breadboard wiring and OLED operation photos. ---- +## Building and Flashing -## πŸ“– 1. Engineering Origin & Technical Challenges Solved - -This project began as a bare-metal hardware exploration to push an ESP32 and simple piezo/magnetic transducers to their physical limitsβ€”evolving from a single-tone passive buzzer test into a 16-track polyphonic-illusion "Desk Rave" synthesizer. - -Key embedded engineering challenges solved during development: - -1. **The "Strobe-Light" MIDI Fragmentation Bug:** Raw MIDI files frequently split sustained notes into microscopic delta-time slices, causing the LEDs and OLED visualizer to strobe at 30+ Hz and exhausting flash memory. -2. **Custom RLE Python Compiler (`scripts/midi_to_c.py`):** We engineered a custom `mido`-based Python parser with a **Run-Length Encoding (RLE)** state machine that coalesces contiguous identical frequency/drum states into solid `SequenceStep` blocks and routes MIDI Channel 10 (plus low bass notes `< MIDI 50`) to hardware percussion triggers. -3. **Piezo Acoustic Resonance Limits:** Passive piezo buzzers physically struggle to resonate at low fundamental bass frequencies. Tracks with deep basslinesβ€”such as *Seven Nation Army*β€”were transposed **+2 full octaves** so the piezo disc resonates clearly while retaining rhythmic punch via the dedicated Kick/Hi-Hat active buzzers. -4. **Dual-Core FreeRTOS Lockless IPC:** To prevent SPI display transfers from introducing timing jitter into microsecond-sensitive audio generation, the firmware partitions tasks across both Tensilica Xtensa LX6 CPU cores: - - **Core 0 (UI & Human Interface):** Runs `oled_visualizer_task` (30 FPS framebuffer renderer using a zero-dependency custom 5Γ—7 font byte array) and `button_task` (50 ms debounced input polling). - - **Core 1 (Real-Time Music Engine):** Runs `buzzer_sequence_task`, reading `SequenceStep` arrays from flash, polling the hardware **PCNT (Pulse Counter)** quadrature rotary encoder for real-time tempo (`0.2x`–`3.0x`) and PWM duty-cycle volume (`0%`–`100%`) scaling, and driving the 13-bit hardware **LEDC PWM** timer. - ---- - -## 🧠 2. System Architecture - -```mermaid -flowchart LR - subgraph Host["Host PC Tooling (Python 3)"] - MIDI["Standard .mid Files"] --> PARSER["scripts/midi_to_c.py (RLE Compressor + Drum Router)"] - end - - subgraph ESP32["ESP32 Dual-Core Firmware (ESP-IDF v5.2.2)"] - PARSER -->|Generates C Struct Arrays| FLASH["Flash Playlist (16 Tracks / SequenceStep[])"] - - subgraph Core1["Core 1: Real-Time Audio Engine"] - FLASH --> ENGINE["buzzer_sequence_task"] - PCNT["Hardware PCNT Unit (Rotary CLK GPIO 32 / DT GPIO 33)"] --> ENGINE - end - - subgraph Core0["Core 0: UI & Input Polling"] - BTN["button_task (Pause GPIO 13 / Next GPIO 14 / Mode SW GPIO 16)"] - OLED_TASK["oled_visualizer_task (30 FPS Framebuffer)"] - end - - BTN <-->|Volatile IPC Flags| ENGINE - ENGINE -->|Volatile Pitch & Drum State| OLED_TASK - end - - subgraph Hardware["Breadboard Peripherals"] - ENGINE -->|LEDC 13-Bit PWM GPIO 25| P_BUZ["Passive Buzzer (Main Melody)"] - ENGINE -->|GPIO 26 + GPIO 21| KICK["Active Buzzer 1 + Kick LED"] - ENGINE -->|GPIO 27 + GPIO 22| HAT["Active Buzzer 2 + Hi-Hat LED"] - OLED_TASK -->|10 MHz SPI2 DMA| OLED["1.3-inch SPI OLED (SH1106 / SSD1306)"] - end -``` - ---- - -## 🎡 3. The 16-Track Flash Symphony (`SequenceStep`) - -All 16 tracks are compiled directly into ESP32 flash memory inside [`main/mmcontroller.c`](main/mmcontroller.c) using a compact 4-field C struct: - -```c -typedef struct { - uint16_t frequency; // Melody pitch in Hz (0 = REST) - uint16_t duration; // Step duration in ms (scaled live by current_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; -``` - -| Index | OLED Tag | Track Title | Arrangement / Engineering Notes | -| :---: | :---: | :--- | :--- | -| `0` | `WHISP` | **Careless Whisper** | Extended Club Edit with synthesized four-on-the-floor kick/hat groove | -| `1` | `TAKE ` | **Take On Me** | High-tempo EDM build-up arrangement | -| `2` | `RICK ` | **Never Gonna Give You Up (Rickroll)** | Double chorus arrangement with syncopated percussion | -| `3` | `ELVIS` | **Can't Help Falling In Love** | Arpeggiated ballad / wedding mix | -| `4` | `GIMME` | **Gimme! Gimme! Gimme!** | ABBA midnight rave synth lead | -| `5` | `ELISE` | **FΓΌr Elise** | Beethoven trance remix | -| `6` | `MOZRT` | **Symphony No. 40** | Mozart intense strings arrangement | -| `7` | `MARIO` | **Super Mario Bros. Theme** | RLE-compressed MIDI conversion with percussion routing | -| `8` | `PIRT1` | **Pirates of the Caribbean 1** | *He's a Pirate* β€” RLE MIDI conversion | -| `9` | `PIRT2` | **Pirates of the Caribbean 2** | *The Black Pearl* β€” RLE MIDI conversion | -| `10` | `ALLA ` | **Rondo Alla Turca** | Mozart Turkish March (`scripts/midillis/Alla_Turca/Alla_Turca.mid`) | -| `11` | `TETRS` | **Tetris (Korobeiniki)** | Classic Type-A theme | -| `12` | `SONIC` | **Sonic the Hedgehog** | *Green Hill Zone* | -| `13` | `CAST ` | **Castlevania** | *Vampire Killer* | -| `14` | `SEVEN` | **Seven Nation Army** | Transposed **+2 octaves** to overcome passive piezo bass roll-off | -| `15` | `MEGA ` | **Mega Man 2** | *Dr. Wily Stage 1* | - ---- - -## 🧱 4. Hardware Pinout & Peripherals - -**Target Microcontroller:** ESP32 Development Board (Dual-Core Tensilica Xtensa LX6 @ 160 MHz) - -### πŸŽ›οΈ Inputs (Hardware PCNT & Active-Low Buttons) -| Component | ESP32 GPIO | Peripheral / Mode | Function | -| :--- | :---: | :--- | :--- | -| **Rotary Encoder CLK** | `GPIO 32` | Hardware `PCNT` Edge/Level | Quadrature rotation tracking (1000 ns hardware glitch filter) | -| **Rotary Encoder DT** | `GPIO 33` | Hardware `PCNT` Edge/Level | Quadrature rotation direction | -| **Rotary Encoder SW** | `GPIO 16` | Input (`GPIO_PULLUP_ONLY`) | Toggles rotary knob mode between **Speed (`SPD`)** and **Volume (`VOL`)** | -| **Pause Button** | `GPIO 13` | Input (`GPIO_PULLUP_ONLY`) | Play / Pause toggle (immediately silences outputs when paused) | -| **Next Track Button** | `GPIO 14` | Input (`GPIO_PULLUP_ONLY`) | Cycles through the 16-track playlist (`0` β†’ `15`) | - -### πŸ”Š Audio & LED Outputs -| Component | ESP32 GPIO | Peripheral / Mode | Function | -| :--- | :---: | :--- | :--- | -| **Passive Buzzer** | `GPIO 25` | `LEDC_CHANNEL_0` (13-bit PWM) | Plays main melody pitch (`step.frequency`) & duty-cycle volume | -| **Active Buzzer 1** | `GPIO 26` | Digital Output | **Kick Drum** acoustic transient (`45 ms` scaled pulse) | -| **Active Buzzer 2** | `GPIO 27` | Digital Output | **Hi-Hat** acoustic transient (`45 ms` scaled pulse) | -| **LED 1 (Kick)** | `GPIO 21` | Digital Output | Flashes in sync with Kick Drum hits | -| **LED 2 (Hi-Hat)** | `GPIO 22` | Digital Output | Flashes in sync with Hi-Hat hits | - -### πŸ“Ί 1.3" 7-Pin SPI OLED Display (SH1106 / SSD1306, 128Γ—64) -| OLED Pin | ESP32 GPIO | SPI2 Function | -| :--- | :---: | :--- | -| **CS** | `GPIO 5` | SPI Chip Select (`10 MHz` clock) | -| **DC** | `GPIO 2` | Data / Command control line | -| **RES** | `GPIO 4` | Hardware Reset line | -| **SDA (MOSI)** | `GPIO 23` | SPI2 Master Out Slave In | -| **SCL (SCK)** | `GPIO 18` | SPI2 Serial Clock | - ---- - -## πŸ› οΈ 5. Building & Flashing (ESP-IDF v5.2.2) - -### Prerequisites -- **ESP-IDF v5.2.2** installed and exported (`source ~/esp/esp-idf/export.sh`) -- **Python 3** with `mido` (`pip install mido`) if converting custom `.mid` files - -### Build & Flash Commands ```bash -# 1. Source the ESP-IDF environment source ~/esp/esp-idf/export.sh - -# 2. Build the firmware image +idf.py set-target esp32 idf.py build - -# 3. Flash to the ESP32 and open the serial monitor idf.py -p /dev/ttyUSB0 flash monitor ``` -### Converting Custom MIDI Files (`scripts/midi_to_c.py`) +To convert a MIDI file into a C array: + ```bash -python3 scripts/midi_to_c.py scripts/midillis/Alla_Turca/Alla_Turca.mid > my_track.h +python3 scripts/midi_to_c.py scripts/midillis/Alla_Turca/Alla_Turca.mid > track_output.h ``` - ---- - -## πŸ“‚ Repository Structure - -```text -β”œβ”€β”€ CMakeLists.txt # Top-level ESP-IDF project build configuration -β”œβ”€β”€ sdkconfig # ESP32 target configuration (Dual-Core @ 160 MHz) -β”œβ”€β”€ main/ -β”‚ β”œβ”€β”€ CMakeLists.txt # Component registration -β”‚ └── mmcontroller.c # Complete 16-track synth, OLED driver, PCNT & FreeRTOS tasks -β”œβ”€β”€ scripts/ -β”‚ β”œβ”€β”€ midi_to_c.py # Custom Python RLE MIDI-to-C struct converter -β”‚ └── midillis/ # Sample MIDI source tracks (Alla Turca, etc.) -└── images/ - β”œβ”€β”€ MIDI_FIRST.jpg # Full breadboard hardware build photo - β”œβ”€β”€ MIDI_SECOND.jpg # Active OLED visualizer & control interface photo - └── MIDI_THIRD.jpg # Buzzer & rotary encoder wiring close-up -``` - ---- - -## πŸ—ΊοΈ Roadmap & Future Iterations -- **Perfboard / Custom PCB Migration:** Consolidating the dual-breadboard layout onto a soldered perfboard or custom KiCad PCB. -- **Optional WS2812B Addressable RGB Strip Expansion:** Re-introducing RMT-driven RGB lighting effects synchronized to `kick_drum` and `hi_hat` triggers.