Files
2026-05-27 23:29:53 +01:00

40 lines
4.1 KiB
Markdown

# @suaveplan/midi — universal MIDI primitives
- **Target path**: `packages/media/audio/midi/`
- **Tier**: universal (zero browser/Node coupling)
- **Date**: 2026-05-23
- **Status**: PROPOSED
## Why
The audio family ships codecs, plugins, analysers, an engine, and a React surface — but no MIDI primitives. Every downstream surface that wants to drive plugin parameters via control surfaces (`@suaveplan/react-control-surface`), accept input from hardware controllers (Maschine, Traktor, KORG nanoKONTROL), expose virtual instruments (SoundFont synth, drum machines), or replicate parameter state across a network session needs a shared MIDI vocabulary. Without this package every consumer hand-rolls byte parsers and types, which inevitably diverges (status nibble vs. channel masking, running status, SysEx framing edge cases, NRPN vs. RPN dispatch).
This change introduces a universal-tier, zero-runtime-dependency package owning the canonical MIDI types, byte parsers, message constructors, CC/NRPN/RPN/Pitch/SysEx dispatchers, GM/GS/XG patch tables, MIDI 1.0 + MIDI 2.0 UMP differentiation, and a structural `MidiPortLike` contract that browser/Node adapters implement. No Web MIDI calls live here — that lives in `@suaveplan/react-midi` (browser) and a future `@suaveplan/midi-node` (server).
## What changes
1. **NEW package** `@suaveplan/midi` at `packages/media/audio/midi/`.
2. **Message types** — `MidiMessage` discriminated union: `NoteOn`, `NoteOff`, `PolyPressure`, `ControlChange`, `ProgramChange`, `ChannelPressure`, `PitchBend`, `SystemExclusive`, `TimeCode`, `SongPosition`, `SongSelect`, `TuneRequest`, `Clock`, `Start`, `Continue`, `Stop`, `ActiveSensing`, `Reset`. Each carries the parsed fields (channel 1-16 or `null` for system messages, plus type-specific payload).
3. **Encoders / decoders** — `encodeMessage(msg): Uint8Array`, `decodeMessage(bytes, offset): { msg, next }`. Round-trip property tests via fast-check. Handles running-status decode (when a stream omits the status byte and reuses the previous one).
4. **High-level dispatch** — `NRPNState` and `RPNState` reducers that accumulate the four-CC dance into a single `NRPNChange` / `RPNChange` event. Same for 14-bit CCs (MSB + LSB on pairs 0-31 / 32-63).
5. **SysEx framing** — `SysExFrame` type + universal manufacturer-id registry (Yamaha, Roland, Korg, NI, Ableton, generic non-commercial). `parseSysEx(bytes)`, `buildSysEx(manufacturer, deviceId, payload)`.
6. **Port contract** — `MidiPortLike { send(bytes): void; onMessage(handler): Unsubscribe; name: string; id: string; manufacturer?: string; state: "connected" | "disconnected" }`. Adapters in `@suaveplan/react-midi` (Web MIDI) and future `@suaveplan/midi-node` (node-midi) implement this.
7. **MIDI Clock helpers** — `clockToBpm(intervalsMs[]): number`, `bpmToClock(bpm): clockMs`. 24 PPQN per the spec.
8. **General MIDI tables** — GM Level 1 (128 patches + 128 percussion notes), GM2 extensions, GS/XG dialects as separate exported tables.
9. **MIDI 2.0 UMP** — Universal MIDI Packet framing types (`UMP32`, `UMP64`, `UMP96`, `UMP128`), high-resolution velocity/CC, per-note controllers. Encode/decode + bridge helpers (`midi1ToUmp`, `umpToMidi1` where lossless).
10. **Error codes** — `MIDI.{INVALID_MESSAGE, INVALID_SYSEX, BUFFER_UNDERRUN, UNSUPPORTED_UMP_VARIANT, NRPN_INCOMPLETE}` via `defineCodes`.
11. **OTel instrumentation** — counter `midi_messages_decoded_total{type}`, histogram `midi_decode_duration_ns{type}`.
## Impact
- New package; nothing else changes today.
- Unblocks `@suaveplan/react-midi` (browser Web MIDI adapter), `@suaveplan/audio-fx-plugin-spessasynth` (consumes MIDI for SF2 playback), `@suaveplan/control-surface-schema` (bindings can reference MIDI CC/note ids), `@suaveplan/network-midi` (transport over WebRTC/WebSocket).
- Zero external runtime deps beyond `@suaveplan/error` + `@suaveplan/types` + `tslib` + `zod`.
## Out of scope (later changes)
- Web MIDI API access — `@suaveplan/react-midi`.
- Node MIDI access — `@suaveplan/midi-node` (Wave 3+).
- Wire transport — `@suaveplan/network-midi`.
- SoundFont loading — `@suaveplan/audio-fx-plugin-spessasynth`.