Audio Loom is a framework-agnostic audio management library built on the Web Audio API. It provides centralized control for organizing, playing, and managing audio assets in games and interactive applications.
Works seamlessly with Phaser, Three.js, React, Angular, and other JavaScript frameworks.
npm install @happy-pixels/audio-loom
# or
pnpm add @happy-pixels/audio-loom
import { AudioManager } from '@happy-pixels/audio-loom';
const audio = new AudioManager();
// Initialize on user interaction (required by browsers)
document.getElementById('startButton')!.onclick = async () => {
await audio.resumeAudioContext();
// Now audio is ready to play!
audio.playAudioTrack('click');
};
// Register sounds
audio.addAudioTrack('click', 'ui', '/sounds/click.wav');
audio.addAudioTrack('explosion', 'sfx', '/sounds/explosion.wav');
audio.addAudioTrack('music', 'music', '/music/theme.mp3');
// Preload for instant playback
await audio.preload(['click', 'explosion']);
// Play one-shot sound effects
audio.playAudioTrack('explosion');
// Play continuous background music
audio.playContinuous('music');
Organize sounds into logical groups for independent volume and mute control:
// Register tracks in different groups
audio.addAudioTrack('explosion', 'sfx', '/sounds/explosion.wav');
audio.addAudioTrack('footstep', 'sfx', '/sounds/footstep.wav');
audio.addAudioTrack('music', 'music', '/music/theme.mp3');
audio.addAudioTrack('rain', 'ambient', '/sounds/rain.mp3');
// Control groups independently
audio.setAudioVolume('sfx', 0.8); // SFX at 80%
audio.setAudioVolume('music', 0.5); // Music at 50%
audio.setAudioEnabled('ambient', false); // Mute ambient sounds
Control overall audio output:
audio.setMasterVolume(0.7); // Set master to 70%
const volume = audio.getMasterVolume();
Register multiple tracks under the same key for natural variation:
// Add multiple footstep sounds
audio.addAudioTrack('footstep', 'sfx', '/sounds/footstep1.wav');
audio.addAudioTrack('footstep', 'sfx', '/sounds/footstep2.wav');
audio.addAudioTrack('footstep', 'sfx', '/sounds/footstep3.wav');
// Each play selects randomly (shuffled, no repeats until all played)
audio.playAudioTrack('footstep');
Preload audio for instant, low-latency playback:
// Check loading status
const status = audio.getLoadStatus('explosion');
console.log(`Loaded ${status.loaded}/${status.total}`);
// Preload specific keys
await audio.preload(['explosion', 'gunshot', 'footstep']);
// Check if ready
if (audio.isLoaded('explosion')) {
audio.playAudioTrack('explosion');
}
Play sound effects that run to completion:
audio.playAudioTrack('explosion');
audio.playAudioTrack('gunshot');
// Pool limits prevent too many concurrent sounds
audio.setGroupPoolSize('sfx', 8); // Max 8 simultaneous SFX
Play looping audio with full control:
// Start background music
audio.playContinuous('music');
// Pause/resume
audio.pauseContinuous();
audio.resumeContinuous();
// Stop
audio.stopContinuous();
// Multiple channels for layered audio
audio.playContinuous('music', 'music-channel');
audio.playContinuous('rain', 'ambient-channel');
// Control channels independently
audio.pauseContinuous('ambient-channel');
audio.setPlaybackRate(0.8, 'music-channel');
Smooth volume transitions using Web Audio API:
// Fade in new music over 2 seconds
await audio.fadeIn('battle-music', 2000);
// Fade out over 1.5 seconds
await audio.fadeOut(1500);
// Cross-fade to new track (simultaneous fade out/in)
await audio.crossFade('victory-music', 2000);
Fine-grained control over continuous playback:
// Playback rate (speed/pitch)
audio.setPlaybackRate(1.5); // 1.5x speed
audio.setPlaybackRate(0.5); // Half speed
// Seeking
audio.seek(30); // Jump to 30 seconds
const currentTime = audio.getCurrentTime();
const duration = audio.getDuration();
// Get full playback info
const info = audio.getPlaybackInfo();
console.log(`${info.currentTime}/${info.duration}s at ${info.playbackRate}x`);
React to audio events using RxJS observables:
// Track starts
audio.onTrackStart$.subscribe(event => {
console.log(`Playing: ${event.key} on ${event.channelId}`);
});
// Track ends
audio.onTrackEnd$.subscribe(event => {
console.log(`Finished: ${event.key}`);
});
// Track loaded
audio.onLoadComplete$.subscribe(event => {
console.log(`Loaded: ${event.key}, duration: ${event.duration}s`);
});
// Errors
audio.onError$.subscribe(event => {
console.error(`Error: ${event.message}`, event.error);
});
Handle browser autoplay restrictions:
// Initialize on user interaction
button.onclick = async () => {
await audio.resumeAudioContext();
};
// Check if ready
if (audio.isAudioReady()) {
audio.playAudioTrack('click');
}
// Suspend when app is in background
document.addEventListener('visibilitychange', async () => {
if (document.hidden) {
await audio.suspendAudioContext();
} else {
await audio.resumeAudioContext();
}
});
Properly dispose of resources:
// Stop all and release resources
audio.destroy();
// React example
useEffect(() => {
const audio = new AudioManager();
return () => audio.destroy();
}, []);
| Method | Description |
|---|---|
initAudio() |
Initialize AudioContext |
resumeAudioContext() |
Resume suspended context |
suspendAudioContext() |
Suspend context |
isAudioReady() |
Check if context is running |
setMasterVolume(volume) |
Set master volume (0-1) |
getMasterVolume() |
Get master volume |
setAudioEnabled(group, enabled) |
Enable/disable group |
setAudioVolume(group, volume) |
Set group volume |
setGroupPoolSize(group, max) |
Set concurrent sound limit |
addAudioTrack(key, group, path) |
Register a track |
preload(keys) |
Preload tracks |
isLoaded(key) |
Check if loaded |
getLoadStatus(key) |
Get loading progress |
playAudioTrack(key) |
Play one-shot sound |
playContinuous(key, channel?) |
Start continuous playback |
stopContinuous(channel?) |
Stop continuous playback |
pauseContinuous(channel?) |
Pause playback |
resumeContinuous(channel?) |
Resume playback |
fadeIn(key, duration, channel?) |
Fade in new track |
fadeOut(duration, channel?) |
Fade out current track |
crossFade(key, duration, channel?) |
Cross-fade to new track |
setPlaybackRate(rate, channel?) |
Set playback speed |
seek(time, channel?) |
Seek to position |
getCurrentTime(channel?) |
Get current position |
getDuration(channel?) |
Get track duration |
getPlaybackInfo(channel?) |
Get full playback info |
getActiveChannels() |
Get all active channels |
getChannelInfo(channel?) |
Get channel info |
stopAllContinuous() |
Stop all channels |
pauseAllContinuous() |
Pause all channels |
resumeAllContinuous() |
Resume all channels |
destroy() |
Clean up resources |
| Observable | Event Type | Description |
|---|---|---|
onTrackStart$ |
TrackStartEvent |
Track started playing |
onTrackEnd$ |
TrackEndEvent |
Track finished |
onLoadComplete$ |
LoadCompleteEvent |
Track metadata loaded |
onError$ |
AudioErrorEvent |
Error occurred |
Full API documentation is available at https://happy-pixels.github.io/audio-loom/
ISC