2.0 KiB
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
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
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 codeREACT_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.