NET OwnaudioNET API Reference
High-level audio API with professional features including multi-track mixing, effects processing, synchronization, and real-time audio manipulation.
Overview
OwnaudioNET extends Ownaudio.Core with high-level features:
- Multi-track mixing - Unlimited simultaneous audio sources
- 15+ professional effects - Reverb, compressor, EQ, and more
- Master Clock synchronization - Sample-accurate timeline-based sync with drift correction
- DAW-style features - Realtime/offline rendering modes, start offsets
- Tempo and pitch control - Independent time-stretch and pitch-shift via SoundTouch
- Channel routing - Direct audio to specific physical output channels on multi-output devices
- Recording - Mix-down to WAV format
- Real-time metering - Peak and RMS level monitoring
- Thread-safe architecture - Lock-free buffers for glitch-free audio
Initialization
OwnaudioNet Class
Central initialization and configuration for OwnaudioNET.
await InitializeAsync()- Prevents 50-5000ms UI freezeawait StopAsync()- Prevents up to 2000ms UI freezeawait ShutdownAsync()- Non-blocking shutdownawait GetOutputDevicesAsync()- Non-blocking device enumeration
// Default initialization (48kHz, stereo, 512 frames)
OwnaudioNet.Initialize();
// Custom configuration
var config = new AudioConfig
{
SampleRate = 44100,
Channels = 2,
BufferSize = 256
};
OwnaudioNet.Initialize(config);
// Shutdown
OwnaudioNet.Shutdown();
// Default async initialization
await OwnaudioNet.InitializeAsync();
// Custom async configuration
var config = new AudioConfig
{
SampleRate = 44100,
Channels = 2,
BufferSize = 256
};
await OwnaudioNet.InitializeAsync(config);
// Async device enumeration
var devices = await OwnaudioNet.GetOutputDevicesAsync();
foreach (var device in devices)
{
Console.WriteLine($"Device: {device.Name}");
}
// Async shutdown
await OwnaudioNet.ShutdownAsync();
| Method | Description | Recommended For |
|---|---|---|
Initialize() |
Initialize with default settings (⚠️ BLOCKS 50-200ms) | Console apps |
Initialize(AudioConfig) |
Initialize with custom config (⚠️ BLOCKS 50-5000ms) | Console apps |
InitializeAsync() |
✅ Async init with default settings (NON-BLOCKING) | UI apps (WPF, MAUI, Avalonia) |
InitializeAsync(AudioConfig) |
✅ Async init with custom config (NON-BLOCKING) | UI apps (WPF, MAUI, Avalonia) |
Shutdown() |
Shutdown and release resources (⚠️ BLOCKS up to 2000ms) | Console apps |
ShutdownAsync() |
✅ Async shutdown (NON-BLOCKING) | UI apps (WPF, MAUI, Avalonia) |
Channel Routing
Channel routing lets you direct audio to specific physical output channels on a multi-output device
(USB interfaces, ASIO cards, monitor controllers, etc.). Set
OutputChannelSelectors in your AudioConfig to select which physical channels
to use. This works with all engines and platforms — not just ASIO.
- PortAudio + ASIO: Native hardware channel selection (zero overhead)
- PortAudio + WASAPI / CoreAudio / ALSA: Software routing in the audio callback
- MiniAudio (default engine): Software routing in the audio callback
Configuration
Set the number of logical Channels equal to the number of selectors.
Each entry in OutputChannelSelectors is a 0-based physical channel index.
// Route stereo output to the second pair of outputs (ch 4 and 5)
// on an 8-channel USB interface
var config = new AudioConfig
{
SampleRate = 48000,
Channels = 2, // 2 logical channels
OutputChannelSelectors = new[] { 4, 5 } // physical ch 4 and 5
};
await OwnaudioNet.InitializeAsync(config);
OwnaudioNet.Start();
// AudioMixer and FileSource work normally - routing is transparent
var engine = OwnaudioNet.Engine!.UnderlyingEngine;
var mixer = new AudioMixer(engine, bufferSizeInFrames: 512);
var music = new FileSource("music.wav", 8192,
targetSampleRate: 48000, targetChannels: 2);
mixer.AddSource(music);
mixer.Start();
music.Play();
How Channel Assignment Works in the Mixer
The AudioMixer sums all sources per logical channel into a single
N-channel buffer, then hands that buffer to the engine. The engine applies the
OutputChannelSelectors routing to map each logical channel to its physical counterpart.
Every BaseAudioSource subclass — FileSource,
SampleSource, InputSource, and custom sources — exposes an
OutputChannelMapping property and a fluent RouteToChannels() helper.
When set, the mixer automatically places that source's audio into the specified logical channel
slots and leaves all other slots silent, so multiple sources can play on distinct physical channels
without a custom IAudioSource implementation.
| Scenario | Result |
|---|---|
Sources share the same logical channel count, no OutputChannelMapping set |
All sources play on the same physical channels – mixed together |
Each source has its own OutputChannelMapping / RouteToChannels() |
Each source is heard on its own dedicated physical channels |
Case 1 – All Sources on the Same Physical Channels (Default)
With a standard 2-channel config and OutputChannelSelectors = [4, 5],
every source added to the mixer plays on physical channels 4 and 5.
This is the simplest and most common use case.
var config = new AudioConfig
{
Channels = 2,
OutputChannelSelectors = new[] { 4, 5 } // physical ch 4 and 5
};
await OwnaudioNet.InitializeAsync(config);
OwnaudioNet.Start();
var engine = OwnaudioNet.Engine!.UnderlyingEngine;
var mixer = new AudioMixer(engine, bufferSizeInFrames: 512);
// Both sources will play on physical ch 4 and 5 (mixed together)
var music = new FileSource("music.wav", 8192, targetSampleRate: 48000, targetChannels: 2);
var metronome = new FileSource("click.wav", 8192, targetSampleRate: 48000, targetChannels: 2);
mixer.AddSource(music);
mixer.AddSource(metronome);
mixer.Start();
music.Play();
metronome.Play();
Case 2 – Sources on Separate Physical Channels
To send each source to a different pair of physical channels, call
RouteToChannels() on each source. The mixer automatically routes
that source's stereo audio into the specified logical channel slots and zeroes out the rest.
// 8-channel interface: music on physical ch 0+1, metronome on physical ch 4+5
// AudioConfig uses 4 logical channels mapped to physical 0, 1, 4, 5
var config = new AudioConfig
{
Channels = 4,
OutputChannelSelectors = new[] { 0, 1, 4, 5 }
};
await OwnaudioNet.InitializeAsync(config);
OwnaudioNet.Start();
var engine = OwnaudioNet.Engine!.UnderlyingEngine;
var mixer = new AudioMixer(engine, bufferSizeInFrames: 512);
var music = new FileSource("music.wav", 8192, targetSampleRate: 48000, targetChannels: 2);
var metronome = new FileSource("click.wav", 8192, targetSampleRate: 48000, targetChannels: 2);
// Route each source to its own logical channel pair.
// The engine then maps logical → physical via OutputChannelSelectors:
// logical 0+1 → physical 0+1 (music)
// logical 2+3 → physical 4+5 (metronome)
music.RouteToChannels(0, 1);
metronome.RouteToChannels(2, 3);
mixer.AddSource(music);
mixer.AddSource(metronome);
mixer.Start();
music.Play();
metronome.Play();
RouteToChannels() and OutputChannelMapping are available on
all BaseAudioSource subclasses: FileSource,
SampleSource, InputSource, and any custom subclass.
The mapping array length must equal the source's Config.Channels.
OutputChannelSelectors array length must equal the Channels value.
Use AudioConfig.Validate() to verify before initializing.
Negative selector indices are also invalid.
Audio Sources
FileSource
File-based audio source with decoding, SoundTouch support, and Master Clock synchronization.
// Create from file
var source = new FileSource("music.mp3");
// Configure playback
source.Volume = 0.8f; // Volume (0.0-1.0)
source.Loop = true; // Enable looping
source.Tempo = 1.2f; // 20% faster (0.8-1.2)
source.PitchShift = -2.0f; // 2 semitones lower (-12..+12)
// Control playback
source.Play();
source.Pause();
source.Stop();
source.Seek(30.0); // Seek to 30 seconds
// Get info
double position = source.Position; // Current position (seconds)
double duration = source.Duration; // Total duration (seconds)
AudioState state = source.State; // Playing/Paused/Stopped
// Cleanup
source.Dispose();
Properties
| Property | Type | Description |
|---|---|---|
Id |
Guid | Unique source identifier |
State |
AudioState | Current playback state |
Volume |
float | Volume level (0.0-1.0) |
Loop |
bool | Enable/disable looping |
Position |
double | Current position in seconds |
Duration |
double | Total duration in seconds |
Tempo |
float | Playback tempo (0.8-1.2, 1.0 = normal) |
PitchShift |
float | Pitch shift in semitones (-12..+12) |
StartOffset |
double | Timeline start position (for Master Clock sync) |
OutputChannelMapping |
int[]? | Logical mix-buffer channel indices this source writes into. Length must equal
Config.Channels. null = write to all channels (default).
Also available on SampleSource and InputSource. |
RouteToChannels(params int[]) |
BaseAudioSource | Fluent helper that sets OutputChannelMapping and returns
this. Example: source.RouteToChannels(2, 3). |
SampleSource
Play audio from memory samples with optional dynamic updates.
// Generate or load samples
float[] samples = GenerateAudioSamples();
// Create source from existing samples
var config = new AudioConfig
{
SampleRate = 48000,
Channels = 2
};
var source = new SampleSource(samples, config);
// OR create empty source for dynamic updates
var dynamicSource = new SampleSource(
bufferSizeInFrames: 48000, // 1 second buffer
config: config
);
// Update samples dynamically
float[] newSamples = GenerateNewSamples();
dynamicSource.SubmitSamples(newSamples);
// Playback control
source.Play();
source.Loop = true;
InputSource
Real-time audio input from microphone or line-in.
// Initialize OwnaudioNET (engine must be running)
OwnaudioNet.Initialize();
// Create input source
var input = new InputSource(
engine: OwnaudioNet.Engine,
bufferSizeInFrames: 8192 // Optional, default is 8192
);
// Add to mixer and start
mixer.AddSource(input);
input.Play();
SourceWithEffects
Wrapper for applying effects chains to any audio source.
var source = new FileSource("guitar.wav");
// Create source with effects wrapper
var sourceWithFx = new SourceWithEffects(source);
// Add effects to the chain
var reverb = new ReverbEffect
{
Mix = 0.3f,
RoomSize = 0.7f,
Damping = 0.5f
};
sourceWithFx.AddEffect(reverb);
var compressor = new CompressorEffect(
threshold: 0.5f,
ratio: 4.0f,
attackTime: 10.0f,
releaseTime: 100.0f,
makeupGain: 1.0f,
sampleRate: 48000f
);
sourceWithFx.AddEffect(compressor);
// Modify effects dynamically
reverb.Enabled = false; // Bypass effect
sourceWithFx.RemoveEffect(compressor);
sourceWithFx.ClearEffects();
AudioMixer
Central mixing engine for multi-track audio playback with Master Clock synchronization.
// Create mixer
var mixer = new AudioMixer(OwnaudioNet.Engine);
mixer.Start();
// Add sources
var drums = new FileSource("drums.wav");
var bass = new FileSource("bass.wav");
var guitar = new FileSource("guitar.wav");
mixer.AddSource(drums);
mixer.AddSource(bass);
mixer.AddSource(guitar);
// Control individual sources
drums.Volume = 0.8f;
bass.Volume = 0.7f;
guitar.Volume = 0.6f;
// Play sources
drums.Play();
bass.Play();
guitar.Play();
// Master controls
mixer.MasterVolume = 0.9f;
// Real-time metering
float leftPeak = mixer.LeftPeak;
float rightPeak = mixer.RightPeak;
// Recording
mixer.StartRecording("output.wav");
// ... playback ...
mixer.StopRecording();
// Cleanup
mixer.Stop();
mixer.Dispose();
Properties
| Property | Type | Description |
|---|---|---|
MasterVolume |
float | Master volume level (0.0-1.0) |
IsRunning |
bool | True if mixer is running |
SourceCount |
int | Number of active sources |
LeftPeak |
float | Left channel peak level |
RightPeak |
float | Right channel peak level |
IsRecording |
bool | True if currently recording |
MasterClock |
MasterClock | Master clock for timeline synchronization |
Methods
| Method | Description |
|---|---|
Start() |
Start the mixer |
Stop() |
Stop the mixer |
AddSource(IAudioSource) |
Add audio source to mixer |
RemoveSource(Guid) |
Remove source by ID |
RemoveAllSources() |
Remove all sources |
AddMasterEffect(IEffectProcessor) |
Add effect to master chain |
RemoveMasterEffect(IEffectProcessor) |
Remove effect from master chain |
StartRecording(string) |
Start recording to WAV file |
StopRecording() |
Stop recording |
Audio Effects
OwnaudioNET includes 15+ professional audio effects with preset support.
Quick Start - Adding Effects
// Create source and wrap with effects capability
var fileSource = new FileSource("audio.mp3");
var source = new SourceWithEffects(fileSource);
// Add effects
var compressor = new CompressorEffect(CompressorPreset.VocalGentle);
var reverb = new ReverbEffect(ReverbPreset.VocalBooth);
var limiter = new LimiterEffect(48000f, LimiterPreset.Mastering);
source.AddEffect(compressor);
source.AddEffect(reverb);
source.AddEffect(limiter);
// Control effects
reverb.Enabled = false; // Bypass
compressor.Threshold = 0.6f; // Adjust parameter
// Add to mixer
mixer.AddSource(source);
mixer.Start();
Available Effects
| Category | Effects |
|---|---|
| Dynamics | Compressor, Limiter, AutoGain, DynamicAmp |
| Frequency | Equalizer (10-band), Equalizer30Band, Enhancer |
| Modulation | Chorus, Flanger, Phaser, Rotary |
| Spatial | Reverb, Delay |
| Distortion | Distortion, Overdrive |
See Also: Full Effects Documentation for detailed parameters, all presets, technical details, and advanced usage examples.
Synchronization
Master Clock Architecture
OwnaudioNET uses a professional DAW-style Master Clock for sample-accurate synchronization with timeline positioning and automatic drift correction.
// Create sources
var drums = new FileSource("drums.wav");
var bass = new FileSource("bass.wav");
var guitar = new FileSource("guitar.wav");
// Add to mixer
mixer.AddSource(drums);
mixer.AddSource(bass);
mixer.AddSource(guitar);
// Attach sources to Master Clock
drums.AttachToClock(mixer.MasterClock);
bass.AttachToClock(mixer.MasterClock);
guitar.AttachToClock(mixer.MasterClock);
// Optional: Set start offsets (timeline positioning)
drums.StartOffset = 0.0; // Starts at 0 seconds
bass.StartOffset = 0.0; // Starts at 0 seconds
guitar.StartOffset = 2.0; // Starts at 2 seconds (delayed)
// Start mixer
mixer.Start();
// Start all sources for playback (IMPORTANT!)
drums.Play();
bass.Play();
guitar.Play();
// All attached sources now play in perfect sync with Master Clock
// Timeline control
mixer.MasterClock.SeekTo(10.0); // Seek all tracks to 10 seconds
// Get current timeline position
double currentTime = mixer.MasterClock.CurrentTimestamp;
long samplePosition = mixer.MasterClock.CurrentSamplePosition;
// Rendering modes
mixer.MasterClock.Mode = ClockMode.Realtime; // Default: dropout → silence + event
mixer.MasterClock.Mode = ClockMode.Offline; // Offline: blocking, deterministic
Automatic Drift Correction
The Master Clock automatically maintains perfect synchronization with sub-10ms drift tolerance.
// Drift correction is automatic with Master Clock
// Tolerance: 10ms (~480 samples @ 48kHz)
// When drift exceeds tolerance, sources automatically resync
// Monitor dropout events (occurs when sources can't keep up)
mixer.TrackDropout += (sender, e) =>
{
Console.WriteLine($"Track dropout: {e.TrackName}");
Console.WriteLine($" At time: {e.MasterTimestamp:F3}s");
Console.WriteLine($" Missed frames: {e.MissedFrames}");
Console.WriteLine($" Reason: {e.Reason}");
};
Timeline Features
// DAW-style timeline positioning
var intro = new FileSource("intro.wav");
var verse = new FileSource("verse.wav");
var chorus = new FileSource("chorus.wav");
intro.AttachToClock(mixer.MasterClock);
verse.AttachToClock(mixer.MasterClock);
chorus.AttachToClock(mixer.MasterClock);
// Position tracks on timeline
intro.StartOffset = 0.0; // 0:00
verse.StartOffset = 8.5; // 0:08.5
chorus.StartOffset = 24.3; // 0:24.3
mixer.AddSource(intro);
mixer.AddSource(verse);
mixer.AddSource(chorus);
mixer.Start();
// Start all sources
intro.Play();
verse.Play();
chorus.Play();
// Tracks play at their scheduled times based on StartOffset
// Timeline navigation
mixer.MasterClock.SeekTo(20.0); // Jump to 20 seconds - all tracks sync