Troubleshooting

What happened, why it happened, and the line of code that fixes it.

๐Ÿ”ฆ

Before anything else, turn the log on. It is silent by default and it will usually tell you the answer outright.

C#
using Logger;

await OwnaudioNet.InitializeAsync(config, logLevel: Log.Level.Info);

// or flip it at any time while running
Log.LoggerLevel = Log.Level.Info;

Nothing plays

Why: almost always one of four things โ€” the engine never started, the mixer never started, the source was never added, or something is at zero volume.

C# โ€” walk down the chain
Console.WriteLine(OwnaudioNet.IsInitialized);   // false โ†’ InitializeAsync never completed
Console.WriteLine(OwnaudioNet.IsRunning);       // false โ†’ you forgot Start()
Console.WriteLine(mixer.IsRunning);             // false โ†’ mixer.Start()
Console.WriteLine(mixer.SourceCount);           // 0     โ†’ AddSource()
Console.WriteLine(mixer.MasterVolume);          // 0     โ†’ there it is
Console.WriteLine(source.State);                // Stopped โ†’ Play()
Console.WriteLine(mixer.LeftPeak);              // 0 while playing โ†’ nothing reaching the bus

Two more that catch people out:

Crackling, clicks or stuttering

Why: the engine asked for the next block of audio and it wasn't ready. The mix thread is not keeping up with the buffer size you gave it.

Fix it in this order โ€” each step costs a little more latency than the last.

StepDo thisCosts
1Raise bufferMultiplier to 16 or 32 at initializationNothing audible โ€” it only deepens the internal safety buffer
2Raise AudioConfig.BufferSize from 512 to 1024~10 ms more latency
3Raise the mixer's bufferSizeInFrames to 1024 for 20+ tracksSlightly coarser position updates
4Look at what your own code is doing on the audio pathโ€”
C# โ€” the usual cure
// 8+ sources, or 2+ VST plugins on the master bus
await OwnaudioNet.InitializeAsync(config, bufferMultiplier: 32);
OwnaudioNet.Start();

var mixer = new AudioMixer(OwnaudioNet.Engine!.UnderlyingEngine, bufferSizeInFrames: 1024);
mixer.Start();

To find out whether it really is an underrun, ask:

C#
mixer.BufferUnderrun += (_, e) =>
    Console.WriteLine($"Underrun: {e.MissedFrames} frames at {e.Position}");

mixer.TrackDropout += (_, e) =>
    Console.WriteLine($"{e.TrackName} fell behind: {e.Reason} ({e.MissedFrames} frames)");
โš ๏ธ

If you wrote a StreamingSource generator, check it for allocations first. A new, a LINQ query or an interpolated string inside the render callback is the most common cause of clicks that survive every buffer increase.

The app freezes at startup

Why: a blocking engine call on the UI thread. Opening a device is not instant โ€” on Linux, PulseAudio enumeration alone can take five seconds.

C#
// โŒ freezes the window
OwnaudioNet.Initialize(config);
OwnaudioNet.Shutdown();

// โœ…
await OwnaudioNet.InitializeAsync(config);
await OwnaudioNet.ShutdownAsync();

The same applies to Stop() / StopAsync(), which waits for the audio thread to join. Everything else โ€” Play, Seek, Volume, AddSource โ€” is safe to call from anywhere.

The audio interface was unplugged

Why: the device vanished mid-stream. By default the engine hops to the system default and hops back when the device returns; if you turned that off it parks in DeviceDisconnected and waits.

C#
config.FallbackToDefaultOnDisconnect = true;   // the default โ€” keep playing on built-in output

// Be told when the stream itself dies, instead of just going quiet
mixer.StreamFaulted += (_, e) =>
{
    if (e.Kind == AudioStreamFaultKind.DeviceNotAvailable)
        ShowToast("Audio device disconnected");
};

// Be told when the engine moves to a different device
OwnaudioNet.Engine!.OutputDeviceChanged += (_, e) =>
    Console.WriteLine($"Now playing on {e.NewDeviceInfo.Name}");

Tracks drift apart

Why: the sources were never attached to the master clock, so each one is free-running on its own decoder. They start together and slowly separate.

C#
// Attach BEFORE Play() โ€” attaching afterwards does not retro-fix the drift
foreach (var src in sources)
{
    src.AttachToClock(mixer.MasterClock);
    mixer.AddSourcePrepared(src);
}
mixer.StartPreparedSources(0.0);

If they are attached and still drift, the engine is telling you it's under pressure:

C#
var diag = fileSource.SyncDiagnostics;

if (diag.IsRelaxed)
    Console.WriteLine($"Tolerances widened {diag.AdaptiveScale:F1}ร— โ€” the mix thread is struggling");

A scale above 1.0 means the sync engine had to relax its thresholds to keep up. Treat it as the same symptom as crackling and give the mix thread more headroom.

A file won't load

Why: either the format isn't in the built-in set, or the file is damaged, or the path is wrong. A DecoderException means the decoder saw the file and gave up on it.

Handled natively, on every platform, with no extra install: MP3, FLAC, WAV (PCM/ADPCM), AAC, ALAC, MP4/M4A, OGG/Vorbis, AIFF.

C#
try
{
    var source = new FileSource(path, targetSampleRate: sr, targetChannels: ch);
    if (source.Duration <= 0)
        Console.WriteLine("Decoded, but empty โ€” the file is probably truncated.");
}
catch (DecoderException ex)
{
    Console.WriteLine($"Cannot decode {path}: {ex.Message}");
}

Anything outside that list โ€” Opus, WMA, AC3 โ€” will not open, and there is no fallback decoder to install. Convert the file ahead of time instead. See Decoding.

