Start here
Quickstart
Two function calls and a user gesture. That's it.
1. Install
pnpm add zvuk 2. Construct the engine
Declare your buses up front. createEngine does not touch the AudioContext —
it just records the shape of the mix. Safe to call at module load.
import { createEngine } from 'zvuk';
export const engine = createEngine({
buses: {
music: { level: 0.8 },
sfx: { level: 1.0 },
ui: { level: 0.7 },
},
master: { headroom: -3 }, // dB
}); 3. Unlock from a user gesture
Browsers block audio until the user does something. Call engine.unlock() from a
click, tap, or keypress handler — it's idempotent and safe to call repeatedly.
document.querySelector('button')!.addEventListener('click', async () => {
await engine.unlock();
await engine.loadSound('coin', ['/sfx/coin.webm', '/sfx/coin.m4a'], { bus: 'sfx' });
engine.sound('coin').play();
}); Pass an array of URLs to ship a codec ladder. The first one your browser can decode wins — see Asset formats for why this matters.
4. Play, fade, mix
const v = engine.sound('coin').play({
volume: { jitter: 0.05 },
pitch: { jitter: 0.04 },
});
// Bus-level fade — click-free
engine.bus('music').fadeTo(0.1, 800);
// Voice-level fade with curve
await v.fade({ to: 0, ms: 800, curve: 'equal-power' });
await v.ended; 5. Clean up
When you're done — e.g. SPA route change, game teardown — call engine.close().
All voices stop, the AudioContext closes, listeners detach. The engine cannot be reused;
construct a new one if needed.
await engine.close(); Where to go next
- The Engine — full lifecycle, state machine, scheduler.
- Bus — routing, concurrency, sidechain.
- Recipes — six clickable patterns to copy.
- Asset formats — webm/Opus + m4a/AAC, the right way.