Z zvuk
Guide

Loading sounds

The patterns for getting audio off the network and into the engine — single, bulk, with progress, with cancellation.

Single sound

ts ts
await engine.loadSound('coin', '/sfx/coin.webm', { bus: 'sfx' });

Codec ladder

Pass an array; the engine picks the first URL the browser can decode. See the asset-formats guide for the encoding pipeline.

ts ts
await engine.loadSound('coin', [
  '/sfx/coin.webm',
  '/sfx/coin.m4a',
], { bus: 'sfx' });

Bulk load with Promise.all

The Decoder is concurrent-safe; firing N requests in parallel is the right move. Network is almost always the bottleneck, not decoding.

ts ts
const manifest = [
  { name: 'coin',     urls: ['/sfx/coin.webm', '/sfx/coin.m4a'],     bus: 'sfx' },
  { name: 'win',      urls: ['/sfx/win.webm', '/sfx/win.m4a'],       bus: 'sfx' },
  { name: 'reel',     urls: ['/sfx/reel.webm', '/sfx/reel.m4a'],     bus: 'sfx' },
  { name: 'bg-music', urls: ['/music/bg.webm', '/music/bg.m4a'],     bus: 'music' },
];

await Promise.all(
  manifest.map(({ name, urls, bus }) =>
    engine.loadSound(name, urls, { bus }),
  ),
);

Progress reporting

ts ts
let loaded = 0;
const total = manifest.length;

await Promise.all(
  manifest.map(async (entry) => {
    await engine.loadSound(entry.name, entry.urls, { bus: entry.bus });
    loaded++;
    onProgress(loaded / total);   // 0..1
  }),
);

Cancellable loads

Pass an AbortSignal. The fetch is cancelled mid-flight; in-progress decodes complete (Web Audio's decodeAudioData is not abortable).

ts ts
const ac = new AbortController();
try {
  await Promise.all(
    manifest.map((m) => engine.loadSound(m.name, m.urls, { bus: m.bus, signal: ac.signal })),
  );
} catch (e) {
  if ((e as Error).name === 'AbortError') console.log('cancelled');
  else throw e;
}

// On route change:
ac.abort();

Cache & memory

Decoded buffers are cached by URL inside the engine. Loading the same URL twice is free; loading 130+ unique URLs evicts the least-recently-used entries (configurable via the internal Decoder limit).

Pitfalls

Don't load before unlock.
The decoder needs a live AudioContext. loadSound calls touch() to construct the context lazily — but on iOS Safari, decodeAudioData can hang on a suspended context. Always await unlock() first, ideally on the same gesture that triggers the load.