Initial Commit
This commit is contained in:
166
packages/ui/react/react-midi/README.md
Normal file
166
packages/ui/react/react-midi/README.md
Normal file
@@ -0,0 +1,166 @@
|
||||
# @suaveplan/react-midi - React 19 adapter for the Web MIDI API: <MidiProvider>, hooks (useMidi, useMidiInput, useMidiOutput, useMidiMessages, useMidiDevices, useNRPN, useRPN, useCC14), and small UI primitives (MidiDeviceList, MidiActivityIndicator).
|
||||
|
||||
React 19 adapter for the Web MIDI API. Provides a strict-mode-safe
|
||||
`<MidiProvider>` that requests `MIDIAccess` exactly once, seven
|
||||
typed hooks for common consumption patterns, and two small UI
|
||||
primitives (`<MidiDeviceList>`, `<MidiActivityIndicator>`).
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [midi-activity-indicator](./src/components/midi-activity-indicator/midi-activity-indicator.md) — `<MidiActivityIndicator>` is a small visual primitive that "blinks" on inbound MIDI traffic. It subscribes to the selec…
|
||||
- [midi-activity-indicator](./src/components/midi-activity-indicator/midi-activity-indicator.md) — `<MidiActivityIndicator>` is a small visual primitive that "blinks" on inbound MIDI traffic. It subscribes to the selec…
|
||||
- [midi-device-list](./src/components/midi-device-list/midi-device-list.md) — `<MidiDeviceList>` is a small, accessible list of the available MIDI devices for a chosen kind (inputs or outputs). It…
|
||||
- [midi-device-list](./src/components/midi-device-list/midi-device-list.md) — `<MidiDeviceList>` is a small, accessible list of the available MIDI devices for a chosen kind (inputs or outputs). It…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — Defines the stable `REACT_MIDI.*` error-code namespace published by `@suaveplan/react-midi`. Every code is generated vi…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — Defines the stable `REACT_MIDI.*` error-code namespace published by `@suaveplan/react-midi`. Every code is generated vi…
|
||||
- [hooks](./src/hooks/use-midi-context.md) — Internal helper that returns the live `MidiContextValue` for the nearest `<MidiProvider>` ancestor. It throws `ReactMid…
|
||||
- [use-cc14](./src/hooks/use-cc14/use-cc14.md) — `useCC14(selector)` exposes the consolidated 14-bit CC change for the selected input. Controllers that need finer resol…
|
||||
- [use-cc14](./src/hooks/use-cc14/use-cc14.md) — `useCC14(selector)` exposes the consolidated 14-bit CC change for the selected input. Controllers that need finer resol…
|
||||
- [use-midi](./src/hooks/use-midi/use-midi.md) — `useMidi()` is the most general hook in `@suaveplan/react-midi`. It returns the live `MidiContextValue` surface as `{ s…
|
||||
- [use-midi](./src/hooks/use-midi/use-midi.md) — `useMidi()` is the most general hook in `@suaveplan/react-midi`. It returns the live `MidiContextValue` surface as `{ s…
|
||||
- [use-midi-devices](./src/hooks/use-midi-devices/use-midi-devices.md) — `useMidiDevices()` returns the live `{ inputs, outputs }` device-list view, re-rendering whenever the provider's `state…
|
||||
- [use-midi-devices](./src/hooks/use-midi-devices/use-midi-devices.md) — `useMidiDevices()` returns the live `{ inputs, outputs }` device-list view, re-rendering whenever the provider's `state…
|
||||
- [use-midi-input](./src/hooks/use-midi-input/use-midi-input.md) — `useMidiInput(selector)` returns a typed `MidiPortLike` adapter over a single Web MIDI input. The adapter parses raw by…
|
||||
- [use-midi-input](./src/hooks/use-midi-input/use-midi-input.md) — `useMidiInput(selector)` returns a typed `MidiPortLike` adapter over a single Web MIDI input. The adapter parses raw by…
|
||||
- [use-midi-messages](./src/hooks/use-midi-messages/use-midi-messages.md) — `useMidiMessages(selector, options?)` exposes a bounded ring buffer of the most recent parsed MIDI messages from the se…
|
||||
- [use-midi-messages](./src/hooks/use-midi-messages/use-midi-messages.md) — `useMidiMessages(selector, options?)` exposes a bounded ring buffer of the most recent parsed MIDI messages from the se…
|
||||
- [use-midi-output](./src/hooks/use-midi-output/use-midi-output.md) — `useMidiOutput(selector)` returns a typed handle for a single Web MIDI output. The handle exposes both `send(bytes)` (r…
|
||||
- [use-midi-output](./src/hooks/use-midi-output/use-midi-output.md) — `useMidiOutput(selector)` returns a typed handle for a single Web MIDI output. The handle exposes both `send(bytes)` (r…
|
||||
- [use-nrpn](./src/hooks/use-nrpn/use-nrpn.md) — `useNRPN(selector)` wraps the universal NRPN reducer from the `midi-contract` module and exposes the consolidated `NRPN…
|
||||
- [use-nrpn](./src/hooks/use-nrpn/use-nrpn.md) — `useNRPN(selector)` wraps the universal NRPN reducer from the `midi-contract` module and exposes the consolidated `NRPN…
|
||||
- [use-rpn](./src/hooks/use-rpn/use-rpn.md) — `useRPN(selector)` wraps the universal RPN reducer and exposes the consolidated `RPNChange` as React state. RPN ("Regis…
|
||||
- [use-rpn](./src/hooks/use-rpn/use-rpn.md) — `useRPN(selector)` wraps the universal RPN reducer and exposes the consolidated `RPNChange` as React state. RPN ("Regis…
|
||||
- [use-midi-context](./src/hooks/use-midi-context.md) — Internal helper that returns the live `MidiContextValue` for the nearest `<MidiProvider>` ancestor. It throws `ReactMid…
|
||||
- [codec](./src/midi-contract/codec.md) — `codec.ts` provides minimal `decodeMessage` / `encodeMessage` helpers for the MIDI 1.0 wire format used internally by `…
|
||||
- [midi-message](./src/midi-contract/midi-message.md) — Defines the local mirror of the `@suaveplan/midi` `MidiMessage` discriminated union plus its peer types (`Channel`, `Mi…
|
||||
- [reducers](./src/midi-contract/reducers.md) — Three small reducers that fold the four-CC NRPN, RPN, and 14-bit CC "dances" into single consolidated events. The Web M…
|
||||
- [port](./src/port/web-midi-port.md) — Bridges the native Web MIDI `WebMIDIInput` / `WebMIDIOutput` shapes into the structural `MidiPortLike` consumed by the…
|
||||
- [web-midi-port](./src/port/web-midi-port.md) — Bridges the native Web MIDI `WebMIDIInput` / `WebMIDIOutput` shapes into the structural `MidiPortLike` consumed by the…
|
||||
- [midi-context](./src/provider/midi-context.md) — Defines the two React contexts that wire `<MidiProvider>` to its descendant hooks. `MidiReactContext` carries the resol…
|
||||
- [midi-provider](./src/provider/midi-provider.md) — `<MidiProvider>` is the root context provider for `@suaveplan/react-midi`. It calls `navigator.requestMIDIAccess({ syse…
|
||||
- [telemetry](./src/telemetry/telemetry.md) — Owns the two OTel counters published by `@suaveplan/react-midi`: `react_midi_messages_received_total` for parsed inboun…
|
||||
- [telemetry](./src/telemetry/telemetry.md) — Owns the two OTel counters published by `@suaveplan/react-midi`: `react_midi_messages_received_total` for parsed inboun…
|
||||
- [test-setup](./src/test-setup.md) — Per-package Vitest bootstrap loaded via `setupFiles` before the first test in a worker runs. Its only job is to registe…
|
||||
- [test-utils](./src/test-utils.md) — Provides the deterministic in-memory implementations of the Web MIDI shape used throughout the `@suaveplan/react-midi`…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
bun add @suaveplan/react-midi
|
||||
```
|
||||
|
||||
Peer deps: `react@^19`, `react-dom@^19`.
|
||||
|
||||
## Permission model
|
||||
|
||||
Web MIDI is gated behind `navigator.requestMIDIAccess`. `<MidiProvider>`
|
||||
calls it once on mount and surfaces the outcome as a `status`:
|
||||
|
||||
- `loading` — request in flight.
|
||||
- `granted` — `MIDIAccess` resolved.
|
||||
- `denied` — user rejected, or the runtime threw (e.g. `NotAllowedError`).
|
||||
- `unsupported` — `navigator.requestMIDIAccess` is undefined (Firefox,
|
||||
sandboxed iframes, etc.).
|
||||
|
||||
SysEx access is opt-in via `<MidiProvider sysex>`. The provider is
|
||||
StrictMode-safe — it tracks an in-flight ref so React's double-mount
|
||||
never produces two parallel requests.
|
||||
|
||||
## Quickstart
|
||||
|
||||
```tsx
|
||||
import {
|
||||
MidiProvider,
|
||||
useMidi,
|
||||
useMidiInput,
|
||||
MidiDeviceList,
|
||||
} from "@suaveplan/react-midi";
|
||||
|
||||
function Workspace() {
|
||||
const { status } = useMidi();
|
||||
if (status === "loading") return <p>Connecting MIDI…</p>;
|
||||
if (status === "denied") return <p>MIDI permission denied.</p>;
|
||||
if (status === "unsupported")
|
||||
return <p>MIDI not available in this browser.</p>;
|
||||
return (
|
||||
<>
|
||||
<MidiDeviceList kind="input" />
|
||||
<NoteLogger />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function NoteLogger() {
|
||||
const port = useMidiInput("first");
|
||||
useEffect(() => {
|
||||
if (port === null) return;
|
||||
return port.onMessage((msg) => {
|
||||
if (msg.type === "noteOn") console.log("note", msg.note);
|
||||
});
|
||||
}, [port]);
|
||||
return null;
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<MidiProvider>
|
||||
<Workspace />
|
||||
</MidiProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Device-picker example
|
||||
|
||||
```tsx
|
||||
function DevicePicker() {
|
||||
const [id, setId] = useState<string | null>(null);
|
||||
return (
|
||||
<>
|
||||
<MidiDeviceList
|
||||
kind="input"
|
||||
selectedId={id}
|
||||
onSelect={setId}
|
||||
/>
|
||||
{id !== null ? <NoteLogger portId={id} /> : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Hooks reference
|
||||
|
||||
- `useMidi()` — `{ status, access, inputs, outputs, error }`.
|
||||
- `useMidiInput(id | "first")` — typed `MidiPortLike` with parsed
|
||||
`MidiMessage` events.
|
||||
- `useMidiOutput(id | "first")` — typed output handle with
|
||||
`sendMessage(msg)` (encoded) and raw `send(bytes)`.
|
||||
- `useMidiMessages(id, { capacity?, filter? })` — ring buffer of
|
||||
recent messages for log/debug UIs.
|
||||
- `useMidiDevices()` — `{ inputs, outputs }`, re-renders on
|
||||
`statechange`.
|
||||
- `useNRPN(id)`, `useRPN(id)`, `useCC14(id)` — high-resolution
|
||||
reducers folded into React state.
|
||||
|
||||
## Error codes
|
||||
|
||||
`REACT_MIDI.{ACCESS_DENIED, UNSUPPORTED_PLATFORM, PORT_NOT_FOUND,
|
||||
SYSEX_NOT_PERMITTED, NO_PROVIDER}`. Thrown as `ReactMidiError`
|
||||
instances; permission failures are surfaced as a `status` value
|
||||
rather than thrown so consumers can render friendly fallbacks.
|
||||
|
||||
## Telemetry
|
||||
|
||||
Two OTel counters tagged with `port`:
|
||||
`react_midi_messages_received_total{port}` and
|
||||
`react_midi_messages_sent_total{port}`.
|
||||
|
||||
## On the `@suaveplan/midi` dependency
|
||||
|
||||
This package ships a local mirror of the `@suaveplan/midi`
|
||||
`MidiMessage` contract in `src/midi-contract/`. Once
|
||||
`@suaveplan/midi` graduates to `workspace:^`, the local mirror is
|
||||
replaced by a re-export from that package without any shape change.
|
||||
|
||||
Reference in New Issue
Block a user