Skip to main content

Audio

A full-stack game audio system, from one-shot to reverb tail: a bussed mixer, 2D spatial sound, designer-placed zones for ambience, crowds, music and mood, real voice management, and an optional software-mixing backend with DSP for occlusion and reverb. Built on MonoGame, NativeAOT-safe, with pure-managed decoders for wav, ogg and mp3.

Feature overview​

The signal path has five stages:

StageWhat happens
SourcesComponents and zones start voices: one-shots, loops, positional sound.
MixerA bus tree with volume, mute and solo, ducking, and mood snapshots.
SpatialDistance attenuation and pan relative to the listener.
VoicesPriority, instance caps, distance culling, virtual voices.
BackendMonoGame or the software mixer, then DSP, then the output device.

Sources and playback​

One component, AudioSourceComponent, covers fire-and-forget SFX, looping ambience, and 2D positional sound. Everything is live-tunable from the inspector while the game runs.

FeatureAPI
One-shots and loops. Play a clip once or loop it as a tracked, controllable voice; overlapping SFX go fire-and-forget.AudioSourceComponent, Play(), Stop()
2D positional. Attenuate and pan by distance to the listener, with an inner full-volume radius and an outer silence radius.Is3D, MinDistance, MaxDistance
Pitch, pan, volume. Per-voice controls synced every frame; inspector edits are heard instantly in Play mode.Volume, Pitch, Pan
Random pitch. Vary pitch by a range per shot so repeated footsteps never sound machine-stamped.RandomPitchRange
Fades and crossfade. Fade any voice in or out, fade to a level, or crossfade to a different clip.FadeIn, FadeOut, FadeTo, CrossfadeTo
Priority and caps. Rank a source so it survives a crowded mix, and cap how many copies of one clip can stack.Priority, MaxInstances

Zones and atmosphere​

Drop a trigger volume, dial the levels, done. These components author a soundscape without code.

ComponentPurpose
AmbienceZoneComponentLooping environmental beds that crossfade in on enter and out on exit. Overlapping zones blend.
CrowdEmitterComponentScatters randomized murmurs and footsteps around a point with a Density knob: a living crowd from one emitter.
MusicZoneComponentStarts a track when the player enters a region; it crossfades from whatever was playing.
MixerSnapshotZoneComponentShifts the whole mix to a "mood" on enter as a smooth per-bus crossfade.
DialogueDuckingComponentWhile anything plays on the Voice bus, music and ambience dip so speech stays clear, then recover.
ReverbZoneComponentMakes a room, tunnel or cathedral reverberant while inside. Software backend only; a no-op elsewhere.

The mixer​

A hierarchical bus tree, the mixing layer MonoGame lacks. Every voice routes through a named bus, so an options slider or a story beat can move whole categories at once.

Master
├── Music
├── SFX
├── UI
├── Ambience
└── Voice
FeatureAPI
Volume, mute, solo. Per-bus gain folded down the parent chain, with a mixing-desk solo rule for auditioning.SetBusVolume, Bus(name)
Ducking. Two independent duck factors, a transient sidechain hit and a sustained dialogue duck, that compose cleanly.Duck(), auto-duck
Mood snapshots. Capture the mixer state and transition to it over time with a smoothstep ease. Master is left for the player's volume.CaptureSnapshot, TransitionTo

Spatial and voice management​

2D spatialization keeps crowded scenes legible, and a real voice manager keeps them under the platform's roughly 32-source ceiling.

FeatureAPI
Distance and pan. Linear attenuation between min and max distance plus a stereo pan from the emitter's offset.AudioListenerComponent
Priority stealing. When the pool is full, a newcomer steals the weakest voice only if it outranks it.MaxVoices
Distance culling. Sounds beyond audible range never take a real voice; inaudible one-shots are dropped early.IsAudible
Virtual voices. A far-away loop goes virtual, tracked but silent, and revives when back in range.IsVirtual
Range gizmos. Every 3D source draws its reach in the editor: green is full volume, orange is silence, cyan is the crowd scatter radius.DebugRender
Live tuning. Edit any audio component in Play mode, even while paused, and hear the change immediately.IUpdatableInPauseMode

Backends and DSP​

One swappable seam under everything. The default plays through MonoGame; an opt-in software mixer owns the PCM loop to unlock effects the per-sound path cannot do, and falls back where unsupported.

FeatureAPI
Two backends. MonoGame per-voice playback (default), or a software mixer that sums every voice into one dynamic stream.IAudioBackend
Clip abstraction. Components deal only in AudioClip, so the representation changes with the backend, not the code.AudioClip, LoadClip
Capability fallback. The software backend probes the platform and falls back to MonoGame if unsupported; DSP no-ops there.IsSupported
Occlusion low-pass. A per-voice low-pass cutoff muffles sounds behind walls or far away.LowPassCutoffHz
Reverb send. A shared Freeverb (8 comb + 4 all-pass); each voice feeds it by a send amount driven by reverb zones.ReverbSend, SetReverb
Managed decoders. wav (RIFF), ogg (NVorbis) and mp3 (NLayer) decoded in managed code; AOT- and trim-safe.AudioDecoders

Cost controls​

Every place that can bound CPU or the voice pool is an explicit knob. Dial these per title, especially for mobile.

KnobBounds
MaxVoicesReal voice ceiling
MaxInstancesPer-clip cap
MaxConcurrentCrowd bits
DensityCrowd spawn rate
SetReverbEnabledDSP toggle
SIMDVectorized output

Architecture: internal signal flow​

How audio flows from the components you place, through the AudioManager, to the swappable backend and the OS.

Setup: which entity gets which components​

The practical composition guide for a game project.

Enabling the software backend (DSP)​

Reverb and occlusion low-pass require the software mixing backend. It's opt-in and set before the AudioManager is constructed (there's a commented line in Voltage.Editor/Program.cs):

Voltage.Audio.AudioManager.PreferSoftwareBackend = true;

It probes the platform and falls back to the MonoGame backend automatically where unsupported; DSP is then a harmless no-op. Watch the console for a [Audio] Using SoftwareMixingAudioBackend… line to confirm.

Muting and IAudioComponent​

Any component that produces audio should implement IAudioComponent and register itself with AudioComponentRegistry so it respects the global mute toggle:

public partial class FootstepPlayer : Component, IUpdatable, IAudioComponent
{
private SoundEffect _sfx;
private SoundEffectInstance _instance;

public override void OnAddedToEntity()
{
AudioComponentRegistry.Register(this);
_sfx = Entity.Scene.Content.LoadSoundEffect("Content/Audio/Footstep.wav");
_instance = _sfx.CreateInstance();
}

public override void OnRemovedFromEntity()
{
AudioComponentRegistry.Unregister(this);
_instance?.Dispose();
}

public void OnAudioStateChanged(bool isAudioOn)
{
if (!isAudioOn)
_instance?.Pause();
else if (_instance?.State == SoundState.Paused)
_instance?.Resume();
}

public void Update()
{
if (Input.IsKeyPressed(Keys.Space) && Core.IsAudioOn)
_instance.Play();
}
}

Core.IsAudioOn — global read/write property. Setting it calls Core.InvokeSwitchAudio, which fires Core.OnSwitchAudio and applies SoundEffect.MasterVolume as a backstop for any audio that bypasses IAudioComponent.