Files
skills/docs/ui/react/react-control-surface/src/runtime/binding-registry/handlers/midi-out.md
2026-05-29 08:20:56 +01:00

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 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.