# `port/web-midi-port` _(no submodules)_ ## Purpose Bridges the native Web MIDI `WebMIDIInput` / `WebMIDIOutput` shapes into the structural `MidiPortLike` consumed by the rest of the package. The adapter handles two responsibilities the underlying DOM types do not: it parses `MIDIMessageEvent.data` into a typed `MidiMessage` (so consumers never touch raw bytes), and it owns the fan-out from the single `onmidimessage` slot to multiple subscribers. Two factories ship: `adaptInput` for inputs (yielding parsed messages via `onMessage`) and `adaptOutput` for outputs (encoding `MidiMessage` to bytes via `encodeMessage` before forwarding to `port.send`). Both record traffic via the `telemetry` module so dashboards see one counter per direction. ## Features - Multiple subscribers on a single input — the adapter fans the single `onmidimessage` slot out to a `Set` of handlers. - Lazy bind/unbind — the `onmidimessage` slot is occupied only while at least one subscriber is active; the last unsubscribe restores `null`. - Input-side decode swallows malformed frames silently (the runtime already filters; defensive `null` check keeps subscribers safe). - Output-side `sendMessage` encodes a typed `MidiMessage`; raw `send(bytes)` remains available for SysEx pass-through. ## Usage: Basic ```ts import { adaptInput, adaptOutput } from "@suaveplan/react-midi/port/web-midi-port"; const port = adaptInput(midiAccess.inputs.get("midi-in-0")!); const unsubscribe = port.onMessage((msg) => { if (msg.type === "noteOn") console.log("note", msg.note); }); ``` ## Usage: Advanced ```ts const out = adaptOutput(midiAccess.outputs.get("midi-out-0")!); // Typed send (encoded): out.sendMessage({ type: "noteOn", channel: 0, note: 60, velocity: 100, }); // Raw send (for SysEx pass-through): out.send(new Uint8Array([0xf0, 0x7e, 0x00, 0xf7])); ``` ## API Reference - `adaptInput(input: WebMIDIInput): MidiPortLike` - `adaptOutput(output: WebMIDIOutput): MidiPortLike & { sendMessage(msg: MidiMessage): void }` ## Implementation Notes The input adapter uses `Set` (not array) for subscribers so unsubscribing during fan-out doesn't reorder iteration over the remaining subscribers. The output adapter's `onMessage` returns a no-op unsubscriber rather than throwing, which lets generic code that walks both directions on a port handle either shape without a runtime type check.