An effect does nothing

Why: three candidates, in order of likelihood.

C# โ€” the wrapper swap
var chain = new SourceWithEffects(source);
chain.AddEffect(new ReverbEffect { Mix = 0.3f });

mixer.RemoveSource(source.Id);   // the raw one comes out
mixer.AddSource(chain);          // the wrapper goes in

If you'd rather see it than guess: an effect tap hands you the signal on both sides of the chain while it plays. Identical spectra means the chain really is doing nothing to the audio, which rules the first two causes in or out in one look.

C#
using var analyzer = new EffectSpectrumAnalyzer(mixer.CreateEffectTap(source.Id));

if (analyzer.Update())
{
    // dry == wet โ‡’ nothing in the chain is touching the audio
    Compare(analyzer.PreMagnitudesDb, analyzer.PostMagnitudesDb);
}

A VST3 plugin won't load

Why: it's an instrument, its architecture doesn't match, or its audio setup failed for the current sample rate.

C#
VST3PluginHost host;
try { host = await VST3PluginHost.CreateAsync(path); }
catch (Exception ex) { ShowError($"Could not load the plugin: {ex.Message}"); return; }

if (!host.IsEffect)
{
    host.Dispose();
    ShowError(host.IsInstrument
        ? "That's an instrument โ€” only effect plugins can go in a slot."
        : "Unknown plugin type.");
    return;
}

if (!await host.InitializeAudioAsync(OwnaudioNet.Engine!.Config.SampleRate, maxBlockSize: 4096))
{
    host.Dispose();
    ShowError("The plugin could not run at this sample rate.");
    return;
}
โš ๏ธ

A 64-bit process cannot load a 32-bit plugin, and vice versa. On Apple Silicon an Intel-only plugin will not load either.

Memory keeps growing

Why: something that owns native memory is never being released. Two usual suspects.

C# โ€” always return input buffers
float[]? buffer = OwnaudioNet.Receive(out int sampleCount);
if (buffer != null)
{
    try     { /* use buffer[0..sampleCount-1] */ }
    finally { OwnaudioNet.ReturnInputBuffer(buffer); }   // never skip this
}

And dispose sources you replace. Loading a new file into a track without disposing the old FileSource leaks its decoder and ring buffer every time.

C#
mixer.RemoveSource(oldSource.Id);
oldSource.Dispose();

The recording is empty or out of time

Why: recording captures what leaves the mixer. If nothing was on the bus, the file is silence. If a live take lands late, that's the hardware capture delay.

C#
// Trim the capture delay off the front so the take lines up with the click
mixer.StartRecording("take.wav", compensateInputLatency: true);
// ...
mixer.StopRecording();

int trimmed = mixer.LastRecordingLatencyOffsetFrames;   // what was dropped
โš ๏ธ

StopRecording() is what finalizes and closes the WAV header. Kill the process without it and you get an unplayable file.

Exceptions you may see

TypeWhat it meansWhat to do
DeviceExceptionThe requested device could not be opened โ€” wrong ID, in exclusive use, or gone.Fall back to OutputDeviceId = null and re-enumerate.
DecoderExceptionThe file could not be decoded.Check the format against the supported list; convert the file if it falls outside.
StreamExceptionThe native audio stream failed to open or died.Try a larger buffer or a different host type.
HostApiNotAvailableExceptionThe HostType you asked for doesn't exist on this machine.Use EngineHostType.None and let the engine choose.
AsioDriverNotFoundExceptionNo ASIO driver installed.Install the interface's driver, or use WASAPI.
AbiVersionMismatchExceptionThe managed package and the native library are different versions.Clear bin/ and obj/, restore, and make sure only one OwnAudioSharp version is referenced.
AudioEngineExceptionA general engine-level failure.Turn on Log.Level.Error โ€” the message names the stage that failed.

FAQ

Which package do I need?

OwnAudioSharp for desktop with everything. OwnAudioSharp.Mobile for Android and iOS. OwnAudioSharp.Basic when you only want audio in and out and care about size. MIDI is its own package, and vocal separation and MT3 are optional add-ons.

Do I need FFmpeg?

No โ€” and you cannot use it either. The Rust decoder handles the common formats out of the box and is the only decoder there is; the FFmpeg fallback of earlier versions was removed in 4.0.

How many sources can one mixer hold?

25 โ€” AudioConstants.MaxAudioSources. Past about eight, give the mix thread more headroom with bufferMultiplier.

What latency can I expect?

BufferSize รท SampleRate. 512 frames at 48 kHz is ~10.6 ms; the low-latency preset's 128 frames is ~2.7 ms. Add the driver's own hardware latency, readable from OwnaudioNet.InputLatencyFrames and OutputLatencyFrames once audio is flowing.

Can I run two mixers at once?

Yes, but one is almost always the right answer. If you do, register the one network sync should follow with OwnaudioNet.SetPrimaryAudioMixer().

Does it work with AOT and trimming?

Yes โ€” the managed side is AOT-compatible and trimmable, and the engine is native anyway.

Can I test without a sound card?

Yes. OwnaudioNet.Initialize(config, useMockEngine: true) gives you the whole API against a mock engine, which is how the library's own ~290 tests run in CI.

Why is my tempo change ignored?

Tempo and PitchShift are wired on FileSource. The other source types accept the value for compatibility but don't apply it. Tempo is also clamped to 0.8โ€“1.2.

Still stuck

Turn on Log.Level.Info, reproduce it, and open an issue with the log, your AudioConfig, and the platform. That is usually enough to spot it from the outside.