
I built M5MusicTab5 because I wanted a single device to sketch beats, practice, and listen to my MIDI files away from a laptop. It's a complete firmware for the M5Stack Tab5 — ESP32-P4, 5" 1280×720 touch screen, stereo audio — written from scratch on ESP-IDF and LVGL 9 (UI built withEEZ Studio).
The center of version v3.0 is the step sequencer. It's the part I kept tuning until it felt like an instrument:
- Euclidean rhythms in one tap. Each track generates a style-aware pattern via the Euclidean algorithm — the same "distributed as evenly as possible" logic behind a lot of classic drum patterns — then you break it by hand until it's yours.
- Per-step velocity, trigger probability and CC locks. Long-press any cell to open its settings. Set a snare to fire 80% of the time and the groove starts breathing.
- Humanized timing. Per-track random timing plus swing, so straight 16ths don't sound like a metronome.
- Six pattern slots with beat-quantized switching. While playing, tap another slot: it blinks until the next bar line, then takes over — no stutters, no restarts. Song structure happens live.
- 23 GM percussion voices from the on-board SF2 synth, with custom soundfonts loadable from SD.
- Everything survives power-off. Patterns save as
.m5pfiles on SD; live performances record to standard.midthat drops straight into any DAW.


Around the sequencer sits the rest of the terminal: a tiny piano and an XY pad with pitch-bend, ear training and chord practice (with an interactive circle of fifths), a metronome with tap tempo, an MP3/MIDI player, a WAV recorder with dual-mic echo cancellation, an AI voice assistant, and a clock/calendar. All input paths — USB Host, BLE(TBD), UART — feed one global MIDI bus, so a keyboard plugged into the USB-A port plays the internal synth like any other voice.
On the engineering side, the things I care most about:
- The sequencer core (
engine_sequencer) is pure logic with a single dependency on the MIDI bus — no UI, no hardware calls. It runs host-side unit tests, with a Python cross-check that the Euclid distribution is actually even. - Timing comes from
esp_timerhooks, not the UI loop — the grid renders as one canvas with direct drawing (no tree of 128 cell widgets), while audio stays on a fixed-priority core. - The synth streams SF2 sample data out of PSRAM with a memory budget gate, so a ~16 MB soundfont loads without starving the UI.
- Flashing takes one browser tab: an esptool-js page with a three-button wizard (connect → flash → watch the boot log auto-open). No toolchain, no drivers.

The full source is on GitHub (MIT), with bilingual documentation in Chinese and English, and demo videos on Bilibili.
Fangbrbr
Michael Wessel
AndrewMcDan
Sven Gregori