AudioMixer
The central mixing engine. Combines multiple audio sources, applies master effects, and drives audio output. Namespace: OwnaudioNET.Mixing
Constructor
// Direct path: mix thread writes straight to the platform audio engine
var mixer = new AudioMixer(OwnaudioNet.Engine!.UnderlyingEngine, bufferSizeInFrames: 512);| Parameter | Type | Default | Description |
|---|---|---|---|
engine | IAudioEngine | â | From OwnaudioNet.Engine!.UnderlyingEngine. |
bufferSizeInFrames | int | 512 | Internal mix chunk size. Use 1024 for 20+ tracks. |
Heavy Load Setup
When running 8 or more simultaneous sources or 2 or more master effects (especially VST plugins), use AudioMixer.Create() instead of the constructor. This routes mixer output through the AudioEngineWrapper's internal CircularBuffer, giving the mix thread a pre-buffer of time to complete heavy DSP before the audio hardware needs its next block.
// bufferMultiplier: 16 â ~170 ms headroom at 48 kHz / 512 frames
// (default 8 â ~85 ms; increase further only if dropouts persist)
await OwnaudioNet.InitializeAsync(config, bufferMultiplier: 16);await OwnaudioNet.InitializeAsync(config, bufferMultiplier: 16);
OwnaudioNet.Start();
// AudioMixer.Create routes output through the CircularBuffer
var mixer = AudioMixer.Create(OwnaudioNet.Engine!, bufferSizeInFrames: 512);
// Add sources and effects as usual
mixer.AddSource(source1);
mixer.AddMasterEffect(new CompressorEffect());
await host.InitializeAudioAsync(sampleRate: 48000, blockSize: 512);
mixer.AddMasterEffect(host.GetProcessor()); // VST3
mixer.Start();| Parameter | Type | Default | Description |
|---|---|---|---|
engineWrapper | AudioEngineWrapper | â | Pass OwnaudioNet.Engine!. The factory extracts the underlying engine and wires the circular buffer. |
bufferSizeInFrames | int | 512 | Internal mix chunk size. Use 1024 for 20+ tracks. |
How it works: the mix thread writes into the CircularBuffer (sized bufferMultiplier à engineBufferSize). A dedicated pump thread reads from the buffer and forwards data to the audio hardware. The two threads operate independently, so a momentarily slow mix cycle does not immediately starve the hardware.
Call OwnaudioNet.Start() before AudioMixer.Create() so the wrapper's pump thread is already running when the mixer starts filling the buffer.
Properties
| Property | Type | Description |
|---|---|---|
MixerId | Guid | Unique identifier. |
Config | AudioConfig | Audio configuration in use. |
IsRunning | bool | Whether the mix thread is active. |
SourceCount | int | Number of active sources. |
MasterVolume | float | Master output volume (0.0 â 1.0). Settable at any time. |
LeftPeak / RightPeak | float | Real-time peak levels (0.0 â 1.0) for VU metering. |
TotalMixedFrames | long | Total frames processed since last Start(). |
TotalUnderruns | long | Total buffer underruns since last Start(). |
IsRecording | bool | Whether WAV recording is active. |
MasterClock | MasterClock | Timeline clock for synchronized multi-track sync. |
RenderingMode | ClockMode | Realtime (default) or Offline. |
Lifecycle
mixer.Start(); // begin audio processing
mixer.Pause(); // freeze mix thread (thread stays alive)
mixer.Stop(); // stop: joins mix thread, then stops all sources
mixer.Seek(double positionInSec); // seek master clock + all attached sources
mixer.Dispose(); // release all resourcesv3.1.7 â deterministic shutdown: Stop() now joins the mix thread (up to 2 s timeout) before stopping sources or returning. This eliminates a use-after-free race where Dispose() could release a source's memory while the mix thread was still reading it. Start() after Stop() always allocates a fresh thread â calling Start() repeatedly is safe regardless of how many times Stop() has been called.
Source Management
// Add and immediately start
mixer.AddSource(source);
// Add without starting â launch all at once for tight sync
mixer.AddSourcePrepared(vocals);
mixer.AddSourcePrepared(backing);
mixer.StartPreparedSources(startPosition: 0.0); // atomic start
// Remove
mixer.RemoveSource(source); // by reference
mixer.RemoveSource(sourceGuid); // by ID
mixer.ClearSources(); // remove + stop all
// Query
IAudioSource[] all = mixer.GetSources();AddSource() and RemoveSource() are lock-free hot-swap operations â safe to call while the mixer is running.
Maximum simultaneous sources per mixer: AudioConstants.MaxAudioSources = 25
Master Effects
Effects applied to the final mixed signal in the order they are added. See Effects for the full effect list.
// Add effects in chain order
mixer.AddMasterEffect(new CompressorEffect { Ratio = 4f });
mixer.AddMasterEffect(new ReverbEffect { RoomSize = 0.5f, Mix = 0.2f });
mixer.AddMasterEffect(new LimiterEffect());
// Manage
mixer.RemoveMasterEffect(effect); // returns bool
mixer.ClearMasterEffects();
IEffectProcessor[] fx = mixer.GetMasterEffects();The effect must have IsReady == true before adding. For VST3 effects, call await host.InitializeAudioAsync() first.
Recording
Records the final mixed output â post master-effects â to a WAV file.
mixer.StartRecording("session.wav"); // start capturing
// ... playback ...
mixer.StopRecording(); // finalize and close the WAV fileVU Metering
// Update at ~10Hz (100ms timer)
_vuTimer = new Timer(_ =>
{
// Master bus
float leftDb = 20f * MathF.Log10(Math.Max(mixer.LeftPeak, 1e-6f));
float rightDb = 20f * MathF.Log10(Math.Max(mixer.RightPeak, 1e-6f));
MasterLeftDb = Math.Max(leftDb, -60f);
MasterRightDb = Math.Max(rightDb, -60f);
// Per-track (if source is BaseAudioSource)
var (l, r) = source.OutputLevels;
TrackLeftDb = 20f * MathF.Log10(Math.Max(l, 1e-6f));
}, null, 0, 100);Events
| Event | Args | Description |
|---|---|---|
PlaybackEnded | EventArgs | All sources reached EndOfStream. |
BufferUnderrun | BufferUnderrunEventArgs | Mix thread couldn't fill buffer in time. |
SourceError | AudioErrorEventArgs | An error occurred in one of the sources. |
TrackDropout | TrackDropoutEventArgs | A source missed its master clock deadline. |
mixer.PlaybackEnded += (_, _) =>
{
// All tracks finished â update UI state
};
mixer.BufferUnderrun += (_, e) =>
{
Console.WriteLine($"Underrun: {e.MissedFrames} frames at position {e.Position}");
};
mixer.TrackDropout += (_, e) =>
{
Console.WriteLine($"Dropout on {e.TrackName}: {e.Reason} ({e.MissedFrames} frames)");
};