Skip to content

BabylonJS - Playing Sounds & Music

Audio is a critical component for creating immersive games and applications. Babylon.js integrates with the browser’s Web Audio API, providing a Sound class to easily load, play, and manage sound effects and background music. You can trigger sounds based on events (like collisions, button clicks, character actions) or play ambient tracks.

The Babylon.js sound engine supports common audio formats like MP3, WAV, Ogg, etc. (support depends on the browser). It offers features like looping, autoplay, volume control, playback rate adjustment, and spatial audio (positional sound).

You create a Sound object by providing a name, the URL to the audio file, the scene, an optional callback function that executes when the sound is loaded and ready to play, and an options object.

import * as BABYLON from '@babylonjs/core';
// Assuming 'scene' is defined
// Define sound options
const soundOptions = {
loop: true, // Repeat the sound indefinitely
autoplay: true, // Start playing as soon as loaded
volume: 0.8 // Volume level (0.0 to 1.0+)
// playbackRate: 1.0 // Speed of playback
};
// Callback function executed when sound is ready
const soundReadyCallback = () => {
console.log("Background music ready and playing.");
// You could potentially call music.play() here if autoplay was false
};
// Create the sound object
const backgroundMusic = new BABYLON.Sound(
"bgMusic", // Name for the sound
"sounds/ambient_track.mp3", // Path to the audio file
scene, // The scene to attach to
soundReadyCallback, // Function to call when ready
soundOptions // Options object
);
  • name: A unique string identifier for the sound.
  • url: The path or URL to the audio file.
  • scene: The Babylon.js scene instance.
  • readyToPlayCallback: (Optional) A function called once the sound has loaded and decoded.
  • options: (Optional) An object containing properties like:
    • loop (boolean): Whether the sound should loop.
    • autoplay (boolean): Whether the sound should play automatically when ready.
    • volume (number): The volume (0.0 to 1.0 typical range, can go higher).
    • playbackRate (number): The speed at which the sound plays (1.0 is normal speed).
    • spatialSound (boolean): Enables 3D spatialization (positional audio). Defaults to false.
    • distanceModel (string): How volume attenuates with distance (‘linear’, ‘inverse’, ‘exponential’). Default ‘linear’. Used with spatialSound.
    • maxDistance (number): The distance beyond which the sound is no longer audible (for ‘linear’ model) or significantly attenuated. Default 100. Used with spatialSound.
    • rolloffFactor (number): How quickly the sound attenuates for ‘inverse’ and ‘exponential’ models. Default 1.
    • refDistance (number): Reference distance for volume reduction calculations. Default 1.
    • panningModel (string): Algorithm for stereo panning (‘equalpower’ or ‘HRTF’). Default ‘equalpower’.
    • useCustomAttenuation (boolean): Allows defining a custom attenuation function.
    • customAttenuationFunction (function): (currentVolume, currentDistance, maxDistance, refDistance, rolloffFactor) => newVolume.

