Concept
Bus
A named mix bucket with its own gain stage, FX chain, voice limit, and (optional) sidechain key.
TL;DR
A Bus is the routing target for one or more sounds. You declare it once
in createEngine, then talk to it by name. Level changes and
mutes ramp over 10ms internally so you never get a click. fadeTo
accepts a curve for longer transitions.
Mental model
API surface
class Bus {
readonly name: string;
readonly input: GainNode; // connect voices/sources here
readonly output: GainNode; // connected to master
level: number; // setter ramps over 10ms (click-free)
muted: boolean; // setter ramps over 10ms
fadeTo(target: number, ms: number, curve?: FadeCurve): Promise<void>;
voiceCount: number;
voices(): readonly Voice[];
concurrency: ConcurrencyConfig | null;
setConcurrency(c: ConcurrencyConfig | null): void;
addFx(fx: FxInsert): void;
removeFx(fx: FxInsert): void;
fx(): readonly FxInsert[];
} Live demo
Slam the slider — no click. Try the fade buttons for curve-based transitions.
Recipes
Direct level vs. fadeTo
// Smooth fade — won't pop because of the 10ms internal ramp on direct writes,
// or use fadeTo() with a curve for longer transitions.
engine.bus('music').level = 0.5;
await engine.bus('music').fadeTo(0, 1200, 'equal-power'); Enumerate active voices on a bus
const bus = engine.bus('sfx');
console.log('voices on this bus:', bus.voiceCount);
for (const v of bus.voices()) v.fade({ to: 0, ms: 200 }); Pitfalls
Don't write to
The Bus class wraps that with a 10ms ramp. Bypassing it produces audible clicks
in Chrome and Firefox.
output.gain.value directly.Don't share an FxInsert between buses.
Each FX node has its own internal connections. Construct one per bus, dispose
when removed.
Related
- Concurrency — voice-limit policies.
- Mixer — the wider bus graph.
- Ducking — sidechain a bus from another.