Audio¶
Add sound effects, background music, and immersive spatial audio to your Brine2D games.
Quick Start¶
using Brine2D.Audio;
public class AudioScene : Scene
{
private readonly ISoundLoader _soundLoader;
private readonly IMusicLoader _musicLoader;
private ISoundEffect? _jumpSound;
private IMusic? _backgroundMusic;
public AudioScene(ISoundLoader soundLoader, IMusicLoader musicLoader)
{
_soundLoader = soundLoader;
_musicLoader = musicLoader;
}
protected override async Task OnLoadAsync(CancellationToken ct, IProgress<float>? progress = null)
{
_jumpSound = await _soundLoader.GetOrLoadSoundAsync("assets/jump.wav", ct);
_backgroundMusic = await _musicLoader.GetOrLoadMusicAsync("assets/music.ogg", ct);
Audio.PlayMusic(_backgroundMusic, loops: -1);
}
protected override void OnUpdate(GameTime gameTime)
{
if (Input.IsKeyPressed(Key.Space))
Audio.PlaySound(_jumpSound!);
}
}
Topics¶
Getting Started¶
| Guide | Description | Difficulty |
|---|---|---|
| Getting Started | Audio basics and setup | |
| Sound Effects | Play sounds (jump, shoot, etc.) | |
| Music Playback | Background music and looping |
Advanced¶
| Guide | Description | Difficulty |
|---|---|---|
| Spatial Audio | 2D positional audio with distance/panning |
Key Concepts¶
Track-Based Audio¶
Brine2D uses a track-based audio system for precise control. PlaySound always returns a track handle:
// Play sound and get track handle
nint track = Audio.PlaySound(_shootSound!, volume: 0.8f);
// Control specific track
Audio.SetTrackVolume(track, 0.6f);
Audio.SetTrackPan(track, 0.5f);
Audio.SetTrackPitch(track, 1.2f);
Audio.PauseTrack(track);
Audio.ResumeTrack(track);
Audio.StopTrack(track);
// Check if track is still playing
if (Audio.IsTrackAlive(track))
Logger.LogInformation("Track still playing");
Features:
- Precise control over individual sounds
- Update volume, pan, and pitch in real-time
- Pause and resume individual tracks
- Priority-based eviction when tracks are full
- Bus-based grouping for batch operations
Audio Architecture¶
graph TB
AS["IAudioService<br/>(ISoundLoader + IMusicLoader + IAudioPlayer)"]
SE["Sound Effects<br/>(Short, frequent)"]
M["Music<br/>(Long, looping)"]
SA["Spatial Audio<br/>(ECS components)"]
AS --> SE
AS --> M
AS --> SA
SE --> T1["Track 1"]
SE --> T2["Track 2"]
SE --> T3["Track 3"]
M --> MT["Music Track"]
SA --> ST1["SoundEffectSourceComponent"]
SA --> ST2["MusicSourceComponent"]
style AS fill:#2d5016,stroke:#4ec9b0,stroke-width:3px,color:#fff
style SE fill:#1e3a5f,stroke:#569cd6,stroke-width:2px,color:#fff
style M fill:#4a2d4a,stroke:#c586c0,stroke-width:2px,color:#fff
style SA fill:#3d3d2a,stroke:#dcdcaa,stroke-width:2px,color:#fff
Narrow interfaces: Depend on only what you need:
| Interface | Purpose |
|---|---|
ISoundLoader |
Load sound effects (GetOrLoadSoundAsync) |
IMusicLoader |
Load music (GetOrLoadMusicAsync) |
IAudioPlayer |
Playback, volume, track control |
IAudioService |
All of the above (composite) |
The Audio property on Scene is IAudioService.
Common Tasks¶
Play Sound Effect¶
private ISoundEffect? _explosionSound;
protected override async Task OnLoadAsync(CancellationToken ct, IProgress<float>? progress = null)
{
_explosionSound = await _soundLoader.GetOrLoadSoundAsync("assets/explosion.wav", ct);
}
protected override void OnUpdate(GameTime gameTime)
{
if (enemyKilled)
{
Audio.PlaySound(_explosionSound!);
Audio.PlaySound(_explosionSound!, volume: 0.7f, pan: -0.3f, pitch: 1.1f);
}
}
Play Background Music¶
private IMusic? _music;
protected override async Task OnLoadAsync(CancellationToken ct, IProgress<float>? progress = null)
{
_music = await _musicLoader.GetOrLoadMusicAsync("assets/background.ogg", ct);
Audio.PlayMusic(_music, loops: -1);
}
protected override void OnUpdate(GameTime gameTime)
{
if (Input.IsKeyPressed(Key.P))
{
if (Audio.IsMusicPaused)
Audio.ResumeMusic();
else
Audio.PauseMusic();
}
}
Spatial Audio (ECS Components)¶
// Create audio listener (player)
var player = World.CreateEntity("Player");
player.AddComponent<TransformComponent>(t => t.Position = new Vector2(400, 300));
player.AddComponent<AudioListenerComponent>();
// Create spatial audio source (enemy)
var enemy = World.CreateEntity("Enemy");
enemy.AddComponent<TransformComponent>(t => t.Position = new Vector2(200, 300));
enemy.AddComponent<SoundEffectSourceComponent>(src =>
{
src.SoundEffect = _enemyGrowlSound;
src.EnableSpatialAudio = true;
src.MinDistance = 100f;
src.MaxDistance = 500f;
src.RolloffFactor = 1.0f;
src.SpatialBlend = 1.0f;
src.LoopCount = -1;
src.PlayOnEnable = true;
});
Volume Control¶
// Master volume (affects all audio)
Audio.MasterVolume = 0.8f;
// Sound effects volume
Audio.SoundVolume = 0.6f;
// Music volume
Audio.MusicVolume = 0.5f;
// Per-sound volume
Audio.PlaySound(_jumpSound!, volume: 0.9f);
// Update track volume/pan/pitch in real-time
nint track = Audio.PlaySound(_engineSound!, loops: -1);
Audio.SetTrackVolume(track, 0.7f);
Audio.SetTrackPan(track, 0.3f);
Audio.SetTrackPitch(track, 0.9f);
Bus-Based Audio Grouping¶
// Play sounds on named buses
nint track = Audio.PlaySound(_uiClick!, bus: "ui");
// Tag existing tracks
Audio.TagTrack(track, "ui");
// Pause/resume/stop entire buses
Audio.PauseBus("sfx");
Audio.ResumeBus("sfx");
Audio.StopBus("ui");
// Set per-bus volume
Audio.SetBusVolume("sfx", 0.5f);
Supported Formats¶
| Format | Sound Effects | Music | Recommended For |
|---|---|---|---|
| WAV | Sound effects (uncompressed) | ||
| OGG | Music (compressed, high quality) | ||
| MP3 | Music (compressed, smaller file) | ||
| FLAC | Music (lossless) |
Recommendations:
- Sound effects: WAV (fast loading, no decompression overhead)
- Music: OGG (good compression, no licensing issues)
Best Practices¶
DO¶
- Load sounds in OnLoadAsync — Keep OnUpdate fast
- Use appropriate formats — WAV for SFX, OGG for music
- Control volume — Don't max out everything
- Use track handles for looping sounds — Stop them when done
- Use buses for group control — Pause all SFX during menus
protected override async Task OnLoadAsync(CancellationToken ct, IProgress<float>? progress = null)
{
_jumpSound = await _soundLoader.GetOrLoadSoundAsync("assets/jump.wav", ct);
_music = await _musicLoader.GetOrLoadMusicAsync("assets/music.ogg", ct);
}
protected override void OnUpdate(GameTime gameTime)
{
Audio.PlaySound(_jumpSound!);
}
protected override void OnExit()
{
Audio.StopAllSounds();
Audio.StopMusic();
}
DON'T¶
- Don't load sounds in OnUpdate — Causes lag
- Don't play too many sounds — Use
MaxConcurrentInstancesonSoundEffectSourceComponentor priority-based eviction - Don't forget to stop music — It plays between scenes
- Don't use MP3 for SFX — Decoding overhead
- Don't max out volume — Causes clipping/distortion
Troubleshooting¶
No Sound Playing¶
-
Check volume levels:
Logger.LogDebug("Master: {M}, Music: {Mu}, Sound: {S}", Audio.MasterVolume, Audio.MusicVolume, Audio.SoundVolume); -
Verify sound loaded —
GetOrLoadSoundAsyncreturns null on failure - Check file exists — Verify path and that assets are copied to output
Music Continues Between Scenes¶
Stop music in OnExit:
protected override void OnExit()
{
Audio.StopMusic();
}
Related Topics¶
- Getting Started — Audio basics
- Sound Effects — Play sounds
- Music Playback — Background music
- Spatial Audio — Positional audio
Ready to add sound? Start with Getting Started!