Z zvuk
Start here

Quickstart

Two function calls and a user gesture. That's it.

1. Install

bash bash
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.

ts ts
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.

ts ts
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

ts ts
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.

ts ts
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.