Clean up README formatting
This commit is contained in:
parent
45cf4bc312
commit
62cc118424
1 changed files with 23 additions and 169 deletions
192
README.md
192
README.md
|
|
@ -1,184 +1,38 @@
|
|||
# 🎛️ MIDI Breadboard Instrument — Desk Rave & 16-Track Symphony Controller
|
||||
# ESP32 Multi-Track MIDI Breadboard Instrument
|
||||
|
||||
[](https://docs.espressif.com/projects/esp-idf/en/v5.2.2/esp32/)
|
||||
[](https://www.espressif.com/en/products/socs/esp32)
|
||||
[](main/mmcontroller.c)
|
||||
[](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 |
|
||||
| :---: | :---: | :---: |
|
||||
|  |  |  |
|
||||
| *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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue