Utility Functions
EZ Web Audio ships with a set of utility functions for common audio programming tasks — loading multiple sounds, synchronized playback, crossfading, preloading, debug logging, and binding UI interactions.
Collection Control
Control multiple sounds at once with a single call:
import { pauseAll, playAll, stopAll } from 'ez-web-audio'
const sounds = [sound1, sound2, sound3]
playAll(sounds) // Play all sounds
pauseAll(sounds) // Pause all tracks (no effect on non-track sounds)
stopAll(sounds) // Stop all soundsGlobal Volume / Mute
Set an app-wide volume or mute switch without touching every sound individually. Both are sugar over setMasterDestination(): the first call lazily creates a managed GainNode and installs it as the master destination, so instances created afterward route through it automatically:
import { createSound, muteAll, setGlobalVolume } from 'ez-web-audio'
setGlobalVolume(0.5) // app-wide volume knob, set once at startup
const music = await createSound('theme.mp3')
music.play() // plays at half the master volume
// Mute button
let isMuted = false
document.getElementById('mute-btn')!.addEventListener('click', () => {
isMuted = !isMuted
muteAll(isMuted) // unmuting restores the last setGlobalVolume() value
})Same caveat as setMasterDestination(): only instances created after the first setGlobalVolume()/muteAll() call pick up the managed bus. Call it early (app startup) if you want it to cover everything, or move an existing instance with instance.setDestination(...). setGlobalVolume() throws a ValidationError for negative values and warns on the console above 1 — the same rule as sound.changeGainTo().
Synchronized Playback
Play multiple sounds at the exact same AudioContext timestamp. Unlike calling play() on each sound sequentially (which introduces tiny timing gaps), playTogether schedules all sources to a shared start time slightly in the future:
import { createOscillator, createSound, playTogether } from 'ez-web-audio'
const bass = await createSound('/audio/bass.mp3')
const melody = await createSound('/audio/melody.mp3')
const chord = await createOscillator({ frequency: 440, type: 'triangle' })
// All three start at precisely the same AudioContext time
await playTogether([bass, melody, chord])Use cases: building chords from oscillators, layering SFX components, and synchronizing stems in a multi-track arrangement.
Works with any mix of Sound, Track, and Oscillator instances.
Batch Loading
Load multiple sounds at once with optional progress tracking:
import { createSounds } from 'ez-web-audio'
const sounds = await createSounds(
['click.mp3', 'whoosh.mp3', 'ding.mp3'],
(loaded, total) => console.log(`${loaded}/${total}`)
)For tracks (music with pause/resume/seek), use createTracks():
import { createTracks } from 'ez-web-audio'
const tracks = await createTracks(
['intro.mp3', 'verse.mp3', 'chorus.mp3'],
(loaded, total, url) => console.log(`${loaded}/${total}: ${url}`)
)Both callbacks fire after each item finishes loading, making it easy to drive a loading progress bar.
Crossfade
Smoothly transition between two tracks using an equal-power curve. Equal-power crossfading keeps the total perceived loudness constant throughout the transition — there is no volume dip at the midpoint that a simple linear cross-fade would produce.
import { createTrack, crossfade } from 'ez-web-audio'
const intro = await createTrack('/music/intro.mp3')
const main = await createTrack('/music/main.mp3')
intro.play()
// Crossfade from intro to main over 3 seconds
// intro fades out; main fades in — overlap uses equal-power curve
await crossfade(intro, main, 3)
// intro is now stopped, main is playing at full volumecrossfade returns a Promise that resolves when the transition is complete and the source track has been stopped. If the destination track is already playing, the fade starts from its current position; otherwise it starts playing at gain 0 and fades in.
Typical use cases: DJ transitions, ambient scene changes, and background music swaps.
Preloading Audio
Cache audio files before they are needed so playback starts instantly:
import { createSound, isPreloaded, preload } from 'ez-web-audio'
// Preload during a loading screen
await preload(['/audio/level1.mp3', '/audio/level2.mp3', '/audio/boss.mp3'])
// Subsequent createSound/createTrack calls hit the cache — no additional fetch
const level1 = await createSound('/audio/level1.mp3')
// Check if a URL is already cached
console.log(isPreloaded('/audio/level1.mp3')) // trueCache Management
Clear the internal preload cache when you no longer need cached audio — useful in long-running apps after level transitions:
import { clearPreloadCache, preload } from 'ez-web-audio'
await preload(['/audio/level1.mp3', '/audio/level2.mp3'])
// Level transition: free memory from level 1 assets
clearPreloadCache('/audio/level1.mp3') // Clear a single URL
// Or clear everything at once
clearPreloadCache()clearPreloadCache() without arguments clears the entire cache. With a URL argument, it removes only that entry. After clearing, the next createSound() or preload() call for that URL fetches it fresh.
Noise Generation
Generate procedural noise for ambience, testing, or synthesis:
import { createNoise } from 'ez-web-audio'
const white = await createNoise('white') // Equal energy — hiss/static
const pink = await createNoise('pink') // 1/f spectrum — natural ambience
const brown = await createNoise('brown') // Random walk — deep rumbleWhite noise has a flat spectrum (equal energy at all frequencies). Pink noise rolls off 3 dB/octave, sounding more balanced to human ears. Brown noise rolls off 6 dB/octave for a deep, rumbling character. All return a 1-second Sound instance — set .loop = true for continuous playback, and add effects and control gain like any other sound.
Debug Mode
Enable debug logging to trace audio events and connections during development:
import { setDebugHandler, setDebugMode } from 'ez-web-audio'
// Enable debug mode — logs all events to the console
setDebugMode(true)
// Example console output:
// [ez-audio:event] [0.000] kick: play
// [ez-audio:connection] [0.021] kick: Effect added at position 0
// [ez-audio:warning] [1.420] bgMusic: AudioContext suspendedEach message is a DebugMessage object with:
| Field | Type | Description |
|---|---|---|
type | 'event' | 'connection' | 'warning' | Category of message |
source | string | Sound name or identifier |
message | string | Human-readable description |
timestamp | number | audioContext.currentTime |
Use setDebugHandler to capture messages in your own logging system:
import { setDebugHandler, setDebugMode } from 'ez-web-audio'
// Capture warning messages only
setDebugHandler((msg) => {
if (msg.type === 'warning') {
myLogger.warn(`[${msg.source}] ${msg.message}`)
}
})
// Restore default console.log handler
setDebugHandler(null)Interaction Helpers
Bind touch and mouse events to a sound for piano-style interactive controls.
Plain DOM conveniences, not framework hooks
useInteractionMethods and preventEventDefaults live in the root package namespace and use the use* prefix, which can read like a React/Vue hook. They're not — both are framework-agnostic addEventListener/removeEventListener wrappers around plain DOM elements, safe to call from vanilla JS, React, Vue, or anywhere else. They stay in the root namespace for now (a future major version may move them under a /dom subpath for clarity).
useInteractionMethods(element, player) attaches touchstart/mousedown → play() and touchend/mouseup/mouseleave → stop(). It returns a cleanup function to remove all listeners:
import { createOscillator, useInteractionMethods } from 'ez-web-audio'
const synth = await createOscillator({ frequency: 440 })
const key = document.getElementById('piano-key')!
const cleanup = await useInteractionMethods(key, synth)
// Touching or clicking the element now plays/stops the synth
// Later — clean up on component unmount
cleanup()preventEventDefaults(element) prevents text selection, context menus, and drag-and-drop on interactive audio elements. It also returns a cleanup function:
import { createOscillator, preventEventDefaults, useInteractionMethods } from 'ez-web-audio'
const synth = await createOscillator({ frequency: 261.63 }) // C4
const key = document.getElementById('key-c4')!
// Prevent browser defaults (selection, context menu, drag) before binding audio
const removePrevention = preventEventDefaults(key)
const cleanup = await useInteractionMethods(key, synth)
// Clean up both when done
removePrevention()
cleanup()Both helpers work with any object that has play() and stop() methods — not just oscillators.
Resource Cleanup
When you're done with a sound, call dispose() to disconnect all audio nodes and free resources:
sound.dispose()
// sound.play() will now throw — the instance is no longer usabledispose() is idempotent — calling it multiple times has no additional effect. This is especially important in single-page applications where audio instances are created and destroyed as users navigate between views.
Context-Free Analyzer
createAnalyzer() works without passing an AudioContext — it resolves the shared context automatically:
const analyzer = await createAnalyzer({ fftSize: 2048 })This matches the pattern used by createFilterEffect() and createGainEffect(), keeping your code consistent without needing to manage context references directly.
Next Steps
- Core Concepts - Sound types, AudioContext lifecycle, audio routing
- Parameter Control - Automate gain, frequency, and pan over time
- Interactive Examples - See utilities in action
- API Reference - Full method documentation