3.1 KiB
3.1 KiB
@suaveplan/react-midi — Web MIDI API React adapter
- Target path:
packages/ui/react/react-midi/ - Tier: browser/react
- Date: 2026-05-23
- Status: PROPOSED
Why
@suaveplan/midi ships universal-tier MIDI types and a MidiPortLike structural contract but never touches the platform. To consume MIDI in a browser app, React components need:
- Permission gating (
navigator.requestMIDIAccess({ sysex })) with promise-shaped state for Suspense + ErrorBoundary integration. - Port enumeration with live add/remove (devices plug/unplug at runtime).
- Per-port input/output hooks that wire into the universal decoder/encoder and surface typed
MidiMessageevents. - SysEx opt-in (separate permission scope).
- Safe StrictMode behaviour (double-mount + cleanup must not double-bind).
This package owns the Web MIDI bridge so every downstream React surface (control-surface runtime, react-control-surface editor, react-audio-player's hardware-controller integration) shares one set of hooks instead of reimplementing permission/state plumbing.
What changes
- NEW package
@suaveplan/react-midiatpackages/ui/react/react-midi/. <MidiProvider>— context provider that triggersnavigator.requestMIDIAccessexactly once (StrictMode-safe), exposes theMIDIAccessstate, supportssysex: booleanprop, surfaces permission denial as a typedMidiPermissionError.- Hooks:
useMidi()— returns{ access, status: "loading"|"granted"|"denied"|"unsupported", inputs: MidiPortLike[], outputs: MidiPortLike[] }.useMidiInput(portId | "first"): MidiPortLike | null— picks a single input; subscribers receive parsedMidiMessageevents viaport.onMessage.useMidiOutput(portId | "first"): MidiPortLike | null— sends bytes via the universalencodeMessage.useMidiMessages(portId, filter?)— returns the most recent N messages (ring buffer) filtered by message type. For UI display.useMidiDevices()— returns the live{ inputs, outputs }array, re-renders onstatechange.useNRPN(portId)/useRPN(portId)/useCC14(portId)— high-resolution-controller reducers exposed as React hooks.
<MidiDeviceList>+<MidiActivityIndicator>— small UI primitives for picker / activity visualisation. Each is composable; consumers pass their own renderProps for custom styling.- Connect-state events — fires
onDeviceAdd/onDeviceRemovevia context, plus per-portstatereads. - Error codes —
REACT_MIDI.{ACCESS_DENIED, UNSUPPORTED_PLATFORM, PORT_NOT_FOUND, SYSEX_NOT_PERMITTED}viadefineCodes. - OTel instrumentation — counter
react_midi_messages_received_total{port}, counterreact_midi_messages_sent_total{port}.
Impact
- New package depending on
@suaveplan/midi@workspace:^and React 19 peer dep. - Browser-tier — uses
navigator.requestMIDIAccess. Falls back gracefully (status: "unsupported") on platforms that lack Web MIDI (Safari without flag, Firefox, sandboxed contexts). - Unblocks
@suaveplan/react-control-surface(CC bindings drive plugin parameters viauseParameterfrom@suaveplan/react-audio).