Make sure you have a sounds folder in your project directory containing the specified audio files (e.g., ambient_track.mp3, explosion.wav).

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Babylon.js - Background Music</title>
<script src="https://cdn.babylonjs.com/babylon.js"></script>
<style>
html, body { overflow: hidden; width: 100%; height: 100%; margin: 0; padding: 0; }
#renderCanvas { width: 100%; height: 100%; touch-action: none; }
#info { position: absolute; top: 10px; left: 10px; color: white; background-color: rgba(0,0,0,0.5); padding: 5px; }
</style>
</head>
<body>
<canvas id="renderCanvas"></canvas>
<div id="info">Loading Scene and Sound...</div>
<script type="text/javascript">
const canvas = document.getElementById("renderCanvas");
const engine = new BABYLON.Engine(canvas, true);
const infoDiv = document.getElementById("info");
const createScene = function () {
const scene = new BABYLON.Scene(engine);
scene.clearColor = new BABYLON.Color4(0, 0.1, 0.2, 1.0);
const camera = new BABYLON.ArcRotateCamera("Camera", -Math.PI / 2, Math.PI / 2.5, 5, BABYLON.Vector3.Zero(), scene);
camera.attachControl(canvas, true);
const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
// Simple box to look at
const box = BABYLON.MeshBuilder.CreateBox("box", {}, scene);
// --- Sound Setup ---
const soundOptions = {
loop: true,
autoplay: true,
volume: 0.5
};
infoDiv.textContent = "Loading sound: sounds/ambient_track.mp3";
let backgroundMusic = null;
try {
backgroundMusic = new BABYLON.Sound("bgMusic", "sounds/ambient_track.mp3", scene,
() => {
// Sound is ready to play (or already playing due to autoplay)
infoDiv.textContent = "Scene Ready. Background Music Playing.";
console.log("Music loaded and ready.");
},
soundOptions
);
// Handle potential loading/decoding errors after creation
backgroundMusic.onErrorObservable.add((error) => {
console.error("Error loading/playing sound:", error);
infoDiv.textContent = "Error loading sound. Check console.";
});
} catch (error) {
console.error("Failed to initialize sound:", error);
infoDiv.textContent = "Failed to initialize sound. Check console and file path.";
}
// ---------------------
return scene;
};
const scene = createScene();
engine.runRenderLoop(function () {
scene.render();
});
window.addEventListener("resize", function () {
engine.resize();
});
</script>
</body>
</html>

This demo displays a simple box. When the page loads, it attempts to load the ambient_track.mp3 file from the sounds folder. If successful, the music starts playing automatically in a loop at 50% volume, and the text in the top-left corner updates to “Scene Ready. Background Music Playing.”. If the sound file cannot be loaded or decoded, an error message appears in the text overlay and the browser console.

Often, you want to play sounds in response to specific events, not automatically. Set autoplay: false and call the play() method on the Sound object when needed. You might want to ensure the sound is loaded before calling play, often done inside the readyToPlayCallback or by checking the isReady() status.

// Example: Play sound on click
let gunshotSound = null;
gunshotSound = new BABYLON.Sound("gunshot", "sounds/explosion.wav", scene, () => {
// Sound is ready, enable playing it
window.addEventListener("mousedown", (evt) => {
if (evt.button === 0 && gunshotSound) {
gunshotSound.play();
}
});
console.log("Gunshot sound ready.");
}, { autoplay: false });
// Example: Play sound on key press
window.addEventListener("keydown", (evt) => {
// Example: Play on space bar (keyCode 32)
if (evt.keyCode === 32 && gunshotSound && gunshotSound.isReady()) {
gunshotSound.play();
}
});

The Sound object provides methods to control playback:

  • sound.play(time?: number): Starts playback, optionally from a specific time (in seconds).
  • sound.pause(): Pauses playback.
  • sound.stop(): Stops playback and resets the position to the beginning.
  • sound.setVolume(volume: number, time?: number): Sets the volume, optionally fading over a duration.
  • sound.setPlaybackRate(rate: number): Changes the playback speed.
  • sound.currentTime: Gets the current playback position (in seconds).

You can react when a sound finishes playing using the onended event handler or the onEndedObservable:

// Using the observable (recommended)
sound.onEndedObservable.add(() => {
console.log("Sound finished playing.");
// Maybe trigger another sound or game event
});
// Using the older onended property
/*
sound.onended = () => {
console.log("Sound finished playing.");
};
*/

Spatial sound makes audio appear to originate from a specific location in the 3D scene. The volume and panning (left/right balance) change as the listener (camera) moves relative to the sound source. To use spatial sound, set spatialSound: true in the options.

const spatialSoundOptions = {
loop: false,
autoplay: false,
spatialSound: true,
distanceModel: "exponential", // Sound fades more naturally over distance
maxDistance: 50, // Becomes very quiet beyond 50 units
rolloffFactor: 2
};
const explosionSound = new BABYLON.Sound("explosion", "sounds/explosion.wav", scene, null, spatialSoundOptions);

For spatial sounds, you often want the sound’s position to follow a moving object. Use the attachToMesh() method to link the sound’s position to a mesh’s position.

