| name | threejs-audio |
| description | Adds spatial and non-spatial audio to Three.js scenes using AudioListener, Audio, PositionalAudio, AudioAnalyser, and AudioLoader. Use when the user asks about 3D audio, spatial sound, positional audio, audio visualization, background music, or Web Audio API integration. Trigger keywords: audio, sound, PositionalAudio, AudioListener, AudioAnalyser, spatial audio, 3D sound, music visualization. |
Three.js Audio
Setup — AudioListener
const listener = new THREE.AudioListener();
camera.add(listener);
Global (Non-Spatial) Audio
const sound = new THREE.Audio(listener);
const loader = new THREE.AudioLoader();
const buffer = await loader.loadAsync("/audio/music.mp3");
sound.setBuffer(buffer);
sound.setLoop(true);
sound.setVolume(0.5);
document.addEventListener("click", () => sound.play(), { once: true });
sound.pause();
sound.stop();
sound.isPlaying;
sound.offset = 10;
sound.playbackRate = 1.5;
Positional (Spatial) Audio
const posSound = new THREE.PositionalAudio(listener);
const buffer = await new THREE.AudioLoader().loadAsync("/audio/engine.mp3");
posSound.setBuffer(buffer);
posSound.setLoop(true);
posSound.setRefDistance(2);
posSound.setRolloffFactor(1);
posSound.setDistanceModel("inverse");
posSound.setMaxDistance(50);
posSound.setDirectionalCone(180, 360, 0.1);
mesh.add(posSound);
document.addEventListener("click", () => posSound.play(), { once: true });
AudioAnalyser (visualization)
const analyser = new THREE.AudioAnalyser(sound, 32);
function animate() {
requestAnimationFrame(animate);
const data = analyser.getFrequencyData();
const avg = analyser.getAverageFrequency();
mesh.scale.setScalar(1 + avg / 255);
for (let i = 0; i < data.length; i++) {
bars[i].scale.y = (data[i] / 255) * maxHeight;
}
renderer.render(scene, camera);
}
Web Audio API (direct access)
const audioContext = listener.context;
const filter = audioContext.createBiquadFilter();
filter.type = "lowpass";
filter.frequency.value = 1000;
sound.setFilter(filter);
sound.setVolume(0.8);
const gainNode = sound.gain;
gainNode.gain.setValueAtTime(0, audioContext.currentTime);
gainNode.gain.linearRampToValueAtTime(1, audioContext.currentTime + 2);
Multiple Sounds / Preloading
const manager = new THREE.LoadingManager();
const loader = new THREE.AudioLoader(manager);
const sounds = {};
sounds.jump = await loader.loadAsync("/audio/jump.wav");
sounds.land = await loader.loadAsync("/audio/land.wav");
sounds.music = await loader.loadAsync("/audio/bg.mp3");
function playJump() {
const s = new THREE.Audio(listener);
s.setBuffer(sounds.jump);
s.play();
s.onEnded = () => s.disconnect();
}
Common Gotchas
- Browsers block autoplay — always start audio inside a user gesture handler (
click, touchstart)
- One
AudioListener per scene — multiple listeners cause unexpected behavior
PositionalAudio must be added to an Object3D (mesh, group) to be positioned
AudioAnalyser fftSize must be a power of 2 between 32 and 32768 — data array length = fftSize / 2
sound.stop() resets playback position — sound.pause() preserves it
- Use
new THREE.Audio(listener) for one-shot effects (not reusing the same Audio object)
- Audio context may be in 'suspended' state until first user interaction — check
listener.context.state