Guide
Loading sounds
The patterns for getting audio off the network and into the engine — single, bulk, with progress, with cancellation.
Single sound
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.
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.
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
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).
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.