Repository navigation
Audio
SparkEngine provides a comprehensive audio system built on XAudio2 (Windows) with OpenAL Soft as a cross-platform fallback for Linux/macOS. It supports 2D and 3D spatial audio with Doppler effects, distance attenuation, efficient source pooling, mix-bus volume/mute/solo, physics-driven occlusion (only when a PhysicsSystem is attached to the mixer), dynamic music, and procedural sound generation. Reverb zones and per-bus DSP chains exist as authoring data only; they are not rendered into the playback path.
Source Files:
-
SparkEngine/Source/Audio/AudioEngine.h-- Core audio engine (XAudio2) -
SparkEngine/Source/Audio/OpenALAudioEngine.h-- Cross-platform backend (OpenAL Soft) -
SparkEngine/Source/Audio/SoundEffect.h-- WAV loading and procedural sound generation -
SparkEngine/Source/Audio/AudioMixer.h-- Mix buses (volume/mute/solo applied), occlusion (applied when physics is attached), reverb zones and DSP effect chains (stored, not applied) -
SparkEngine/Source/Audio/MusicManager.h-- Music playback, playlists, dynamic music -
SparkEngine/Source/Audio/HRTFProcessor.h-- Binaural HRTF approximation (ITD + ILD + head shadow) for headphone listeners
+------------------------------------------------------------------+
| AudioEngine |
| (XAudio2 on Windows, OpenAL Soft on Linux/macOS) |
| |
| IXAudio2* m_xAudio2 (XAudio2 engine interface) |
| IXAudio2MasteringVoice* (final output stage) |
| vector<AudioSource> (source voice pool) |
| unordered_map<SoundEffect> (loaded sounds by name) |
| |
| Initialize(maxSources) ──> CreateMasteringVoice |
| LoadSound(name, file) ──> SoundEffect::LoadFromFile |
| PlaySound(name, vol) ──> GetAvailableSource -> CreateVoice |
| PlaySound3D(name, pos) ──> Apply3DAudioToSource |
| Update(dt) ──> UpdateSources + Update3DAudio |
+------------------------------------------------------------------+
| | |
v v v
+----------------+ +------------------+ +------------------+
| AudioMixer | | MusicManager | | SoundEffectFactory|
| | | | | |
| Mix Buses | | Track playback | | CreateGunshot() |
| Reverb Zones | | Crossfading | | CreateExplosion()|
| DSP Effects | | Playlists | | CreateFootstep() |
| Occlusion | | Dynamic music | | CreateBeep(f, d) |
| Snapshots | | Combat intensity | | CreateNoise(d) |
+----------------+ +------------------+ +------------------+
SoundEffect (WAV data)
│
v
AudioSource (IXAudio2SourceVoice)
│
├── Per-source gain: request * category volume (SFX/Music/...) *
│ mix-bus effective volume (mute/solo and the parent-bus chain)
├── 3D: inverse-distance attenuation clamped between the source's
│ minDistance and maxDistance, Doppler, panning
└── Occlusion volume scale (only while a PhysicsSystem is attached to AudioMixer)
│
v
IXAudio2MasteringVoice (master volume applied exactly once here)
│
v
Hardware Audio Output
There are no submix voices in the playback path, and no XAPO DSP or reverb
rendering: AddBusEffect and reverb zones are bookkeeping that nothing applies to
a voice. Master volume is applied only on the mastering voice (it was previously
applied twice, once per source and once on the master).
XAudio2 device loss (headphones unplugged, default device changed) is detected
from XAUDIO2_E_DEVICE_INVALIDATED; AudioEngine::Update rebuilds the mastering
voice at most once per second, and IAudioBackend::IsAvailable() /
AudioEngine::IsAvailable() report that live state rather than a platform
constant.
AudioEngine audio;
HRESULT hr = audio.Initialize(32); // max 32 simultaneous audio sources
if (FAILED(hr))
{
// Handle initialization failure
Logger::Error("AudioEngine initialization failed: {}", hr);
}The Initialize method:
- Creates the XAudio2 engine via
XAudio2Create() - Creates a mastering voice for final output
- Allocates the audio source pool (32-64 sources is typical)
- Initializes 3D audio state (listener position, Doppler scale)
audio.LoadSound("gunshot", L"Assets/Audio/gunshot.wav");
audio.LoadSound("music", L"Assets/Audio/background.wav");
audio.LoadSound("footstep", L"Assets/Audio/footstep_concrete.wav");Note: On Windows, LoadSound takes a std::wstring for the file path. On Linux/macOS with OpenAL, it takes a std::string.
class SoundEffect
{
public:
SoundEffect();
~SoundEffect();
HRESULT LoadFromFile(const std::wstring& filename);
HRESULT LoadFromMemory(const BYTE* data, DWORD dataSize);
void Unload();
const WAVEFORMATEX& GetFormat() const;
const BYTE* GetData() const;
DWORD GetDataSize() const;
bool IsLoaded() const;
float GetDuration() const; // seconds
DWORD GetSampleRate() const; // Hz
WORD GetChannels() const; // 1=mono, 2=stereo
WORD GetBitsPerSample() const; // 8, 16, or 24
};Currently supports WAV format audio files. The internal parsing reads RIFF headers, locates fmt and data chunks, and stores the raw PCM data in memory.
AudioSource* src = audio.PlaySound("music", 0.8f, 1.0f, true);
// Parameters: name, volume, pitch, loopXMFLOAT3 position = {10.0f, 0.0f, 5.0f};
AudioSource* src = audio.PlaySound3D("gunshot", position, 1.0f, 1.0f, false);
// Parameters: name, position, volume, pitch, loopaudio.StopSound(src); // Stop a specific source
audio.StopAllSounds(); // Stop all playing sources
audio.PauseAllSounds(); // Pause all (can resume later)
audio.ResumeAllSounds(); // Resume all paused sourcesstruct AudioSource
{
IXAudio2SourceVoice* Voice; // XAudio2 source voice for playback
XMFLOAT3 Position; // 3D world position
XMFLOAT3 Velocity; // 3D velocity (for Doppler)
float Volume; // Volume level (0.0 to 1.0+)
float Pitch; // Pitch multiplier (1.0 = normal)
bool Is3D; // Whether 3D positioning is enabled
bool IsLooping; // Loop continuously
bool IsPlaying; // Currently active
SoundEffect* Sound; // Associated sound effect data
uint32_t SourceID; // Unique ID for console tracking
};Three independent volume channels with multiplicative stacking:
| Channel | Description | Default |
|---|---|---|
| Master | Overall volume multiplier applied to all output | 1.0 |
| SFX | Sound effects volume | 1.0 |
| Music | Background music volume. The AudioCategory::Music volume chain works, but no shipped call site requests that category yet (the Sequencer still plays every cue through IAudioBackend as SFX) |
1.0 |
audio.SetMasterVolume(1.0f);
audio.SetSFXVolume(0.7f);
audio.SetMusicVolume(0.5f);
float master = audio.GetMasterVolume();
float sfx = audio.GetSFXVolume();
float music = audio.GetMusicVolume();Effective volume for a sound = sourceVolume * channelVolume * masterVolume.
Sound volume decreases with distance from the listener. The attenuation is controlled by the distanceScale parameter.
Pitch shifts based on relative velocity between source and listener. Controlled by dopplerScale (default 1.0; set to 0.0 to disable).
Sounds are spatialized relative to the listener position and orientation, producing stereo panning effects.
// Must be called every frame for 3D audio to work correctly
audio.SetListenerPosition(cameraPosition);
audio.SetListenerOrientation(cameraForward, cameraUp);audio.Console_SetListenerPosition(x, y, z);
audio.Console_SetListenerOrientation(fwdX, fwdY, fwdZ, upX, upY, upZ);
audio.Console_SetDopplerScale(1.5f); // 0.0-2.0
audio.Console_SetDistanceScale(2.0f); // 0.1-10.0
audio.Console_Set3DAudio(true); // Enable/disable 3D processingFor headphone listeners the engine ships a dependency-free
Spark::Audio::HRTFProcessor that approximates the three dominant
head-related transfer function cues analytically:
-
Interaural Time Difference (ITD) — Woodworth formula
headRadius / speedOfSound * (sinθ + θ), expressed in samples at the current sample rate; the far ear is delayed with a per-sample fractional ring buffer. -
Interaural Level Difference (ILD) — a first-order one-pole low-pass
whose cutoff shrinks with azimuth, biased by
cos²(θ/2)gain for the shadowed ear. - Elevation shelf — mild pinna brightness cue that rises with source elevation.
The processor is SDK-free — no platform-specific HRIR database is shipped today, but the interface is designed so a measured HRIR set can be swapped in later without touching the call sites.
#include "Audio/HRTFProcessor.h"
Spark::Audio::HRTFProcessor hrtf;
hrtf.Initialize(48000); // sample rate
hrtf.SetListener({0, 0, 0}, {0, 0, 1}, // position, forward
{0, 1, 0}); // up
hrtf.SetSource({5.0f, 0.0f, 2.0f}); // in world space
// Per frame — mono in, interleaved stereo out:
std::array<float, 1024> mono{};
std::array<float, 2048> stereo{};
hrtf.Process(mono.data(), stereo.data(), mono.size());The processor exposes a small diagnostic snapshot (GetLastSnapshot())
with the resolved ITD samples, azimuth / elevation, and per-ear gain so
automated tests can assert without having to read back audio buffers.
Source: SparkEngine/Source/Audio/HRTFProcessor.h — see
Tests/TestHRTFProcessor.cpp for the reference test suite.
| Method | Signature | Description |
|---|---|---|
| Constructor | AudioEngine() |
Initialize member defaults |
| Destructor | ~AudioEngine() |
Calls Shutdown()
|
Initialize |
HRESULT Initialize(size_t maxSources) |
Set up XAudio2, create mastering voice, allocate pool |
Update |
void Update(float deltaTime) |
Update sources, 3D audio, pool management |
Shutdown |
void Shutdown() |
Stop all, release XAudio2 resources |
| Method | Signature | Description |
|---|---|---|
LoadSound |
HRESULT LoadSound(const std::string& name, const std::wstring& filename) |
Load WAV file |
UnloadSound |
void UnloadSound(const std::string& name) |
Unload and stop instances |
GetSound |
SoundEffect* GetSound(const std::string& name) |
Get loaded sound (nullptr if not found) |
| Method | Signature | Description |
|---|---|---|
PlaySound |
AudioSource* PlaySound(name, volume, pitch, loop) |
Play 2D sound |
PlaySound3D |
AudioSource* PlaySound3D(name, position, volume, pitch, loop) |
Play 3D sound |
StopSound |
void StopSound(AudioSource* source) |
Stop specific source |
StopAllSounds |
void StopAllSounds() |
Stop all |
PauseAllSounds |
void PauseAllSounds() |
Pause all |
ResumeAllSounds |
void ResumeAllSounds() |
Resume all |
| Method | Signature | Description |
|---|---|---|
SetMasterVolume |
void SetMasterVolume(float volume) |
Set master (0.0-1.0) |
SetSFXVolume |
void SetSFXVolume(float volume) |
Set SFX channel |
SetMusicVolume |
void SetMusicVolume(float volume) |
Set music channel |
GetMasterVolume |
float GetMasterVolume() const |
Get master |
GetSFXVolume |
float GetSFXVolume() const |
Get SFX |
GetMusicVolume |
float GetMusicVolume() const |
Get music |
| Method | Signature | Description |
|---|---|---|
GetActiveSourceCount |
size_t GetActiveSourceCount() const |
Number of playing sources |
GetXAudio2 |
IXAudio2* GetXAudio2() const |
Raw XAudio2 interface |
GetMasteringVoice |
IXAudio2MasteringVoice* GetMasteringVoice() const |
Mastering voice |
CreateSubmixVoice |
IXAudio2SubmixVoice* CreateSubmixVoice(channels, sampleRate) |
Create submix for bus routing |
struct AudioMetrics
{
size_t activeSources; // Currently playing
size_t totalSources; // Pool capacity
size_t loadedSounds; // Loaded sound effects
float masterVolume;
float sfxVolume;
float musicVolume;
float cpuUsage; // Audio CPU percentage
size_t memoryUsage; // Audio memory (bytes)
bool is3DEnabled;
XMFLOAT3 listenerPosition;
XMFLOAT3 listenerVelocity;
float dopplerScale;
float distanceScale;
};
struct AudioSettings
{
float masterVolume;
float sfxVolume;
float musicVolume;
float dopplerScale; // 0.0-2.0
float distanceScale; // 0.1-10.0
bool enable3D;
bool enableReverb;
bool enableEAX;
int maxSources;
XMFLOAT3 listenerPosition;
XMFLOAT3 listenerVelocity;
XMFLOAT3 listenerForward;
XMFLOAT3 listenerUp;
};The AudioMixer (in Spark::Audio namespace) provides mix buses (volume, mute, solo -- applied to live sources through AudioEngine::SetMixer), audio occlusion (applied when a PhysicsSystem is attached), mixer snapshots, and reverb-zone / DSP-effect authoring data that is stored but not applied to any voice.
AudioMixer mixer;
mixer.Initialize(); // Creates Master, SFX, Music, Voice, Ambient buses
mixer.CreateBus("SFX");
mixer.CreateBus("Music");
mixer.CreateBus("Ambient");
mixer.CreateBus("Voice");
mixer.CreateBus("UI");
mixer.SetBusVolume("SFX", 0.8f);
mixer.SetBusMuted("Music", false);
mixer.SetBusSolo("Voice", true); // Solo: only this bus is audible
float effectiveVol = mixer.GetEffectiveBusVolume("SFX"); // Cascaded through parents
std::vector<std::string> names = mixer.GetBusNames();struct MixBus
{
std::string name; // Bus identifier
float volume = 1.0f; // Volume multiplier (0.0-1.0)
bool muted = false; // Mute all sounds on this bus
bool solo = false; // Solo this bus
std::string parentBus; // Parent bus name (empty = Master)
};Per-bus effect chains. Stored, not applied: AddBusEffect records the chain on
the bus, but no XAPO or software DSP consumes it, so these settings have no audible
effect today (see Stub and Abandoned Features).
enum class DSPEffectType
{
LowPass, // Low-pass filter
HighPass, // High-pass filter
BandPass, // Band-pass filter
Equalizer, // Multi-band EQ
Compressor, // Dynamic range compressor
Limiter, // Hard limiter
Delay, // Echo/delay
Chorus, // Chorus/flanger
Distortion // Distortion/overdrive
};
struct DSPEffect
{
DSPEffectType type = DSPEffectType::LowPass;
float param1 = 0.0f; // Effect-specific parameter 1
float param2 = 0.0f; // Effect-specific parameter 2
float param3 = 0.0f; // Effect-specific parameter 3
float wetDry = 1.0f; // 0.0 = bypass, 1.0 = full effect
bool enabled = true;
};
// Add effects to a bus
DSPEffect lowPass;
lowPass.type = DSPEffectType::LowPass;
lowPass.param1 = 5000.0f; // Cutoff frequency in Hz
mixer.AddBusEffect("SFX", lowPass);
mixer.ClearBusEffects("SFX"); // Remove all effects from a busSpatial volumes that describe reverb for a region. Authoring data only:
GetReverbAtPosition returns the blended parameters, but no reverb effect is
rendered into the playback path.
enum class ReverbPreset
{
None, SmallRoom, MediumRoom, LargeRoom,
Hall, Cave, Sewer, Outdoor, Forest, Underwater, Custom
};
struct ReverbParameters
{
ReverbPreset preset = ReverbPreset::None;
float decayTime = 1.0f; // Seconds
float earlyReflections = 0.5f; // 0.0-1.0
float lateReverb = 0.5f; // 0.0-1.0
float diffusion = 0.7f; // 0.0-1.0
float density = 0.8f; // 0.0-1.0
float roomSize = 0.5f; // Virtual room size
float wetDryMix = 0.3f; // 0.0=dry, 1.0=fully wet
float highFreqDamping = 0.5f; // 0.0-1.0
};
// Create and add a reverb zone
ReverbZone cave;
cave.name = "CaveReverb";
cave.position = {50.0f, 0.0f, 30.0f};
cave.halfExtents = {15.0f, 10.0f, 15.0f};
cave.innerRadius = 10.0f;
cave.outerRadius = 25.0f;
cave.reverb.preset = ReverbPreset::Cave;
cave.reverb.decayTime = 3.0f;
cave.priority = 1;
mixer.AddReverbZone(cave);
// Query reverb at a position
ReverbParameters params = mixer.GetReverbAtPosition(playerPosition);
mixer.RemoveReverbZone("CaveReverb");Raycasted obstruction between listener and source:
mixer.SetOcclusionEnabled(true);
OcclusionResult result = mixer.CalculateOcclusion(listenerPos, sourcePos);
// result.occlusionFactor: 0.0 = unobstructed, 1.0 = fully blocked
// result.lowPassCutoff: filter frequency in Hz
// result.volumeScale: volume multiplier from occlusion
// result.wallCount: number of walls between listener and sourceOcclusion is real only after AudioMixer::SetPhysics() has been given the engine's
PhysicsSystem (the gameplay lifecycle does this). It counts WorldStatic hits
between listener and source and applies volumeScale to the source voice.
AudioMixer::IsOcclusionAvailable() reports whether occlusion is enabled and
physics is attached; when it is false, an "unoccluded" result means not
measured, not "clear line of sight". lowPassCutoff is reported but is not yet
applied as a filter.
Save and restore complete mixer state:
mixer.SaveSnapshot("gameplay");
mixer.SaveSnapshot("menu");
// Transition to menu audio
mixer.RestoreSnapshot("menu", 1.0f); // 1-second blend
// Back to gameplay
mixer.RestoreSnapshot("gameplay", 0.5f);The MusicManager (singleton) handles background music, playlists, crossfading, and dynamic music:
auto& music = MusicManager::GetInstance();
music.Initialize();struct MusicTrack
{
std::string name;
std::string filepath;
float bpm = 120.0f; // Beats per minute (for sync)
float loopStartTime = 0.0f; // Where to loop back to
float loopEndTime = -1.0f; // -1 = end of file
bool loop = true;
std::string nextTrack; // Auto-chain to next track
};
MusicTrack track;
track.name = "exploration";
track.filepath = "Assets/Audio/Music/exploration.wav";
track.bpm = 100.0f;
track.loop = true;
music.RegisterTrack(track);
music.UnregisterTrack("exploration");music.Play("exploration", 1.0f); // 1-second fade-in
music.CrossfadeTo("combat", 2.0f); // 2-second crossfade
music.Stop(1.5f); // 1.5-second fade-out
music.Pause();
music.Resume();
bool playing = music.IsPlaying();
const std::string& current = music.GetCurrentTrackName();enum class PlaylistMode
{
Sequential, // Play in order
Shuffle, // Random order
Loop, // Loop entire playlist
LoopOne // Loop current track
};
Playlist playlist;
playlist.name = "exploration";
playlist.trackNames = {"track1", "track2", "track3"};
playlist.mode = PlaylistMode::Shuffle;
music.RegisterPlaylist(playlist);
music.PlayPlaylist("exploration");
music.SetPlaylistMode(PlaylistMode::Shuffle);
music.NextTrack();
music.PreviousTrack();Automatically transitions music based on gameplay intensity:
enum class CombatIntensity
{
Exploration, // Calm ambient music
LowThreat, // Tension building
Combat, // Full combat music
BossFight // Maximum intensity
};
DynamicMusicState state;
state.explorationTrack = "exploration";
state.lowThreatTrack = "tension";
state.combatTrack = "combat";
state.bossFightTrack = "boss_battle";
state.transitionDuration = 2.0f; // Crossfade time between levels
music.SetDynamicMusicState(state);
// Game events drive intensity changes
music.SetCombatIntensity(CombatIntensity::Combat);
music.SetCombatIntensity(CombatIntensity::BossFight);
CombatIntensity current = music.GetCombatIntensity();Generate common FPS sounds programmatically with SoundEffectFactory:
auto gunshot = SoundEffectFactory::CreateGunshot();
auto explosion = SoundEffectFactory::CreateExplosion();
auto footstep = SoundEffectFactory::CreateFootstep();
auto reload = SoundEffectFactory::CreateReload();
auto pickup = SoundEffectFactory::CreatePickup();
auto beep = SoundEffectFactory::CreateBeep(440.0f, 0.5f); // frequency, duration
auto sine = SoundEffectFactory::CreateSine(880.0f, 1.0f); // pure sine wave
auto noise = SoundEffectFactory::CreateNoise(0.3f); // white noiseAll factory methods return std::unique_ptr<SoundEffect> in standard PCM WAV format at 44100 Hz sample rate.
| Factory Method | Parameters | Description |
|---|---|---|
CreateBeep |
freq=440Hz, dur=0.5s |
Square wave beep tone |
CreateSine |
freq=440Hz, dur=1.0s |
Pure sine wave |
CreateNoise |
dur=1.0s |
White noise |
CreateGunshot |
none | Procedural gunshot with envelope |
CreateExplosion |
none | Low-frequency rumble + high-frequency crack |
CreateFootstep |
none | Footstep impact sound |
CreateReload |
none | Metallic click reload sound |
CreatePickup |
none | Pleasant item collection sound |
The audio engine uses an object pool for efficient source management. Sources are pre-allocated during Initialize() and reused:
- When
PlaySound/PlaySound3Dis called,GetAvailableSource()retrieves an idle source from the pool. - When playback completes or
StopSoundis called,ReturnSource()marks the source as available. - If no sources are available, the engine may stop the oldest playing source to free one up.
Pool size is set by the maxSources parameter in Initialize(). Typical values: 32 for small games, 64 for open-world.
On non-Windows platforms, OpenALAudioEngine provides the same feature set:
// Compiled only when !SPARK_PLATFORM_WINDOWS
Spark::Audio::OpenALAudioEngine audio;
audio.Initialize(32);
audio.LoadSound("gunshot", "Assets/Audio/gunshot.wav"); // std::string, not wstring
auto* src = audio.PlaySound("gunshot", 1.0f, 1.0f, false);
audio.SetListenerPosition({0.0f, 0.0f, 0.0f});
audio.Update(deltaTime);Uses Spark::Audio::Float3 instead of XMFLOAT3 for positions/velocities. The interface mirrors the XAudio2 AudioEngine so game code can use a platform abstraction layer.
Use AudioSourceComponent on entities:
auto& src = world.AddComponent<AudioSourceComponent>(entity);
src.soundFile = "gunshot";
src.volume = 1.0f;
src.pitch = 1.0f;
src.loop = false;
src.is3D = true;
src.minDistance = 1.0f;
src.maxDistance = 50.0f;The AudioUpdateSystem automatically updates 3D source positions from entity transforms each frame, keeping spatial audio in sync with entity movement.
audio_info # Show audio system status and metrics
audio_master <vol> # Set master volume (0.0-1.0)
audio_sfx <vol> # Set SFX volume
audio_music <vol> # Set music volume
audio_play <name> # Play a loaded sound by name
audio_stop_all # Stop all playing sounds
audio_list # List all loaded sounds with durations
audio_sources # Show active audio source count and details
| Method | Signature | Description |
|---|---|---|
Console_SetMasterVolume |
void Console_SetMasterVolume(float volume) |
Set master via console |
Console_SetSFXVolume |
void Console_SetSFXVolume(float volume) |
Set SFX via console |
Console_SetMusicVolume |
void Console_SetMusicVolume(float volume) |
Set music via console |
Console_PlayTestSound |
uint32_t Console_PlayTestSound(name, is3D) |
Play test sound, returns source ID |
Console_StopSound |
void Console_StopSound(uint32_t sourceID) |
Stop by source ID |
Console_StopAllSounds |
void Console_StopAllSounds() |
Stop all |
Console_ListSounds |
std::string Console_ListSounds() const |
List loaded sounds |
Console_GetMetrics |
AudioMetrics Console_GetMetrics() const |
Get audio metrics |
Console_GetSettings |
AudioSettings Console_GetSettings() const |
Get current settings |
Console_ApplySettings |
void Console_ApplySettings(const AudioSettings&) |
Apply settings |
Console_ResetToDefaults |
void Console_ResetToDefaults() |
Reset all to defaults |
Console_RefreshAudio |
void Console_RefreshAudio() |
Force audio refresh |
Console_GetSourceInfo |
std::string Console_GetSourceInfo(uint32_t) |
Get source details |
Console_RegisterStateCallback |
void Console_RegisterStateCallback(function) |
State change notifications |
| Scenario | Behavior |
|---|---|
Initialize fails (no audio device) |
Returns E_FAIL; all subsequent Play calls return nullptr
|
LoadSound with invalid file path |
Returns E_FAIL; sound is not added to the map |
LoadSound with non-WAV file |
Returns E_FAIL; WAV parsing fails |
PlaySound with unknown name |
Returns nullptr
|
PlaySound with no available sources |
May stop oldest source; returns nullptr if impossible |
StopSound with nullptr
|
No-op (safe) |
SetMasterVolume with negative value |
Clamped to 0.0 |
| Missing OpenAL on Linux |
Initialize returns false; engine runs without audio |
- Source pool avoids per-play allocation. Pre-allocate enough sources during
Initialize. - WAV data is stored in memory after loading. For large files (music tracks), consider streaming.
- 3D audio calculations (
Apply3DAudioToSource) run per active 3D source per frame. Keep 3D source count reasonable (under 20 simultaneous). - Audio occlusion raycasts are expensive. Limit
maxRaysinOcclusionSettings(default 4). - Mixer bus volume cascading is O(depth) per bus, which is negligible in practice.
-
SoundEffectFactorygenerates sounds on the calling thread. Generate procedural sounds during loading, not during gameplay.
-
AudioEngineusesstd::mutex(m_metricsMutex) for thread-safe metrics access viaGetMetricsThreadSafe(). -
Console_GetMetrics()is safe to call from any thread. - All playback methods (
PlaySound,StopSound, etc.) must be called from the main thread. -
OpenALAudioEngineuses a mutex (m_mutex) for source pool access but playback should still be main-thread only. -
MusicManageris not thread-safe; call all methods from the main thread. -
AudioMixeris not thread-safe; callUpdate()from the main thread.
| Platform | Backend | 3D Audio | Mixer | Music | Status |
|---|---|---|---|---|---|
| Windows | XAudio2 | Implemented; the listener follows the EngineContext camera each frame via AudioEngine::SetListenerFromCamera
|
Bus volume/mute/solo applied; occlusion applied when physics is attached; reverb zones and per-bus DSP are authoring data only | Volume chain works; no shipped caller routes cues to AudioCategory::Music yet |
In stable-v1, blocked and uncertified |
| Linux | OpenAL Soft | Implemented | Same mixer limits as Windows | Same as Windows | Experimental, outside stable-v1
|
| macOS | OpenAL Soft | Implemented | Same mixer limits as Windows | Same as Windows | Experimental, outside stable-v1
|
| Problem | Cause | Solution |
|---|---|---|
| No sound output | Initialize not called or failed | Check Initialize() return value |
| Silent after scene change |
StopAllSounds called, new sounds not started |
Re-trigger sounds in new scene |
| 3D audio sounds flat | Listener not updated (the gameplay lifecycle calls SetListenerFromCamera each frame; standalone hosts must do so themselves) |
Call SetListenerPosition/Orientation or SetListenerFromCamera each frame |
| Audio crackling | Too many sources or update not called | Reduce sources; ensure Update(dt) every frame |
| Music crossfade glitch |
MusicManager::Update not called |
Call music.Update(deltaTime) every frame |
| Reverb not applying | Listener outside reverb zone | Check zone radii and position |
| Occlusion not working | Physics system unavailable | Ensure the Jolt dependency configured successfully and physics initialized |
| OpenAL not found (Linux) | Library not installed | sudo apt install libopenal-dev |
| WAV loading fails | File not found or wrong format | Verify path and WAV format (PCM only) |
| Source pool exhausted | Too many simultaneous sounds | Increase maxSources or prioritize sounds |
- Entity Component System -- AudioSourceComponent
- Asset Pipeline -- Loading audio assets
- Cinematic Sequencer -- Audio tracks in cinematics
- Event System -- Triggering audio from game events
- Day Night Cycle and Weather -- Ambient audio for weather and time of day
- Dialogue System -- Voice clip playback integration
- Physics -- Required for audio occlusion raycasts
Published from 2b03dc797148. Edit the canonical source in wiki/.
- Documentation
- Docs route
- Wiki index
- Guides
- Tutorials
- Samples
- Examples
- API Reference
- API route
- Reference
- Build Guide
- Dependencies
- FAQ
- Changelog
- Roadmap
- Contributing
- Code of Conduct
- Home
- FAQ
- Getting Started
- Quick-Start Tutorial
- Making Your First Game
- Making Your First Multiplayer Game
- Artist Workflow Guide
- Editor Walkthrough
- Migration Guide
- How SparkEngine Works
- Architecture Overview
- Engine Architecture Flowchart
- Creating a Game Module
- Game Modules (catalog)
- Entity Component System
- Rendering and Graphics
- Physics
- Cloth Simulation
- Audio
- Input System
- Camera System
- Scripting with AngelScript
- Visual Scripting
- AI and Navigation
- Animation
- 2D Systems
- Networking
- Dedicated Server
- Multiplayer Quick Start
- Area Server Architecture
- Scene Management
- Large World Support
- Collaborative Editing
- Coroutine System
- Event System
- Event Response System
- Job System
- UI System
- UI Layout Extensions
- Localization
- Dialogue System
- Destruction System
- Replay System
- Achievement System
- Loading System
- Mod System
- Content Delivery
- Tween System
- Memory Integrity
- Gameplay Systems
- Terrain and Procedural Generation
- Save System
- Persistence System
- Day Night Cycle and Weather
- Cinematic Sequencer
- Runtime Prefabs
- SparkEditor
- Editor Tutorials
- SparkConsole
- SparkDaemon
- Shader Pipeline
- Asset Pipeline
- Asset Validation
- Asset Migration
- Game Packaging
- Online Services
- DataTable System
- Loot and Crafting System
- CSG System
- Font System
- Timer Manager
- Movie Render Pipeline
- HLOD and World Partition
- Remote Debug System
- Selection Manager
- Asset Dependency Graph
- Editor Automation
- File Watcher
- Project Templates
- System Requirements
- VR Support
- Mobile Platform
- Accessibility
- Platform Input
- Platform Certification
- Cross-Compilation: Wine Testing
- RHI Abstraction Layer
- D3D11 Backend
- D3D12 Backend
- Vulkan Backend
- OpenGL Backend
- Metal Backend
- DXR Raytracing
- Hybrid Ray Tracing
- Upscaling (DLSS/FSR)
- Render Graph
- Shader Graph
- GPU Particles
- GPU-Driven Rendering
- Volumetric Fog
- Volumetric Clouds
- Global Illumination
- Virtual Texturing
- Water Rendering
- Clustered Lighting
- Material System
- Post-Processing
- Shadow System
- Particle System
- Decal System
- Sky and Atmosphere
- Foliage System
- Mesh Shaders
- Neural Rendering
- Configuration Reference
- Performance Tips
- Benchmark Framework
- Threading Model
- Fuzz Policy and Parser Security
- Memory Safety
- Memory Management Patterns
- Build System and CMake Modules
- Profiler and Debugging
- Performance Profiling Guide
- Telemetry System
- Crash Reporting
- Golden Image Testing
- Utilities
- Testing
- Fuzz Policy and Parser Security
- Codebase Statistics
- Codebase Health
- Error Handling Patterns
- Hot Reload Overview
- Troubleshooting
- Contributing
- Workflow Patterns
- Build Optimizations
- CI Reproducible Builds
- GitHub API and PR Checks
- Git Rebase Conflicts
- Clang-Format
- Code Quality Violations
- AI Bloat Pattern
- MinGW + Wine Cross-Compilation
- Live Editor Testing
- Engine & Renderer Landscape
- DuetOS Portability Catalog
- Five-Engine Analysis
- Eleven-Engine Analysis
- ThorVG / Unity Graphics Analysis
- Advanced Techniques Catalog
- Third-Party Library Evaluation
- Engine Viability Evaluation
- Engine Feature Recommendations
- Project Recommendations
- Mac Compatibility Analysis
- Codebase Observations
- Codebase Bloat Audit
- Test Suite Audit
- Documentation Coverage Audit
- ThirdParty Dependencies Audit
- Load Test Baseline
- Gameplay Systems Status
- SparkGame Module Status
- Stub and Abandoned Features
- Memory Integrity System
- Memory Safety Evaluation
- Hardware Acceleration Systems
- Jolt Physics Integration
- GPU/CPU Separation Plan
- Daemon Services Architecture
- Reflection & Polymorphism Refactoring Plan
- SparkBuild In-Tree
- Wine No-JobSystem Breakthrough
- Wine Role and Fallback Tiers