Audio Channels
Learn about the channel-based mixing system with independent volume control for SFX, Music, Voice, and Master channels.
Web Engine uses a channel-based audio mixing system that routes sounds through independent channels. This allows players to adjust SFX, Music, and Voice volumes separately while maintaining a master volume control.
Audio Channels#
The audio system provides four built-in channels:
Master Channel#
- Controls overall volume for all audio
- All other channels are routed through Master
- Muting Master mutes all audio
- Perfect for global volume control in settings
SFX Channel#
- Sound effects, impacts, explosions, UI sounds
- Short, one-shot sounds and gameplay audio
- Environmental sounds (footsteps, doors, ambience)
- Channel index:
0orAudioChannel.SFX
Music Channel#
- Background music, themes, soundtracks
- Long-playing, looping audio tracks
- Ambient musical elements
- Channel index:
1orAudioChannel.Music
Voice Channel#
- Character dialogue, narration, voice-overs
- Radio communications, announcements
- Any spoken audio content
- Channel index:
2orAudioChannel.Voice
Channel Routing#
Every audio source is routed through one channel. Set the channel using the group property:
import { AudioSource, AudioChannel } from '@web-engine-dev/core'; // Route to SFX channel (default)AudioSource.group[explosionEntity] = AudioChannel.SFX; // 0 // Route to Music channelAudioSource.group[musicEntity] = AudioChannel.Music; // 1 // Route to Voice channelAudioSource.group[dialogueEntity] = AudioChannel.Voice; // 2The final volume calculation flows through the channel hierarchy:
// Volume calculation hierarchyfinalVolume = source.volume * channel.volume // SFX, Music, or Voice * master.volume; // Master channel // Examplesource.volume = 0.8; // Source at 80%sfx.volume = 0.5; // SFX channel at 50%master.volume = 0.7; // Master at 70% finalVolume = 0.8 * 0.5 * 0.7 = 0.28 (28%)AudioMixer API#
The AudioMixer provides centralized control over all channel volumes and mute states:
Setting Volume#
import { audioMixer, AudioChannel } from '@web-engine-dev/core'; // Set individual channel volume (0.0 to 1.0)audioMixer.setVolume(AudioChannel.Master, 0.8);audioMixer.setVolume(AudioChannel.SFX, 0.7);audioMixer.setVolume(AudioChannel.Music, 0.4);audioMixer.setVolume(AudioChannel.Voice, 1.0); // Set multiple volumes at once (more efficient)audioMixer.setVolumes({ [AudioChannel.Master]: 0.8, [AudioChannel.SFX]: 0.7, [AudioChannel.Music]: 0.4, [AudioChannel.Voice]: 1.0,}); // Get current volumeconst sfxVolume = audioMixer.getVolume(AudioChannel.SFX);console.log('SFX Volume:', sfxVolume);Muting Channels#
// Mute a single channelaudioMixer.setMuted(AudioChannel.Music, true); // Mute musicaudioMixer.setMuted(AudioChannel.Music, false); // Unmute music // Toggle mute stateaudioMixer.toggleMute(AudioChannel.SFX); // Mute all channels at onceaudioMixer.setMuted('all', true); // Check if channel is mutedconst isMusicMuted = audioMixer.isMuted(AudioChannel.Music);console.log('Music muted:', isMusicMuted);Master Mute Override
When Master is muted, all audio is silenced regardless of individual channel mute states. Unmuting a channel while Master is muted will not produce sound until Master is also unmuted.
Getting Mixer State#
// Get complete mixer state snapshotconst state = audioMixer.snapshot(); console.log(state);// {// master: 0.8,// music: 0.4,// sfx: 0.7,// voice: 1.0,// muteMaster: false,// muteMusic: true,// muteSfx: false,// muteVoice: false// }Volume Persistence#
For player settings, you'll typically want to save and restore volume preferences:
import { audioMixer, AudioChannel } from '@web-engine-dev/core'; // Save volume settings to localStoragefunction saveAudioSettings() { const settings = { master: audioMixer.getVolume(AudioChannel.Master), sfx: audioMixer.getVolume(AudioChannel.SFX), music: audioMixer.getVolume(AudioChannel.Music), voice: audioMixer.getVolume(AudioChannel.Voice), muteMaster: audioMixer.isMuted(AudioChannel.Master), muteMusic: audioMixer.isMuted(AudioChannel.Music), muteSfx: audioMixer.isMuted(AudioChannel.SFX), muteVoice: audioMixer.isMuted(AudioChannel.Voice), }; localStorage.setItem('audioSettings', JSON.stringify(settings));} // Load volume settings from localStoragefunction loadAudioSettings() { const stored = localStorage.getItem('audioSettings'); if (!stored) return; const settings = JSON.parse(stored); // Restore volumes audioMixer.setVolumes({ [AudioChannel.Master]: settings.master ?? 1.0, [AudioChannel.SFX]: settings.sfx ?? 1.0, [AudioChannel.Music]: settings.music ?? 1.0, [AudioChannel.Voice]: settings.voice ?? 1.0, }); // Restore mute states audioMixer.setMuted(AudioChannel.Master, settings.muteMaster ?? false); audioMixer.setMuted(AudioChannel.Music, settings.muteMusic ?? false); audioMixer.setMuted(AudioChannel.SFX, settings.muteSfx ?? false); audioMixer.setMuted(AudioChannel.Voice, settings.muteVoice ?? false);} // Call on game startloadAudioSettings();UI Integration#
Create volume sliders in your settings menu that control the mixer:
import { audioMixer, AudioChannel } from '@web-engine-dev/core';import { useState, useEffect } from 'react'; function AudioSettings() { const [masterVolume, setMasterVolume] = useState(1.0); const [sfxVolume, setSfxVolume] = useState(1.0); const [musicVolume, setMusicVolume] = useState(1.0); const [voiceVolume, setVoiceVolume] = useState(1.0); // Load initial values useEffect(() => { setMasterVolume(audioMixer.getVolume(AudioChannel.Master) ?? 1.0); setSfxVolume(audioMixer.getVolume(AudioChannel.SFX) ?? 1.0); setMusicVolume(audioMixer.getVolume(AudioChannel.Music) ?? 1.0); setVoiceVolume(audioMixer.getVolume(AudioChannel.Voice) ?? 1.0); }, []); const handleMasterChange = (value: number) => { setMasterVolume(value); audioMixer.setVolume(AudioChannel.Master, value); }; const handleSfxChange = (value: number) => { setSfxVolume(value); audioMixer.setVolume(AudioChannel.SFX, value); }; return ( <div className="audio-settings"> <label> Master Volume <input type="range" min="0" max="1" step="0.01" value={masterVolume} onChange={(e) => handleMasterChange(parseFloat(e.target.value))} /> </label> <label> SFX Volume <input type="range" min="0" max="1" step="0.01" value={sfxVolume} onChange={(e) => handleSfxChange(parseFloat(e.target.value))} /> </label> {/* Repeat for Music and Voice */} </div> );}Category Management Best Practices#
Follow these guidelines for routing sounds to the appropriate channel:
SFX Channel Usage#
- Weapon fire, explosions, impacts
- Footsteps, movement sounds
- Environmental sounds (doors, switches, machinery)
- UI sounds (button clicks, notifications)
- Physics sounds (collisions, breaking objects)
- Power-ups, collectibles
Music Channel Usage#
- Background music tracks
- Musical themes and motifs
- Ambient musical loops
- Menu/UI music
- Victory/defeat music
Voice Channel Usage#
- Character dialogue
- Narrator voice-over
- Radio/comms chatter
- Announcer voices
- Tutorial instructions
- Character grunts/reactions (optional - could also be SFX)
When to Use Voice vs SFX
Character vocalizations can go in either Voice or SFX depending on context. Use Voice for dialogue and clear speech. Use SFX for combat grunts, pain sounds, and other non-verbal vocalizations.
Mixer Performance#
The AudioMixer is optimized for zero-GC performance:
- Rate Limiting — Updates are rate-limited to prevent excessive WebAudio API calls
- Dirty Flags — Only applies changes when volumes actually change
- Batch Updates — Use setVolumes() to update multiple channels efficiently
- Epsilon Comparison — Tiny volume changes are ignored to reduce updates
// Inefficient - multiple callsaudioMixer.setVolume(AudioChannel.Master, 0.8);audioMixer.setVolume(AudioChannel.SFX, 0.7);audioMixer.setVolume(AudioChannel.Music, 0.4); // Efficient - batch updateaudioMixer.setVolumes({ [AudioChannel.Master]: 0.8, [AudioChannel.SFX]: 0.7, [AudioChannel.Music]: 0.4,});Complete Example: Audio Settings#
import { audioMixer, AudioChannel } from '@web-engine-dev/core'; class AudioSettingsPanel { private storageKey = 'gameAudioSettings'; // Initialize and load saved settings init() { this.loadSettings(); this.setupUI(); } // Save current settings saveSettings() { const settings = { master: audioMixer.getVolume(AudioChannel.Master), sfx: audioMixer.getVolume(AudioChannel.SFX), music: audioMixer.getVolume(AudioChannel.Music), voice: audioMixer.getVolume(AudioChannel.Voice), muteMaster: audioMixer.isMuted(AudioChannel.Master), muteMusic: audioMixer.isMuted(AudioChannel.Music), }; localStorage.setItem(this.storageKey, JSON.stringify(settings)); } // Load saved settings loadSettings() { const stored = localStorage.getItem(this.storageKey); if (!stored) return; try { const settings = JSON.parse(stored); audioMixer.setVolumes({ [AudioChannel.Master]: settings.master ?? 1.0, [AudioChannel.SFX]: settings.sfx ?? 1.0, [AudioChannel.Music]: settings.music ?? 0.7, [AudioChannel.Voice]: settings.voice ?? 1.0, }); if (settings.muteMaster) { audioMixer.setMuted(AudioChannel.Master, true); } if (settings.muteMusic) { audioMixer.setMuted(AudioChannel.Music, true); } } catch (e) { console.error('Failed to load audio settings:', e); } } // Setup UI event handlers setupUI() { // Master volume slider document.getElementById('masterSlider')?.addEventListener('input', (e) => { const value = parseFloat((e.target as HTMLInputElement).value); audioMixer.setVolume(AudioChannel.Master, value); this.saveSettings(); }); // SFX volume slider document.getElementById('sfxSlider')?.addEventListener('input', (e) => { const value = parseFloat((e.target as HTMLInputElement).value); audioMixer.setVolume(AudioChannel.SFX, value); this.saveSettings(); }); // Music mute toggle document.getElementById('muteMusic')?.addEventListener('click', () => { audioMixer.toggleMute(AudioChannel.Music); this.saveSettings(); }); } // Reset to defaults resetToDefaults() { audioMixer.setVolumes({ [AudioChannel.Master]: 1.0, [AudioChannel.SFX]: 1.0, [AudioChannel.Music]: 0.7, [AudioChannel.Voice]: 1.0, }); audioMixer.setMuted('all', false); this.saveSettings(); }} // Usageconst audioSettings = new AudioSettingsPanel();audioSettings.init();