Step 2 of 8

AudioMixer

The bus. Sources go in, one signal comes out. Namespace: OwnaudioNET.Mixing

The mixer sums every source you give it and pushes the result to the engine. It also owns everything that belongs to "the whole mix": master volume and pan, the master effect chain, peak levels for your meters, WAV recording of the output, and the master clock that keeps tracks together.

One mixer is enough for almost every application.

Creating one

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.

Building through the engine wrapper

The constructor wires the mixer straight at the engine. AudioMixer.Create() wires it at the AudioEngineWrapper instead, so the mixer also picks up the wrapper's lifecycle and device events — a device being unplugged or swapped reaches the mixer instead of only the engine.

C# — create the mixer through the wrapper
await OwnaudioNet.InitializeAsync(config);
OwnaudioNet.Start();

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, maxBlockSize: 512);
mixer.AddMasterEffect(host.GetProcessor()); // VST3

mixer.Start();
ParameterTypeDefaultDescription
engineWrapperAudioEngineWrapper—Pass OwnaudioNet.Engine!. The factory extracts the underlying engine and keeps the wrapper.
bufferSizeInFramesint512Internal mix chunk size. Use 1024 for 20+ tracks.
â„šī¸

Playback headroom is not tunable from managed code. Since 4.0 the render ring lives on the Rust side, so neither AudioMixer.Create() nor the bufferMultiplier argument changes how much slack the output has. If you hear crackling under a heavy mix, raise bufferSizeInFrames (or the device buffer in AudioConfig) instead.

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.
MasterPanfloatMaster stereo pan (-1.0 left â€Ļ 0.0 center â€Ļ +1.0 right). Settable at any time.
LeftPeak / RightPeakfloatReal-time peak levels (0.0 – 1.0) for VU metering.
TotalMixedFrameslongTotal frames processed 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
mixer.Stop();                       // stop and clear all sources
mixer.Seek(double positionInSec);   // seek master clock + all attached sources
mixer.Dispose();                    // release all resources

Sources on the bus

AddSource() adds and starts. When several tracks must begin on the same frame, add them prepared instead and release them together.

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

Applied to the finished mix, in the order you add them — like pedals on a board. For an effect that should only touch one track, wrap that track in a SourceWithEffects instead. See Effects for the full 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(sampleRate: 48000f));

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

// Recording live input? Trim the capture delay so the take starts
// where you hit record, not a few ms late.
mixer.StartRecording("take.wav", compensateInputLatency: true);
// ...
mixer.StopRecording();
int trimmed = mixer.LastRecordingLatencyOffsetFrames; // frames dropped off the front
💡

compensateInputLatency drops InputLatencyFrames worth of frames off the start, so a live take lines up with the moment you pressed record. Off by default (samples untouched); a no-op when input isn't running or the backend reports no latency.

VU metering

LeftPeak and RightPeak are linear peaks between 0 and 1. Poll them on a timer at about 10 Hz — faster than the audio buffer interval gains you nothing but CPU — and convert to dBFS yourself.

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.
SourceErrorAudioErrorEventArgsAn error occurred in one of the sources.
StreamFaultedAudioStreamFaultEventArgsThe native output stream died — usually a device being unplugged. Without this the stream just goes quiet and nothing tells you why.
C#
mixer.PlaybackEnded += (_, _) =>
{
    // All tracks finished — update UI state
};

mixer.StreamFaulted += (_, e) =>
{
    if (e.Kind == AudioStreamFaultKind.DeviceNotAvailable)
        Console.WriteLine("Output device disappeared.");
};

// Underruns are counted by the engine, not the mixer
OwnaudioNet.Engine!.BufferUnderrun += (_, e) =>
{
    Console.WriteLine($"Underrun: {e.MissedFrames} frames at position {e.Position}");
};
â„šī¸

Underrun and dropout detection lived in the managed mix thread, which the native chain replaced. Watch AudioEngineWrapper.BufferUnderrun and AudioEngineWrapper.TotalUnderruns for starvation, and StreamFaulted for a dead output stream.

Next