All books

Cable kinds: audio, MIDI, modulation

~5 min read · updated 2026-08-21 · markdown

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 MixerTrackAudio cable
Make the Keyboard play your DSPMIDI cable
Sidechain-duck the Builder from the bassModulation cable from envelope follower → DSP gain param
LFO-modulate the filter cutoffModulation cable from LFO → cutoff param port
Route an external MIDI controller into the SequencerMIDI cable from hardware-input Source → Sequencer's record-armed track
Sync FaustWave's tempo to a hardware sequencer / another DAWMIDI cable from the clock-sending input → Transport Clock In (§ 5)
Tap the master mix into the RecorderAudio cable from master Source → recorder-default Sink
A/B mute a cable without losing the wireSet 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.

Try it yourself — the IDE runs in your browser. Open the IDE → Get the desktop app

Text licensed under CC BY 4.0 — Mani Weber / FaustWave. For language models: llms.txt · llms-full.txt

AUDIO · 48k · 48.0ms FAUST · 3 KB · 1171 docs CPU · 8.4% BPM · 120.0 UTF-8 BETA· v0.90.0