Skip to main content

Spatial Audio

Spatial audio (3D audio) allows you to position sounds in a three-dimensional space, creating an immersive audio experience. The Sound Manager supports the Web Audio API's spatial audio capabilities.

Types and Interfaces

PanningModel

The PanningModel determines how the sound is spatialized in 3D space.

export enum PanningModel {
EQUALPOWER = 'equalpower',
HRTF = 'HRTF',
}
ModelDescription
equalpowerA simple and efficient panning algorithm. Good for basic 3D positioning.
HRTFHead-Related Transfer Function. More realistic 3D audio using convolution of impulse responses from a human head. More computationally expensive but provides better directional perception.

DistanceModel

The DistanceModel determines how the sound volume attenuates as the listener moves away from the sound source.

export enum DistanceModel {
LINEAR = 'linear',
INVERSE = 'inverse',
EXPONENTIAL = 'exponential',
}
ModelDescription
linearVolume decreases linearly with distance. Uses refDistance and maxDistance.
inverseVolume decreases inversely with distance. More natural sounding. Uses refDistance and rolloffFactor.
exponentialVolume decreases exponentially with distance. Uses refDistance and rolloffFactor.

SoundPannerConfig

The SoundPannerConfig interface configures the panner node for spatial audio.

export interface SoundPannerConfig {
coneInnerAngle?: number; // Inner cone angle in degrees (default: 360)
coneOuterAngle?: number; // Outer cone angle in degrees (default: 360)
coneOuterGain?: number; // Gain outside the outer cone (default: 0)
distanceModel?: DistanceModel; // DistanceModel.LINEAR, .INVERSE, or .EXPONENTIAL
maxDistance?: number; // Maximum distance for linear model (default: 10000)
panningModel?: PanningModel; // PanningModel.EQUALPOWER or .HRTF
refDistance?: number; // Reference distance for attenuation (default: 1)
rolloffFactor?: number; // How quickly volume decreases (default: 1)
}

Checking Spatial Audio Support

const mySoundManager = new SoundManager();

// Check if spatial audio is supported in the current environment
if (mySoundManager.isSpatialAudioSupported()) {
console.log('Spatial audio is supported!');
} else {
console.log('Spatial audio is not supported in this browser.');
}

// Check if spatial audio is enabled
if (mySoundManager.isSpatialAudioEnabled()) {
console.log('Spatial audio is enabled.');
}

Setting Spatial Position for a Sound

const mySoundManager = new SoundManager();
await mySoundManager.loadSound('gunshot', '/audio/gunshot.mp3');

// Play a sound with spatial audio
mySoundManager.play('gunshot', {
panType: SoundPanType.SPATIAL,
panSpatialPosition: { x: 5, y: 0, z: -10 },
pannerConfig: {
panningModel: 'HRTF',
distanceModel: 'inverse',
refDistance: 1,
maxDistance: 50,
rolloffFactor: 2,
},
});

// Update the spatial position dynamically
mySoundManager.setSpatialPosition(10, 0, -20, 'gunshot');

// Get the current spatial position
const position = mySoundManager.getSpatialPosition('gunshot');
console.log('Current position:', position); // { x: 10, y: 0, z: -20 }

Master Spatial Position

Set a global spatial position that affects all sounds without individual spatial positions:

const mySoundManager = new SoundManager();

// Set the master (listener) spatial position
mySoundManager.setMasterSpatialPosition(0, 0, 0, {
panningModel: 'HRTF',
distanceModel: 'linear',
refDistance: 1,
maxDistance: 100,
rolloffFactor: 1,
});

// Get the current master position
const masterPos = mySoundManager.getMasterSpatialPosition();
console.log('Master position:', masterPos);

// Reset the master position to default (0, 0, 0)
mySoundManager.resetMasterSpatialPosition();

Updating Panner Configuration

const mySoundManager = new SoundManager();
await mySoundManager.loadSound('ambient', '/audio/ambient.mp3');

// Play with initial panner config
mySoundManager.play('ambient', {
panType: SoundPanType.SPATIAL,
panSpatialPosition: { x: 0, y: 0, z: -5 },
pannerConfig: {
panningModel: 'equalpower',
distanceModel: 'linear',
refDistance: 1,
maxDistance: 30,
},
});

// Update the panner configuration later
mySoundManager.updatePannerConfigById('ambient', {
panningModel: 'HRTF',
rolloffFactor: 3,
});

Removing Spatial Effects

const mySoundManager = new SoundManager();
await mySoundManager.loadSound('effect', '/audio/effect.mp3');

mySoundManager.play('effect', {
panType: SoundPanType.SPATIAL,
panSpatialPosition: { x: 5, y: 0, z: -5 },
});

// Remove spatial effect and revert to normal stereo playback
mySoundManager.removeSpatialEffect('effect');

Practical Example: Moving Sound Source

const mySoundManager = new SoundManager();
await mySoundManager.loadSound('fly', '/audio/fly-buzz.mp3');

// Play a buzzing fly with spatial audio
mySoundManager.play('fly', {
panType: SoundPanType.SPATIAL,
panSpatialPosition: { x: 0, y: 0, z: -3 },
pannerConfig: {
panningModel: 'HRTF',
distanceModel: 'inverse',
refDistance: 0.5,
maxDistance: 10,
rolloffFactor: 2,
},
loop: true,
});

// Animate the fly circling around the listener
let angle = 0;
const radius = 3;

const flyInterval = setInterval(() => {
angle += 0.05;
const x = Math.cos(angle) * radius;
const z = Math.sin(angle) * radius - 3;

mySoundManager.setSpatialPosition(x, 0.5, z, 'fly');
}, 100);

// After 10 seconds, stop the fly
setTimeout(() => {
clearInterval(flyInterval);
mySoundManager.stop('fly');
}, 10000);

Practical Example: Weather Sound Environment

const mySoundManager = new SoundManager();
await mySoundManager.loadSounds([
{ id: 'techno-tune', url: '/audio/techno-tune.mp3' },
{ id: 'thunder', url: '/audio/thunder.mp3' },
{ id: 'wind', url: '/audio/wind.mp3' },
]);

// Create a techno-tune sound that surrounds the listener
mySoundManager.play('techno-tune', {
panType: SoundPanType.SPATIAL,
panSpatialPosition: { x: 0, y: 5, z: -10 },
pannerConfig: {
panningModel: 'equalpower',
distanceModel: 'linear',
refDistance: 5,
maxDistance: 30,
},
loop: true,
volume: 0.4,
});

// Wind from the left
mySoundManager.play('wind', {
panType: SoundPanType.SPATIAL,
panSpatialPosition: { x: -15, y: 3, z: 0 },
pannerConfig: {
panningModel: 'equalpower',
distanceModel: 'inverse',
refDistance: 1,
maxDistance: 40,
rolloffFactor: 1.5,
},
loop: true,
volume: 0.3,
});

// Play thunder at a distant position
setTimeout(() => {
mySoundManager.play('thunder', {
panType: SoundPanType.SPATIAL,
panSpatialPosition: { x: 20, y: 10, z: -30 },
pannerConfig: {
panningModel: 'HRTF',
distanceModel: 'exponential',
refDistance: 10,
maxDistance: 100,
rolloffFactor: 2,
},
volume: 0.8,
});
}, 3000);
X: 0.0|Y: 0.0|Z: -5.0
X (left → right)0.0
-1010
Y (down → up)0.0
-1010
Z (front → back)-5.0
-205
Try it: Play the helicopter sound and drag the X, Y, Z sliders to position the sound in 3D space. Click Auto-Rotate to make the sound circle around the listener — demonstrating dynamic spatial audio.