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,64 @@
# `midi-out` handler
## Purpose
The `midiOut` handler is the binding-registry slot that converts a
control's value into an outbound MIDI Control Change. The default export
is the "unwired" implementation that throws
`REACT_CONTROL_SURFACE.BINDING_HANDLER_MISSING`; consumers replace it with
a closure that captures a `MidiOutputHandle` from
`useMidiOutput(binding.portId)` and emits CC traffic on the bound
`channel`/`cc` pair, scaled into the binding's optional `range`.
Throwing at the default keeps a partially-wired surface from masquerading
as live. Once `useMidiOutput` is bridged the runtime forwards every drag,
press, or keyboard increment as a single CC message.
## Features
- Strongly-typed signature `(binding: MidiOutBinding, value: number) => never`.
- Error context preserves `portId`, `channel`, `cc`, and the dropped value.
- Encoding/range scaling are deferred to the production handler — this
default is value-agnostic.
## Usage: Basic
```ts
import { BindingRegistry } from "@suaveplan/react-control-surface/runtime/binding-registry";
const registry = new BindingRegistry({
midiOut: (binding, value) =>
output.sendMessage({
kind: "controlChange",
channel: binding.channel,
controller: binding.cc,
value: Math.round(value * 127),
}),
});
```
## Usage: Advanced
```ts
import { midiOutHandler } from "@suaveplan/react-control-surface/runtime/binding-registry";
expect(() =>
midiOutHandler(
{ kind: "midiOut", portId: "p1", channel: 1, cc: 7 },
0.5
)
).toThrow();
```
## API Reference
- `midiOutHandler(binding: MidiOutBinding, value: number): never`
— always throws with code `REACT_CONTROL_SURFACE.BINDING_HANDLER_MISSING`.
## Implementation Notes
Range scaling is the consumer's responsibility because the schema's
optional `range` can flip min/max, and the production handler may want to
interpret it differently (linear vs. log) per surface. Keeping the default
value-agnostic ensures the consumer never inherits an opinionated mapping
they can't override.