{"id":25411344,"url":"https://github.com/sauloverissimo/esp32_host_midi","last_synced_at":"2026-05-03T18:06:06.102Z","repository":{"id":276319697,"uuid":"928928457","full_name":"sauloverissimo/ESP32_Host_MIDI","owner":"sauloverissimo","description":"This project provides a complete solution for receiving, interpreting, and displaying MIDI messages via USB and BLE on the ESP32 (especially ESP32-S3) with the T-Display S3.","archived":false,"fork":false,"pushed_at":"2025-02-14T19:11:08.000Z","size":290,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-14T20:20:46.529Z","etag":null,"topics":["ble","esp32","esp32-arduino","host","lilygo-tdisplay-s3","lovyangfx","midi","otg","pcm5102a","st7789","t-display","t-display-s3","usb"],"latest_commit_sha":null,"homepage":"https://github.com/sauloverissimo/ESP32_Host_MIDI","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sauloverissimo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-02-07T13:52:17.000Z","updated_at":"2025-02-12T02:00:05.000Z","dependencies_parsed_at":"2025-02-07T14:51:07.145Z","dependency_job_id":null,"html_url":"https://github.com/sauloverissimo/ESP32_Host_MIDI","commit_stats":null,"previous_names":["sauloverissimo/esp32_host_midi"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sauloverissimo%2FESP32_Host_MIDI","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sauloverissimo%2FESP32_Host_MIDI/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sauloverissimo%2FESP32_Host_MIDI/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sauloverissimo%2FESP32_Host_MIDI/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sauloverissimo","download_url":"https://codeload.github.com/sauloverissimo/ESP32_Host_MIDI/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239114268,"owners_count":19583986,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ble","esp32","esp32-arduino","host","lilygo-tdisplay-s3","lovyangfx","midi","otg","pcm5102a","st7789","t-display","t-display-s3","usb"],"created_at":"2025-02-16T10:17:12.163Z","updated_at":"2026-02-23T04:28:19.343Z","avatar_url":"https://github.com/sauloverissimo.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ESP32_Host_MIDI\n\n![image](https://github.com/user-attachments/assets/bba1c679-6c76-45b7-aa29-a3201a69b36a)\n\n![Arduino](https://img.shields.io/badge/Arduino-IDE%20%7C%20CLI-00979D?logo=arduino\u0026logoColor=white)\n![PlatformIO](https://img.shields.io/badge/PlatformIO-Compatible-FF7F00?logo=platformio\u0026logoColor=white)\n![ESP-IDF](https://img.shields.io/badge/ESP--IDF-Arduino%20Component-E7352C?logo=espressif\u0026logoColor=white)\n![License](https://img.shields.io/github/license/sauloverissimo/ESP32_Host_MIDI)\n![Version](https://img.shields.io/github/v/release/sauloverissimo/ESP32_Host_MIDI)\n\n**Receive and send MIDI on ESP32 — via USB-OTG, Bluetooth Low Energy, and ESP-NOW.**\n\nESP32_Host_MIDI turns your ESP32 into a MIDI hub. Plug a USB MIDI keyboard, connect a phone app via Bluetooth, bridge two ESP32s wirelessly with ESP-NOW, or do all at the same time. The library handles the low-level transport and gives you a clean, high-level API to read notes, detect chords, and send MIDI messages back.\n\n```cpp\n#include \u003cESP32_Host_MIDI.h\u003e\n\nvoid setup() {\n    midiHandler.begin();\n}\n\nvoid loop() {\n    midiHandler.task();\n\n    // Read what's playing\n    auto notes = midiHandler.getActiveNotesVector();  // [\"C4\", \"E4\", \"G4\"]\n\n    // Send MIDI via any connected transport\n    midiHandler.sendNoteOn(1, 60, 100);   // Channel 1, Middle C, velocity 100\n}\n```\n\n---\n\n## What it does\n\n- **USB Host MIDI** — ESP32 acts as USB host. Plug any class-compliant MIDI controller (keyboard, pad, etc.) directly into the ESP32's USB-OTG port.\n- **BLE MIDI (bidirectional)** — ESP32 acts as a BLE MIDI peripheral. A phone app or DAW connects to it. Both can send and receive MIDI.\n- **ESP-NOW MIDI** — Ultra-low latency wireless MIDI (~1-5ms) between ESP32 devices. No WiFi network needed. Broadcast (no pairing) or unicast (peer-to-peer).\n- **Transport abstraction** — `MIDITransport` interface lets you add custom transports (ESP-NOW, RTP-MIDI, serial, etc.) via `addTransport()`. USB and BLE are built-in; others plug in at runtime.\n- **MIDI processing** — Parses NoteOn, NoteOff, ControlChange, ProgramChange, PitchBend, ChannelPressure. Converts note numbers to names (\"C4\"), groups simultaneous notes into chords, tracks active notes.\n- **Thread-safe** — All transports use ring buffers with spinlock protection. USB runs on a dedicated FreeRTOS task (Core 0). All MIDI processing happens on the main loop (Core 1), so your code never sees race conditions.\n\n---\n\n## Platform compatibility\n\n### Build systems\n\n| Platform | Compatible? | Notes |\n|----------|:-----------:|-------|\n| **Arduino IDE** | Yes | Native support. Just install and use. |\n| **PlatformIO (Arduino framework)** | Yes | Add to `lib_deps` in `platformio.ini` (see below). |\n| **PlatformIO (ESP-IDF + Arduino)** | Yes | Use `framework = arduino, espidf` in `platformio.ini`. |\n| **ESP-IDF pure (no Arduino)** | No | Requires `Arduino.h`, `String`, `millis()`, and the ESP32 BLE Arduino library. |\n\n**PlatformIO example:**\n\n```ini\n[env:esp32s3]\nplatform = espressif32\nboard = esp32-s3-devkitc-1\nframework = arduino\nlib_deps =\n    https://github.com/sauloverissimo/ESP32_Host_MIDI.git\n```\n\n\u003e **Other languages** (MicroPython, Rust, TinyGo, Lua, etc.): not compatible. The library is C++ and depends on the Arduino-ESP32 core and ESP-IDF APIs.\n\n### ESP32 chip support\n\nThe library uses **compile-time feature detection** to automatically enable or disable USB and BLE based on the target chip. Select the correct board — the library adapts. ESP-NOW is available on all chips.\n\n| Chip | USB Host | BLE | ESP-NOW | Dual-Core | PSRAM | Status |\n|------|:--------:|:---:|:-------:|:---------:|:-----:|--------|\n| **ESP32-S3** | Yes | Yes | Yes | Yes | Yes | **Recommended** |\n| **ESP32-S2** | Yes | No | Yes | No | depends | Supported |\n| **ESP32** (classic) | No | Yes | Yes | Yes | depends | Supported |\n| **ESP32-C3** | No | Yes | Yes | No | No | Supported |\n| **ESP32-C6** | No | Yes | Yes | No | No | Supported |\n| **ESP32-H2** | No | Yes | No | No | No | Supported |\n| **ESP32-P4** | Yes | No | TBD | Yes | Yes | Supported |\n\n\u003e **Single-core chips** (S2, C3, C6, H2): USB or BLE shares CPU time with `loop()`. Keep `loop()` lightweight and call `midiHandler.task()` frequently.\n\n### Feature detection macros\n\n```cpp\n#if ESP32_HOST_MIDI_HAS_USB\n  // USB Host is available (ESP32-S2, S3, P4)\n#endif\n#if ESP32_HOST_MIDI_HAS_BLE\n  // BLE MIDI is available (ESP32, S3, C3, C6, H2)\n#endif\n#if ESP32_HOST_MIDI_HAS_PSRAM\n  // PSRAM is available for history buffer\n#endif\n```\n\n### Other boards\n\n| Board | Compatible? | Reason |\n|-------|:-----------:|--------|\n| **Arduino Nano ESP32** (ESP32-S3) | Yes | Works natively |\n| **Seeed XIAO ESP32-S3** | Yes | Works natively |\n| **Seeed XIAO ESP32-C3** | Yes | BLE only |\n| **Raspberry Pi Pico** | No | Different USB Host API |\n| **Teensy 4.x** | No | Different USB Host API |\n| **Arduino Uno / Mega** (AVR) | No | No USB Host, no BLE, no STL |\n\n\u003e See [ROADMAP.md](ROADMAP.md) for plans to support additional platforms.\n\n---\n\n## Getting started\n\n### Installation\n\n1. **Arduino IDE**: Download as ZIP and install via *Sketch \u003e Include Library \u003e Add .ZIP Library*, or clone directly into your Arduino `libraries/` folder.\n2. **PlatformIO**: Add the GitHub URL to `lib_deps` in your `platformio.ini` (see above).\n\n### Dependencies\n\n- **ESP32 Arduino Core** 2.0.0+ (includes USB Host and BLE support)\n- **[LovyanGFX](https://github.com/lovyan03/LovyanGFX)** 0.4.x+ (only for display examples)\n- **[Gingoduino](https://github.com/sauloverissimo/gingoduino)** v0.2.2+ (optional, for music theory analysis)\n\n### Quick start\n\n```cpp\n#include \u003cESP32_Host_MIDI.h\u003e\n\nvoid setup() {\n    Serial.begin(115200);\n\n    MIDIHandlerConfig config;\n    config.bleName = \"My MIDI Device\";    // BLE advertising name\n    config.chordTimeWindow = 50;          // 50ms chord grouping window\n\n    midiHandler.begin(config);\n}\n\nvoid loop() {\n    midiHandler.task();  // MUST be called every loop iteration\n\n    // Check what's playing\n    if (midiHandler.getActiveNotesCount() \u003e 0) {\n        Serial.println(midiHandler.getActiveNotes().c_str());  // \"{C4, E4, G4}\"\n    }\n}\n```\n\n### Board settings (Arduino IDE)\n\n- Board: **ESP32S3 Dev Module** (or your specific board)\n- USB Mode: **USB-OTG (TinyUSB)** (required for USB Host)\n\n---\n\n## Receiving MIDI\n\nBoth USB and BLE reception work automatically after calling `midiHandler.begin()`. MIDI events are parsed, converted, and made available through the API.\n\n### Reading the event queue\n\n```cpp\nconst auto\u0026 queue = midiHandler.getQueue();\nfor (const auto\u0026 event : queue) {\n    Serial.printf(\"[%s] ch=%d note=%s vel=%d\\n\",\n        event.status.c_str(), event.channel,\n        event.noteOctave.c_str(), event.velocity);\n}\n```\n\n### Active notes\n\n```cpp\n// As a formatted string\nString notes = midiHandler.getActiveNotes().c_str();  // \"{C4, E4, G4}\"\n\n// As a vector\nauto vec = midiHandler.getActiveNotesVector();  // [\"C4\", \"E4\", \"G4\"]\n\n// As a bool array (best for real-time rendering)\nbool active[128];\nmidiHandler.fillActiveNotes(active);\nif (active[60]) { /* middle C is pressed */ }\n```\n\n### Chord detection\n\n```cpp\nint chord = midiHandler.lastChord(midiHandler.getQueue());\nauto noteNames = midiHandler.getChord(chord, midiHandler.getQueue(), {\"noteName\"});\n// noteNames: [\"C\", \"E\", \"G\"]\n\n// Shorthand for the last chord:\nauto answer = midiHandler.getAnswer(\"noteName\");\n```\n\n### MIDIEventData fields\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `index` | `int` | Global event counter |\n| `msgIndex` | `int` | Links NoteOn/NoteOff pairs |\n| `timestamp` | `unsigned long` | Timestamp in ms (`millis()`) |\n| `delay` | `unsigned long` | Delta time since previous event |\n| `channel` | `int` | MIDI channel (1-16) |\n| `status` | `std::string` | `\"NoteOn\"`, `\"NoteOff\"`, `\"ControlChange\"`, `\"ProgramChange\"`, `\"PitchBend\"`, `\"ChannelPressure\"` |\n| `note` | `int` | MIDI note number (or CC number) |\n| `noteName` | `std::string` | Musical note name (\"C\", \"D#\") |\n| `noteOctave` | `std::string` | Note with octave (\"C4\", \"D#5\") |\n| `velocity` | `int` | Velocity (or CC value, program number, pressure) |\n| `chordIndex` | `int` | Groups simultaneously pressed notes |\n| `pitchBend` | `int` | 14-bit value (0-16383, center = 8192) |\n\n---\n\n## Sending MIDI\n\nSend methods work via any transport that supports sending (BLE, ESP-NOW, or custom transports). All methods return `true` if the message was sent, `false` if no transport is available.\n\n```cpp\n// Notes (channel: 1-16)\nmidiHandler.sendNoteOn(1, 60, 100);    // Channel 1, Middle C, velocity 100\nmidiHandler.sendNoteOff(1, 60, 0);     // Release\n\n// Control Change\nmidiHandler.sendControlChange(1, 1, 64);   // CC#1 (Mod Wheel), value 64\n\n// Program Change\nmidiHandler.sendProgramChange(1, 5);   // Program 5\n\n// Pitch Bend (-8192 to 8191, 0 = center)\nmidiHandler.sendPitchBend(1, 0);       // Center\nmidiHandler.sendPitchBend(1, 4096);    // Bend up\n\n// Raw MIDI bytes (status + data, no headers)\nuint8_t raw[] = {0x90, 60, 100};\nmidiHandler.sendRaw(raw, 3);\n\n// Check BLE connection\n#if ESP32_HOST_MIDI_HAS_BLE\nif (midiHandler.isBleConnected()) {\n    // A phone/DAW is connected via BLE\n}\n#endif\n```\n\n### USB-to-BLE bridge example\n\n```cpp\n#include \u003cESP32_Host_MIDI.h\u003e\n\nvoid setup() {\n    midiHandler.begin();\n    midiHandler.setRawMidiCallback(onRawMidi);\n}\n\n// Forward every incoming MIDI message to BLE\nvoid onRawMidi(const uint8_t* raw, size_t rawLen, const uint8_t* midi3) {\n    midiHandler.sendRaw(midi3, 3);\n}\n\nvoid loop() {\n    midiHandler.task();\n}\n```\n\n---\n\n## Transport abstraction\n\nThe library uses a `MIDITransport` interface that decouples MIDI processing from specific hardware. USB and BLE are built-in transports registered automatically. You can add custom transports via `addTransport()`.\n\n### Architecture\n\n```\nMIDIHandler ──[MIDITransport*]──\u003e USBConnection     (built-in, automatic)\n             ──[MIDITransport*]──\u003e BLEConnection     (built-in, automatic)\n             ──[MIDITransport*]──\u003e ESPNowConnection  (addTransport, manual)\n             ──[MIDITransport*]──\u003e YourTransport     (addTransport, manual)\n```\n\n### Creating a custom transport\n\n```cpp\n#include \"MIDITransport.h\"\n\nclass MyTransport : public MIDITransport {\npublic:\n    bool begin() { /* init hardware */ return true; }\n\n    void task() override {\n        // Read from hardware, then deliver to MIDIHandler:\n        if (hasData) dispatchMidiData(midiBytes, 3);\n    }\n\n    bool isConnected() const override { return initialized; }\n\n    // Optional: override sendMidiMessage() if your transport supports sending\n    bool sendMidiMessage(const uint8_t* data, size_t length) override {\n        // Send bytes over your transport\n        return true;\n    }\n};\n\nMyTransport custom;\nvoid setup() {\n    custom.begin();\n    midiHandler.addTransport(\u0026custom);\n    midiHandler.begin();\n}\n```\n\n---\n\n## ESP-NOW MIDI\n\nESP-NOW provides ultra-low latency wireless MIDI (~1-5ms) between ESP32 devices. No WiFi router needed — devices communicate directly. Ideal for wireless MIDI on stage.\n\n| Feature | Value |\n|---------|-------|\n| Latency | ~1-5ms (vs 10-20ms for BLE) |\n| Range | ~200m outdoor, ~50m indoor |\n| Pairing | Not required (broadcast mode) |\n| Max peers | 20 (unicast) / unlimited (broadcast) |\n| Bidirectional | Yes |\n\n### Basic usage\n\n```cpp\n#include \"ESP32_Host_MIDI.h\"\n#include \"ESPNowConnection.h\"\n\nESPNowConnection espNow;\n\nvoid setup() {\n    espNow.begin();                          // Init WiFi + ESP-NOW\n    midiHandler.addTransport(\u0026espNow);       // Register with MIDIHandler\n    midiHandler.begin();\n}\n\nvoid loop() {\n    midiHandler.task();\n    // Incoming ESP-NOW MIDI is now parsed like USB/BLE:\n    // getActiveNotes(), getAnswer(), sendNoteOn(), etc.\n}\n```\n\n### Standalone (without MIDIHandler)\n\n```cpp\n#include \"ESPNowConnection.h\"\n\nESPNowConnection espNow;\n\nvoid onData(void* ctx, const uint8_t* data, size_t len) {\n    Serial.printf(\"MIDI: %02X %02X %02X\\n\", data[0], data[1], data[2]);\n}\n\nvoid setup() {\n    espNow.begin();\n    espNow.setMidiCallback(onData, nullptr);\n}\n\nvoid loop() {\n    espNow.task();\n    // Send a NoteOn\n    uint8_t msg[] = {0x90, 60, 100};\n    espNow.sendMidiMessage(msg, 3);\n}\n```\n\n### Peer management\n\n```cpp\n// By default, ESP-NOW broadcasts to all devices on the same channel.\n// To target specific devices:\nuint8_t peerMAC[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};\nespNow.addPeer(peerMAC);\n\n// Get this device's MAC (tell the other device to add it):\nuint8_t myMAC[6];\nespNow.getLocalMAC(myMAC);\n```\n\n---\n\n## Using raw MIDI data (without MIDIHandler)\n\nFor custom parsers, MIDI routing, or minimal footprint, use `USBConnection` or `BLEConnection` directly with callbacks:\n\n```cpp\n#include \"USBConnection.h\"\n#include \"BLEConnection.h\"\n\nUSBConnection usb;\nBLEConnection ble;\n\nvoid onUsbData(void* ctx, const uint8_t* data, size_t len) {\n    // USB-MIDI: 4 bytes per event [CIN, Status, Data1, Data2]\n    Serial.printf(\"USB: %02X %02X %02X %02X\\n\", data[0], data[1], data[2], data[3]);\n}\n\nvoid onBleData(void* ctx, const uint8_t* data, size_t len) {\n    // BLE: raw MIDI bytes (header already stripped)\n    Serial.printf(\"BLE: %02X %02X %02X\\n\", data[0], data[1], data[2]);\n}\n\nvoid setup() {\n    usb.setMidiCallback(onUsbData, nullptr);\n    usb.setConnectionCallbacks(onConnect, onDisconnect, nullptr);\n    usb.begin();\n\n    ble.setMidiCallback(onBleData, nullptr);\n    ble.begin(\"My Device\");\n}\n\nvoid loop() {\n    usb.task();\n    ble.task();\n}\n```\n\n---\n\n## Configuration\n\n```cpp\nMIDIHandlerConfig config;\nconfig.maxEvents = 30;            // Event queue size (SRAM)\nconfig.chordTimeWindow = 50;      // Chord grouping window (ms). 0 = legacy\nconfig.velocityThreshold = 10;    // Ignore ghost notes below this velocity\nconfig.historyCapacity = 1000;    // PSRAM history buffer. 0 = disabled\nconfig.bleName = \"My MIDI Device\"; // BLE advertising name\n\nmidiHandler.begin(config);\n```\n\n| Parameter | Default | Description |\n|-----------|---------|-------------|\n| `maxEvents` | 20 | Maximum events in the active queue (SRAM) |\n| `chordTimeWindow` | 0 | Time window (ms) for chord grouping. 0 = legacy mode |\n| `velocityThreshold` | 0 | Minimum velocity to accept NoteOn. 0 = accept all |\n| `historyCapacity` | 0 | PSRAM history buffer size. 0 = disabled |\n| `bleName` | `\"ESP32 MIDI BLE\"` | BLE advertising device name |\n\n---\n\n## Architecture\n\nAll transports share the same pattern: data arrives in a background task/callback, gets enqueued into a spinlock-protected ring buffer, and is processed on the main loop via `task()`.\n\n```\n┌──────────────────────────────┐    ┌──────────────────────────────┐\n│       Background Tasks        │    │     Core 1 (main loop)        │\n│                               │    │                               │\n│  USB: _usbTask (Core 0)      │    │   midiHandler.task()          │\n│  • USB host polling           │───\u003e│   • transport[0]-\u003etask()      │\n│  • Enqueue to ring buffer     │    │   • transport[1]-\u003etask()      │\n│                               │    │   • transport[N]-\u003etask()      │\n│  BLE: ESP-IDF BLE task        │    │   • handleMidiMessage()       │\n│  • onWrite callback           │───\u003e│   • User logic                │\n│  • Enqueue to ring buffer     │    │   • Display rendering         │\n│                               │    │                               │\n│  ESP-NOW: WiFi task           │    │                               │\n│  • _onReceive callback        │───\u003e│                               │\n│  • Enqueue to ring buffer     │    │                               │\n└──────────────────────────────┘    └──────────────────────────────┘\n          ▲                                     │\n          │      Ring Buffers (64 msgs each)    │\n          └──────── spinlock-safe ──────────────┘\n```\n\n### Connection lifecycle (BLE)\n\n- **Advertising** starts automatically in `begin()`.\n- When a central (phone/DAW) **connects**, the transport dispatches a connection event.\n- When it **disconnects**, active notes are cleared (prevents stuck notes) and advertising restarts automatically.\n\n---\n\n## Music theory with Gingoduino\n\nFor chord identification, harmonic field deduction, and progression analysis, use the [Gingoduino](https://github.com/sauloverissimo/gingoduino) library (v0.2.2+) with the optional `GingoAdapter.h`:\n\n```cpp\n#include \u003cESP32_Host_MIDI.h\u003e\n#include \u003cGingoAdapter.h\u003e\n\nvoid loop() {\n    midiHandler.task();\n\n    char chordName[16];\n    if (GingoAdapter::identifyLastChord(midiHandler, chordName, sizeof(chordName))) {\n        Serial.println(chordName);  // \"CM\", \"Am7\", \"Gdim\"\n    }\n}\n```\n\n\u003e See the **T-Display-S3-Gingoduino** example for a complete working sketch.\n\n---\n\n## Examples\n\n| Example | Description |\n|---------|-------------|\n| **Raw-USB-BLE** | USB and BLE raw access via callbacks without MIDIHandler. Serial output only. |\n| **ESP-NOW-MIDI** | Wireless MIDI between two ESP32 devices via ESP-NOW. Broadcast mode. |\n| **T-Display-S3** | Note names on ST7789 display using `getAnswer(\"noteName\")`. |\n| **T-Display-S3-Queue** | Full event queue and active notes on display. Button to clear. |\n| **T-Display-S3-Gingoduino** | Music theory: notes, intervals, chords, harmonic fields on display. |\n| **T-Display-S3-Piano** | 25-key piano visualizer + PCM5102A synth + Gingoduino analysis. |\n| **T-Display-S3-Piano-Debug** | On-display MIDI monitor for debugging without Serial (USB Host mode). |\n\n---\n\n## File structure\n\n```\nsrc/\n  ESP32_Host_MIDI.h       — Main header (includes everything)\n  MIDITransport.h         — Abstract transport interface\n  MIDIHandlerConfig.h     — Configuration struct\n  MIDIHandler.h/.cpp      — MIDI parsing, event queue, chord detection, transport orchestration\n  USBConnection.h/.cpp    — USB Host MIDI (ring buffer, Core 0 task)\n  BLEConnection.h/.cpp    — BLE MIDI (ring buffer, send/receive, GATT server)\n  ESPNowConnection.h/.cpp — ESP-NOW MIDI (ring buffer, broadcast/unicast)\n  GingoAdapter.h          — Optional bridge to Gingoduino\n\nexamples/\n  Raw-USB-BLE/              — Raw MIDI via callbacks (no MIDIHandler)\n  ESP-NOW-MIDI/             — Wireless MIDI between ESP32 devices\n  T-Display-S3/             — Basic display example\n  T-Display-S3-Queue/       — Event queue visualization\n  T-Display-S3-Gingoduino/  — Music theory analysis\n  T-Display-S3-Piano/       — Piano visualizer + synth\n  T-Display-S3-Piano-Debug/ — On-display MIDI debugger\n```\n\n---\n\n## Contributing\n\nContributions, bug reports, and suggestions are welcome! Open an issue or submit a pull request on [GitHub](https://github.com/sauloverissimo/ESP32_Host_MIDI).\n\n## License\n\nMIT License. See [LICENSE](LICENSE.txt).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsauloverissimo%2Fesp32_host_midi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsauloverissimo%2Fesp32_host_midi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsauloverissimo%2Fesp32_host_midi/lists"}