Concept
Mixer
The named bus graph that routes every voice from source to speakers.
TL;DR
The Mixer is your declarative routing model. You name your buses
(music, sfx, voice, etc.), give each
one a level and behavior, and the engine builds the audio graph for you.
The mixer is what separates "I called .play()" from
"I shipped a mix."
Mental model
Sources fan in through their default bus. Each bus has its own level, optional FX inserts, optional concurrency cap, and an optional sidechain key. Bus outputs sum at the master, which applies headroom and sends to the destination.
API surface
const engine = createEngine({
buses: {
music: { level: 0.8, concurrency: { max: 4 } },
sfx: { level: 1.0, concurrency: { max: 32, steal: 'oldest' } },
voice: { level: 1.0, sidechain: { from: 'music', amount: 0.5, attack: 80, release: 400 } },
},
master: { headroom: -3 },
}); engine.bus('music').level = 0.5;
engine.bus('sfx').fadeTo(0, 800);
engine.bus('voice').muted = true; Live demo
Sliders, mute, real voice counter — live engine, real samples.
engine.state = cold
0 voices
Recipes
Insert an FX chain on a bus
import { Compressor, Reverb } from 'zvuk';
const comp = new Compressor(engine.context, { threshold: -18, ratio: 4 });
const reverb = new Reverb(engine.context, { wet: 0.3, decay: { seconds: 1.4 } });
engine.bus('music').addFx(reverb);
engine.bus('sfx').addFx(comp); // FX run between bus.input and bus.output Pitfalls
Don't add buses dynamically after createEngine.
Bus topology is declared once. If you need a temporary effect, route into
an existing bus and bypass when not in use, or add a fresh FX insert.
Don't write directly to ctx.destination.
Going around the master skips headroom and breaks snapshots. Always route
through a declared bus.
Related
- Bus — single-bus details and the FX chain.
- Snapshot — capture & crossfade the whole mix.
- Building your mix — practical guide.