Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
midi-sequencer
A browser-based MIDI step sequencer with a C ALSA backend for accurate timing and a Node.js/Express web UI.
Architecture
Browser (app.mjs)
↕ HTTP + SSE
Node server (server.mjs, port 3000)
↕ Unix socket (/tmp/midi-sequencer.sock)
C backend (midi-sequencer binary)
↕ ALSA sequencer API
MIDI output
protocol.yaml— single source of truth for the binary protocol; drives codegen for both C and Nodecodegen/gen.mjs— generatesc-backend/generated/protocol.{h,c}andnode-server/src/generated/protocol.mjsc-backend/— C11, drift-free tick thread usingclock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME), links libasound + libpthreadnode-server/— Express 5 web app, serves the UI and bridges the browser to the C backend
Features
- Multi-track playback — up to 16 patterns playing simultaneously, each looping at its own independent length
- Live track control — add/remove patterns from the playing set without stopping; mute and solo per track
- GM percussion editor — channel 10 patterns show GM drum names in drum-machine layout with family colour coding
- Auto-save — step grid edits are sent to the backend immediately on each toggle
- Preview notes — per-row ▶ button auditions a note without affecting sequencer state
- Click-to-rename — note and percussion labels are editable inline
- Sub-patterns — patterns can reference other patterns as sub-sequences at any step
- BPM — tempo settable to 0.1 BPM resolution (stored as
bpm_x10uint16, no floats in protocol)
Protocol
Binary frames: [record_type: u8][payload_length: u16le][payload…]
| ID | Name | Direction | Description |
|---|---|---|---|
| 0x01 | HELLO | node→C | Version handshake |
| 0x02 | DEFINE_PATTERN | node→C | Create/redefine a pattern |
| 0x03 | CLEAR_PATTERN | node→C | Remove all notes from a pattern |
| 0x04 | ADD_NOTE | node→C | Add a note event at a step |
| 0x05 | ADD_SUB_PATTERN | node→C | Schedule a sub-pattern within a pattern |
| 0x06 | PLAY | node→C | Single-pattern play (backward compat) |
| 0x07 | STOP | node→C | Stop all playback |
| 0x08 | SET_TEMPO | node→C | Set BPM (as bpm×10) |
| 0x09 | PREVIEW_NOTE | node→C | Play a single note immediately |
| 0x0A | ADD_TRACK | node→C | Add a pattern to the multi-track set |
| 0x0B | REMOVE_TRACK | node→C | Remove a pattern from the multi-track set |
| 0x0C | PLAY_TRACKS | node→C | Start the multi-track engine |
| 0x0D | SET_TRACK_MUTE | node→C | Mute/unmute a track |
| 0x81 | ACK | C→node | Acknowledge a command |
| 0x82 | ERROR | C→node | Report an error |
| 0x83 | BEAT_TICK | C→node | Step notification (one per track per tick) |
| 0x84 | PATTERN_END | C→node | Pattern completed one full cycle |
Build
Requires: GCC, make, libasound2-dev, Node.js ≥ 18
# Full build (codegen + npm install + C binary)
make
# C backend only
cd c-backend && make
# Run the Node server
cd node-server && make start
# → http://localhost:3000
The C backend runs separately and is started independently:
./c-backend/midi-sequencer [socket_path] [alsa_client_name]
# defaults: /tmp/midi-sequencer.sock midi-sequencer
Usage
- Start the C backend
- Start the Node server (
make startinnode-server/) - Open
http://localhost:3000 - Create melodic or drum patterns with + Melodic / 🥁 Drums
- Click the ● dot next to each pattern to add it to the playback set
- Press ▶ Play to start — all active patterns loop independently
Timing
One step = one 16th note. Step duration: 150,000,000,000 ns / bpm_x10
At 120.0 BPM: 125 ms/step. The tick thread uses an absolute deadline so tempo drift does not accumulate.
Description
Languages
JavaScript
51.2%
C
38.9%
HTML
8.5%
Makefile
1.4%