Events & Reference
Event argument types, enumerations, constants, and threading best practices.
Events Reference
AudioStateChangedEventArgs
Raised by OwnaudioNet.AudioStateChanged and by each audio source's StateChanged event.
| Property | Type | Description |
|---|---|---|
PreviousState | AudioState | State before the transition. |
CurrentState | AudioState | New state after the transition. |
Timestamp | DateTime | When the transition occurred. |
OwnaudioNet.AudioStateChanged += (_, e) =>
{
Console.WriteLine($"{e.PreviousState} → {e.CurrentState} at {e.Timestamp:T}");
};BufferUnderrunEventArgs
Raised by OwnaudioNet.BufferUnderrun when the audio output buffer runs dry.
| Property | Type | Description |
|---|---|---|
UnderrunCount | int | Cumulative underrun count since engine start. |
Timestamp | DateTime | When the underrun occurred. |
AudioErrorEventArgs
Raised by OwnaudioNet.AudioError on engine-level errors.
| Property | Type | Description |
|---|---|---|
Message | string | Human-readable error description. |
Exception | Exception? | Original exception (if any). |
Timestamp | DateTime | When the error occurred. |
TrackDropoutEventArgs
Raised by AudioMixer.TrackDropout when a hard-sync correction (Red Zone) occurs. See Drift Correction Zones.
| Property | Type | Description |
|---|---|---|
TrackId | Guid | ID of the source that dropped out. |
TrackName | string | Source name or type string. |
MasterTimestamp | double | Clock position in seconds when dropout occurred. |
MasterSamplePosition | long | Clock position in samples. |
MissedFrames | int | Number of frames that were silent due to the dropout. |
Reason | string | Human-readable cause description. |
EventTimestamp | DateTime | Wall-clock time of the event. |
AudioDeviceInfo
Returned by OwnaudioNet.GetOutputDevices() and GetInputDevices().
| Property | Type | Description |
|---|---|---|
DeviceId | string | Unique device identifier — pass to AudioConfig.OutputDeviceId. |
Name | string | Human-readable device name. |
EngineName | string | Backend: Wasapi, CoreAudio, PulseAudio, … |
IsInput / IsOutput | bool | Device direction. |
IsDefault | bool | System default device for its direction. |
MaxInputChannels / MaxOutputChannels | int | Hardware channel limits. |
DefaultSampleRate | double | Native sample rate from the platform API. 0 = unavailable. Check > 0 before use. PortAudio and MiniAudio backends only. Added in v3.1.7. |
State | AudioDeviceState | Active, Disabled, NotPresent, Unplugged. |
Enumerations
AudioState
AudioState.Stopped // Not playing, position at 0
AudioState.Playing // Actively reading and outputting audio
AudioState.Paused // Paused, resumes from current position
AudioState.EndOfStream // Reached end (Loop = false)
AudioState.Error // Fatal error in sourceClockMode
ClockMode.Realtime // Non-blocking — dropouts produce silence (live playback)
ClockMode.Offline // Blocking — waits for data (deterministic file rendering)
ClockMode.NetworkServer // Broadcasts clock to LAN clients
ClockMode.NetworkClient // Follows a remote server clockEngineStatus
EngineStatus.Idle // Initialized, not started
EngineStatus.Running // Processing audio
EngineStatus.DeviceDisconnected // Device unplugged (monitoring for reconnect)
EngineStatus.Error // Fatal engine errorEngineHostType
EngineHostType.None // Auto-select (recommended default)
EngineHostType.WASAPI // Windows Audio Session API
EngineHostType.ASIO // ASIO — Windows, ultra-low latency
EngineHostType.CoreAudio // macOS Core Audio
EngineHostType.ALSA // Linux ALSA
EngineHostType.Miniaudio // Cross-platform fallback
EngineHostType.Portaudio // PortAudio backendBest Practices
Thread Safety
| Operation | Safe Thread | Notes |
|---|---|---|
OwnaudioNet.Initialize() | Background / Task.Run | Blocks 50ms–5s (Linux PulseAudio). Use InitializeAsync(). |
OwnaudioNet.Stop() / Shutdown() | Background / Task.Run | Waits for audio thread join (up to 2s). Use async variants. |
mixer.Start(), mixer.Stop() | Any | Thread-safe. Stop() blocks up to ~2 s waiting for the mix thread to finish its current callback before returning. v3.1.7+ |
mixer.AddSource() / RemoveSource() | Any | Lock-free hot-swap. |
source.Volume = …, source.Tempo = … | Any | Atomic property writes. |
OwnaudioNet.Send() | Any | Lock-free ring buffer write, never blocks. |
Never call Initialize(), Stop(), or Shutdown() from a UI thread. These operations block and will freeze your application. Always use the async variants or Task.Run.
Recommended Startup Pattern
protected override async Task OnInitializedAsync()
{
var config = OwnaudioNet.CreateDefaultConfig();
config.EnableInput = false;
if (OperatingSystem.IsWindows())
config.HostType = EngineHostType.WASAPI;
await OwnaudioNet.InitializeAsync(config);
OwnaudioNet.Start();
_mixer = new AudioMixer(OwnaudioNet.Engine!.UnderlyingEngine, bufferSizeInFrames: 1024);
_mixer.Start();
}Recommended Shutdown Pattern
public async Task DisposeAsync()
{
_mixer?.Stop();
_mixer?.Dispose();
await OwnaudioNet.ShutdownAsync();
}Zero-Allocation Rules
Never allocate memory inside the audio render loop. Use Span<T>, pre-allocated buffers, and pool-returned arrays. Always call ReturnInputBuffer() after Receive() — omitting this call leaks native memory.
var buffer = OwnaudioNet.Receive();
if (buffer != null)
{
try
{
// Process buffer.Data ...
}
finally
{
OwnaudioNet.ReturnInputBuffer(buffer); // MUST always be called
}
}VU Metering Pattern
// Poll at ~10 Hz — do not poll faster than the audio buffer interval
_vuTimer = new Timer(_ =>
{
float leftDb = 20f * MathF.Log10(Math.Max(_mixer.LeftPeak, 1e-6f));
float rightDb = 20f * MathF.Log10(Math.Max(_mixer.RightPeak, 1e-6f));
LeftDb = Math.Max(leftDb, -60f); // clamp to -60 dBFS floor
RightDb = Math.Max(rightDb, -60f);
// Per-track stereo levels
var (l, r) = source.OutputLevels;
}, null, 0, 100);Position Tracking Pattern
private double _lastEnginePos;
private double _lastEnginePosAt;
private readonly Stopwatch _watch = Stopwatch.StartNew();
// Update at ~30 Hz (33 ms timer)
private void OnPositionTimer()
{
double enginePos = _mixer.MasterClock.CurrentTimestamp;
double nowSec = _watch.Elapsed.TotalSeconds;
if (enginePos != _lastEnginePos)
{
_lastEnginePos = enginePos;
_lastEnginePosAt = nowSec;
}
// Interpolate between engine callbacks for smooth UI
double displayPos = _lastEnginePos + (nowSec - _lastEnginePosAt);
CurrentPositionSeconds = displayPos;
}
// Or use ISynchronizable for sample-accurate per-source position
if (source is ISynchronizable sync)
{
int sampleRate = OwnaudioNet.Engine!.Config.SampleRate;
double posSeconds = sync.SamplePosition / (double)sampleRate;
}AudioConstants
AudioConstants.MaxAudioSources // 25 — maximum simultaneous sources per mixer
AudioConstants.MinTempo // 0.8 — minimum tempo multiplier (80% speed)
AudioConstants.MaxTempo // 1.2 — maximum tempo multiplier (120% speed)Volume Range
All source Volume properties accept values from 0.0 (silence) to 20.0 (maximum amplification). The default is 1.0 (unity gain). Values above 1.0 amplify the signal and may clip without a limiter.
Pitch Shift Range
The PitchShift property on audio sources accepts values from -12 to +12 semitones. Use in combination with Tempo to time-stretch without changing pitch.
Changelog
v3.1.7
| Area | Type | Change |
|---|---|---|
AudioDeviceInfo |
New property | DefaultSampleRate (double) — native sample rate as reported by the platform at enumeration time. Available on PortAudio and MiniAudio backends; 0 on other backends. Useful for opening a device without resampling. |
AudioConfig.OutputDeviceId / InputDeviceId |
Bug fix | The configured device ID is now honoured at Initialize() time on MiniAudio and PortAudio backends. Previously the engine always opened the system default device first and switched only after a post-init SetOutputDevice call. If the requested device fails to open, the engine falls back to the system default and clears the stored ID — no exception is thrown. |
AudioMixer.Stop() / Dispose() |
Bug fix | Stop() now joins the mix thread (2 s timeout) before stopping sources or returning. Previously the thread could still be executing a ReadSamples callback while Dispose() was releasing source memory, causing a potential AccessViolationException. The fix also makes repeated Stop() → Start() cycles safe: Start() allocates a fresh Thread whenever the previous one has exited. |
FFmpeg Integration (Optional)
OwnAudioSharp automatically detects FFmpeg 8+ dynamic libraries on startup and uses them as the primary decoder. This is not part of the public API — no code changes are required. If FFmpeg is not installed, the built-in MP3/WAV/FLAC decoders are used as a seamless fallback.
Decoder priority: FFmpeg → MiniAudio (native) → built-in managed decoder (MP3 / WAV / FLAC).
using Ownaudio.Core;
// Optional: set a custom directory before first decoder use (default = empty = system paths)
FFmpegConfig.CustomLibraryPath = @"C:\ffmpeg\bin"; // Windows example
// FFmpegConfig.CustomLibraryPath = "/opt/homebrew/lib"; // macOS example
// Check availability — FFmpegConfig.IsAvailable is set automatically at load time
if (FFmpegConfig.IsAvailable)
Console.WriteLine("FFmpeg active — AAC, OGG, Opus, WMA, AIFF and more are supported.");
else
Console.WriteLine("FFmpeg not found — using built-in decoders (MP3 / WAV / FLAC).");
// No other changes needed; AudioDecoderFactory picks the best decoder automatically
using var decoder = AudioDecoderFactory.Create("audio.aac", targetSampleRate: 48000, targetChannels: 2);| Platform | Automatic search paths |
|---|---|
| Windows | App directory, PATH — e.g. avcodec-62.dll, avutil-60.dll |
| macOS | /opt/homebrew/lib, /usr/local/lib — install via brew install ffmpeg |
| Linux | /usr/lib/<arch>-linux-gnu, /usr/lib, /usr/local/lib — install via apt install libavcodec-dev |