diff --git a/README.md b/README.md new file mode 100644 index 0000000..1b9e7f3 --- /dev/null +++ b/README.md @@ -0,0 +1,93 @@ +# 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.