first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
# `port/web-midi-port`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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.