first commit
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user