Audio · class

SoundRegistry

Explained in Your first game.

class SoundRegistry
import { SoundRegistry } from '@driftengine/audio';

Constructor

new

constructor(fetchImpl?: FetchLike)
ParameterTypeDescription
fetchImpl?FetchLike

Accessors

NameTypeDescription
slotsgetreadonly SoundSlot[]Which slots exist, so callers can pick among them (e.g. a daily track).
unbuiltgetReadonlyMap<SoundSlot, string>Slots whose stand-in threw, and what it said.
More

Empty is the normal state. A consumer with a dev overlay should show this: a silent slot is otherwise indistinguishable from one nobody triggered.

resolvedgetReadonlyMap<SoundSlot, SoundOrigin>

Methods

register

register(slot: SoundSlot, source: SoundSource): void
ParameterTypeDescription
slotSoundSlot
sourceSoundSource

adopt

adopt(from: SoundRegistry): void

Adopt already-decoded buffers, skipping every fetch and decode.

ParameterTypeDescription
fromSoundRegistry
More

For building a second graph over the same sounds — an offline render of a mix that is already loaded. An AudioBuffer is PCM and a sample rate, not a handle onto the context that made it, so it can be used by any context running at the same rate; decoding the library again would cost the whole payload a second time and, worse, could resolve a slot differently from the mix being reproduced.

get

get(slot: SoundSlot): AudioBuffer | undefined
ParameterTypeDescription
slotSoundSlot

load

load(ctx: BaseAudioContext): Promise<void>

Resolve every slot: the files in parallel, then the stand-ins one at a time.

ParameterTypeDescription
ctxBaseAudioContext
More

Slots settle independently: one missing or corrupt asset must not silence the rest of the game, which is the likeliest real failure once assets are being dropped in by hand. Every failure mode — network error, HTTP status, undecodable bytes — lands on the same fallback, because from the player's side they are the same event.

The two halves are separated because they cost completely different things. Fetching is waiting, and twenty slots should wait together. Synthesis is arithmetic on the main thread — an ambience bed is seven seconds of filtered noise at the context's sample rate — and twenty of those settling together lands as one block of work.

Which is exactly what it did. Traced on 2026-08-07 on a game that starts its audio at boot, the fallbacks arrived as microtask blocks of 11, 14, 14, 21 and 34 ms while the player was already running, and the frame loop went 125 ms between frames because the vsync deadline kept landing inside one. So each stand-in gets its own task. The work is the same; what changes is that a frame can be drawn between any two of them.

The yield is a timer rather than a microtask, and that is the whole point — a microtask would rejoin the block it is trying to leave. Whatever the browser clamps the delay to only spaces the work further.