Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.
MonoGame Audio Implementation Guide
This skill guides implementation of audio systems in MonoGame. For detailed API signatures, see references/audio.md.
Choosing the Right Audio Class
| Class | Use case |
|---|---|
SoundEffect | Short clips (SFX, UI sounds). Loaded entirely in memory. Call Play() for a fire-and-forget shot. |
SoundEffectInstance | When you need control: loop, pause, resume, adjust volume/pitch at runtime. One instance = one concurrent voice. |
Song + MediaPlayer | Background music. Streams from disk. Only one Song plays at a time. |
DynamicSoundEffectInstance | Procedural audio, WAV streaming, microphone echo. You push PCM buffers manually. |
Never use Song for short SFX — it streams from disk and has latency. Never use SoundEffect.Play() when you need to stop or loop — use a SoundEffectInstance instead.
Loading Audio
Always load in LoadContent(), never in Update() or Draw():
_jumpSound = Content.Load<SoundEffect>("Audio/Jump");
_bgMusic = Content.Load<Song>("Audio/Theme");
_footstep = Content.Load<SoundEffect>("Audio/Footstep");Playing Sound Effects
Fire-and-forget (no control needed)
_jumpSound.Play(); // volume=1, pitch=0, pan=0
_jumpSound.Play(volume: 0.8f, pitch: 0.1f, pan: 0f);With control (loop, stop, pitch changes)
// Create the instance once in LoadContent:
_footstepInstance = _footstep.CreateInstance();
_footstepInstance.IsLooped = true;
// Start/stop in Update:
_footstepInstance.Play();
_footstepInstance.Stop();
_footstepInstance.Pause();
// Real-time adjustments:
_footstepInstance.Volume = 0.6f; // 0.0 - 1.0
_footstepInstance.Pitch = 0.2f; // -1.0 - 1.0 (semitone shift × 12)
_footstepInstance.Pan = -0.5f; // -1.0 (left) - 1.0 (right)Background Music
// In LoadContent:
MediaPlayer.Volume = 0.7f;
MediaPlayer.IsRepeating = true;
MediaPlayer.Play(_bgMusic);
// Pause/resume:
MediaPlayer.Pause();
MediaPlayer.Resume();
// State check:
if (MediaPlayer.State == MediaState.Stopped) { ... }Only one Song can play at a time. Calling MediaPlayer.Play(newSong) while another plays stops the current one automatically.
Sound Instance Pooling
Concurrent voice limit: ~256 on desktop, ~32 on mobile. For high-frequency sounds (gunshots, particles), maintain a pool:
// Create pool once in LoadContent:
private const int PoolSize = 8;
private SoundEffectInstance[] _gunPool;
private int _gunPoolIndex;
_gunPool = new SoundEffectInstance[PoolSize];
for (int i = 0; i < PoolSize; i++)
_gunPool[i] = _gunSound.CreateInstance();
// Play from pool (round-robin):
private void PlayGunshot()
{
var instance = _gunPool[_gunPoolIndex];
_gunPoolIndex = (_gunPoolIndex + 1) % PoolSize;
instance.Stop(); // stop if already playing
instance.Play();
}3D Positional Audio
Use AudioListener (camera/player) and AudioEmitter (sound source). Both are class fields — do not instantiate per frame.
// Fields:
private AudioListener _listener = new AudioListener();
private AudioEmitter _emitter = new AudioEmitter();
// Update each frame:
_listener.Position = new Vector3(playerPos, 0f);
_emitter.Position = new Vector3(enemyPos, 0f);
// Apply to instance (call before or after Play):
_instance.Apply3D(_listener, _emitter);Apply3D sets volume and pan automatically based on distance and direction. It overrides manual Volume and Pan settings.
Dispose and Cleanup
SoundEffect and SoundEffectInstance hold unmanaged audio resources. Dispose them explicitly when unloading a scene:
protected override void UnloadContent()
{
_footstepInstance?.Dispose();
// SoundEffect assets are disposed by ContentManager.Unload()
}Never dispose a SoundEffectInstance while it is playing — call Stop() first.
AudioController Wrapper (Alca.MonoGame.Kernel)
The library provides AudioController as a managed wrapper over MonoGame raw audio. Access it via Core.Audio. It auto-tracks playing instances and simplifies common operations.
// Play a sound effect (returns the SoundEffectInstance for optional further control):
SoundEffectInstance inst = Core.Audio.PlaySoundEffect(_jumpSound);
SoundEffectInstance loop = Core.Audio.PlaySoundEffect(_engine, volume: 0.8f, isLooped: true);
// Optional parameters: volume (1f), pitch (1f), pan (0f), isLooped (false)
// Background music — static wrapper around MediaPlayer:
AudioController.PlaySong(_bgMusic, isRepeating: true);
// Mute / pause controls:
Core.Audio.MuteAudio();
Core.Audio.UnmuteAudio();
Core.Audio.ToggleMute();
Core.Audio.PauseAudio();
Core.Audio.ResumeAudio();
// Pool for high-frequency sounds — created once in LoadContent:
SoundEffectPool gunPool = AudioController.CreatePool(_gunSound, capacity: 8);
gunPool.Play(); // round-robin across the 8 pre-created instances
// 3D audio — update the listener each frame:
Core.Audio.UpdateListener(playerPosition3D, forwardVector3D);SoundEffectPool is the library equivalent of the manual round-robin pool described above — prefer it over hand-rolled pools.
Rules
- Load all audio in
LoadContent()— no exceptions. - Never create
SoundEffectInstanceinsideUpdate()orDraw()— create inLoadContent()and reuse. - Never play more than ~255 concurrent voices — pool high-frequency sounds.
- Do not allocate
AudioListenerorAudioEmitterper frame — they are class fields. - On mobile, test with the 32-voice limit in mind — desktop limits do not apply.
Loading Raw WAV via TitleContainer
Use TitleContainer.OpenStream when you need the raw PCM bytes (e.g. for DynamicSoundEffectInstance or procedural audio). In MGCB, set the file's Build Action to `Copy` (not the default Compress), otherwise the pipeline encodes the file and the raw header won't be readable.
using Stream wavStream = TitleContainer.OpenStream(@"Content\MySound.wav");
SoundEffect sfx = SoundEffect.FromStream(wavStream);For streaming with DynamicSoundEffectInstance, parse the WAV header manually to extract sampleRate and channels before constructing the instance — see references/audio.md.
Microphone Recording
Only available on OpenAL platforms: DesktopGL, iOS, Android. Not available on DirectX/Windows.
// Fields:
private Microphone _mic;
private byte[] _micBuffer;
private DynamicSoundEffectInstance _playback;
// Init (call once — e.g. LoadContent or first Update):
_mic = Microphone.Default;
if (_mic != null)
{
_mic.BufferDuration = TimeSpan.FromMilliseconds(100);
_micBuffer = new byte[_mic.GetSampleSizeInBytes(_mic.BufferDuration)];
_mic.BufferReady += OnMicBufferReady;
_playback = new DynamicSoundEffectInstance(_mic.SampleRate, AudioChannels.Mono);
}
// Start / stop:
_mic?.Start();
_mic?.Stop();
// BufferReady handler — called on the audio thread:
private void OnMicBufferReady(object sender, EventArgs e)
{
try
{
int size = _mic.GetData(_micBuffer);
_playback.SubmitBuffer(_micBuffer, 0, size); // echo to speakers
_playback.Play();
}
catch (NoMicrophoneConnectedException) { }
}Always guard with Microphone.Default != null — the device may have no microphone. Catch NoMicrophoneConnectedException in both Start/Stop and GetData — the mic can be disconnected mid-session.
Voice Limit and InstancePlayLimitException
- Desktop: ~256 simultaneous voices. Mobile: ~32.
SoundEffect.Play()(fire-and-forget) returnsfalsesilently when the limit is reached.- Creating a
SoundEffectInstanceand callingPlay()on it throws `InstancePlayLimitException` when the limit is exceeded. Pool instances to avoid this. DynamicSoundEffectInstancecounts against the voice limit.Song/MediaPlayeris independent of the voice count.
Reference
For full API signatures, WAV streaming, and microphone patterns, see references/audio.md.

