Reference
The lookup page โ architecture, events, enumerations, constants and the threading rules.
Architecture (4.0)
OwnAudioSharp 4.0 is a thin C# surface over a purpose-built native Rust engine. The public API is unchanged from 3.x โ the change is entirely underneath it. From the first sample to the last byte, decoding, mixing, effects, resampling, playback and capture all run in native code, so not a single sample is processed in managed code. The result is an audio path that is completely unaffected by the .NET runtime.
Application
โโ OwnaudioNet / AudioMixer / sources / effects [your unchanged C# code]
โโ AudioEngineWrapper [lock-free, non-blocking bridge]
โโ Native Rust engine [ownaudio-ffi + ownaudio-core]
โโ Audio hardware [WASAPI / CoreAudio / ALSA ยท ASIO optional]| Layer | Language | Responsibility |
|---|---|---|
| OwnaudioNet + API | C# | The developer-facing surface โ same types and methods as previous versions. |
| AudioEngineWrapper | C# | Lock-free, non-blocking managed bridge to the native engine. |
| ownaudio-ffi | Rust | Stable C ABI: opaque handles, error mapping, panic-safe entry points, callback trampolines. |
| ownaudio-core | Rust | Device I/O, mixing, 17 DSP effects (15 of them surfaced in the managed API), sinc resampler, lock-free ring buffers โ zero-allocation real-time path. |
Why it matters: no matter what the surrounding C# code is doing, the audio never stutters โ because the real-time path never runs in managed code. This is the payoff of the 4.0 redesign, delivered without breaking your existing code.
Events
Nothing here is a static event on OwnaudioNet. Events live on the object they concern โ the source, the mixer, or the engine wrapper. This table tells you where to subscribe.
| Event | Lives on | Args | Fires when |
|---|---|---|---|
StateChanged | every source | AudioStateChangedEventArgs | Playback state changed โ including reaching EndOfStream. |
Error | every source | AudioErrorEventArgs | Something went wrong inside that source. |
BufferUnderrun | OwnaudioNet.Engine | BufferUnderrunEventArgs | Audio was needed and wasn't ready. |
PositionChanged | every source | EventArgs | Position moved noticeably. Throttled to ~50 ms. |
PlaybackEnded | mixer | EventArgs | Every source has reached its end. |
SourceError | mixer | AudioErrorEventArgs | One of the sources on the bus failed. |
StreamFaulted | mixer | AudioStreamFaultEventArgs | The native output stream died โ usually an unplugged device. |
OutputDeviceChangedInputDeviceChanged | OwnaudioNet.Engine | AudioDeviceChangedEventArgs | The engine moved to a different device. |
DeviceStateChanged | OwnaudioNet.Engine | AudioDeviceStateChangedEventArgs | A device was added, removed or changed state. |
NetworkSyncConnectionChanged | OwnaudioNet (static) | ConnectionStateChangedEventArgs | Network sync connected or dropped. |
AudioStateChangedEventArgs
| Property | Type | Description |
|---|---|---|
OldState | AudioState | State before the transition. |
NewState | AudioState | State after it. |
Timestamp | DateTime | When it happened. |
source.StateChanged += (_, e) =>
{
if (e.NewState == AudioState.EndOfStream)
Console.WriteLine("Track finished.");
};BufferUnderrunEventArgs
| Property | Type | Description |
|---|---|---|
MissedFrames | int | How many frames came out silent. |
Position | long | Frame position where it happened. |
Timestamp | DateTime | When it happened. |
AudioErrorEventArgs
| Property | Type | Description |
|---|---|---|
Message | string | Human-readable error description. |
Exception | Exception? | Original exception, if there was one. |
Timestamp | DateTime | When the error occurred. |
AudioStreamFaultEventArgs
The backend records a fault on its own callback; the mixer's control tick polls it and raises this. Without it a dead stream simply goes silent.
| Property | Type | Description |
|---|---|---|
Kind | AudioStreamFaultKind | DeviceNotAvailable (unplug, sleep/wake, rate change) or BackendSpecific. |
ErrorCount | ulong | Faults recorded so far. |
EventTimestamp | DateTime | When it was raised. |
TrackDropoutEventArgs
The type is still exported, but nothing raises it any more: dropout detection lived in the managed mix thread that the native chain replaced. Watch StreamFaulted instead.
| 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. |
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.ASIO // ASIO โ Windows, ultra-low latency
EngineHostType.COREAUDIO // macOS Core Audio
EngineHostType.ALSA // Linux ALSA
EngineHostType.WDMKS // Windows kernel streaming
EngineHostType.JACK // JACK server โ Linux and macOS
EngineHostType.WASAPI // Windows Audio Session API
EngineHostType.AAUDIO // Android 8.0+
EngineHostType.OPENSL // Older Android and embedded
EngineHostType.WEBAUDIO // Browsers, via Web AudioThreading rules
Two rules cover it. Never block the UI thread โ opening and closing a device does block. Never allocate in a render callback โ it runs against a deadline. Everything else is fair game from anywhere.
What is safe where
| 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. |
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. |
tap.Read() / analyzer.Update() | One thread at a time | Single consumer on a lock-free ring. Poll from a UI timer, never spin. |
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.
float[]? buffer = OwnaudioNet.Receive(out int sampleCount);
if (buffer != null)
{
try
{
// process buffer[0..sampleCount-1] ...
}
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.
Pan Range
Source Pan and the mixer's MasterPan accept values from -1.0 (hard left) through 0.0 (center, the default) to +1.0 (hard right). An equal-power law keeps the perceived loudness constant across the sweep, and a centered value leaves the signal unchanged.
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.
Decoding
As of 4.0 decoding is handled entirely by the native Rust engine, which reads MP3, FLAC, WAV (PCM/ADPCM), AAC, ALAC, MP4/M4A, OGG/Vorbis and AIFF out of the box with no external dependencies. There is exactly one decoder and no fallback behind it: the FFmpeg path that earlier versions could use was removed together with the managed engines, so a format outside the list above will not open.
The AudioFormat.FFmpeg enum member survives from that era and no longer means anything about how a file is decoded โ it is only a hint for the temporary file extension when you decode from a Stream. There is no FFmpegConfig type; documentation describing one was describing a version that no longer exists.
using Ownaudio.Core;
using Ownaudio.Decoders;
// Format is sniffed from the content; 0 means "leave it as the source has it"
using var decoder = AudioDecoderFactory.Create("audio.aac", targetSampleRate: 48000, targetChannels: 2);
// Anything the Symphonia backend cannot open throws AudioException