Three.js Audio — Positional and Directional Sound in 3D
In this tutorial, you will learn about Three.js Audio. We cover key concepts, practical examples, and best practices to help you master this topic.
Three.js audio creates spatialized sound in 3D scenes using the Web Audio API, with positional audio sources that get louder or softer based on listener distance and direction.
What You'll Learn
By the end of this guide, you will create a positional audio listener, attach audio sources to 3D objects, configure directional cones, control volume and playback, and build an immersive audio scene.
Why 3D Audio Matters
Sound is half the experience in 3D. In Durga Antivirus Pro, audio cues indicate threat proximity — a low hum that intensifies as malware approaches, or a directional alert that tells analysts where to look. Three.js audio makes this possible with just a few lines of code.
flowchart LR
A[Audio File] --> B[Audio Buffer]
B --> C[Positional Audio Source]
D[3D Object] --> C
E[Listener on Camera] --> F[Distance Calculation]
C --> F
F --> G[Spatialized Output]
Setting Up the Audio Listener
The AudioListener is attached to the camera. It simulates the user's ears.
var listener = new THREE.AudioListener();
camera.add(listener);
Why attach to the camera: The listener position and orientation determine how audio is spatialized. As the camera moves, the listener moves with it, creating the illusion that the user is inside the 3D space.
Creating a Positional Audio Source
var audioLoader = new THREE.AudioLoader();
var sound = new THREE.PositionalAudio(listener);
audioLoader.load('sounds/engine.mp3', function(buffer) {
sound.setBuffer(buffer);
sound.setRefDistance(20);
sound.setVolume(0.5);
sound.play();
});
mesh.add(sound);
Expected output: A 3D object with engine sound. As the camera approaches the object, the sound gets louder. As the camera moves away, the sound fades.
Why refDistance: This is the reference distance for volume rolloff. At this distance, the sound plays at its set volume. Closer is louder, farther is quieter.
Directional Audio Cones
sound.setDirectionalCone(120, 230, 0.1);
sound.setConeOuterGain(0.3);
// Assuming the mesh faces forward on -Z
sound.position.set(0, 0, 0);
// Later, rotate the sound source
mesh.rotation.y = Math.PI;
sound.updateMatrixWorld();
Expected output: The audio is loud when the listener is within the cone angle in front of the source, and quieter when behind it.
Why cones: Directional audio simulates real-world sound projection — a person talking only projects sound forward, not behind.
Audio Visualizer — Frequency Data
var analyser = new THREE.AudioAnalyser(sound, 32);
function animate() {
var data = analyser.getFrequencyData();
// data is an array of 32 frequency bins (0-255)
var average = data.reduce(function(a, b) { return a + b; }, 0) / data.length;
someMesh.scale.y = average / 128;
requestAnimationFrame(animate);
}
Expected output: A mesh that pulses and scales based on the audio's frequency spectrum, creating a visualizer effect.
Common Mistakes
1. AudioContext Not Resumed
Modern browsers require user interaction before playing audio. Handle this:
document.addEventListener('click', function() {
listener.context.resume();
});
2. Not Attaching Listener to Camera
Without camera.add(listener), the audio has no listener position and remains 2D.
3. Forgetting to Call updateMatrixWorld After Moving Audio Source
Three.js reads the world matrix for spatialization. If you move the source's parent without updating matrices, audio position lags.
4. Loading Large Audio Files Blocking the Main Thread
Load audio asynchronously with AudioLoader. For longer files, stream with HTML5 Audio element instead of loading into a buffer.
5. CORS Issues With External Audio Files
Audio loaded from external domains must have CORS headers. Host audio files on the same origin or use a CORS-enabled CDN.
Practice Questions
Q1: Why must the audio listener be attached to the camera? A: The listener position and orientation determine how spatial audio is calculated. Attaching it to the camera makes audio respond to the user's viewpoint.
Q2: What does setRefDistance control?
A: It sets the reference distance for volume rolloff. At this distance, volume is at the set level. Closer is louder, farther is quieter.
Q3: How do directional audio cones work? A: The inner cone plays full volume. Between inner and outer cone angles, volume fades to coneOuterGain. Outside the outer cone, sound plays at the outer gain level.
Q4: Why does audio not play on page load? A: Browsers require a user gesture (click, keypress) before creating or resuming an AudioContext for autoplay policy Compliance.
Q5: What does AudioAnalyser provide? A: It provides frequency data (amplitude per frequency bin) and average time-domain data for visualizations.
Challenge: Build a scene with three audio sources at different positions. Each plays a different instrument loop. The user walks between them with WASD controls and hears each instrument getting louder or softer based on proximity.
FAQ
Try It Yourself — 3D Audio Scene
Build a complete page with three audio sources placed at different positions. The listener follows the camera as the user orbits. Each source plays a different tone, and the volume changes with distance.
<!DOCTYPE html>
<html>
<head>
<title>3D Audio Scene</title>
<style>
body { margin: 0; overflow: hidden; font-family: sans-serif; }
#info { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.8); color: white; padding: 10px 20px; border-radius: 8px; font-size: 14px; }
#startBtn { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); padding: 20px 40px; font-size: 20px; cursor: pointer; z-index: 10; }
</style>
</head>
<body>
<button id="startBtn">Click to Start Audio</button>
<div id="info">Orbit the scene — audio changes with distance</div>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
var scene = new THREE.Scene();
scene.background = new THREE.Color(0x111122);
var camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 100);
camera.position.set(0, 5, 12);
var renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(devicePixelRatio);
document.body.appendChild(renderer.domElement);
var controls = new OrbitControls(camera, renderer.domElement);
var ambient = new THREE.AmbientLight(0x222244);
scene.add(ambient);
var hemi = new THREE.HemisphereLight(0x4488ff, 0x442200, 0.6);
scene.add(hemi);
// Audio setup
var listener = new THREE.AudioListener();
camera.add(listener);
var audioLoader = new THREE.AudioLoader();
var audioContext = listener.context;
// Create audio context from oscillator for tone generation
function createToneSource(frequency, label, color) {
var group = new THREE.Group();
var mesh = new THREE.Mesh(
new THREE.SphereGeometry(0.8, 32, 32),
new THREE.MeshStandardMaterial({ color: color, emissive: color, emissiveIntensity: 0.3 })
);
group.add(mesh);
var labelSprite = new THREE.Sprite(
new THREE.SpriteMaterial({ map: makeTextCanvas(label) })
);
labelSprite.position.y = 1.5;
labelSprite.scale.set(2, 1, 1);
group.add(labelSprite);
scene.add(group);
var sound = new THREE.PositionalAudio(listener);
group.add(sound);
return { group, sound, frequency };
}
function makeTextCanvas(text) {
var canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 64;
var ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgba(0,0,0,0.5)';
ctx.fillRect(0, 0, 256, 64);
ctx.fillStyle = 'white';
ctx.font = '24px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(text, 128, 42);
return new THREE.CanvasTexture(canvas);
}
var sources = [
createToneSource(220, 'Bass 220Hz', 0xff6b35),
createToneSource(440, 'Mid 440Hz', 0x4ecdc4),
createToneSource(880, 'Treble 880Hz', 0x45b7d1)
];
sources[0].group.position.set(-4, 1, 0);
sources[1].group.position.set(0, 1, -4);
sources[2].group.position.set(4, 1, 0);
var oscillators = [];
document.getElementById('startBtn').addEventListener('click', function() {
this.style.display = 'none';
sources.forEach(function(src) {
var osc = audioContext.createOscillator();
var gain = audioContext.createGain();
osc.type = 'sine';
osc.frequency.value = src.frequency;
gain.gain.value = 0.3;
// Connect to sound's input
var dest = src.sound.input;
osc.connect(gain);
gain.connect(dest);
osc.start();
oscillators.push({ osc, gain, sound: src.sound });
});
});
function animate() {
oscillators.forEach(function(oscData) {
oscData.sound.updateMatrixWorld();
});
sources[0].group.rotation.y += 0.01;
sources[1].group.rotation.x += 0.005;
sources[2].group.rotation.z += 0.008;
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
animate();
window.addEventListener("resize", function() {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
</script>
</body>
</html>
What's Next
Explore VR and AR with Three.js for immersive 3D experiences.
VR AR — WebXR with Three.js. Performance Optimization — Optimizing Three.js scenes.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro