Audio
Sliver’s SoundManager is a small wrapper around the Web Audio API (AudioContext).
You typically:
- create a single
SoundManagerfor your game - preload sounds (SFX + music) with
loadSound - “unlock” audio after a user interaction
- use
playSoundfor one-shots andplaySongfor background music
Getting the SoundManager
SoundManager is available through the GameContext:
const sound = game.getContext().getSoundManager();
Your Game instance receives a soundManager in its constructor options, so you usually create it once and pass it in:
import { Game, SoundManager } from "sliver-engine";
const soundManager = new SoundManager();
const game = new Game({
canvas,
scenes,
soundManager: soundManager,
});
Unlocking audio (required by browsers)
Browsers block audio autoplay. Until the user clicks/taps/presses a key, the underlying AudioContext is typically “suspended”.
Call unlock() from a user interaction handler (recommended), once, as early as possible:
window.addEventListener("pointerdown", async () => {
await game.getContext().getSoundManager().unlock();
});
I suggest you to integrate this unlock on your game in the following way
- Your start scene has nothing but a button for
start game - Clicking on this button transition to a scene and unlocks the audio
class MyButton extends GameObject {
// {...}
@onClick((self) => {
self.getContext().getSoundManager().unlock();
})
override handleEvent(event: GameEvent) {
super.handleEvent(event);
}
}
// or
class MyButton extends GameObject {
// {...}
@onClick((self) => {
self.getContext().getSoundManager().playSound("button_click");
// If the first audio that's played comes from a user
// interaction, the unlock happens automatically
})
override handleEvent(event: GameEvent) {
super.handleEvent(event);
}
}
Why this matters:
unlock()connects the internalmasterGainto the audio output and resumes theAudioContext.playSound()/playSong()try to unlock automatically when needed, but they don’tawaitit, so your very first sound is more reliable if you unlock explicitly on input.
Preloading sounds
Load audio files into an in-memory library (decoded into an AudioBuffer):
const sound = game.getContext().getSoundManager();
await sound.loadSound(
"jump",
new URL("/audio/jump.wav", window.location.href),
["sfx", "player"]
);
await sound.loadSound(
"bgm:forest",
new URL("/audio/forest-theme.ogg", window.location.href),
["music"]
);
Notes:
loadSound(name, url, tags?)fetches the asset and decodes it. If the fetch fails, it throws with the HTTP status.tagsare stored with the sound entry (useful for your own filtering/organization viagetLoadedSounds()).- [TODO]
tagswill also be used to create a gain control for each tag in the future
Master volume
SoundManager routes all sounds through a master gain node, so you can implement global volume/mute easily:
const sound = game.getContext().getSoundManager();
sound.setMasterGain(0.6); // clamps to >= 0
console.log(sound.getMasterGain()); // -> 0.6
Per-sound default volume
Each loaded sound keeps a default volume (used when you don’t pass an explicit volume to playSound / playSong):
sound.setSoundVolume("jump", 0.3); // clamps to >= 0
console.log(sound.getSoundVolume("jump")); // -> 0.3
This is a good place to normalize loud assets so your call sites can stay simple.
Playing one-shot sound effects
Use playSound(name, options) for SFX. It returns a small controller with a stop() method.
const sfx = sound.playSound("jump", {
volume: 0.8,
playbackRate: 1.1,
});
// ...later (optional)
sfx.stop();
Useful options
loop: repeat until stopped (defaults tofalse)playbackRate: speed/pitch (defaults to1)wait: delay before playing (seconds)start: offset into the audio file (seconds)end: stop at an absolute time in the file (seconds)duration: how long to play (seconds). If present, it takes precedence overend.
Example: play only a short “blip” segment, after a delay:
sound.playSound("ui:beep", {
wait: 0.05,
start: 0.12,
duration: 0.08,
});
Why duration wins over end: it’s often easier to express “play 80ms from here” than to compute an absolute end time when you change start.
Playing and controlling background music (“songs”)
Use playSong(name, options) for tracked background music.
Behavior:
- If you request the same song that’s already playing, nothing changes and you get the existing controller back.
- If you request a different song, the previous song is stopped and the new one starts.
By default, songs loop (loop defaults to true for playSong).
Fading between songs
playSong supports fade transitions (in seconds) via fade, fadeIn, and fadeOut:
sound.playSong("bgm:forest", { volume: 0.5, fade: 0.6 });
// Later, cross-fade to another track
sound.playSong("bgm:cave", { volume: 0.5, fadeOut: 0.6, fadeIn: 0.8 });
To stop the current song:
sound.stopSong({ fadeOut: 0.5 });
Or use the controller returned by playSong:
const song = sound.playSong("bgm:forest");
song.stop({ fadeOut: 0.5 });
Introspection and debugging
console.log(sound.getCurrentSongName()); // string | undefined
console.log(Object.keys(sound.getLoadedSounds())); // names you loaded