// Assuming 'movingBox' is a mesh in your scene
// and 'explosionSound' is a spatial sound
explosionSound.attachToMesh(movingBox);
// Now, if movingBox moves, the perceived source of explosionSound moves with it.
// You can play the sound when needed:
// movingBox.actionManager = new BABYLON.ActionManager(scene);
// movingBox.actionManager.registerAction(
// new BABYLON.ExecuteCodeAction(BABYLON.ActionManager.OnPickTrigger, () => {
// explosionSound.play();
// })
// );

You can control the master volume for all sounds managed by the Babylon.js audio engine:

// Set global volume to 70%
BABYLON.Engine.audioEngine.setGlobalVolume(0.7);
// Get the global volume
const currentGlobalVolume = BABYLON.Engine.audioEngine.getGlobalVolume();
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Babylon.js - Spatial Sound</title>
<script src="https://cdn.babylonjs.com/babylon.js"></script>
<style>
html, body { overflow: hidden; width: 100%; height: 100%; margin: 0; padding: 0; }
#renderCanvas { width: 100%; height: 100%; touch-action: none; }
#info { position: absolute; bottom: 10px; left: 10px; color: white; background-color: rgba(0,0,0,0.5); padding: 5px; }
</style>
</head>
<body>
<canvas id="renderCanvas"></canvas>
<div id="info">Click the box to play spatial sound</div>
<script type="text/javascript">
const canvas = document.getElementById("renderCanvas");
const engine = new BABYLON.Engine(canvas, true);
const infoDiv = document.getElementById("info");
const createScene = function () {
const scene = new BABYLON.Scene(engine);
scene.clearColor = new BABYLON.Color4(0.2, 0.1, 0.1, 1.0);
// Listener position is camera position
scene.audioEnabled = true; // Ensure audio is enabled
const camera = new BABYLON.ArcRotateCamera("Camera", Math.PI / 4, Math.PI / 3, 20, BABYLON.Vector3.Zero(), scene);
camera.attachControl(canvas, true);
const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
const box = BABYLON.MeshBuilder.CreateBox("box", { size: 2 }, scene);
const boxMat = new BABYLON.StandardMaterial("boxMat", scene);
boxMat.diffuseColor = new BABYLON.Color3(1, 0.5, 0.2); // Orange
box.material = boxMat;
// --- Spatial Sound Setup ---
let explosionSound = null;
const spatialSoundOptions = {
loop: false,
autoplay: false,
spatialSound: true,
distanceModel: "exponential",
maxDistance: 30,
rolloffFactor: 1.5
};
try {
explosionSound = new BABYLON.Sound("explosion", "sounds/explosion.wav", scene,
() => { console.log("Explosion sound ready."); infoDiv.textContent = "Click the box."; },
spatialSoundOptions);
// Attach sound to the box
explosionSound.attachToMesh(box);
// Play sound on click using Action Manager
box.actionManager = new BABYLON.ActionManager(scene);
box.actionManager.registerAction(
new BABYLON.ExecuteCodeAction(BABYLON.ActionManager.OnPickTrigger, () => {
if (explosionSound && explosionSound.isReady()) {
explosionSound.play();
infoDiv.textContent = "Playing sound...";
explosionSound.onEndedObservable.addOnce(() => { infoDiv.textContent = "Click the box."; });
}
})
);
} catch (error) {
console.error("Failed to initialize sound:", error);
infoDiv.textContent = "Error loading sound. Check console.";
}
// --------------------------
// Animate box movement
let angle = 0;
scene.onBeforeRenderObservable.add(() => {
angle += 0.01;
box.position.x = 5 * Math.sin(angle);
box.position.z = 5 * Math.cos(angle);
});
return scene;
};
const scene = createScene();
engine.runRenderLoop(function () { scene.render(); });
window.addEventListener("resize", function () { engine.resize(); });
</script>
</body>
</html>

This demo shows an orange box moving in a circular path. An explosion sound (explosion.wav from the sounds folder) is loaded as a spatial sound and attached to the box. When you click the box, the explosion sound plays. As the box moves and as you rotate the camera, you should notice the sound’s perceived location and volume changing based on its position relative to the camera (the listener).