# 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 Node - **`codegen/gen.mjs`** — generates `c-backend/generated/protocol.{h,c}` and `node-server/src/generated/protocol.mjs` - **`c-backend/`** — C11, drift-free tick thread using `clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME)`, links libasound + libpthread - **`node-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_x10` uint16, 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 ```sh # 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: ```sh ./c-backend/midi-sequencer [socket_path] [alsa_client_name] # defaults: /tmp/midi-sequencer.sock midi-sequencer ``` ## Usage 1. Start the C backend 2. Start the Node server (`make start` in `node-server/`) 3. Open `http://localhost:3000` 4. Create melodic or drum patterns with **+ Melodic** / **🥁 Drums** 5. Click the **●** dot next to each pattern to add it to the playback set 6. 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.