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, duration: number, curve?: FadeCurve): Promise<void>; // duration in seconds
voiceCount: number;
voices(): readonly Voice[];
concurrency: ConcurrencyConfig | null;
setConcurrency(c: ConcurrencyConfig | null): void;
addFx(fx: FxInsert): void;
removeFx(fx: FxInsert): void;
fx(): readonly FxInsert[];
meter(): { rms: number; peak: number }; // live amplitude readout (lazy analyser tap)
send(target: Bus, options?: { amount?: number; post?: boolean }): Send;
removeSend(send: Send): void;
sends(): readonly Send[];
solo(on?: boolean): void; // engine coordinates "any soloed → mute the rest"
unsolo(): void;
readonly soloed: boolean;
} 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, 1.2, '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, duration: 0.2 }); Live VU meter via bus.meter()
Returns { rms, peak } as linear values in [0..1].
The first call attaches a passive AnalyserNode tap — no cost until you read,
no audio-path change. See the rhythm-metronome example
(examples/rhythm-metronome/)
for a full demo.
// Drive a VU bar from bus.meter() inside your render loop.
function tick() {
const m = engine.bus('music').meter();
vuBar.style.width = (m.rms * 200) + '%';
peakDot.style.left = (m.peak * 100) + '%';
requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
// First call lazily attaches an AnalyserNode as a sibling of bus.output —
// no cost until you read. Subsequent calls reuse the same analyser. Sends — route a copy of one bus into another
The Wwise primitive: send a configurable share of a bus's signal into another bus, instead of inserting an FX directly on the source. A typical reverb routing — "send 30 % of music to a verb-only bus" — is two lines and adjustable live.
// Send 30% of music to a dedicated reverb bus.
const verbSend = engine.bus('music').send(engine.bus('reverb'), { amount: 0.3 });
// Adjust live; the setter ramps over 10ms to avoid clicks.
verbSend.amount = 0.5;
await verbSend.fadeTo(0, 1.2); // smooth fade-out
// Remove the send entirely.
verbSend.dispose();
// or
engine.bus('music').removeSend(verbSend);
// Pre-fader / pre-FX tap (rare — useful for monitor sends that should
// hear the dry signal regardless of how the source bus is faded).
engine.bus('music').send(engine.bus('monitor'), { post: false }); Solo — A/B one bus without disturbing the rest
The engine coordinates the global rule: while any bus is in the solo set,
every non-soloed bus is muted via a tiny ramp; when the solo set drains,
every bus is restored. Solo state is independent of muted,
so un-soloing returns each bus to its user-visible mute setting.
// Solo this bus — every other bus is muted while the solo set is non-empty.
engine.bus('voice').solo();
engine.bus('music').solo(); // additive — both still audible
engine.bus('voice').unsolo(); // music is still soloed; everyone else still muted
engine.bus('music').unsolo(); // solo set drains — every bus restored
// Solo state is independent of muted: un-soloing returns each bus to its
// own .muted setting, not unconditionally to "audible". Bus groups — address several buses at once
A BusGroup is a logical handle, not an audio node — it
applies level / fadeTo / muted /
solo to every member in parallel. Useful when several
buses always move together (combat = weapons + enemies + environment;
voice = dialogue + effort sounds; etc).
// Group several buses so a single call addresses all of them.
const combat = engine.busGroup('combat', [
engine.bus('weapons'),
engine.bus('enemies'),
engine.bus('environment'),
]);
combat.level = 0.5; // sets every member's level
await combat.fadeTo(0, 0.8); // fades every member in parallel
combat.muted = true; // mutes the whole group
combat.solo(); // solos every member at once
// Look up later by name.
engine.busGroup('combat').level = 1; Pitfalls
output.gain.value directly.Related
- Concurrency — voice-limit policies.
- Mixer — the wider bus graph.
- Ducking — sidechain a bus from another.