# Multichannel audio bundles

> This document answers how an audio cable carries 1 to 32 lanes, how widths are inferred through nested definitions, which routers exist, and how each host maps logical lanes to rea

Source: https://docs.tune.toys/contributors/multichannel-audio/

This document answers how an audio cable carries 1 to 32 lanes, how widths are inferred through nested definitions, which routers exist, and how each host maps logical lanes to real buses.

**Status:** bounded 1–32-channel source inference, shared Rust processing, bundle routing, observations and host adapters are implemented; every [DSP processor](https://docs.tune.toys/contributors/dsp-primitives/) builds on them. Surround speaker layouts, ambisonic processing, hardware configurations and DAWs are not qualified.

## Portable contracts and inference

An audio port has a fixed `channels` count from 1 to 32 or `"auto"`, plus optional ordered `labels`. Labels constrain the width to their length and describe lanes; they do not imply a speaker layout. Logical channels and device assignments stay separate, and no device identifier is stored in a Toy or session.

Primitive version `0.1.0` retains the four fixed-stereo foundation contracts and algorithms. Version `0.2.0` supports automatic widths through the same gain, low-pass, delay and oscillator algorithms. Legacy Toys are not rewritten on load; changing a legacy node's channel fields in the inspector upgrades that node to 0.2.0, keeping its two channels, in one operation with Undo.

```mermaid
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#FAFAF7', 'primaryTextColor': '#1C1C1B', 'primaryBorderColor': '#1C1C1B', 'lineColor': '#64645F', 'fontFamily': 'DM Sans, Inter, system-ui, sans-serif'}}}%%
flowchart LR
  IN["$in<br/>channels: 2"] --> G["Group instance A<br/>ports: auto"]
  IN2["Oscillator<br/>channels: 6"] --> G2["Group instance B<br/>same definition, own variables"]
  G --> OUT["$out<br/>channels: auto → 2"]
  G2 --> OUT2["$out<br/>channels: auto → 6"]
  SOLVE["Solver<br/>fixed/equality · concatenation · max-width<br/>runs before preparation"]
  SOLVE -. "resolves" .-> G
  SOLVE -. "resolves" .-> G2

  classDef source fill:#F0442E,stroke:#1C1C1B,color:#FAFAF7
  classDef shared fill:#DCEBDD,stroke:#1C1C1B,color:#1C1C1B
  classDef host fill:#DED8F0,stroke:#1C1C1B,color:#1C1C1B
  class IN,IN2 source
  class G,G2,SOLVE shared
  class OUT,OUT2 host
```

`packages/node-catalog/src/channels.ts` and `crates/compiler/src/channels.rs` resolve fixed/equality, concatenation and maximum-width constraints before preparation. **Each nested definition instance receives separate variables**, so one definition can run at different widths. Fixed ports constrain inferred ports in either direction. Derived widths resolve before otherwise unconstrained lanes default to mono. Sources default to mono in version 0.2; a declared count prepares independent generator states. Public numeric controls currently apply to every lane; per-channel automation arrays remain planned. The same solver resolves `video` streams and `control` lanes, with shared `SELECTORS` and `COMBINERS` per type ([signal types and domains](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/signal-types-and-domains.md#lanes-and-the-solver)); control and MIDI cables are described in [MIDI, control and mixing](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/midi-and-control.md).

| Input adaptation policy | Behavior                         |
| ----------------------- | -------------------------------- |
| `strict` (default)      | Requires matching widths         |
| `broadcast`             | Expands only mono inputs         |
| `pad`                   | Fills missing lanes with silence |
| `repeat`                | Cycles narrower bundles          |

Primitive `audio` configuration declares an optional output width and one of these policies. **No policy reduces a wider input**; reduction or selection requires an explicit router. Connections retain one driver per inlet; the Mixer exposes intentional fan-in.

Inference and schema checks cover unused definitions as well as active instances. A malformed label count, incompatible fixed constraint, unsupported legacy configuration, out-of-range selector or matrix route is rejected. The existing source byte/depth/expansion budgets still apply. The 32-channel limit is an execution-profile admission bound, not a special stereo/surround format.

## Mixing and routing

All four routers use version 0.2.0:

| Primitive                        | Behavior                                                                                                                                                                                                       |
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `std.mixer` — Mixer              | Up to 32 channels, each with an ID-scoped audio inlet, dB gain, pan, mute and solo. Broadcast adaptation infers the widest input unless an output width is configured. Reorder is presentation only.           |
| `std.combine` — Combine channels | Concatenates inputs A and B in that order. No summing. The output count is their combined width.                                                                                                               |
| `std.select` — Select channels   | Extracts a contiguous range beginning at the one-based `audio.firstChannel`, with `audio.channels` outputs (default one). Multiple selectors split a bundle.                                                   |
| `std.matrix` — Channel matrix    | A fixed output width and up to 256 explicit `audio.routes`, each with one-based `from`, `to` and gain −8…8. Routes add without normalization; omitted destinations are silent. An empty route list is silence. |

The Channels section of a node's Properties edits output counts, adaptation policies, selection ranges, matrix and Delay feedback routes, and, on Audio input and Audio output, port widths and channel names. It appears only on nodes whose width can be configured: combiners derive theirs, and converters, MIDI, data and bus nodes have none. Each change is one Undo step; a combination the solver rejects stays as a draft beside its error until another field makes it valid or it is reverted. Ports and cables show inferred widths for the selected nested instance. Changes use the existing source validator, authoring history and renderer; no second writable graph exists. Node discovery and auditions use the same contracts, and temporary audition input adapters are real admitted matrix nodes.

## Prepared engine and ABI

Compiled Program 2 carries explicit input/output counts, operator widths, input bus widths, adaptation policies and prepared routes. Program 1 remains accepted with stereo defaults for older serialized programs. Input arity and numeric vectors are sized per operator during preparation. Fixed members and canonical ID-ordered repeat members share a generated maximum of 72 input slots and 288 parameter slots. Shared generated DSP contracts provide discrete selectors and control metadata.

The engine allocates channel-sized scratch, filter history, delay history, generator phases and observation storage during preparation and checks channel-weighted buffers against the 128 MiB profile. Processing uses prepared lane maps, bounded loops and fixed stack scratch; it does not infer widths, allocate, lock or perform I/O. A changed topology is a replacement prepared generation, and existing host candidate failure handling retains the active program.

ABI 1 gains additive `tt_input_channels`, `tt_output_channels`, `tt_signal_channels` and `tt_meter_channel` queries. Buffer views contain interleaved `max_frames × channels` floats. Legacy meter fields keep their original meanings; the new channel queries expose every lane. Frame meters render the supplied lane arrays through the shared renderer, with an expandable, bounded list for bundles wider than stereo. Tail preparation includes mixer/matrix gain bounds and all relevant input paths.

## Host mappings

| Host            | Input                                                                                           | Output / monitoring                                                                                                                                                                                                                                                                                                  |
| --------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Browser worklet | Discrete source channels; Audio settings choose ordered zero-padding or repetition              | The worklet preserves logical lanes. A browser-local device mixer applies explicit discrete routing, alternating-lane stereo fold, first pair or mono average through native Web Audio nodes; per-device strips add gain/mute/solo/polarity and lane overrides. Offline WAV rendering keeps the logical output count |
| JUCE adapter    | 0–32 bus channels; missing lanes are zero; a mono logical input averages supplied host channels | 1–32 bus channels; matching widths map directly; differing counts fold lanes cyclically, averaging contributors per destination; mono duplicates for two-channel monitoring                                                                                                                                          |

The browser preserves the engine's logical bundle. Legacy programs keep the former mono-input repetition default. Routing policies, device bindings and strip identities/levels are browser-local host preferences, not Toy fields. See the [device mixer](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/audio-device-mixer.md). Native snapshot metadata describes the mappings and actual host counts and is explicitly excluded from serialized DAW state. The default product bus remains stereo. Fixtures qualify software behavior; physical devices, channel-layout conventions and actual DAW configurations require separate qualification.

## Evidence and references

The shared channel corpus (`tests/conformance/channel-cases.mjs`) checks browser, native and WASM admission, including repeated generic definitions at different widths. Runtime fixtures (`packages/runtime-web/test/multichannel.test.mjs`) exercise 1/2/3/5/8/16/32 lanes, per-lane telemetry, independent instances, explicit adaptation and routing, memory rejection, native/WASM PCM comparisons and block partitioning at 44.1/48/96 kHz. Rust and native host allocation fixtures include multichannel callbacks. Browser checks cover channel editing, inference, Undo, matrix configuration and monitor controls. Root `npm run check` remains required.

The design uses Cycling '74's documented [MC bundles and inlet summing](https://docs.cycling74.com/userguide/mc/mc_patchcords/), [wrapper expansion](https://docs.cycling74.com/userguide/mc/mc_wrapper/), [replication policies](https://docs.cycling74.com/reference/mc.%2A~/), [combining](https://docs.cycling74.com/reference/mc.combine~/) and [mixdown](https://docs.cycling74.com/reference/mc.mixdown~/) as behavioral references. Implementation and fixtures are original TuneToys code; no Max, Hotwire or synth.ist source or sibling build dependency was added.

Explicit speaker-layout matrices, multichannel spatial reverbs, per-lane automation/voice allocation, floating channel counts inside an active generation, external audio buses (side-chains, media and recorder audio) and MIDI 2.0 remain planned.

## Implementation

- [`packages/node-catalog`](https://github.com/joshstovall/TuneToys/blob/main/packages/node-catalog/README.md) — `src/channels.ts`
- [`crates/compiler`](https://github.com/joshstovall/TuneToys/blob/main/crates/compiler/README.md) — `src/channels.rs`; [`crates/engine`](https://github.com/joshstovall/TuneToys/blob/main/crates/engine/README.md); [`crates/engine-ffi`](https://github.com/joshstovall/TuneToys/blob/main/crates/engine-ffi/README.md)
- [`packages/runtime-web`](https://github.com/joshstovall/TuneToys/blob/main/packages/runtime-web/README.md) and [`native/plugin-shell`](https://github.com/joshstovall/TuneToys/blob/main/native/plugin-shell/README.md) — host mappings

## Related

- [Shared DSP primitives](https://docs.tune.toys/contributors/dsp-primitives/) and [execution](https://docs.tune.toys/contributors/execution/)
- [Signal types and domains](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/signal-types-and-domains.md)
- [Toy source format](https://docs.tune.toys/contributors/toy-format/)
- [Native Player core](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/native-player.md)
