Skip to the content
PCBKitNew board
2 layers72 × 46 mmMidi receiver, the printed circuit board in 3D

Midi receiver

TTeuber/AirMidi1

Wireless MIDI interface firmware for ESP32-C6/S3: TRS MIDI to BLE MIDI, Network MIDI 2.0, RTP-MIDI, USB MIDI and ESP-NOW. MIDI-CI builds the web UI's controls from the synth; signed OTA updates with rollback. C11 core with host tests, hardware-in-the-loop tests.

Size
72 × 46 mm
Copper layers
2
Thickness
1.6 mm
Parts
22
Nets
35
Pads
87
Tracks, vias
119, 1
Pours
1
Routed
Yes
KiCad
KiCad 10.0 (file 20260206)
Created
10 Oct 2026
Updated
28 Oct 2026

Overview

Midi receiver is an open-source printed circuit board by TTeuber, from the TTeuber/AirMidi repository on GitHub, published under the MIT licence. It is a 2-layer board of 72 × 46 mm with 22 parts on 35 nets, routed with 119 tracks and 1 via.

Its parts include 7 resistors, 4 ICs, 3 connectors, 2 diodes and 2 switches. It belongs with the audio and music and radio and wireless boards and the ESP32 and RISC-V boards of this library.

README

README.md

AirMidi

ci License: MIT

A battery-powered wireless MIDI interface: it gives any TRS MIDI device (or DIN, with an adapter) Bluetooth and Wi-Fi, is set up from a browser or a phone, and updates itself over the air. This repo is its firmware, for an ESP32-C6 or ESP32-S3 on a custom PCB in a 3D-printed box, and the apps that play it: one for Android phones and one for the Mac, which sequences hardware synths through the box.

The box: TRS MIDI in and out on the front, power switch on the side, status LED and button on top

