Cross-platform audio engine library for desktop platforms (Windows, Linux, macOS)
$ dotnet add package OwnAudioSharpOwnAudio is a platform-independent C# audio library that provides a high-level API for audio playback, recording, and processing. By default, it uses Miniaudio for audio I/O. If FFmpeg or PortAudio is installed, it automatically uses Portaudio and FFmpeg. This way, it can work with MiniAudio without any external dependencies. The implementation of MiniAudio also allowed the API to be used on mobile platforms. It is possible to manipulate audio data in real time (pitch change, tempo change, and various real-time effects). The API is able to detect musical chords from audio and create a timed list of chords. A feature has been built in to help game developers manage sound effects.
Check out the sample application OwnAudioSharpDemo that demonstrates the capabilities of the OwnAudioSharp audio library through an Avalonia MVVM application using ReactiveUI. MainWindowViewModel.cs contains the core logic for audio processing, playback, effects application, and UI control.
Professional mastering automatically - single line of code!
analyzer.ProcessEQMatching("source.wav", "reference.wav", "mastered.wav");
⚡ What you get:
🎯 Result: Your source audio will sound exactly like the reference track - professional mastering studio quality.
The table below summarizes the supported operating systems, the APIs used, and their testing status.
| System | APIs | Status |
|---|---|---|
| Windows | PortAudio 2, MiniAudio, FFmpeg 6 | Tested |
| Linux | PortAudio 2, MiniAudio, FFmpeg 6 | Tested |
| macOS | PortAudio 2, MiniAudio, FFmpeg 6 | Tested |
| Android | MiniAudio | This project is tested with BrowserStack |
| iOS | MiniAudio | This project is tested with BrowserStack |
The library will attempt to find these dependencies in standard system locations but also supports specifying custom paths.
You can add this library to your project via NuGet (when published) or by directly referencing the project.
You will find the required files in the LIBS folder in a compressed file. Extract the package appropriate for your operating system into the folder containing the compressed file. Depending on your operating system, you will need the following:
Here's a quick example of how to use OwnAudio to play an audio file:
using Ownaudio;
using Ownaudio.Sources;
using System;
using System.Threading.Tasks;
try
{
// Initialize OwnAudio
OwnAudio.Initialize();
// Create a source manager
var sourceManager = SourceManager.Instance;
// Add an audio file
await sourceManager.AddOutputSource("path/to/audio.mp3");
// Play the audio
sourceManager.Play();
// Wait for the audio to finish
Console.WriteLine("Press any key to stop playback...");
Console.ReadKey();
// Stop playback and clean up
sourceManager.Stop();
}
catch (Exception ex)
{
Console.WriteLine($"Audio error: {ex.Message}");
}
finally
{
OwnAudio.Free();
}using Ownaudio;
using Ownaudio.Sources;
// Add multiple audio files
await sourceManager.AddOutputSource("path/to/audio1.mp3", "Track1Name");
await sourceManager.AddOutputSource("path/to/audio2.mp3", "Track2Name");
// Adjust volume for individual sources
sourceManager["Track1Name"].Volume = 0.8f; // 80% volume for first source
sourceManager["Track2Name"].Volume = 0.6f; // 60% volume for second source
// Play mixed audio
sourceManager.Play();// Add an input source
await sourceManager.AddInputSource();
// Start recording to file
sourceManager.Play("output.wav", 16); // 16-bit recording// Change tempo without affecting pitch (value range -20 to +20)
sourceManager["Track1Name"].Tempo = 10.0; // Speed up by 10%
// Change pitch without affecting tempo (value range -6 to +6 semitones)
sourceManager["Track1Name"].Pitch = 2.0; // Raise pitch by 2 semitones// Seek to a specific position
sourceManager.Seek(TimeSpan.FromSeconds(30)); // Seek to 30 secondsOwnAudio includes a comprehensive effects library:
using Ownaudio.Effects;
// Apply reverb effect
var reverb = new Reverb(0.5f, 0.3f, 0.4f, 0.7f);
sourceManager.CustomSampleProcessor = reverb;
// Apply delay effect
var delay = new Delay(500, 0.4f, 0.3f, 44100);
sourceManager.CustomSampleProcessor = delay;
// Apply compressor
var compressor = new Compressor(0.5f, 4.0f, 100f, 200f, 1.0f, 44100f);
sourceManager.CustomSampleProcessor = compressor;
// Apply equalizer
var equalizer = new Equalizer(44100);
equalizer.SetBandGain(0, 100f, 1.4f, 3.0f); // Boost bass
sourceManager.CustomSampleProcessor = equalizer;You can implement custom audio processing by implementing the SampleProcessorBase class:
using Ownaudio.Processors;
public class MyAudioProcessor : SampleProcessorBase
{
public override void Process(Span<float> samples)
{
// Process audio samples
for (int i = 0; i < samples.Length; i++)
{
// Example: Simple gain adjustment
samples[i] *= 0.5f; // 50% volume
}
}
public override void Reset()
{
// Reset internal state if needed
}
}
// Apply the processor to source manager
var processor = new MyAudioProcessor();
sourceManager.CustomSampleProcessor = processor;Game-Optimized Audio Effects
The SourceSpark class is a simplified audio source designed specifically for short audio clips and sound effects, making it perfect for game development. Unlike standard sources, SourceSpark loads entire audio files into memory for instant, zero-latency playback.
using Ownaudio;
using Ownaudio.Sources;
// Add a simple sound effect
var gunshot = sourceManager.AddSparkSource("sounds/gunshot.wav", looping: false, volume: 0.9f);
gunshot.Play(); // Direct playback
// Add looping ambient sound
var rainSound = sourceManager.AddSparkSource("ambient/rain.wav", looping: true, volume: 0.4f);
rainSound.Play(); // Start looping
// Start the source manager to handle all sources
sourceManager.Play();// Dynamic engine sound for vehicles
var engineSound = sourceManager.AddSparkSource("car_engine.wav", looping: true, volume: 0.6f);
engineSound.Play();
// Real-time sound modification based on game state
void UpdateEngineSound(float speed) {
engineSound.Pitch = speed * 0.01; // Higher pitch = faster speed
engineSound.Volume = Math.Min(0.8f, 0.3f + speed * 0.005f);
}
// Multiple overlapping gunshots - create separate instances
var pistol1 = sourceManager.AddSparkSource("weapons/pistol.wav");
var pistol2 = sourceManager.AddSparkSource("weapons/pistol.wav");
pistol1.Play(); // First shot
pistol2.Play(); // Second shot (overlapping)// Weather system with looping sounds
var currentWeather = sourceManager.AddSparkSource("weather/clear.wav", looping: true, volume: 0.2f);
currentWeather.Play();
void ChangeWeather(WeatherType weather) {
// Stop current weather sound
currentWeather.Stop();
sourceManager.RemoveSparkSource(currentWeather);
// Start new weather sound
string weatherFile = weather switch {
WeatherType.Rain => "weather/rain.wav",
WeatherType.Storm => "weather/thunder.wav",
WeatherType.Wind => "weather/wind.wav",
_ => "weather/clear.wav"
};
currentWeather = sourceManager.AddSparkSource(weatherFile, looping: true, volume: 0.4f);
currentWeather.Play();
}// Loop control during gameplay
sparkSource.IsLooping = true; // Enable looping
sparkSource.IsLooping = false; // Disable looping
// State management
sparkSource.Play(); // Start playback
sparkSource.Pause(); // Pause playback
sparkSource.Resume(); // Resume from pause
sparkSource.Stop(); // Stop and reset
// Real-time audio effects
sparkSource.Volume = 0.5f; // Adjust volume
sparkSource.Pitch = 2.0; // Raise pitch by 2 semitones
sparkSource.Tempo = 1.5; // Speed up by 50%Perfect for:
Benefits:
Memory Guidelines:
// Automatic cleanup for one-shot sounds
sparkSource.StateChanged += (sender, e) => {
if (sparkSource.HasFinished && !sparkSource.IsLooping) {
// Non-looping sounds are automatically removed
sourceManager.RemoveSparkSource(sparkSource);
}
};
// Manual cleanup for persistent sounds
if (sparkSource.IsLooping) {
sparkSource.Stop();
sourceManager.RemoveSparkSource(sparkSource);
}The memory-based approach makes SourceSpark ideal for games where audio responsiveness and reliability are crucial, trading memory usage for guaranteed performance and zero-latency audio playback.
using System.Threading.Tasks;
// Add a real-time source
var realtimeSource = sourceManager.AddRealTimeSource(1.0f, 2); // Volume 1.0, stereo
// Submit audio samples in real-time
float[] samples = new float[1024]; // Your generated audio data
realtimeSource.SubmitSamples(samples);The SourceSound class enables real-time audio streaming, perfect for:
using Ownaudio;
using Ownaudio.Sources;
using System;
using System.Threading.Tasks;
// Create a real-time audio source
var liveSource = sourceManager.AddRealTimeSource(1.0f, 2); // Volume, channels
// Example: Generate and stream sine wave in real-time
await Task.Run(async () =>
{
int sampleRate = 44100;
int frequency = 440; // A4 note
float amplitude = 0.3f;
int samplesPerBuffer = 1024;
double phase = 0;
double phaseIncrement = 2.0 * Math.PI * frequency / sampleRate;
while (liveSource.State != SourceState.Idle)
{
float[] buffer = new float[samplesPerBuffer * 2]; // Stereo
for (int i = 0; i < samplesPerBuffer; i++)
{
float sample = (float)(Math.Sin(phase) * amplitude);
buffer[i * 2] = sample; // Left channel
buffer[i * 2 + 1] = sample; // Right channel
phase += phaseIncrement;
if (phase >= 2.0 * Math.PI)
phase -= 2.0 * Math.PI;
}
// Submit samples for real-time playback
liveSource.SubmitSamples(buffer);
// Control timing for smooth playback
await Task.Delay(10);
}
});
// Start playback
sourceManager.Play();// Example: Receive audio data from network and play in real-time
var networkSource = sourceManager.AddRealTimeSource(1.0f, 2);
// Network audio receiver (pseudo-code)
networkClient.OnAudioDataReceived += (audioData) =>
{
// Convert received network data to float array
float[] samples = ConvertBytesToFloats(audioData);
// Submit to real-time source for immediate playback
networkSource.SubmitSamples(samples);
};
sourceManager.Play();using System;
using System.Threading.Tasks;
public class AudioGenerator
{
private SourceSound _source;
private int _sampleRate;
private bool _isGenerating;
public AudioGenerator(SourceManager manager, int sampleRate = 44100)
{
_sampleRate = sampleRate;
_source = manager.AddRealTimeSource(1.0f, 2);
}
public void StartGeneration()
{
_isGenerating = true;
Task.Run(async () =>
{
while (_isGenerating)
{
float[] audioBuffer = GenerateAudio(1024);
_source.SubmitSamples(audioBuffer);
await Task.Delay(5); // Smooth streaming
}
});
}
public void StopGeneration()
{
_isGenerating = false;
}
private float[] GenerateAudio(int samples)
{
// Your custom audio generation logic here
float[] buffer = new float[samples * 2]; // Stereo
// Fill buffer with generated audio data
for (int i = 0; i < samples; i++)
{
float sample = GenerateSample(); // Your generation method
buffer[i * 2] = sample; // Left
buffer[i * 2 + 1] = sample; // Right
}
return buffer;
}
private float GenerateSample()
{
// Implement your audio generation algorithm
return 0.0f;
}
}
// Usage
var generator = new AudioGenerator(sourceManager);
generator.StartGeneration();
sourceManager.Play();// Load source audio data into a byte array
byte[] audioByte = sourceManager.Sources[0].GetByteAudioData(TimeSpan.Zero);
// Load source audio data into a float array
float[] audioFloat = sourceManager.Sources[0].GetFloatAudioData(TimeSpan.Zero);Real-time or offline chord detection from musical notes.
using Ownaudio.Utilities.OwnChordDetect.Detectors;
using Ownaudio.Utilities.OwnChordDetect.Analysis;
using Ownaudio.Utilities.Extensions;
// Basic chord detection
var detector = new BaseChordDetector(confidenceThreshold: 0.7f);
var analysis = detector.AnalyzeChord(notes);
Console.WriteLine($"Detected: {analysis.ChordName} (confidence: {analysis.Confidence})");
// Extended chord detection with alternatives
var extendedDetector = new OptimizedChordDetector();
var (chord, confidence, isAmbiguous, alternatives) = extendedDetector.DetectChordAdvanced(notes);
// Real-time analysis
var realtimeDetector = new RealTimeChordDetector(bufferSize: 5);
var (stableChord, stability) = realtimeDetector.ProcessNotes(newNotes);
// Full song analysis
var songAnalyzer = new SongChordAnalyzer(windowSize: 1.0f, hopSize: 0.5f);
var timedChords = songAnalyzer.AnalyzeSong(allSongNotes);
// Complete example with SourceManager
var sourceManager = SourceManager.Instance;
await sourceManager.AddOutputSource("music.mp3", "MusicTrack");
// Detect chords from the loaded audio
var (timedChords, detectedKey, tempo) = sourceManager.DetectChords("MusicTrack", intervalSecond: 1.0f);
Console.WriteLine($"Detected Key: {detectedKey}");
Console.WriteLine($"Detected Tempo: {tempo} BPM");
foreach (var chord in timedChords)
{
Console.WriteLine($"{chord.StartTime:F1}s-{chord.EndTime:F1}s: {chord.ChordName} ({chord.Confidence:F2})");
}The library expects Note objects with:
Pitch - MIDI pitch numberAmplitude - Note volume (0.0-1.0)StartTime / EndTime - Timing in secondsA flexible, resource-efficient audio waveform visualization component for Avalonia applications.
The following example demonstrates how to use the WaveAvaloniaDisplay component in an Avalonia application:
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:audio="using:Ownaudio.Utilities"
x:Class="MyAudioApp.MainWindow"
Title="Audio Visualizer" Height="450" Width="800">
<Grid>
<audio:WaveAvaloniaDisplay x:Name="waveformDisplay"
WaveformBrush="DodgerBlue"
PlaybackPositionBrush="Red"
VerticalScale="1.0"
DisplayStyle="MinMax"/>
</Grid>
</Window>using Ownaudio.Utilities;
using System;
using System.IO;
using System.Threading.Tasks;
// Set audio data from existing float array
waveformDisplay.SetAudioData(sourceManager.Sources[0].GetFloatAudioData(TimeSpan.Zero));
// Handle playback position changes
waveformDisplay.PlaybackPositionChanged += OnPlaybackPositionChanged;
// Load directly from audio file
waveformDisplay.LoadFromAudioFile("audio.mp3");
// Load with specific decoder preference
waveformDisplay.LoadFromAudioFile("audio.mp3", preferFFmpeg: true);
// Asynchronous loading
await waveformDisplay.LoadFromAudioFileAsync("large_audio.wav");
// Loading from stream
using var fileStream = File.OpenRead("audio.mp3");
waveformDisplay.LoadFromAudioStream(fileStream);
private void OnPlaybackPositionChanged(object sender, double position)
{
// Update the actual audio playback position
sourceManager.Seek(TimeSpan.FromSeconds(position * sourceManager.Duration.TotalSeconds));
}| Property | Type | Description |
|---|---|---|
| WaveformBrush | IBrush | The color of the waveform |
| PlaybackPositionBrush | IBrush | The color of the playback position indicator |
| VerticalScale | double | Vertical scaling of the waveform (1.0 = original size) |
| DisplayStyle | WaveformDisplayStyle | The waveform display style (MinMax, Positive, RMS) |
| ZoomFactor | double | Zoom factor (1.0 = full view, larger values = more detailed view) |
| ScrollOffset | double | Horizontal scroll position (0.0 - 1.0) |
| PlaybackPosition | double | Current playback position (0.0 - 1.0) |
| Event | Parameter | Description |
|---|---|---|
| PlaybackPositionChanged | double | Triggered when the user changes the playback position |
The library follows a layered architecture:
You can configure the audio engine with specific parameters:
using Ownaudio.Engines;
// Initialize first
OwnAudio.Initialize();
// Configure output engine options
SourceManager.OutputEngineOptions = new AudioEngineOutputOptions(
OwnAudioEngine.EngineChannels.Stereo,
44100,
0.02 // Low latency
);
// Configure input engine options
SourceManager.InputEngineOptions = new AudioEngineInputOptions(
OwnAudioEngine.EngineChannels.Mono,
44100,
0.02 // Low latency
);
// Set frames per buffer
SourceManager.EngineFramesPerBuffer = 512;The professional audio matchering function built into OwnAudioSharp automatically analyzes and adjusts the spectral and dynamic properties of source audio to match the characteristics of a target audio file. This technology enables achieving consistent sound across musical masters or reproducing the sound of reference tracks.
using Ownaudio.Utilities.Matchering;
// Audio matchering example
var analyzer = new AudioAnalyzer();
// Process source and target audio files
analyzer.ProcessEQMatching(
sourceFile: "input_track.wav", // Audio to be processed
targetFile: "reference.wav", // Reference audio
outputFile: "mastered_track.wav" // Output
);
// The processing automatically:
// 1. Analyzes spectral properties of both files
// 2. Calculates optimal EQ settings
// 3. Applies multiband compression
// 4. Performs dynamic amplification
// 5. Generates distortion-protected output// Individual audio spectrum analysis
var sourceSpectrum = analyzer.AnalyzeAudioFile("source.wav");
Console.WriteLine($"RMS level: {sourceSpectrum.RMSLevel:F3}");
Console.WriteLine($"Peak level: {sourceSpectrum.PeakLevel:F3}");
Console.WriteLine($"Dynamic range: {sourceSpectrum.DynamicRange:F1} dB");
Console.WriteLine($"Loudness: {sourceSpectrum.Loudness:F1} LUFS");
// Output frequency band energies
for (int i = 0; i < sourceSpectrum.FrequencyBands.Length; i++)
{
Console.WriteLine($"Band {i}: {sourceSpectrum.FrequencyBands[i]:F3}");
}=== ANALYSIS RESULTS ===
Source - RMS: 0.123, Peak: 0.876, Loudness: -14.2 LUFS
Target - RMS: 0.187, Peak: 0.932, Loudness: -9.8 LUFS
Crest Factor - Source: 15.1dB, Target: 13.9dB
Distortion Risk: LOW (Total boost: 8.2dB)
EQ Adjustments (with distortion protection):
31Hz: +2.1 dB
63Hz: +3.4 dB
125Hz: +1.8 dB
250Hz: -0.5 dB
500Hz: +0.2 dB
1kHz: -1.2 dB
2kHz: +1.6 dB
4kHz: +0.8 dB
8kHz: -2.1 dB
16kHz: +1.9 dB
=== SAFETY FEATURES ACTIVE ===
✓ Frequency-specific boost limits
✓ Dynamic headroom calculation
✓ Psychoacoustic weighting
✓ EQ curve smoothing
✓ Safety limiter (-0.1dB ceiling)
✓ Real-time clipping detection
Audio matchering automatically applies the most modern psychoacoustic algorithms and safe processing techniques to ensure professional-quality results for every use case.
Special thanks to the creators of the following repositories, whose code was instrumental in the development of OwnAudio: