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.

Layer stack
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]
LayerLanguageResponsibility
OwnaudioNet + APIC#The developer-facing surface โ€” same types and methods as previous versions.
AudioEngineWrapperC#Lock-free, non-blocking managed bridge to the native engine.
ownaudio-ffiRustStable C ABI: opaque handles, error mapping, panic-safe entry points, callback trampolines.
ownaudio-coreRustDevice 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.

EventLives onArgsFires when
StateChangedevery sourceAudioStateChangedEventArgsPlayback state changed โ€” including reaching EndOfStream.
Errorevery sourceAudioErrorEventArgsSomething went wrong inside that source.
BufferUnderrunOwnaudioNet.EngineBufferUnderrunEventArgsAudio was needed and wasn't ready.
PositionChangedevery sourceEventArgsPosition moved noticeably. Throttled to ~50 ms.
PlaybackEndedmixerEventArgsEvery source has reached its end.
SourceErrormixerAudioErrorEventArgsOne of the sources on the bus failed.
StreamFaultedmixerAudioStreamFaultEventArgsThe native output stream died โ€” usually an unplugged device.
OutputDeviceChanged
InputDeviceChanged
OwnaudioNet.EngineAudioDeviceChangedEventArgsThe engine moved to a different device.
DeviceStateChangedOwnaudioNet.EngineAudioDeviceStateChangedEventArgsA device was added, removed or changed state.
NetworkSyncConnectionChangedOwnaudioNet (static)ConnectionStateChangedEventArgsNetwork sync connected or dropped.

AudioStateChangedEventArgs

PropertyTypeDescription
OldStateAudioStateState before the transition.
NewStateAudioStateState after it.
TimestampDateTimeWhen it happened.
C#
source.StateChanged += (_, e) =>
{
    if (e.NewState == AudioState.EndOfStream)
        Console.WriteLine("Track finished.");
};

BufferUnderrunEventArgs

PropertyTypeDescription
MissedFramesintHow many frames came out silent.
PositionlongFrame position where it happened.
TimestampDateTimeWhen it happened.

AudioErrorEventArgs

PropertyTypeDescription
MessagestringHuman-readable error description.
ExceptionException?Original exception, if there was one.
TimestampDateTimeWhen 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.

PropertyTypeDescription
KindAudioStreamFaultKindDeviceNotAvailable (unplug, sleep/wake, rate change) or BackendSpecific.
ErrorCountulongFaults recorded so far.
EventTimestampDateTimeWhen 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.

PropertyTypeDescription
TrackIdGuidID of the source that dropped out.
TrackNamestringSource name or type string.
MasterTimestampdoubleClock position in seconds when dropout occurred.
MasterSamplePositionlongClock position in samples.
MissedFramesintNumber of frames that were silent due to the dropout.
ReasonstringHuman-readable cause description.
EventTimestampDateTimeWall-clock time of the event.

AudioDeviceInfo

Returned by OwnaudioNet.GetOutputDevices() and GetInputDevices().

PropertyTypeDescription
DeviceIdstringUnique device identifier โ€” pass to AudioConfig.OutputDeviceId.
NamestringHuman-readable device name.
EngineNamestringBackend: Wasapi, CoreAudio, PulseAudio, โ€ฆ
IsInput / IsOutputboolDevice direction.
IsDefaultboolSystem default device for its direction.
MaxInputChannels / MaxOutputChannelsintHardware channel limits.

Enumerations

AudioState

C#
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 source

ClockMode

C#
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 clock

EngineStatus

C#
EngineStatus.Idle               // Initialized, not started
EngineStatus.Running            // Processing audio
EngineStatus.DeviceDisconnected // Device unplugged (monitoring for reconnect)
EngineStatus.Error              // Fatal engine error

EngineHostType

C#
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 Audio

Threading 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

OperationSafe ThreadNotes
OwnaudioNet.Initialize()Background / Task.RunBlocks 50msโ€“5s (Linux PulseAudio). Use InitializeAsync().
OwnaudioNet.Stop() / Shutdown()Background / Task.RunWaits for audio thread join (up to 2s). Use async variants.
mixer.Start(), mixer.Stop()AnyThread-safe.
mixer.AddSource() / RemoveSource()AnyLock-free hot-swap.
source.Volume = โ€ฆ, source.Tempo = โ€ฆAnyAtomic property writes.
OwnaudioNet.Send()AnyLock-free ring buffer write, never blocks.
tap.Read() / analyzer.Update()One thread at a timeSingle 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

C#
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

C#
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.

C# โ€” Correct buffer return
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

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

C# โ€” High-precision display with interpolation
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

C#
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.

C#
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