Highlights

  • Six MIDI transports through one router: TRS (31250 baud UART), BLE MIDI (peripheral and central), Network MIDI 2.0, RTP-MIDI, USB MIDI and an ESP-NOW link between two boxes. Everything is UMP (MIDI 2.0's packet format) inside, translated to and from MIDI 1.0 at the edges, with a per-output merge that never breaks a SysEx message.
  • Protocols built from their specs and tested against real implementations: macOS's own Network MIDI 2.0 and RTP-MIDI drivers connect to the box; the RTP-MIDI recovery journal (RFC 6295) repairs lost packets; MIDI-CI Property Exchange reads a synth's controls over a 31250 baud cable and the web UI draws them.
  • A portable C11 core (lib/, no ESP-IDF headers) with 17 suites of host tests under AddressSanitizer and UndefinedBehaviorSanitizer, including property tests (the journal through 30-50% packet loss).
  • Measured on the hardware, not assumed: an audio interface cabled to the box's jacks puts both ends of every test on the Mac's clock, so latency and loss are measured one way, per transport. Every number below comes from those tests.
  • Safe updates: signed images (RSA-3072), a self-test after each update and automatic rollback, core dumps from crashes; releases are built and signed in GitHub Actions.
  • Apps for phone and desktop from one Flutter project: an Android app that sets the box up over Bluetooth and plays it as an MPE instrument, and a macOS app with a MIDI-only DAW for hardware synths, whose tracks know each synth's controls from what it sent over MIDI-CI.

Hardware

The receiver PCB in the open enclosure

The receiver PCB: a Seeed XIAO ESP32-C6 (or ESP32-S3) on headers, MIDI out through a TRS Type A jack, MIDI in through a 6N138 optocoupler, a Li-ion cell with a TP4057 USB-C charger and a TPS63020 buck-boost to 3.3 V, a power switch, a button and an LED. An ESP32-S3-DevKitC-1 serves as the second box for USB MIDI and the ESP-NOW link. The board layer (board-and-settings.md) is the only code that names a GPIO.

hardware/ has the schematic, the KiCad project, Gerbers, BOM and the printable enclosure, with the board's revision history: the photo shows rev A, and rev B fixes what its bring-up found.

Features

  • TRS <-> BLE MIDI, up to two BLE centrals at once. One way through the box: BLE -> TRS 8.4 ms median, TRS -> BLE 9.5 ms, nothing lost in the hardware tests.
  • Wi-Fi modes: Play (Wi-Fi off), Network (joins stored networks, reachable as <name>.local), Setup (its own access point). A long press switches mode, an 8 s press opens Setup, a double press sends all notes off.
  • Web UI at http://<name>.local: live traffic per port, battery, signal, settings, Wi-Fi networks, firmware updates. Plain JS, no framework, 18 KB, built into the firmware.
  • Network MIDI 2.0 host (UDP, _midi2._udp): macOS connects to it natively. FEC, retransmit and in-order delivery over Wi-Fi: 0 of 600 lost at 5 ms spacing, 6.7 ms median Mac -> TRS, 4.2 ms back.
  • MIDI-CI over TRS: Discovery and Property Exchange with the synth on the jacks (a full read in under a second at 31250 baud, ~50 ms for a known device from the flash cache). The play page draws the synth's own faders, pickers and toggles from its control list (M2-117); synths without MIDI-CI get the same from an uploaded JSON profile. A MIDI-CI host behind the box reaches the synth through it: macOS finds it over Network MIDI 2.0.
  • RTP-MIDI (AppleMIDI, _apple-midi._udp) for clients without Network MIDI 2.0: lost packets repaired from the recovery journal, sessions taken back after the box restarts. With macOS's driver: 0 of 600 lost each way at 5 ms spacing, 7.3 / 5.5 ms median.
  • BLE central: the box connects by itself to a BLE MIDI keyboard or controller (any, by name or by address). 13.0 ms median keyboard -> TRS.
  • USB MIDI on the S3: a class-compliant USB MIDI device named after the box, with a serial console beside it.
  • ESP-NOW link: two boxes pass MIDI radio to radio, no router, and find each other's Wi-Fi channel by themselves. TRS -> box -> ESP-NOW -> box -> USB in 3.8 ms median, nothing lost.
  • Updates: upload in the browser, fetch the latest GitHub release, or send it over BLE when the box is not on Wi-Fi. Only signed images install; a new image must pass a self-test or the box rolls back.
  • Battery: gauge, low warning, and all notes off plus deep sleep when the cell is empty.

Web UI

The web UI's status page, served by the box

The play page, drawn from the control list the synth on the TRS jacks sent over MIDI-CI

Apps

One Flutter project (app/) builds two apps over shared code: the box pages, the BLE and Wi-Fi links, and the synth page drawn from the synth's MIDI-CI control list.

Phone (Android; plan and decisions):

  • Finds the box over Bluetooth or Wi-Fi and sets it up: mode, Wi-Fi networks from the box's own scan, trusted phones. Setup goes over a BLE GATT service that carries the same JSON API as the web UI's HTTP.
  • Plays the synth on the box's TRS jacks: pedal-steel strips (each finger bends its own note, one MPE channel per strip), keys, or drum pads; over BLE MIDI, the box's WebSocket, or Android's MIDI service straight to a computer.
  • The synth page: envelopes, a cutoff and resonance pad, wave pickers and faders, built from the controls daisy_synth (devices/daisy_synth) describes over MIDI-CI.

The strips: six strings tuned like a guitar, one finger sliding between g and g# The synth page, drawn from the control list the synth sent over MIDI-CI Drum pads on channel 10

Desktop (macOS now, Windows and Linux later; plan, DAW design):

  • The computer's MIDI ports through libremidi over FFI (app/packages/midi_engine), a router between them, and the box's play view: the synth's controls over the keys.
  • A MIDI-only DAW for hardware: MIDI clips in a piano roll with controller lanes and ramps, MPE takes with each note's own bend, pressure and timbre, and step-sequencer patterns with velocity, gate, probability, nudge, repeats and parameter locks. Records from any input, MPE included.
  • Each track keeps its synth's MIDI-CI control list, so lanes and locks have the synth's own names and units. It also keeps the synth's program, bank and settings, which can be read back through the box. All of it is sent before play, so the song sounds the same on the next day.
  • Click and count-in, latency offsets per port, projects saved as JSON every few seconds, Standard MIDI File import and export.

The synth's controls over the keys, drawn from what the synth on the box's jacks sent over MIDI-CI

The arrangement, with an MPE take open in the piano roll: one note's own pressure in the lane below

A step-sequencer pattern whose lowest row locks the synth's cutoff to a different value on every step

How it is tested

  • Host tests (test/): the portable core under ASan + UBSan, with randomized round trips and property tests.
  • App tests (app/test, flutter test): the strips, MPE and BLE MIDI packing, the router, and the DAW (editing, timing, files, recording, the player against a fake MIDI engine).
  • CI (.github/workflows/ci.yml): host tests, the clang-format check, and the firmware for all three boards on every push. tools/check.sh runs the same steps locally. Tagging vX.Y.Z builds, signs and publishes a release.
  • Hardware in the loop (tools/hil/): Python and Swift tools on the Mac drive the box and an Arturia AudioFuse wired to its jacks. Their first runs found real bugs; each one is written up in docs/design.
TestChecks
bridge_test.pyTRS <-> BLE both ways: notes, CCs on all 16 channels, clock, long SysEx, bursts, two sources merged (15)
ota_test.py, release_test.py, ble_ota_test.pyunsigned, corrupted and wrongly signed images refused; good ones installed over Wi-Fi, from a release and over BLE; rollback after a failed self-test or a crash (13, 8, 9)
web_test.py, ws_test.pythe web API, and MIDI between the page and the jacks over WebSocket (14)
ci_test.py, mac_ci_test.pyMIDI-CI against a stand-in synth on the Mac, and macOS's own MIDI-CI through the box (21, 12)
resume_test.py, rtp_test.pyNetwork MIDI 2.0 and RTP-MIDI against macOS's drivers: loss, latency, box restarts (14, 13)
central_test.pythe box as BLE central against a BLE MIDI stand-in on the Mac (14)
usb_test.py, espnow_test.pyUSB MIDI against BLE, and two boxes linked over ESP-NOW (9)
latency.py, sleep_test.pyone-way latency per path, MIDI through each power mode

Design notes

One note per module, with the reasoning, the alternatives considered and what the hardware tests found:

  • core.md: UMP, MIDI 1.0 parsing and encoding, MIDI 1.0 <-> 2.0 translation
  • transports.md: the bus and merge, TRS, BLE MIDI, BLE central, USB MIDI, ESP-NOW, power
  • network-midi.md: Network MIDI 2.0
  • rtp-midi.md: RTP-MIDI, the recovery journal, and what macOS needed
  • midi-ci.md: MIDI-CI Discovery and Property Exchange, the generated play page
  • ota.md: flash layout, signed updates, self-test and rollback, releases
  • board-and-settings.md: the board layer and the settings store
  • config-over-ble.md: one JSON API for HTTP and BLE, trusted phones, Wi-Fi setup from a phone
  • testing.md: the test rig and CI
  • measurements.md: every number in this README, with its method
  • plan.md: the development plan and a decision log of every choice and measured result
  • app.md: the Android app (app/): plan, phases and its decision log
  • desktop.md: the desktop app (plan): MIDI engine, router, who keeps time; daw.md: the DAW
  • system: how the box, the app and future instruments fit together: tiers, links, the instrument checklist

Layout

text
lib/        portable C11 core, no ESP-IDF headers         docs/design/  one note per module
test/       host unit tests for lib/ (Unity)              tools/hil/    hardware-in-the-loop tools for the Mac
firmware/   ESP-IDF project (C6 and S3)                   spikes/       early experiments, results in the plan
web/        the web UI (HTML/CSS/JS, gzipped into the firmware)
devices/    MIDI-CI device side: the Mac stand-in (CoreMIDI) and daisy_synth, the demo synth
hardware/   the receiver PCB and enclosure: schematic, KiCad project, Gerbers, BOM, STL
app/        Flutter: the Android app, the macOS app with the DAW, and the code they share

Build

sh
tools/check.sh                    # what CI runs: host tests, clang-format check, firmware for every board
firmware/build.sh c6 flash        # build and flash the C6 board (s3 for the XIAO S3, s3kit for an S3 dev kit)
cd app && flutter test            # the apps' tests
flutter build apk --release       # the Android app (flutter build macos --release for the Mac)

Needs ESP-IDF v5.5.5 or later in the v5.5 series (IDF_PATH, default ~/Developer/esp-idf), CMake, and uv (for the pinned clang-format and the Python test tools). Each release has the update image per board and a zip for a first install over USB (esptool.py write_flash @flash_args).

License

The firmware, software and documentation are MIT (LICENSE); the hardware in hardware/ is CERN-OHL-P-2.0 (hardware/LICENSE), the permissive open hardware licence. Two bundled third-party parts keep their own licenses: the Unity test framework in test/unity/ (MIT, test/unity/LICENSE.txt) and the Geist font in web/ (SIL Open Font License 1.1, web/OFL-Geist.txt).

Parts

22
QtyValueFootprintReferences
4M3MountingHole:MountingHole_3.2mm_M3H1, H2, H3, H4
2100kResistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_HorizontalR1, R2
21kResistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_HorizontalR5, R7
110RResistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_HorizontalR3
11N4148Diode_THT:D_DO-35_SOD27_P7.62mm_HorizontalD1
1220RResistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_HorizontalR6
133RResistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_HorizontalR4
16N138Package_DIP:DIP-8_W7.62mm_SocketU4
1BAT JST-PHConnector_JST:JST_PH_S2B-PH-K_1x02_P2.00mm_HorizontalJ1
1MIDI INlcsc:AUDIO-TH_PJ307CJ3
1MIDI OUTlcsc:AUDIO-TH_PJ307CJ2
1POWERlcsc:SW-TH_3P-P4.80_HX-SS12D10G6-WJSW1
1STATUSLED_THT:LED_D3.0mmLED1
1TP4057 chargermeasured:TP4057_ModuleU2
1TPS63020 3V3measured:TPS63020_ModuleU3
1USERButton_Switch_THT:SW_PUSH_6mm_H13mmSW2
1XIAO ESP32S3measured:XIAO_ESP32S3U1

Questions

How big is the Midi receiver PCB?

It measures 72 × 46 mm and has 2 copper layers, 1.6 mm thick.

What parts does the Midi receiver use?

22 parts. The full list of its parts is on this page, with links to each part's symbol and footprint where PCBKit's library has them.

Where can I download the Midi receiver KiCad files?

From the TTeuber/AirMidi repository on GitHub, at the commit this page was made from: the board is hardware/pcb/kicad/midi_receiver.kicad_pcb and its schematic hardware/pcb/kicad/midi_receiver.kicad_sch. PCBKit opens the board in the browser, with nothing to install.

Can I use the Midi receiver design in my own project?

Its repository publishes it under the MIT licence: you may reuse it on that licence's terms (keep the author's notice, and for a copyleft licence share your changes alike).

