Mixer
The named bus graph that routes every voice from source to speakers.
Every project ships a volume slider. Most ship three. The interesting part is never the slider; it is having somewhere for it to point that survives the twentieth sound you add.
The mixer is the declarative routing model. It is a set of named buses, each with a level, optional FX inserts, an optional voice cap, and an optional sidechain key. You name them at construction and the engine builds the audio graph. Bus outputs sum at the master, which applies headroom and sends to the destination.
Why declare buses up front?
Because the alternative is deciding routing at every call site, and call sites multiply. With named buses, "duck the music while dialogue plays" is one rule written once, and it applies to dialogue lines that don't exist yet.
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: 0.08, release: 0.4 } },
},
master: {
headroom: -3,
// Fast-attack soft limiter on master out — best-effort peak control for
// when FX stack on busy mixes. Disable by omitting the field.
limiter: { threshold: -1, ratio: 20, attack: 0.001, release: 0.05 },
},
}); engine.bus('music').level = 0.5;
engine.bus('sfx').fadeTo(0, 0.8);
engine.bus('voice').muted = true; Live demo
Three buses, six samples, a live voice counter. Sliders and mute drive the running engine.
Recipes
Insert an FX chain on a bus
import { Compressor, Reverb } from '@schmooky/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 Master limiter
Headroom is a static gain offset. The optional limiter is a fast-attack
DynamicsCompressor at ratio 20 with a 1 ms attack, used as a
soft limiter on the master output. It catches most of the transients
headroom alone can't tame. It is not a brick wall: with a finite attack
and no lookahead, peaks above the threshold still get through, so don't
treat it as a guaranteed 0 dBFS ceiling.
// The master limiter is configured at construction (see above). There is
// no public runtime accessor yet — to change or disable it, recreate the
// engine with a different master.limiter (or omit the field).
createEngine({ master: { limiter: { threshold: -0.5, ratio: 20 } } }); Pitfalls
Related
- Bus covers a single bus and its FX chain.
- Snapshot captures and crossfades the whole mix.
- Building your mix is the practical version of this page.