AudioEngine
audio/audio-engine.ts:73 Extends AbstractGameComponent
Game-tier root of the audio graph: owns the Web Audio AudioContext, the
master and per-category gain buses, and the seam used by
AssetLibrary to decode AudioAssets and by the audio
components (AudioSource, Music) to construct playback
voices.
AudioEngine is auto-attached to every Game and reached through
Game.audio. It lives at the game tier, not the world tier, because
the audio context outlives any individual world: ramping a volume slider
in the pause menu, or letting a music track continue across a world
transition, both depend on a single long-lived context.
Graph layout
[BufferSource] → [Gain] → [Panner] ─┐ ┌─→ master ─→ destination
├─→ music gain ────┤
[BufferSource] → [Gain] → [Panner] ─┘ │
[BufferSource] → [Gain] → [Panner] ─┐ │
├─→ sfx gain ──────┘
[BufferSource] → [Gain] → [Panner] ─┘Per-instance gain and panner nodes are managed by AudioInstance;
this class owns the three category buses and the connection from each bus
up to context.destination. Adjusting AudioEngine.masterVolume,
AudioEngine.musicVolume, or AudioEngine.sfxVolume ramps
the corresponding GainNode instantly and affects every instance routed
through it.
Headless mode
If the runtime has no AudioContext constructor — node, jsdom, an
audio: { disabled: true } opt-out — the engine enters headless mode.
In headless mode, AudioEngine.available returns false, the gain
nodes are null, and every AudioInstance this engine produces is
an inert no-op. This is the same model the Mouse and
Keyboard samplers use to keep tests and SSR builds compiling and
running without the page-scoped browser globals their production paths
need.
Autoplay policy
Browsers suspend a freshly-created AudioContext until the page has
received a user gesture. AudioEngine.resume is a thin wrapper
over context.resume() for callers that want to hook the first click /
key press explicitly; the engine does not silently resume the context on
its own, because doing so on every play() would mask legitimate
suspension cases (a user-paused tab).
Constructors
constructor(host: Game, options: AudioEngineOptions): AudioEngine Parameters
-
hostGame -
optionsAudioEngineOptions
Properties
enabled: boolean Per-component gate on the three update hooks (onPreUpdate,
onUpdate, onPostUpdate). When explicitly false, the engine skips
all three for this component during the host's tick — useful for
temporarily pausing behaviour (e.g. a freeze powerup) without removing
the component and losing its internal state.
Does not gate onAdded or onDestroy; those always fire so a host can
never end up with a half-attached component.
Accessors
available: boolean Whether audio is actually available in this runtime — true when a
Web Audio context exists, false in headless mode. Branch on this if
you want to skip work (e.g. don't even load audio assets) when the
environment can't play them anyway.
game: Game The Game this component is attached to — identical to
AbstractComponent.host at this tier, exposed under the
game name so subclass code reads the same on every tier.
masterVolume: number Master volume from 0 to 1. The single knob at the bottom of the
audio graph; every category bus and every instance routes through it.
Setting this in headless mode silently retains the value but has no
audible effect.
musicVolume: number Music-bus volume from 0 to 1. Multiplied with the master gain at
playback time; affects every Music instance and any other
playback routed through AudioCategory.Music.
raw: null | AudioContext Direct access to the underlying Web Audio AudioContext, or null in
headless mode.
Use with care. raw is an intentional escape hatch for cases the
arcade2d API doesn't cover — custom nodes (filters, reverbs,
analysers), advanced scheduling against context.currentTime, anything
we haven't decided how to model yet. Code that touches raw is coupled
to the Web Audio API and may break when arcade2d swaps the audio
backend.
None of those will be treated as breaking changes to arcade2d's own
surface. Prefer the typed methods on this component; reach for raw
only when no equivalent exists, and isolate the access behind your own
helper so the coupling is in one place.
sfxVolume: number SFX-bus volume from 0 to 1. Multiplied with the master gain at
playback time; affects every AudioSource voice and any other
playback routed through AudioCategory.Sfx.
Methods
createInstance(buffer: AudioBuffer, category: AudioCategory, options: AudioInstanceOptions): AudioInstance Constructs a fresh AudioInstance routed through the requested
AudioCategory. The instance is created in the idle state —
call AudioInstance.play to start playback.
Parameters
-
bufferAudioBuffer -
categoryAudioCategory -
optionsAudioInstanceOptions
decodeAudioData(bytes: ArrayBuffer): Promise<AudioBuffer> Decodes an ArrayBuffer of encoded audio bytes into a Web Audio
AudioBuffer using the engine's context. This is the seam
AssetLibrary uses to decode AudioAssets after fetching
the raw bytes — exposed so tests and advanced callers can decode bytes
they obtained through other means (e.g. a recorder, a custom loader).
Parameters
-
bytesArrayBuffer
Returns
Promise<AudioBuffer> A promise resolving to the decoded buffer.
Throws
EngineError with code ErrorCode.AUDIO_UNAVAILABLE when the engine is in headless mode.
loadAudioBuffer(path: string): Promise<AudioBuffer> Convenience wrapper around fetch + AudioEngine.decodeAudioData:
loads the bytes at path and returns the decoded buffer.
This is the path AssetLibrary takes for audio asset loads. Most game code uses AssetLibrary.load (or a bundle) and never reaches for this directly; reach for it when you need a one-off decode outside the asset library's lifecycle model.
Parameters
-
pathstring
Returns
Promise<AudioBuffer> The decoded AudioBuffer.
Throws
EngineError with code ErrorCode.AUDIO_UNAVAILABLE when the engine is in headless mode.
Any error fetch or decodeAudioData produces. Callers that
want the engine-error wrapping go through AssetLibrary.load.
onAdded(_deps: Record): void Lifecycle hook that is called when the component is added to the host object. Should not be called directly.
Parameters
-
_depsRecord
Returns
void onDestroy(): void Closes the underlying AudioContext and drops the engine's
references. Idempotent; safe to call multiple times. Invoked
automatically when the Game tears down.
Returns
void onUpdate(_update: WorldUpdate, _deps: Record): void Lifecycle hook for the main update phase. Called once per world
tick, after every component's onPreUpdate and before any
onPostUpdate.
Parameters
-
_updateWorldUpdate -
_depsRecord
Returns
void resume(): Promise<void> Resumes the underlying AudioContext if browser autoplay policy
suspended it. Returns a promise that resolves once the context is
running (or immediately if it never needed resuming, or if the engine
is headless).
Most games hook this to their first user input — a "Click to start"
splash, the first key press, the pause-menu close button. Doing it on
every play() is wasteful and masks legitimate suspension states.
Returns
Promise<void> suspend(): Promise<void> Suspends the underlying AudioContext. Use this when the game loses
focus or the player pauses — every running source freezes in place and
resumes audibly seamless on the next AudioEngine.resume.
No-op in headless mode.
Returns
Promise<void>