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
// 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. |
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.
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();| Parameter | Type | Default | Description |
|---|---|---|---|
engineWrapper | AudioEngineWrapper | â | Pass OwnaudioNet.Engine!. The factory extracts the underlying engine and keeps the wrapper. |
bufferSizeInFrames | int | 512 | Internal 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
| 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. |
MasterPan | float | Master stereo pan (-1.0 left âĻ 0.0 center âĻ +1.0 right). 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(). |
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
mixer.Stop(); // stop and clear all sources
mixer.Seek(double positionInSec); // seek master clock + all attached sources
mixer.Dispose(); // release all resourcesSources on the bus
AddSource() adds and starts. When several tracks must begin on the same frame, add them prepared instead and release them together.
// 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.
// 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.
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 frontcompensateInputLatency 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.
// 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. |
SourceError | AudioErrorEventArgs | An error occurred in one of the sources. |
StreamFaulted | AudioStreamFaultEventArgs | The native output stream died â usually a device being unplugged. Without this the stream just goes quiet and nothing tells you why. |
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.