docs / core-concepts / audio

Audio

AudioManager in @g2w/core follows the same pattern as the asset system: an injected backend loads sounds, requests are cached and deduplicated, and failed loads are retryable. Core never interprets audio data — everything browser-specific lives behind the AudioBackendcontract.

Load and play

play(key, options?) loads the sound (usually a cache hit), starts one playback, and returns a SoundPlayback handle. Invalid options — a non-finite or negative volume — throwAudioError before any backend call:

src/audio.ts
import { AudioManager } from "@g2w/core";
import { WebAudioBackend } from "@g2w/audio-web";

const audio = new AudioManager({
  backend: new WebAudioBackend(),
});

const laser = await audio.load("laser.wav");
const shot = await audio.play("laser.wav", { volume: 0.5 });

// a playback is a handle you may keep and stop early
shot.stop(); // idempotent: stopping twice is a no-op

// or stop everything the manager started
audio.stopAll();

Repeated and concurrent load(key) requests share one load and one Sound value. If the backend cannot start playback — for example a browser autoplay policy holds the context suspended — the rejection propagates unchanged; it is never silently swallowed.

Looping music, scene-scoped

inside a scene
const music = await audio.play("theme.ogg", {
  loop: true, volume: 0.4,
});

// leaving the gameplay scene:
exit() { music.stop(); },

Ownership rules

The manager owns playback lifetimes, never the sounds themselves.Sound values are caller/backend-owned — decoded audio may be shared freely, even across managers. dispose() stops all active playbacks, clears the cache, and calls backend.dispose() once. It is terminal and idempotent: load and play after disposal throw AudioError.

Wire disposal into the game

src/main.ts
const audio = new AudioManager({
  backend: new WebAudioBackend(),
});

// after game.init(): audio joins the LIFO finalizer chain,
// releasing playbacks + AudioContext before the renderer
game.addFinalizer(() => audio.dispose());
ScopeIntentionally unsupported: mixers, spatial/3D audio, adaptive music, and streaming. Audio is fire-and-forget from the caller's perspective — no second frame loop, no hidden timer.

Next steps

See Scenes for where to trigger sounds from game state, orarchitecture notes for the backend contract in full.