Similar boards

  • 2 layers158 × 11 mmDaylight Matchstick, the printed circuit board in 3D

    Daylight Matchstick

    VoronDesign/Voron-Hardware

    Cool Hardware designed by the Voron Design group

    22 parts1.1kGPL-3.0
  • 2 layers57 × 65 mmRgb to hdmi, the printed circuit board in 3D

    Rgb to hdmi

    hoglet67/RGBtoHDMI

    Bare-metal Raspberry Pi project that provides pixel-perfect sampling of Retro Computer RGB/YUV video and conversion to HDMI

    22 parts1kGPL-3.0
  • 2 layers81 × 57.2 mmRP2040-Macropad, the printed circuit board in 3D

    RP2040-Macropad

    hackclub/OnBoard

    💡 Join 1,000 teenagers and make your first PCB with a $100 grant!

    22 parts915
  • 2 layers54.6 × 70.9 mmBeagleLogic Cape, the printed circuit board in 3D

    BeagleLogic Cape

    abhishek-kakkar/BeagleLogic

    A Software Suite that implements a logic analyzer with the PRU on the BeagleBone / BeagleBone Black.

    22 parts479GPL-3.0
  • 2 layers66.4 × 24.5 mmConnecting piece, the printed circuit board in 3D

    Connecting piece

    Chrismettal/flipper-zero-backpacks

    Backpack-style addon boards for the Flipper Zero!

    22 parts456
  • 2 layers51 × 70 mmDMG A04-01, the printed circuit board in 3D

    DMG A04-01

    HDR/NintendoPCBs

    Replacement pcb's for Nintendo games.

    22 parts321
  • 2 layers27.8 × 16 mmSpi 1v8 level shifter, the printed circuit board in 3D

    Spi 1v8 level shifter

    CRImier/MyKiCad

    My KiCad boards. Can't be bothered uploading individually anymore, so here you go.

    22 parts253GPL-3.0