94 lines
3.7 KiB
Markdown
94 lines
3.7 KiB
Markdown
# 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.
|