Skip to content

Audio

Awaken plays sound through a single lightweight WebAudio backend: attach an AudioSource to an object to play a clip when the game starts, or trigger sounds on demand from a script with api.playSound.

The model

Audio in Awaken is deliberately small. There are two ways sound reaches your player's speakers:

  • AudioSource component - a "play this clip when the scene starts" flag you attach to an object in the editor. Good for ambience, music, or a one-shot that fires on spawn.
  • api.playSound(clip, opts?) - a script call that plays a clip at any moment (a jump, a pickup, a hit). This is how most gameplay audio works.

Both funnel into the same AudioEngine interface, a tiny pluggable backend whose only method is play(clip, opts?). The editor's Game view and the shipped player each provide a browser WebAudioEngine (WebAudio API); headless contexts and tests use a NoopAudioEngine that does nothing. Because the runtime only ever talks to the AudioEngine interface, gameplay logic stays fully testable without a browser.

The AudioSource component

Add AudioSource from the Inspector's + Add Component menu. It has four fields:

FieldTypeDefaultMeaning
clipasset ref"beep"Which audio clip to play (an imported clip id, or the built-in beep).
volumenumber1Linear gain, 01.
loopboolfalseRepeat the clip until the scene ends.
playOnStartbooltruePlay automatically the moment Play begins.

When you enter Play, the runtime walks every object that has both a Transform and an enabled AudioSource, and for each one whose playOnStart is set it calls play(clip, { volume, loop }). A disabled component (its enabled checkbox off) is skipped. That is the entire lifecycle - an AudioSource fires once at start; there is no built-in "play again" trigger on the component itself. For repeated or conditional sounds, drive audio from a script instead.

📸 Screenshot - save as img/media-audiosource-inspector.png

The Inspector showing an AudioSource component with its clip, volume, loop, and playOnStart fields on a selected object.

Where clips come from

The engine ships with one built-in sound, the beep - a short 440 Hz tone the WebAudioEngine synthesises on the fly. It is also the fallback for any clip id: name a clip the engine has not loaded and it plays the beep rather than failing silently. That is why AudioSource.clip defaults to "beep".

No audio import yet

There is currently no audio-import pipeline. No loader decodes .wav / .mp3 / .ogg files into the engine, and nothing calls the engine's addClip(id, buffer) method - so the beep is, in practice, the only sound Awaken can play today. Whatever string you put in an AudioSource.clip field or pass to playSound, it resolves to the beep. The editor's WebAudioEngine already contains a buffer-playback path (an addClip method backed by a decoded-AudioBuffer map) that is ready for the day clip import lands; the shipped player's engine is beep-only by construction.

Triggering sounds from scripts

Inside a script, call api.playSound:

ts
export default class Coin implements ScriptBehavior {
  onStart(api: ScriptApi) {
    // Play the built-in beep at half volume when this coin spawns.
    api.playSound("beep", { volume: 0.5 });
  }

  onUpdate(api: ScriptApi) {
    if (api.input.isDown(" ")) {     // spacebar is the " " key
      api.playSound("jump");         // any id resolves to the beep until audio import lands
    }
  }
}

The signature is playSound(clip: string, opts?: { volume?: number; loop?: boolean }). Both options are optional - omit them for a one-shot at full volume. See the full surface on the Scripting API page.

Playback is fire-and-forget: playSound returns nothing, so there is no handle to stop an individual sound later. A looping sound started this way plays until the scene changes or the game ends.

How the WebAudio engine works

The WebAudioEngine (apps/editor/src/audio/webAudio.ts) is a thin wrapper over the browser's WebAudio API:

  • It lazily creates a single AudioContext. Because browsers block audio until a user gesture, the engine listens for the first pointerdown or keydown and resumes the context then. Sounds that "fire" before the player has clicked or pressed a key stay silent until that first interaction.
  • addClip(name, buffer) stores decoded AudioBuffers in a map keyed by clip id.
  • play(clip, opts) looks the clip up. On a hit it wires a BufferSource → GainNode → destination graph, sets loop and gain.value from the options, and starts it. On a miss it plays the beep.

Because nothing currently calls addClip, the buffer map is always empty, so the "clip loaded" branch below is dormant and every play takes the beep path today. The shipped player uses a simpler WebAudioEngine with no buffer map at all - it always synthesises the beep.

Limitations (be honest)

Awaken audio is intentionally minimal for now:

  • No imported audio. There is no audio-import pipeline yet, and the shipped player's engine is beep-only by construction - so the built-in 440 Hz beep is currently the only sound you can actually hear. Any other clip id falls back to it.
  • 2D only. There is no positional / 3D audio - no distance falloff, no panning by object position. volume is a flat gain applied to every listener.
  • No stop / pause API. A started sound plays to its end (or forever, if looping). Looping sources stop only when the scene changes or the game ends.
  • No mixing groups or effects. Each play builds an independent source → gain → destination chain.

If you need one of these, script your gameplay around the fire-and-forget model (for example, keep looping ambience on an object you destroy to "stop" it).

See also

Awaken — browser-native WebGPU game engine.