AudioMixer

The central mixing engine. Combines multiple audio sources, applies master effects, and drives audio output. Namespace: OwnaudioNET.Mixing

Constructor

C# — Standard (up to ~8 sources, no VST effects)
// Direct path: mix thread writes straight to the platform audio engine
var mixer = new AudioMixer(OwnaudioNet.Engine!.UnderlyingEngine, bufferSizeInFrames: 512);
ParameterTypeDefaultDescription
engineIAudioEngine—From OwnaudioNet.Engine!.UnderlyingEngine.
bufferSizeInFramesint512Internal 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.

C# — Step 1: initialize with larger buffer headroom
// 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);
C# — Step 2: create mixer via factory (buffered path)
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();
ParameterTypeDefaultDescription
engineWrapperAudioEngineWrapper—Pass OwnaudioNet.Engine!. The factory extracts the underlying engine and wires the circular buffer.
bufferSizeInFramesint512Internal 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

PropertyTypeDescription
MixerIdGuidUnique identifier.
ConfigAudioConfigAudio configuration in use.
IsRunningboolWhether the mix thread is active.
SourceCountintNumber of active sources.
MasterVolumefloatMaster output volume (0.0 – 1.0). Settable at any time.
LeftPeak / RightPeakfloatReal-time peak levels (0.0 – 1.0) for VU metering.
TotalMixedFrameslongTotal frames processed since last Start().
TotalUnderrunslongTotal buffer underruns since last Start().
IsRecordingboolWhether WAV recording is active.
MasterClockMasterClockTimeline clock for synchronized multi-track sync.
RenderingModeClockModeRealtime (default) or Offline.

Lifecycle

C#
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 resources
â„šī¸

v3.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

C#
// 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.

C#
// 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.

C#
mixer.StartRecording("session.wav"); // start capturing
// ... playback ...
mixer.StopRecording();               // finalize and close the WAV file

VU Metering

C#
// 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

EventArgsDescription
PlaybackEndedEventArgsAll sources reached EndOfStream.
BufferUnderrunBufferUnderrunEventArgsMix thread couldn't fill buffer in time.
SourceErrorAudioErrorEventArgsAn error occurred in one of the sources.
TrackDropoutTrackDropoutEventArgsA source missed its master clock deadline.
C#
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)");
};