Initial scaffold: ALSA MIDI C backend + Node ESM sequencer web app

- 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>
This commit is contained in:
2026-04-25 00:54:38 +00:00
commit 9aba8057a8
23 changed files with 3037 additions and 0 deletions

147
protocol.yaml Normal file
View File

@@ -0,0 +1,147 @@
version: 1
description: Binary protocol between Node sequencer and C ALSA MIDI backend
# Frame format: [record_type: uint8][payload_length: uint16le][payload: bytes...]
# Header size: 3 bytes
# High bit set (0x80+) means C → Node direction
frame:
- name: record_type
type: uint8
- name: payload_length
type: uint16
# Type mapping used by codegen
types:
uint8: { c_type: uint8_t, c_put: put_u8, c_get: get_u8, node_write: writeUInt8, node_read: readUInt8, size: 1 }
uint16: { c_type: uint16_t, c_put: put_u16, c_get: get_u16, node_write: writeUInt16LE, node_read: readUInt16LE, size: 2 }
records:
HELLO:
id: 0x01
direction: node_to_c
description: Protocol version handshake
fields:
- name: version
type: uint8
DEFINE_PATTERN:
id: 0x02
direction: node_to_c
description: Define or redefine a pattern (clears existing data)
fields:
- name: pattern_id
type: uint16
- name: steps
type: uint8
note: Total step count e.g. 16 for a one-bar pattern at 16th-note resolution
- name: channel
type: uint8
note: MIDI channel 0-15
CLEAR_PATTERN:
id: 0x03
direction: node_to_c
description: Remove all notes and sub-pattern references from a pattern
fields:
- name: pattern_id
type: uint16
ADD_NOTE:
id: 0x04
direction: node_to_c
description: Add a note event to a pattern at a specific step
fields:
- name: pattern_id
type: uint16
- name: step
type: uint8
note: 0-based step index within the pattern
- name: note
type: uint8
note: MIDI note number 0-127 (middle C = 60)
- name: velocity
type: uint8
note: 0-127
- name: duration_steps
type: uint8
note: Duration in steps (1 = one step)
ADD_SUB_PATTERN:
id: 0x05
direction: node_to_c
description: Schedule a sub-pattern to start at a given step within a parent pattern
fields:
- name: pattern_id
type: uint16
note: Parent pattern ID
- name: step
type: uint8
note: Step within parent at which the sub-pattern begins playing
- name: sub_pattern_id
type: uint16
PLAY:
id: 0x06
direction: node_to_c
description: Start playing a pattern from step 0
fields:
- name: pattern_id
type: uint16
STOP:
id: 0x07
direction: node_to_c
description: Stop all playback and send MIDI all-notes-off
fields: []
SET_TEMPO:
id: 0x08
direction: node_to_c
description: Set global tempo (applies immediately, even mid-sequence)
fields:
- name: bpm_x10
type: uint16
note: "BPM × 10 for 0.1 BPM resolution — e.g. 1200 = 120.0 BPM"
ACK:
id: 0x81
direction: c_to_node
description: Acknowledge a received command
fields:
- name: acked_type
type: uint8
note: Record type being acknowledged
ERROR:
id: 0x82
direction: c_to_node
description: Report an error condition
fields:
- name: code
type: uint8
- name: context_type
type: uint8
note: Record type that triggered this error
BEAT_TICK:
id: 0x83
direction: c_to_node
description: Timing notification sent on every sequencer step
fields:
- name: pattern_id
type: uint16
- name: step
type: uint8
note: Current step within the pattern (0-based)
- name: beat
type: uint8
note: Current beat number within the current cycle (wraps at 255)
PATTERN_END:
id: 0x84
direction: c_to_node
description: Notification that a pattern has completed one full cycle
fields:
- name: pattern_id
type: uint16