Complete AudioMixer Example
This comprehensive example demonstrates the usage of OwnaudioNET with multi-track playback, master effects, and synchronized playback control.
Overview
This example program demonstrates the following features:
- Audio Engine initialization with custom configuration
- AudioMixer creation with direct engine access
- Master effects chain (Equalizer, Compressor, Dynamic Amp)
- Multi-track playback with four separate audio files
- Synchronized playback with drift correction
- Real-time monitoring with position and peak levels
- Dynamic effect activation during playback
Step 1: Audio Engine Initialization
The first step is to initialize the audio engine with a custom configuration. We configure the sample rate, channel count, and buffer size for optimal performance.
using Ownaudio.Core;
using OwnaudioNET.Core;
AudioConfig config = new AudioConfig()
{
SampleRate = 48000,
Channels = 2,
BufferSize = 512
};
OwnaudioNet.Initialize(config);
Console.WriteLine($"Initialized: {OwnaudioNet.IsInitialized}");
Console.WriteLine($"Version: {OwnaudioNet.Version}");
Console.WriteLine($"Sample Rate: {OwnaudioNet.Engine?.Config.SampleRate} Hz");
Console.WriteLine($"Channels: {OwnaudioNet.Engine?.Config.Channels}");
Console.WriteLine($"Buffer Size: {OwnaudioNet.Engine?.FramesPerBuffer} frames");
What happens here:
- Create an
AudioConfigobject with 48kHz sample rate, stereo (2 channels), and 512 frames buffer size - Initialize the OwnaudioNET static instance with this configuration
- The engine automatically detects the platform and uses the appropriate native audio API (WASAPI on Windows, Core Audio on macOS, PulseAudio on Linux)
- Display initialization status and audio configuration parameters
Step 2: Starting the Audio Engine
After initialization, we start the audio engine to begin processing audio.
OwnaudioNet.Start();
Console.WriteLine($"Engine running: {OwnaudioNet.IsRunning}");
What happens here:
- The
Start()method activates the audio engine's real-time processing thread - The engine begins requesting audio buffers from registered sources
- At this point, the engine is ready to accept audio sources and play them
Step 3: Creating the Audio Mixer
We create an AudioMixer instance with direct engine access for optimal performance, bypassing additional wrapper layers.
using OwnaudioNET.Mixing;
var Engine = OwnaudioNet.Engine!.UnderlyingEngine;
mixer = new AudioMixer(Engine, bufferSizeInFrames: 512);
mixer.MasterVolume = 0.8f;
mixer.SourceError += (sender, e) =>
{
Console.WriteLine($"Source error: {e.Message}");
};
What happens here:
- Get direct access to the underlying engine (
IAudioEngine) for maximum performance - Create an
AudioMixerwith 512 frames internal buffer size - Set master volume to 80% (0.8f) which applies to the final mixed output
- Register an error handler for any source-related errors during playback
- The mixer will handle mixing multiple audio sources into a single output stream
Step 4: Master Effects Configuration
Configure and add master effects that will be applied to the final mixed output. These effects process all sources together.
using OwnaudioNET.Effects;
var _equalizer = new Equalizer30BandEffect();
_equalizer.SetBandGain(band: 0, frequency: 20, q: 0.5f, gainDB: 0.2f);
_equalizer.SetBandGain(band: 1, frequency: 25, q: 0.5f, gainDB: 0.4f);
_equalizer.SetBandGain(band: 2, frequency: 31, q: 0.6f, gainDB: 0.6f);
_equalizer.SetBandGain(band: 3, frequency: 40, q: 0.7f, gainDB: 0.8f);
_equalizer.SetBandGain(band: 4, frequency: 50, q: 0.7f, gainDB: 0.8f);
_equalizer.SetBandGain(band: 5, frequency: 63, q: 0.7f, gainDB: -0.3f);
_equalizer.SetBandGain(band: 6, frequency: 80, q: 0.8f, gainDB: 0.3f);
_equalizer.SetBandGain(band: 7, frequency: 100, q: 0.8f, gainDB: 0.5f);
_equalizer.SetBandGain(band: 8, frequency: 125, q: 0.9f, gainDB: 0.3f);
_equalizer.SetBandGain(band: 9, frequency: 160, q: 0.9f, gainDB: 0.1f);
_equalizer.SetBandGain(band: 10, frequency: 200, q: 1.0f, gainDB: -0.4f);
_equalizer.SetBandGain(band: 11, frequency: 250, q: 1.0f, gainDB: -0.8f);
_equalizer.SetBandGain(band: 12, frequency: 315, q: 1.1f, gainDB: -0.7f);
_equalizer.SetBandGain(band: 13, frequency: 400, q: 1.1f, gainDB: -0.5f);
_equalizer.SetBandGain(band: 14, frequency: 500, q: 1.0f, gainDB: -0.2f);
_equalizer.SetBandGain(band: 15, frequency: 630, q: 1.0f, gainDB: -0.1f);
_equalizer.SetBandGain(band: 16, frequency: 800, q: 1.0f, gainDB: 0.0f);
_equalizer.SetBandGain(band: 17, frequency: 1000, q: 1.0f, gainDB: 0.0f);
_equalizer.SetBandGain(band: 18, frequency: 1250, q: 1.0f, gainDB: 0.0f);
_equalizer.SetBandGain(band: 19, frequency: 1600, q: 1.0f, gainDB: 0.1f);
_equalizer.SetBandGain(band: 20, frequency: 2000, q: 1.0f, gainDB: 0.3f);
_equalizer.SetBandGain(band: 21, frequency: 2500, q: 0.9f, gainDB: 0.5f);
_equalizer.SetBandGain(band: 22, frequency: 3150, q: 0.9f, gainDB: 0.7f);
_equalizer.SetBandGain(band: 23, frequency: 4000, q: 0.8f, gainDB: 0.5f);
_equalizer.SetBandGain(band: 24, frequency: 5000, q: 0.7f, gainDB: 0.3f);
_equalizer.SetBandGain(band: 25, frequency: 6300, q: 0.7f, gainDB: 0.3f);
_equalizer.SetBandGain(band: 26, frequency: 8000, q: 0.6f, gainDB: 0.6f);
_equalizer.SetBandGain(band: 27, frequency: 10000, q: 0.6f, gainDB: 0.8f);
_equalizer.SetBandGain(band: 28, frequency: 12500, q: 0.5f, gainDB: 1.0f);
_equalizer.SetBandGain(band: 29, frequency: 16000, q: 0.5f, gainDB: 1.0f);
What happens here:
- Create a 30-band parametric equalizer with professional frequency bands
- Sub-bass (20-63 Hz): Enhanced low-end with controlled boost
- Bass (80-160 Hz): Punch and body for kick drums and bass
- Low-mid (200-500 Hz): Reduce mud and boxiness
- Mid (630-1600 Hz): Neutral balance for vocal clarity
- Upper-mid (2000-5000 Hz): Presence and definition boost
- High (6300-16000 Hz): Airiness and brilliance
var _compressor = new CompressorEffect(CompressorPreset.Vintage);
mixer.AddMasterEffect(_equalizer);
mixer.AddMasterEffect(_compressor);
mixer.AddMasterEffect(new DynamicAmpEffect(DynamicAmpPreset.Live));
_equalizer.Enabled = false;
_compressor.Enabled = false;
What happens here:
- Create a vintage-style compressor for warm, musical compression
- Add all three effects to the master effects chain (processed in order)
- Add a dynamic amplifier with "Live" preset for final output enhancement
- Initially disable equalizer and compressor (they will be enabled at 30 seconds)
- Master effects are applied to the final mixed output from all sources
Step 5: Creating Audio Sources
Create multiple audio sources from WAV files and configure them for synchronized playback.
using OwnaudioNET.Sources;
using System.Reflection;
string? exePath = Assembly.GetExecutingAssembly().Location;
string? exeDirectory = Path.GetDirectoryName(exePath);
string audioFilePath0 = Path.Combine(exeDirectory, "media", "drums.wav");
string audioFilePath1 = Path.Combine(exeDirectory, "media", "bass.wav");
string audioFilePath2 = Path.Combine(exeDirectory, "media", "other.wav");
string audioFilePath3 = Path.Combine(exeDirectory, "media", "vocals.wav");
int targetSampleRate = OwnaudioNet.Engine!.Config.SampleRate;
int targetChannels = OwnaudioNet.Engine!.Config.Channels;
fileSource0 = new FileSource(audioFilePath0, 8192, targetSampleRate, targetChannels);
fileSource1 = new FileSource(audioFilePath1, 8192, targetSampleRate, targetChannels);
fileSource2 = new FileSource(audioFilePath2, 8192, targetSampleRate, targetChannels);
fileSource3 = new FileSource(audioFilePath3, 8192, targetSampleRate, targetChannels);
fileSource0.Volume = 0.9f;
fileSource1.Volume = 0.85f;
fileSource2.Volume = 0.8f;
fileSource3.Volume = 1.0f;
What happens here:
- Locate the application directory and construct paths to audio files
- Get the engine's target sample rate and channel configuration
- Create four
FileSourceinstances with 8192 sample internal buffer - Each source automatically resamples and converts to match engine format
- Set individual volume levels for each track (drums, bass, other, vocals)
- The sources are ready but not yet playing
Step 6: Synchronized Playback Setup
Add sources to the mixer and configure Master Clock synchronization for sample-accurate playback.
mixer.AddSource(fileSource0);
mixer.AddSource(fileSource1);
mixer.AddSource(fileSource2);
mixer.AddSource(fileSource3);
// Attach all sources to Master Clock for sample-accurate sync
fileSource0.AttachToClock(mixer.MasterClock);
fileSource1.AttachToClock(mixer.MasterClock);
fileSource2.AttachToClock(mixer.MasterClock);
fileSource3.AttachToClock(mixer.MasterClock);
// Optional: Set timeline positions (all start at 0.0 by default)
fileSource0.StartOffset = 0.0; // Drums start immediately
fileSource1.StartOffset = 0.0; // Bass starts immediately
fileSource2.StartOffset = 0.0; // Other starts immediately
fileSource3.StartOffset = 0.0; // Vocals start immediately
// Start mixer
mixer.Start();
// Start all sources for playback
fileSource0.Play();
fileSource1.Play();
fileSource2.Play();
fileSource3.Play();
// All attached sources now play in perfect sync
What happens here:
- Add all four audio sources to the mixer's processing pipeline
- Attach to Master Clock: Each source subscribes to the mixer's timeline
- StartOffset property: Positions tracks on the timeline (DAW-style regions)
- Start the mixer to begin the mixing engine
- Play() each source: Explicitly start playback for each attached source
- Sample-accurate sync: Master Clock ensures precise timing alignment
- Automatic drift correction: Tracks resync if drift exceeds 10ms (~480 samples @ 48kHz)
- Timeline control: Use
mixer.MasterClock.SeekTo(time)to jump to any position - Dropout events: Subscribe to
mixer.TrackDropoutto monitor buffer underruns
- Timeline-based positioning with start offsets
- Sample-accurate synchronization (not just frame-accurate)
- Realtime and Offline rendering modes
- Automatic tempo-independent timing
- Per-track tempo control with synchronized master timeline
Step 7: Real-time Monitoring and Dynamic Effects
Monitor playback progress and peak levels in real-time, with dynamic effect activation during playback.
DateTime startTime = DateTime.Now;
while (fileSource0.State == SourceState.Playing)
{
Thread.Sleep(100);
double position = fileSource0.Position;
double duration = fileSource0.Duration;
int progressPercent = (int)((position / duration) * 100);
int barWidth = 40;
int filledWidth = (int)((position / duration) * barWidth);
string progressBar = new string('█', filledWidth) + new string('░', barWidth - filledWidth);
string infoLine = $"Position: {TimeSpan.FromSeconds(position)} / {TimeSpan.FromSeconds(duration)} [{progressBar}] {progressPercent}%";
string peakLine = $"| Peaks: L={mixer.LeftPeak:F2} R={mixer.RightPeak:F2}";
Console.Write(infoLine + peakLine);
if (position > 30 && position < 35)
{
_equalizer.Enabled = true;
_compressor.Enabled = true;
}
}
What happens here:
- Record the start time for tempo accuracy calculation
- Loop while the first source is in
Playingstate - Sleep 100ms between updates to avoid excessive CPU usage
- Query current playback position and total duration from the source
- Calculate progress percentage and create a visual progress bar
- Display position, duration, progress bar, and peak levels (left/right channels)
- Dynamic effect activation: Enable equalizer and compressor between 30-35 seconds
- This demonstrates real-time effect toggling without interrupting playback
Console.WriteLine("Playback completed!");
TimeSpan elapsed = DateTime.Now - startTime;
double finalPosition = fileSource0.Position;
Console.WriteLine($"Real-time elapsed: {elapsed.TotalSeconds:F2} seconds");
Console.WriteLine($"Audio position reached: {finalPosition:F2} seconds");
double tempoRatio = finalPosition / elapsed.TotalSeconds;
double tempoError = (tempoRatio - 1.0) * 100.0;
Console.WriteLine($"Tempo ratio: {tempoRatio:F4} (1.0000 = perfect)");
if (Math.Abs(tempoError) < 0.5)
{
Console.WriteLine($"Tempo accuracy: EXCELLENT ({tempoError:+0.00;-0.00}%)");
}
else if (Math.Abs(tempoError) < 2.0)
{
Console.WriteLine($"Tempo accuracy: Good ({tempoError:+0.00;-0.00}%)");
}
else
{
Console.WriteLine($"Tempo accuracy: POOR ({tempoError:+0.00;-0.00}%)");
}
What happens here:
- Calculate elapsed real-world time and final audio position
- Compute tempo ratio (should be 1.0 for perfect playback speed)
- Calculate tempo error as percentage deviation from ideal
- Display accuracy assessment: Excellent (<0.5% error), Good (<2% error), or Poor
- This verifies that the audio engine maintains accurate playback timing
Step 8: Cleanup and Shutdown
Properly dispose of all resources and shut down the audio engine.
Console.WriteLine("=== FINAL STATISTICS ===");
Console.WriteLine($"Total mixed frames: {mixer.TotalMixedFrames}");
Console.WriteLine($"Total underruns: {mixer.TotalUnderruns}");
Console.WriteLine($"Master volume: {mixer.MasterVolume:P0}");
Console.WriteLine($"Source state: {fileSource0.State}");
Console.WriteLine($"Final position: {fileSource0.Position:F2}s / {fileSource0.Duration:F2}s");
Console.WriteLine("=== CLEANUP ===");
mixer.Stop();
mixer.StopSyncGroup("Demo");
mixer.Dispose();
fileSource0.Dispose();
fileSource1.Dispose();
fileSource2.Dispose();
fileSource3.Dispose();
OwnaudioNet.Stop();
OwnaudioNet.Shutdown();
What happens here:
- Display final statistics including total processed frames and underrun count
- Stop the mixer's processing and the sync group
- Dispose the mixer to release its internal resources
- Dispose all file sources to close file handles and free buffers
- Stop the audio engine (halts the real-time processing thread)
- Shutdown OwnaudioNET completely, releasing all native audio resources
- Important: Always dispose resources in this order: sources → mixer → engine
Complete Code
Here's the complete example with all necessary using statements and error handling:
using Ownaudio.Core;
using OwnaudioNET.Core;
using OwnaudioNET.Effects;
using OwnaudioNET.Mixing;
using OwnaudioNET.Sources;
using System.Reflection;
namespace OwnaudioNET.Test;
public class TestProgram
{
public static void Main(string[] args)
{
AudioMixer? mixer = null;
FileSource? fileSource0 = null;
FileSource? fileSource1 = null;
FileSource? fileSource2 = null;
FileSource? fileSource3 = null;
try
{
AudioConfig config = new AudioConfig()
{
SampleRate = 48000,
Channels = 2,
BufferSize = 512
};
OwnaudioNet.Initialize(config);
OwnaudioNet.Start();
var Engine = OwnaudioNet.Engine!.UnderlyingEngine;
mixer = new AudioMixer(Engine, bufferSizeInFrames: 512);
mixer.MasterVolume = 0.8f;
var _equalizer = new Equalizer30BandEffect();
var _compressor = new CompressorEffect(CompressorPreset.Vintage);
mixer.AddMasterEffect(_equalizer);
mixer.AddMasterEffect(_compressor);
mixer.AddMasterEffect(new DynamicAmpEffect(DynamicAmpPreset.Live));
_equalizer.Enabled = false;
_compressor.Enabled = false;
string? exeDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
int targetSampleRate = OwnaudioNet.Engine!.Config.SampleRate;
int targetChannels = OwnaudioNet.Engine!.Config.Channels;
fileSource0 = new FileSource(Path.Combine(exeDirectory, "media", "drums.wav"), 8192, targetSampleRate, targetChannels);
fileSource1 = new FileSource(Path.Combine(exeDirectory, "media", "bass.wav"), 8192, targetSampleRate, targetChannels);
fileSource2 = new FileSource(Path.Combine(exeDirectory, "media", "other.wav"), 8192, targetSampleRate, targetChannels);
fileSource3 = new FileSource(Path.Combine(exeDirectory, "media", "vocals.wav"), 8192, targetSampleRate, targetChannels);
mixer.AddSource(fileSource0);
mixer.AddSource(fileSource1);
mixer.AddSource(fileSource2);
mixer.AddSource(fileSource3);
var syncGroup = mixer.CreateSyncGroup("Demo", fileSource0, fileSource1, fileSource2, fileSource3);
mixer.Start();
mixer.StartSyncGroup("Demo");
DateTime startTime = DateTime.Now;
while (fileSource0.State == SourceState.Playing)
{
Thread.Sleep(100);
double position = fileSource0.Position;
double duration = fileSource0.Duration;
if (position > 30 && position < 35)
{
_equalizer.Enabled = true;
_compressor.Enabled = true;
}
}
mixer.Stop();
mixer.StopSyncGroup("Demo");
mixer.Dispose();
fileSource0.Dispose();
fileSource1.Dispose();
fileSource2.Dispose();
fileSource3.Dispose();
OwnaudioNet.Stop();
OwnaudioNet.Shutdown();
}
catch (Exception ex)
{
Console.WriteLine($"ERROR: {ex.Message}");
fileSource0?.Dispose();
fileSource1?.Dispose();
fileSource2?.Dispose();
fileSource3?.Dispose();
mixer?.Dispose();
OwnaudioNet.Shutdown();
Environment.Exit(1);
}
}
}
Next Steps
📚 Core API Reference
Learn about low-level audio engine components and direct platform access.
View Core API →