Cable kinds: audio, MIDI, modulation
The routing table carries three kinds of cable. They share the same from / to / id shape but their per-cable controls and their behaviour under change are different. Knowing which knob to reach for saves you from "why is this MIDI cable's gain ignored?" debugging.
Audio cables
Carry: sample buffers. The port's channels field declares the channel count (1 = mono, 2 = stereo, more for multi-channel outs). The routing engine fans channels out as needed when a Source's port count and the Sink's port count differ — a mono Source feeding a stereo Sink duplicates to both channels.
Stereo cables on the Builder canvas
Inside a Builder graph (one zoom level deeper than the routing table), paired-port stereo Kinds carry their channels: 2 marker into the visual: the Builder's wire-validator treats paired-port halves (Output's in_l / in_r, mono_to_stereo's out_l / out_r, etc.) as siblings + the cable stroke-width doubles when both halves are wired. Connecting only one half surfaces a "wire the partner too" hint. Same data model as the routing-table audio cable (this is all rendered in libfaust later) — just made visible at the Builder graph level. See FaustWave — Builder § 1 Paired-port stereo and FaustWave — Node-Pack Authoring § 3 for the underlying channels: 2 semantics.
Per-cable controls:
gain— linear 0..1, default 1.0 (unity). Display this as dB via20 · log10(gain). Settinggain: 0.5is roughly −6 dB.enabled— Boolean.falsesuspends the cable: the routing engine ramps the per-connection GainNode to 0 over 5 ms. Re-enabling ramps back to the declared gain.
Both controls are rendered as a 5 ms time-constant ramp, so changing gain while audio is flowing doesn't click. You can therefore wire routing.set { gain } into an MCP action chain without click-prevention guards.
Mixer fader == per-cable gain: when you move a track fader in the Mixer panel, the change writes through to routing.set { connection_id: <cable>, gain } for that MixerTrack's connection to master. The Mixer is the per-track UI for the audio side of the routing table.
MIDI cables
Carry: MIDI events — note-on / note-off, CC, program change, clock, sysex. Events dispatch when the Source emits them; there's no continuous sample-rate buffer to ramp.
Per-cable controls:
enabled— Boolean.falsemakes the dispatch loop skip the Sink for this cable's events. No gain (it would be meaningless: 50 % of a note-on is just a note-on).- No gain field.
routing.set { connection_id: <midi cable>, gain: 0.5 }returns an error.
Per-Sink channel filtering is independent of the cable's enabled flag: a Builder's midi_note node listens on its own configured channel; a MIDI cable fan-out delivers events to every wired Sink, each Sink filters by channel locally. So muting a MIDI cable is the routing-table answer to "stop these events from reaching this Sink"; channel filtering is the per-Sink answer to "stop reacting to these specific channels".
Modulation cables
Carry: scalar control values from a modulator (LFO, envelope follower, slow CCs converted to param-rate) to a specific Faust param on an instrument.
Per-cable controls:
amount— signed scalar (typically −1..1, but the routing engine doesn't clamp; the Sink param's own range absorbs the scaling).enabled— Boolean. Suspends modulation; the underlying param stays at its last set value, no ramp needed because the modulator simply stops contributing.
No gain — use amount for scaling.
The Sink is per-param: a single instrument exposes one modulation Sink entity, with a separate input port per exposed param. Wiring an LFO to cutoff and a second LFO to resonance is two separate cables to the SAME Sink entity, distinguished by to.portId = <instrument-id>/cutoff vs <instrument-id>/resonance.
Discover the exposed param ports with routing.list and filter sinks for kind === "modulation". Each entry's inputs[] is the bank of param ports.
When to use which
| You want to… | Reach for |
|---|---|
| Send the Builder's output to a MixerTrack | Audio cable |
| Make the Keyboard play your DSP | MIDI cable |
| Sidechain-duck the Builder from the bass | Modulation cable from envelope follower → DSP gain param |
| LFO-modulate the filter cutoff | Modulation cable from LFO → cutoff param port |
| Route an external MIDI controller into the Sequencer | MIDI cable from hardware-input Source → Sequencer's record-armed track |
| Sync FaustWave's tempo to a hardware sequencer / another DAW | MIDI cable from the clock-sending input → Transport Clock In (§ 5) |
| Tap the master mix into the Recorder | Audio cable from master Source → recorder-default Sink |
| A/B mute a cable without losing the wire | Set enabled: false (works on all three kinds) |
Common gotchas
- Audio cable looks dead but everything checks out: confirm
gain > 0ANDenabled: trueAND the Source module is running (Builder's RUN button is on, not just open). The cable can be wired and the Source not producing audio. - MIDI cable wired but Sink doesn't react: the Sink might be filtering on a different channel than the Source sends. Match the Source's
keyboard.channel.set/ sequencer track channel to the Sink's expected channel (or set the Source to Omni / channel 0). - Modulation cable wired but the param doesn't move: confirm
amount ≠ 0. Also confirm the modulator Source is actually emitting — an LFO with rate 0 produces a constant value, and a freshly-added LFO might not be running until you trigger it. - “My channel switch stranded a note”: see the Keyboard section § Stuck notes. The fix is
keyboard.notes.clearbefore the channel change.
Multiple cables to the same Sink
- Multiple Audio cables into
master→ typically one per MixerTrack (sources feed tracks, tracks feed master); they sum. Several sources can also feed one MixerTrack's:in, summing before the track fader. - Multiple MIDI cables into one
*:midi-inSink → fine, every cable's events interleave at the dispatch layer (the Sink processes them in arrival order). - Multiple Modulation cables to the same param port (e.g. two LFOs both wired to
cutoff) → the param sees their sum × their per-cableamount. Useful for layered modulation; mind the headroom.
Next section: doing all of this from MCP — routing.list, routing.connect, routing.disconnect, routing.set.