The spatial audio plugin extends Howler.js with 3D positional audio capabilities and stereo panning, enabling immersive audio experiences with realistic spatial positioning, orientation, and distance-based attenuation.
Control stereo positioning of sounds across the left-right audio field.
/**
* Helper method to update stereo panning position globally
* @param pan - Panning value (-1.0 = full left, 0.0 = center, 1.0 = full right)
* @returns Howler instance for chaining
*/
Howler.stereo(pan: number): Howler;
/**
* Set stereo panning for specific sound or sound group
* @param pan - Panning value (-1.0 to 1.0)
* @param id - Sound ID (optional, affects all sounds in group if not provided)
* @returns Current panning if getting, Howl instance if setting
*/
Howl.prototype.stereo(pan: number, id?: number): Howl | number;Usage Examples:
import { Howl, Howler } from "howler";
// Global stereo panning for all sounds
Howler.stereo(-0.5); // Pan all sounds to the left
// Per-sound stereo panning
const leftSound = new Howl({
src: ['left.mp3']
});
const rightSound = new Howl({
src: ['right.mp3']
});
leftSound.stereo(-1.0); // Full left
rightSound.stereo(1.0); // Full right
// Control specific sound instances
const id = leftSound.play();
leftSound.stereo(0.5, id); // Pan this specific instance to the right
// Get current panning
const currentPan = leftSound.stereo(); // Returns numberSet the position of the audio listener in 3D space, affecting how all positioned sounds are heard.
/**
* Get/set the position of the listener in 3D cartesian space
* @param x - The x-position of the listener
* @param y - The y-position of the listener (optional)
* @param z - The z-position of the listener (optional)
* @returns Current position array if getting, Howler instance if setting
*/
Howler.pos(x: number, y?: number, z?: number): Howler | number[];Usage Examples:
// Set listener position
Howler.pos(0, 0, 0); // Center position
Howler.pos(10, 5, -2); // Move listener to specific coordinates
// Get current listener position
const listenerPos = Howler.pos(); // Returns [x, y, z] array
// Update position over time (e.g., player movement)
function updatePlayerPosition(x, y, z) {
Howler.pos(x, y, z);
}Control the direction the listener is facing and their up vector in 3D space.
/**
* Get/set the direction the listener is pointing in 3D space
* @param x - The x-orientation of the listener's front vector
* @param y - The y-orientation of the listener's front vector
* @param z - The z-orientation of the listener's front vector
* @param xUp - The x-orientation of the listener's up vector
* @param yUp - The y-orientation of the listener's up vector
* @param zUp - The z-orientation of the listener's up vector
* @returns Current orientation vectors if getting, Howler instance if setting
*/
Howler.orientation(x: number, y: number, z: number, xUp: number, yUp: number, zUp: number): Howler | number[];Usage Examples:
// Set listener to face forward with normal up vector
Howler.orientation(0, 0, -1, 0, 1, 0);
// Face right
Howler.orientation(1, 0, 0, 0, 1, 0);
// Get current orientation
const orientation = Howler.orientation(); // Returns [x, y, z, xUp, yUp, zUp]
// Rotate listener based on player camera
function updateListenerRotation(forward, up) {
Howler.orientation(forward.x, forward.y, forward.z, up.x, up.y, up.z);
}Position individual sounds in 3D space relative to the listener.
/**
* Set 3D position for sound or sound group
* @param x - The x-position of the sound
* @param y - The y-position of the sound (optional)
* @param z - The z-position of the sound (optional)
* @param id - Sound ID (optional, affects all sounds in group if not provided)
* @returns Current position if getting, Howl instance if setting
*/
Howl.prototype.pos(x: number, y?: number, z?: number, id?: number): Howl | number[];Usage Examples:
const ambientSound = new Howl({
src: ['forest.mp3'],
loop: true
});
const footsteps = new Howl({
src: ['footsteps.mp3']
});
// Position sounds in 3D space
ambientSound.pos(0, 0, 0); // Center
footsteps.pos(-5, 0, 10); // To the left and forward
// Position specific sound instance
const stepId = footsteps.play();
footsteps.pos(2, 0, 5, stepId); // Move this specific footstep
// Get current position
const soundPos = ambientSound.pos(); // Returns [x, y, z]
// Animate sound movement
let x = -10;
setInterval(() => {
x += 0.1;
footsteps.pos(x, 0, 0); // Sound moves from left to right
}, 100);Set the directional orientation of sounds for directional audio effects.
/**
* Set 3D orientation for sound
* @param x - The x-orientation of the sound's front vector
* @param y - The y-orientation of the sound's front vector
* @param z - The z-orientation of the sound's front vector
* @param id - Sound ID (optional, affects all sounds in group if not provided)
* @returns Current orientation if getting, Howl instance if setting
*/
Howl.prototype.orientation(x: number, y: number, z: number, id?: number): Howl | number[];Configure detailed 3D audio properties for realistic sound attenuation and directional effects.
/**
* Set 3D panner attributes for realistic audio positioning
* @param o - Panner attributes object (optional for getting current attributes)
* @param id - Sound ID (optional, affects all sounds in group if not provided)
* @returns Current attributes if getting, Howl instance if setting
*/
Howl.prototype.pannerAttr(o?: PannerAttributes, id?: number): Howl | PannerAttributes;
interface PannerAttributes {
/** Inner cone angle in degrees (default: 360) */
coneInnerAngle?: number;
/** Outer cone angle in degrees (default: 360) */
coneOuterAngle?: number;
/** Gain outside the outer cone (0.0-1.0, default: 0) */
coneOuterGain?: number;
/** Distance model for attenuation ('inverse' | 'linear' | 'exponential') */
distanceModel?: 'inverse' | 'linear' | 'exponential';
/** Maximum distance for attenuation (default: 10000) */
maxDistance?: number;
/** Panning model ('equalpower' | 'HRTF') */
panningModel?: 'equalpower' | 'HRTF';
/** Reference distance for attenuation (default: 1) */
refDistance?: number;
/** Rolloff factor for attenuation (default: 1) */
rolloffFactor?: number;
}Usage Examples:
const directionalSound = new Howl({
src: ['speaker.mp3'],
loop: true
});
// Configure realistic speaker characteristics
directionalSound.pannerAttr({
coneInnerAngle: 40, // 40-degree inner cone
coneOuterAngle: 120, // 120-degree outer cone
coneOuterGain: 0.3, // 30% volume outside cone
distanceModel: 'inverse', // Realistic distance falloff
maxDistance: 100, // Inaudible beyond 100 units
refDistance: 1, // Full volume at 1 unit
rolloffFactor: 2, // Faster distance falloff
panningModel: 'HRTF' // Head-related transfer function
});
// Position and orient the directional sound
directionalSound.pos(0, 0, 10); // 10 units forward
directionalSound.orientation(0, 0, -1); // Pointing toward listener
// Get current panner attributes
const attrs = directionalSound.pannerAttr();
console.log(attrs.coneInnerAngle); // 40class GameAudio {
constructor() {
this.sounds = new Map();
// Initialize listener at player position
Howler.pos(0, 0, 0);
Howler.orientation(0, 0, -1, 0, 1, 0);
}
createPositionalSound(name, src, loop = false) {
const sound = new Howl({
src: src,
loop: loop
});
// Configure for realistic 3D audio
sound.pannerAttr({
distanceModel: 'inverse',
maxDistance: 50,
refDistance: 1,
rolloffFactor: 1.5
});
this.sounds.set(name, sound);
return sound;
}
updatePlayerPosition(x, y, z, forwardX, forwardY, forwardZ) {
Howler.pos(x, y, z);
Howler.orientation(forwardX, forwardY, forwardZ, 0, 1, 0);
}
playAt(soundName, x, y, z) {
const sound = this.sounds.get(soundName);
if (sound) {
sound.pos(x, y, z);
return sound.play();
}
}
}
// Usage
const gameAudio = new GameAudio();
gameAudio.createPositionalSound('gunshot', ['gunshot.mp3']);
gameAudio.createPositionalSound('footsteps', ['footsteps.mp3']);
// Play sound at specific location
gameAudio.playAt('gunshot', -10, 0, 5);// Create moving stereo effect
const movingSound = new Howl({
src: ['car.mp3'],
loop: true
});
// Animate from left to right
let pan = -1.0;
const moveTimer = setInterval(() => {
movingSound.stereo(pan);
pan += 0.1;
if (pan > 1.0) {
pan = -1.0; // Reset to left
}
}, 100);
movingSound.play();class AudioZone {
constructor(sound, centerX, centerZ, radius, maxVolume = 1.0) {
this.sound = sound;
this.centerX = centerX;
this.centerZ = centerZ;
this.radius = radius;
this.maxVolume = maxVolume;
this.soundId = null;
}
updateVolume(listenerX, listenerZ) {
const distance = Math.sqrt(
Math.pow(listenerX - this.centerX, 2) +
Math.pow(listenerZ - this.centerZ, 2)
);
if (distance <= this.radius) {
const volume = this.maxVolume * (1 - (distance / this.radius));
if (!this.soundId) {
this.soundId = this.sound.play();
}
this.sound.volume(volume, this.soundId);
} else if (this.soundId) {
this.sound.stop(this.soundId);
this.soundId = null;
}
}
}
// Create environmental zones
const forestZone = new AudioZone(
new Howl({ src: ['forest.mp3'], loop: true }),
0, 0, 20, 0.8
);
const riverZone = new AudioZone(
new Howl({ src: ['river.mp3'], loop: true }),
30, 10, 15, 0.6
);
// Update as player moves
function updateEnvironmentalAudio(playerX, playerZ) {
forestZone.updateVolume(playerX, playerZ);
riverZone.updateVolume(playerX, playerZ);
}Spatial audio features require Web Audio API support:
Graceful Degradation:
// Check for spatial audio support
if (Howler.usingWebAudio && Howler.ctx && Howler.ctx.listener) {
// Use full spatial audio features
sound.pos(x, y, z);
sound.pannerAttr({ panningModel: 'HRTF' });
} else {
// Fallback to stereo panning only
const pan = (x > 0) ? Math.min(x / 10, 1) : Math.max(x / 10, -1);
sound.stereo(pan);
}