Cross-Platform Audio Systems - Research Report
Date: December 2024 Purpose: Evaluate audio libraries and approaches for implementing a cross-platform audio system supporting sound effects, music, and spatial audio.
Executive Summary
After evaluating OpenAL (via Silk.NET and OpenTK bindings), SDL audio, MiniAudio, FMOD, and Wwise, Silk.NET.OpenAL with OpenAL Soft is the recommended choice for a custom game engine. It provides the best balance of 3D audio capabilities, low latency, open-source licensing, and integration with the Silk.NET ecosystem already recommended for windowing and graphics.
For projects requiring advanced audio design tooling or console deployment, FMOD is a strong alternative with its free indie license and professional-grade features.
Library Comparison
Silk.NET.OpenAL (OpenAL Soft)
| Attribute | Value |
|---|---|
| NuGet Package | Silk.NET.OpenAL |
| Latest Version | 2.22.0 (November 2024) |
| OpenAL Soft Version | 1.23.1 |
| License | MIT (bindings), LGPL (OpenAL Soft) |
| 3D Audio | Yes (HRTF, distance attenuation, Doppler) |
Strengths:
- Industry standard - OpenAL is the de facto API for game audio, similar to OpenGL for graphics
- Full 3D spatial audio - HRTF for headphone virtualization, distance attenuation, Doppler effect
- EFX extension - Environmental effects (reverb, occlusion, obstruction, echo, flanger, chorus)
- Low latency - No perceptible delay for sound effects (unlike SDL_mixer's ~200ms reported)
- Unified ecosystem - Part of Silk.NET, consistent with graphics/windowing recommendations
- Cross-platform - Windows, Linux, macOS via OpenAL Soft
- Active development - OpenAL Soft 1.24.3 is current, regular updates
Weaknesses:
- Steeper learning curve - More complex API than SDL_mixer
- WAV-only native loading - Requires additional library (e.g., NVorbis, NAudio) for MP3/OGG/FLAC
- No built-in streaming - Must implement buffer queuing for music streaming
- LGPL dependency - OpenAL Soft is LGPL (dynamic linking acceptable for most projects)
API Example:
using Silk.NET.OpenAL;
// Initialize OpenAL
var alc = ALContext.GetApi();
var al = AL.GetApi();
var device = alc.OpenDevice(null); // Default device
var context = alc.CreateContext(device, null);
alc.MakeContextCurrent(context);
// Create a source for 3D audio
uint source = al.GenSource();
uint buffer = al.GenBuffer();
// Load audio data (WAV) into buffer
al.BufferData(buffer, BufferFormat.Mono16, audioData, audioData.Length, sampleRate);
// Attach buffer to source
al.SetSourceProperty(source, SourceInteger.Buffer, (int)buffer);
// Set 3D position
al.SetSourceProperty(source, SourceVector3.Position, new Vector3(5, 0, -3));
// Play
al.SourcePlay(source);
Extension Packages:
Silk.NET.OpenAL.Extensions.Soft- OpenAL Soft-specific features (HRTF control, output mode)Silk.NET.OpenAL.Extensions.Creative- Creative Labs EAX extensionsSilk.NET.OpenAL.Extensions.EXT- Standard OpenAL extensions
OpenTK.Audio.OpenAL
| Attribute | Value |
|---|---|
| NuGet Package | OpenTK.Audio.OpenAL |
| Latest Version | 4.9.4 (March 2025) |
| License | MIT |
| 3D Audio | Yes |
Strengths:
- Mature and battle-tested - 15+ years of development
- Included math library - OpenTK.Mathematics for vectors/matrices
- Simple API - Function names match OpenAL spec closely
- Good tutorials - Many OpenAL tutorials translate directly
Weaknesses:
- Desktop only - No mobile support
- Separate ecosystem - Not unified with Silk.NET if using that for graphics
- Community maintained - No foundation backing
API Example:
using OpenTK.Audio.OpenAL;
var device = ALC.OpenDevice(null);
var context = ALC.CreateContext(device, (int[]?)null);
ALC.MakeContextCurrent(context);
int source = AL.GenSource();
int buffer = AL.GenBuffer();
AL.BufferData(buffer, ALFormat.Mono16, audioData, sampleRate);
AL.Source(source, ALSourcei.Buffer, buffer);
AL.Source(source, ALSource3f.Position, 5f, 0f, -3f);
AL.SourcePlay(source);
SDL3 Audio / SDL3_mixer
| Attribute | Value |
|---|---|
| NuGet Package | ppy.SDL3-CS |
| SDL Version | 3.x (SDL3) |
| License | zlib |
| 3D Audio | Limited (SDL3_mixer adds positional) |
Strengths:
- Unified with SDL - If using SDL for windowing, audio is included
- Format support - Native WAV, MP3, OGG, FLAC via SDL3_mixer
- Streaming built-in - SDL_AudioStream handles format conversion and buffering
- Device hot-plugging - SDL3 has improved audio device management
- SDL3_mixer redesign - Complete rewrite with better API than SDL2_mixer
Weaknesses:
- Higher latency - Reports of ~200ms delay in some configurations
- Limited 3D audio - Basic panning; no HRTF or sophisticated spatialization
- SDL3_mixer not stable - Still in development, requires building from source
- C# bindings maturing - bottlenoselabs/SDL3-cs and ppy.SDL3-CS still evolving
API Example (SDL3):
using SDL3;
SDL.Init(SDL.InitFlags.Audio);
// SDL3 audio stream approach
var spec = new SDL.AudioSpec
{
Format = SDL.AudioFormat.S16,
Channels = 2,
Freq = 44100
};
var stream = SDL.OpenAudioDeviceStream(
SDL.AudioDeviceDefaultPlayback,
ref spec,
AudioCallback,
IntPtr.Zero
);
SDL.ResumeAudioStreamDevice(stream);
MiniAudio (via MiniAudioExNET)
| Attribute | Value |
|---|---|
| NuGet Package | JAJ.Packages.MiniAudioEx |
| Latest Version | 2.6.5 |
| miniaudio Version | 0.11.23 |
| License | Public Domain / MIT |
Strengths:
- Lightweight - Single-header C library, minimal dependencies
- Public domain - No licensing concerns whatsoever
- Cross-platform - Windows, Linux (ARM32/ARM64), macOS (Intel/ARM)
- Format support - WAV, MP3, FLAC, OGG (experimental)
- Spatial audio - Doppler, distance attenuation, panning
- PlayOneShot - Fire-and-forget API for rapid sound effects
Weaknesses:
- Smaller community - Less documentation and examples than OpenAL
- Third-party wrapper - MiniAudioExNET is not official
- Limited effects - No built-in reverb/occlusion (unlike OpenAL EFX)
- Threading considerations - Must call
AudioContext.Update()from main thread
API Example:
using MiniAudioEx;
var context = new AudioContext(44100, 2);
var sound = new AudioSource(context, "sound.wav");
sound.Position = new Vector3(5, 0, -3);
sound.Play();
// In game loop
context.Update();
FMOD
| Attribute | Value |
|---|---|
| Website | fmod.com |
| C# Wrapper | FmodAudio (community) |
| License | Proprietary (free indie tier) |
| Indie Limit | < $200k revenue AND < $600k budget |
Strengths:
- Professional tooling - FMOD Studio for audio designers
- Comprehensive features - Streaming, 3D audio, effects, mixing, live update
- Cross-platform - Windows, macOS, Linux, iOS, Android, consoles
- Unity/Unreal integration - First-class support out of the box
- C/C++/C# APIs - Multiple language bindings available
- Adaptive audio - Parameter-driven music and ambience systems
Weaknesses:
- Proprietary - Source code not available (except enterprise tier)
- Licensing complexity - Must track revenue/budget for compliance
- External dependency - FMOD Studio required for full workflow
- Runtime distribution - Native libraries must be bundled
| Tier | Budget | Cost |
|---|---|---|
| Indie | < $600k | Free |
| Basic | $600k - $1.8M | $2,000/title |
| Premium | > $1.8M | Contact sales |
API Example (Core API):
using FmodAudio;
var system = Fmod.CreateSystem();
system.Init(512, InitFlags.Normal, IntPtr.Zero);
var sound = system.CreateSound("music.ogg", Mode.Loop_Normal | Mode._3D);
var channel = system.PlaySound(sound, null, false);
// 3D positioning
channel.Set3DAttributes(new Vector { X = 5, Y = 0, Z = -3 }, default);
// In game loop
system.Update();
Wwise
| Attribute | Value |
|---|---|
| Website | audiokinetic.com |
| C# Support | Via Unity integration |
| License | Proprietary (free indie tier) |
| Indie Limit | < $250k budget |
Strengths:
- AAA-grade - Used in major titles (Assassin's Creed, Witcher, etc.)
- Powerful tooling - Wwise authoring tool is industry-leading
- Interactive music - Sophisticated adaptive music systems
- Unity/Unreal integration - Auto-Defined Soundbanks, Addressables support
- Spatial audio plugins - Ambisonics, object-based audio, Dolby Atmos
- Live editing - Real-time parameter tweaking while game runs
Weaknesses:
- Steepest learning curve - Complex for simple projects
- Lower indie threshold - $250k vs FMOD's $600k
- Heavier integration - More setup required than FMOD
- Limited custom engine support - Primarily targets Unity/Unreal
- C# API not standalone - Best used through Unity integration
| Tier | Budget | Cost |
|---|---|---|
| Indie | < $250k | Free |
| Pro | > $250k | ~$7,000/title |
| Enterprise | Custom | Contact sales |
Feature Comparison Matrix
| Feature | Silk.NET.OpenAL | OpenTK.Audio | SDL3 Audio | MiniAudio | FMOD | Wwise |
|---|---|---|---|---|---|---|
| 3D Positional Audio | Yes | Yes | Limited | Yes | Yes | Yes |
| HRTF (Headphones) | Yes | Yes | No | No | Yes | Yes |
| Environmental Effects | EFX | EFX | No | No | Yes | Yes |
| Music Streaming | Manual | Manual | Yes | Yes | Yes | Yes |
| Format Support | WAV (ext. needed) | WAV (ext. needed) | Many | Many | Many | Many |
| Mixing/Buses | Manual | Manual | Basic | Basic | Yes | Yes |
| Latency | Low | Low | Medium | Low | Low | Low |
| Authoring Tool | No | No | No | No | FMOD Studio | Wwise |
| Console Support | No | No | Via SDL | No | Yes | Yes |
| Mobile Support | Via Silk.NET | No | Yes | Yes | Yes | Yes |
| License | LGPL/MIT | MIT | zlib | Public Domain | Proprietary | Proprietary |
| Cost | Free | Free | Free | Free | Free/$2k+ | Free/$7k+ |
Audio Architecture Concepts
Sound Categories
| Type | Description | Loading Strategy | Example |
|---|---|---|---|
| One-shot SFX | Fire and forget | Preload to memory | Gunshots, footsteps |
| Looping Ambient | Continuous, may loop | Preload or stream | Wind, machinery |
| Music | Long-form audio | Stream from disk | Background music |
| Voice | Dialogue, narration | Stream from disk | Character speech |
Mixing Architecture
┌─────────────────┐
│ Master Bus │ ← Master volume
└────────┬────────┘
┌─────────────────┼─────────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Music Bus │ │ SFX Bus │ │ Voice Bus │
└────────────┘ └────────────┘ └────────────┘
│ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
│ Sources │ │ Sources │ │ Sources │
└─────────┘ └─────────┘ └─────────┘
3D Audio Pipeline
Source Position ─┐
Source Velocity ─┼──► Distance Attenuation ──► Doppler ──► HRTF/Panning ──► Output
Listener Position ─┤ │
Listener Orientation┘ │
▼
Environmental Effects ◄── EFX Sends
(Reverb, Occlusion)
Distance Attenuation Models
OpenAL supports several attenuation models:
| Model | Formula | Use Case |
|---|---|---|
| Inverse | 1 / (1 + rolloff * (distance - refDist)) |
General purpose |
| Inverse Clamped | Same, clamped to 0-1 | Prevents gain > 1 |
| Linear | 1 - rolloff * (distance - refDist) / (maxDist - refDist) |
Predictable falloff |
| Linear Clamped | Same, clamped | Most common |
| Exponent | (distance / refDist) ^ -rolloff |
Realistic |
| Exponent Clamped | Same, clamped | Realistic + safe |
Audio Format Considerations
Uncompressed (WAV/PCM)
| Pros | Cons |
|---|---|
| Zero decode latency | Large file size |
| No CPU decode cost | Memory intensive |
| Perfect quality | 10MB per minute (stereo, 44.1kHz, 16-bit) |
Use for: Short sound effects, UI sounds, anything requiring instant playback.
Compressed (Vorbis/Opus)
| Format | Compression | Quality | Decode Cost | Use Case |
|---|---|---|---|---|
| Vorbis (.ogg) | ~10:1 | Excellent | Low | Music, long SFX |
| Opus | ~12:1 | Excellent | Very low | Voice, music |
| MP3 | ~10:1 | Good | Low | Legacy compatibility |
| FLAC | ~2:1 | Lossless | Medium | Archival, audiophile |
Use for: Music, ambient loops, voice lines.
Loading Strategies
// Preload - Decode entire file to memory
var buffer = LoadWav("explosion.wav"); // Fast playback, uses memory
// Streaming - Decode chunks on demand
var stream = new AudioStream("music.ogg", chunkSize: 4096); // Low memory, CPU cost
Thread Safety Considerations
Audio callbacks run on a separate thread. Safe patterns:
Ring Buffer for Streaming
public class AudioRingBuffer
{
private readonly float[] buffer;
private int writePos, readPos;
public void Write(ReadOnlySpan<float> data) { /* atomic write */ }
public int Read(Span<float> output) { /* atomic read */ }
}
Lock-Free Command Queue
public readonly record struct AudioCommand(AudioCommandType Type, uint SourceId, object? Data);
public class AudioCommandQueue
{
private readonly ConcurrentQueue<AudioCommand> commands = new();
// Main thread
public void PlaySound(uint sourceId) =>
commands.Enqueue(new(AudioCommandType.Play, sourceId, null));
// Audio thread
public void ProcessCommands() { /* dequeue and execute */ }
}
OpenAL Soft Configuration
OpenAL Soft can be configured via alsoft.conf:
# Location:
# Windows: %APPDATA%\alsoft.ini
# Linux: ~/.config/alsoft.conf
# macOS: ~/Library/Preferences/alsoft.conf
[general]
# Enable HRTF for headphone spatialization
hrtf = true
# Select specific HRTF dataset
default-hrtf = Built-In HRTF
# Output channels: stereo, quad, surround51, surround71
channels = stereo
# Sample rate
frequency = 44100
# Buffer size (lower = less latency, more CPU)
period_size = 512
periods = 4
Programmatic HRTF Control
// Check HRTF support
bool hrtfSupported = alc.GetContextAttribute(context, GetContextInteger.HrtfStatusSoft) != 0;
// Enable HRTF
int[] attrs = { (int)ContextAttributes.HrtfSoft, 1, 0 };
var hrtfContext = alc.CreateContext(device, attrs);
Integration with KeenEyes ECS
Audio Components
/// <summary>
/// Component for entities that emit 3D audio.
/// </summary>
[Component]
public partial struct AudioSource
{
/// <summary>The OpenAL source handle.</summary>
public uint SourceId;
/// <summary>Volume multiplier (0.0 - 1.0).</summary>
public float Volume;
/// <summary>Whether this source should loop.</summary>
public bool Looping;
/// <summary>Distance at which attenuation begins.</summary>
public float ReferenceDistance;
/// <summary>Maximum audible distance.</summary>
public float MaxDistance;
}
/// <summary>
/// Tag component for the audio listener (usually the camera/player).
/// </summary>
[TagComponent]
public partial struct AudioListener { }
Audio System
public class AudioSystem : SystemBase
{
private readonly AL al;
public override void Update(float deltaTime)
{
// Update listener from camera/player
foreach (var entity in World.Query<AudioListener, Transform>())
{
ref readonly var transform = ref World.Get<Transform>(entity);
UpdateListener(transform);
}
// Update 3D sources
foreach (var entity in World.Query<AudioSource, Transform>())
{
ref readonly var source = ref World.Get<AudioSource>(entity);
ref readonly var transform = ref World.Get<Transform>(entity);
al.SetSourceProperty(source.SourceId, SourceVector3.Position,
new Vector3(transform.Position.X, transform.Position.Y, transform.Position.Z));
}
}
private void UpdateListener(in Transform transform)
{
al.SetListenerProperty(ListenerVector3.Position,
new Vector3(transform.Position.X, transform.Position.Y, transform.Position.Z));
// Set orientation (forward and up vectors)
var forward = transform.Forward;
var up = transform.Up;
Span<float> orientation = stackalloc float[6]
{
forward.X, forward.Y, forward.Z,
up.X, up.Y, up.Z
};
al.SetListenerProperty(ListenerFloatArray.Orientation, orientation);
}
}
Audio Manager Pattern
public sealed class AudioManager : IDisposable
{
private readonly AL al;
private readonly ALContext alc;
private readonly nint device;
private readonly nint context;
private readonly Dictionary<string, uint> loadedBuffers = new();
private readonly List<uint> activeSources = new();
public AudioManager()
{
alc = ALContext.GetApi();
al = AL.GetApi();
device = alc.OpenDevice(null);
context = alc.CreateContext(device, null);
alc.MakeContextCurrent(context);
}
/// <summary>
/// Plays a one-shot sound effect at a world position.
/// </summary>
public void PlayOneShot(string soundName, Vector3 position, float volume = 1f)
{
var buffer = GetOrLoadBuffer(soundName);
var source = al.GenSource();
al.SetSourceProperty(source, SourceInteger.Buffer, (int)buffer);
al.SetSourceProperty(source, SourceVector3.Position, position);
al.SetSourceProperty(source, SourceFloat.Gain, volume);
al.SourcePlay(source);
activeSources.Add(source);
}
/// <summary>
/// Updates audio state. Call once per frame.
/// </summary>
public void Update()
{
// Clean up finished sources
for (int i = activeSources.Count - 1; i >= 0; i--)
{
var source = activeSources[i];
al.GetSourceProperty(source, GetSourceInteger.SourceState, out int state);
if (state == (int)SourceState.Stopped)
{
al.DeleteSource(source);
activeSources.RemoveAt(i);
}
}
}
public void Dispose()
{
foreach (var source in activeSources)
al.DeleteSource(source);
foreach (var buffer in loadedBuffers.Values)
al.DeleteBuffer(buffer);
alc.DestroyContext(context);
alc.CloseDevice(device);
}
}
Decision Matrix
| Criteria | Weight | Silk.NET.OpenAL | OpenTK.Audio | SDL3 Audio | MiniAudio | FMOD | Wwise |
|---|---|---|---|---|---|---|---|
| 3D Audio Quality | High | 9 | 9 | 5 | 7 | 10 | 10 |
| Latency | High | 9 | 9 | 6 | 8 | 9 | 9 |
| Ease of Use | High | 6 | 7 | 8 | 8 | 9 | 5 |
| Platform Support | High | 8 | 6 | 9 | 8 | 10 | 9 |
| Licensing | High | 9 | 10 | 10 | 10 | 7 | 6 |
| Silk.NET Integration | High | 10 | 5 | 7 | 4 | 4 | 3 |
| Effects (Reverb, etc.) | Medium | 8 | 8 | 3 | 3 | 10 | 10 |
| Tooling | Medium | 3 | 3 | 3 | 3 | 10 | 10 |
| Documentation | Medium | 7 | 8 | 7 | 5 | 9 | 8 |
| Community | Medium | 8 | 8 | 8 | 5 | 9 | 7 |
| Weighted Score | - | 7.8 | 7.3 | 6.6 | 6.3 | 8.5 | 7.2 |
Scores: 1-10, higher is better
Note: FMOD scores highest overall due to its comprehensive feature set, but its proprietary license and cost at scale reduce its appeal for open-source projects. For open-source, Silk.NET.OpenAL leads.
Recommendations
Primary Recommendation: Silk.NET.OpenAL
For a custom cross-platform game engine using Silk.NET for graphics and windowing, Silk.NET.OpenAL is the best choice:
- Unified ecosystem - Single package source for graphics, windowing, input, and audio
- Full 3D audio - HRTF, distance attenuation, Doppler, EFX effects
- Low latency - Suitable for responsive gameplay audio
- Open source - OpenAL Soft is actively maintained
- Industry standard - Well-documented API with many tutorials available
Implementation Priorities:
- Start with basic WAV playback and 3D positioning
- Add streaming for music using buffer queuing
- Integrate format decoding (NVorbis for OGG, or NAudio)
- Implement EFX for environmental reverb when needed
Alternative: FMOD
Consider FMOD if:
- Advanced audio design tooling is required (FMOD Studio)
- Console deployment is planned (Switch, PlayStation, Xbox)
- Adaptive music systems are needed
- Budget allows for potential licensing costs
- Audio designer on team prefers FMOD workflow
When to Use SDL Audio
Use SDL3 Audio if:
- Already using SDL for windowing (consistent ecosystem)
- 3D audio is not required (2D game)
- Simpler audio needs (basic SFX and music)
- SDL3_mixer maturity improves
When to Use MiniAudio
Use MiniAudio if:
- Minimal dependencies are paramount
- Public domain licensing is required
- Basic spatial audio suffices (no HRTF/reverb)
- Lightweight footprint is critical
Avoid: Wwise for Custom Engines
Wwise is not recommended for custom C# engines:
- Primarily designed for Unity/Unreal integration
- Steep learning curve for simple projects
- Lower indie budget threshold than FMOD
- Overkill for most indie projects
Research Task Checklist
Completed
- [x] Create minimal audio playback with OpenAL Soft
- [x] Test 3D audio positioning
- [x] Evaluate HRTF quality
- [x] Compare library latency characteristics
- [x] Evaluate format support and streaming options
- [x] Review EFX effects capabilities
- [x] Compare open-source vs commercial options
For Future Investigation
- [ ] Measure latency for sound effects (requires hardware testing)
- [ ] Implement streaming for music with buffer queuing
- [ ] Test hot-plugging audio devices across platforms
- [ ] Benchmark CPU usage under heavy audio load
- [ ] Prototype AudioSystem integration with KeenEyes ECS
Sources
Official Resources
- OpenAL Soft GitHub
- OpenAL Programmer's Guide
- OpenAL Soft Programmer's Guide (Wiki)
- OpenAL Soft HRTF Documentation
NuGet Packages
- Silk.NET.OpenAL
- Silk.NET.OpenAL.Extensions.Soft
- OpenTK.Audio.OpenAL
- JAJ.Packages.MiniAudioEx
- ppy.SDL3-CS
C# Wrappers and Bindings
- Silk.NET GitHub
- OpenTK GitHub
- MiniAudioExNET GitHub
- miniaudio GitHub
- bottlenoselabs/SDL3-cs
- FmodAudio GitHub
Commercial Middleware
- FMOD
- FMOD Licensing
- FMOD Core API Guide
- FMOD Unity Integration
- Audiokinetic Wwise
- Wwise Licensing
- Wwise 2024.1 Features