Skip to content

Audio

vgai audio is built on the Web Audio API via Three.js’s AudioListener. Setup (packages/engine/src/setup/setup-audio.ts) creates the listener, a master gain, and three category buses.

music, sfx, and voice buses feed a master gain into the listener; spatial sounds attenuate with distance
The bus hierarchy and spatial attenuation.

The AudioContext provides:

  • listener — a THREE.AudioListener (attached to the camera, so spatial audio is heard from the camera’s position).
  • masterGain — the final gain node everything routes through.
  • buses — music, sfx, and voice gain nodes, each independently adjustable.
  • resume() — call on the first user interaction; browsers start the audio context suspended until a gesture.

An entity plays sound via the audio schema (packages/engine/src/adapter/system-adapter.ts):

FieldMeaning
srcpath to a WAV / MP3 / OGG file
spatialenable 3D positional audio
volume0–1 playback volume
refDistancedistance at which attenuation begins (spatial only)
rolloffFactorhow fast volume falls with distance (spatial only)
maxDistancedistance beyond which it’s inaudible (spatial only)
looploop the clip
autoplaystart when the scene loads

For spatial sounds, refDistance / rolloffFactor / maxDistance shape the falloff curve; non-spatial sounds play at a constant volume (UI, music).

The engine ships no sound generators and no Tone.js. Write procedural sound with the Web Audio API directly, or add the music capability for Tone.js:

Terminal window
vgai add music

That copies src/lib/music/ into your project and adds tone to your dependencies. It gives you the context bridge (so Tone and Three share one AudioContext and one master gain), a transport locked to the canonical AnimationClock, deterministic offline render for cinematics, and the transport readout in the editor’s Audio panel. Scores are written with Tone’s own API — new Tone.Synth(), Tone.getTransport() — and the copied source is yours to edit.

Why it is not in the engine: the engine holds the editor’s adapter interfaces and nothing else, so what you must learn stays small and everything else is ordinary library code you can read in your own project.

AudioBufferSourceNode and OscillatorNode are single-use. A rapid-fire sound path therefore owns a bounded list of active voices, removes each voice on ended, and stops the oldest before starting a new one when the list is full. Do not build an engine sound manager around that rule. The readable project implementation is examples/first-person/src/lib/audio/sfx.ts (createGameSfx({ maxVoices })), including truthful active-voice nodes in its registered Audio adapter.

Ducking is ordinary AudioParam automation on the bus that should yield. The same file’s createGainDucker(context, gain, options) helper handles repeated triggers by cancelling the previous automation, holding the current value, and restoring the authored resting gain. The project still owns the real GainNode and can continue using the Web Audio API directly.