Files
midi-sequencer/README.md
2026-04-25 04:31:11 +00:00

3.7 KiB
Raw Permalink Blame History

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

# 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

  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.