Protocol:
- Add PREVIEW_NOTE record (id 0x09, node→C): channel, note, velocity,
duration_ms — plays a note immediately without touching sequencer state
C backend:
- sequencer_preview_note(): fires ALSA note-on, spawns detached thread
for note-off after duration_ms
- RT_PREVIEW_NOTE handler in on_frame()
Node server:
- encode_preview_note imported from generated protocol
- POST /api/preview route forwards to backend
Frontend (app.mjs):
- Remove pending_notes / is_dirty / Save Notes button; every step-button
toggle now calls PUT /api/patterns/:id/notes immediately (auto-save)
- Single delegated click handler on #content — no per-render listener
accumulation
- Row-level ▶ play button per note row → POST /api/preview
- Click-to-rename on note/percussion labels: inline <input>, saves to
state.custom_labels (Map keyed by "patternId:note"), Escape to cancel
- pattern_updated SSE no longer guarded by is_dirty
CSS (index.html):
- .note-label: cursor pointer + hover highlight
- .note-label-input for inline rename field
- .row-play-btn and .row-play-spacer for per-row preview buttons
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- protocol.yaml: SSoT for the binary framing protocol (12 record types)
- codegen/gen.mjs: generates C header/source and Node ESM from protocol.yaml
- c-backend: ALSA sequencer with drift-free clock_nanosleep tick thread,
pattern store (hierarchical sub-patterns), Unix socket server
- node-server: Express 5 web app — REST API, SSE for real-time beat events,
step-sequencer frontend with pending-edit / explicit-save flow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>