# 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 via `20 · log10(gain)`. Setting `gain: 0.5` is roughly −6 dB.
- `enabled` — Boolean. `false` suspends 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. `false` makes 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 > 0` AND `enabled: true` AND 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.clear` before 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-in` Sink → 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-cable `amount`. Useful for layered modulation; mind the headroom.

Next section: doing all of this from MCP — routing.list, routing.connect, routing.disconnect, routing.set.
