Core Concepts
Five pieces. Learn what each one is for and the rest of the API is just detail.
OwnAudioSharp has a small vocabulary: an engine that owns the sound card, a mixer that combines audio, sources that produce it, effects that shape it, and a clock that keeps everything on the same timeline. Everything else in these docs hangs off those five.
How audio flows
Sound moves in one direction, from your sources down to the speakers. Every feature in the library plugs in somewhere on this path.
1 Β· The engine
OwnaudioNet is a static class and there is exactly one engine per application. It opens the audio device, starts the real-time thread, and stays out of your way after that.
Three calls make up its whole life: initialize (pick the format and device), start (audio begins to flow), shut down (release the device). Nothing else works before initialization.
await OwnaudioNet.InitializeAsync(); // 48 kHz, stereo, 512 frames
OwnaudioNet.Start();
// ... your app runs ...
await OwnaudioNet.ShutdownAsync();Use the Async variants in anything with a window. Opening a device can block for seconds β on Linux, PulseAudio alone can take five. More on the lifecycle β
2 Β· The mixer
AudioMixer is the bus. You hand it sources, it sums them into a single signal and pushes that to the engine. One mixer is enough for almost every app.
It also owns the things that belong to "the whole mix": master volume and pan, peak levels for your VU meters, the master effect chain, WAV recording of the output, and the master clock.
var mixer = new AudioMixer(OwnaudioNet.Engine!.UnderlyingEngine);
mixer.Start();
mixer.AddSource(track); // hot-swappable, safe while playing
mixer.MasterVolume = 0.85f; // 0.0 β 1.0Adding and removing sources is lock-free β you can do it mid-playback without a click. The mixer in full β
3 Β· Sources
A source is anything that produces audio. They all implement IAudioSource, so once you know one you know all of them: Play(), Pause(), Stop(), Seek(), plus Volume, Pan, Loop, Position and Duration.
| Type | Reach for it when⦠|
|---|---|
FileSource | You have a file. MP3, FLAC, WAV, AAC, ALAC, MP4, OGG, AIFF β decoded natively, streamed from disk, seekable, tempo- and pitch-shiftable. |
SampleSource | The audio is already a float[] in memory. Short clips, sound effects, one-shots. |
StreamingSource | You want to generate audio β a synth, a metronome, a test tone, a network stream. You write a callback, it asks you for samples. |
InputSource | The audio is coming in live from a microphone or line input. |
SourceWithEffects | Not a source of its own β a wrapper that gives any of the above its own effect chain. |
// Match the engine's format so nothing has to be resampled later
int sr = OwnaudioNet.Engine!.Config.SampleRate;
int ch = OwnaudioNet.Engine!.Config.Channels;
var vocals = new FileSource("vocals.wav", targetSampleRate: sr, targetChannels: ch);
vocals.Volume = 0.9f;
vocals.Pan = -0.2f; // a touch to the left
mixer.AddSource(vocals);
vocals.Play();A source keeps playing on its own once started β the mixer only decides whether it can be heard. All source types β
4 Β· Effects
An effect is anything implementing IEffectProcessor: the 15 built-ins, SmartMaster, or a VST3 plugin. They all go in the same two places.
| Where | How | Affects |
|---|---|---|
| One track | new SourceWithEffects(source), then AddEffect() | Only that source |
| The whole mix | mixer.AddMasterEffect() | Everything, after summing |
Order matters β effects run in the order you add them, exactly like pedals on a board.
// Compressor first, then reverb β on the vocal only
var vocalChain = new SourceWithEffects(vocals);
vocalChain.AddEffect(new CompressorEffect { Ratio = 4f, AttackTimeMs = 10f });
vocalChain.AddEffect(new ReverbEffect { RoomSize = 0.6f, Mix = 0.25f });
mixer.AddSource(vocalChain); // add the wrapper, not the raw source
// Catch the peaks on the way out
mixer.AddMasterEffect(new LimiterEffect());5 Β· The clock
Start two files at "the same time" and they will drift apart β different decoders, different buffer boundaries, different rounding. Over three minutes that becomes audible flam.
The MasterClock fixes this. Every mixer owns one, and it is the single source of truth for "where are we on the timeline". Attach a source to it and the engine continuously compares that source's position to the clock and nudges it back into line.
// Attach BEFORE Play()
vocals.AttachToClock(mixer.MasterClock);
backing.AttachToClock(mixer.MasterClock);
backing.StartOffset = 2.5; // this one enters 2.5 s into the song
mixer.Seek(0); // moves the clock and every attached source
vocals.Play();
backing.Play();
double now = mixer.MasterClock.CurrentTimestamp; // seconds β what your playhead readsCorrection happens in three zones: under 5 ms nothing is done, 5β25 ms is smoothed out with a tiny tempo nudge, above that the source is snapped back hard. Drift correction β
What runs where
Two rules cover nearly everything you need to know about threading.
Your UI thread β Play / Seek / Volume / AddSource (fast, non-blocking)
Engine thread β decode Β· mix Β· effects Β· device I/O (native Rust, never yours)- Never block the UI thread.
InitializeAsync(),StopAsync()andShutdownAsync()exist for exactly this reason; heavy loads belong inTask.Run. - Never allocate in a render callback. If you write a
StreamingSourcegenerator, nonew, no LINQ, no string building β it runs against a deadline.
Everything else β setting Volume, adding a source, seeking β is safe from any thread and designed to be called from a slider's event handler.
Glossary
The words that show up in every audio API, in plain terms.
| Term | What it means |
|---|---|
| Sample | One number describing the signal at one instant. In this library, a float between β1.0 and +1.0. |
| Frame | One sample for every channel. In stereo, 1 frame = 2 samples. Buffer sizes are counted in frames. |
| Sample rate | Frames per second. 48000 is the default and the safest choice; 44100 is CD audio. |
| Channels | 1 = mono, 2 = stereo. More than 2 means a multi-output interface. |
| Interleaved | How samples are laid out in memory: L R L R L Rβ¦, not all-left-then-all-right. Every buffer you touch is interleaved. |
| Buffer size | How many frames the engine hands the sound card at a time. Smaller = lower latency but more chances to fall behind. |
| Latency | The delay between a sample being produced and being heard. bufferSize Γ· sampleRate β 512 frames at 48 kHz is ~10.6 ms. |
| Underrun / dropout | The engine needed audio and it wasn't ready. You hear a click or a gap. Almost always means the buffer is too small for the work being done. |
| dBFS | Level in decibels where 0 is the loudest a digital signal can go. Everything useful is negative. 20 Γ log10(peak). |
| Host / backend | The OS audio system the engine talks to: WASAPI or ASIO on Windows, CoreAudio on macOS, ALSA on Linux. Leave it on auto unless you have a reason. |
| Device | A specific input or output β built-in speakers, an audio interface, a virtual cable. Identified by DeviceId. |
| Real-time thread | The thread that must deliver audio on schedule, every time. In 4.0 it lives entirely in Rust, which is why a garbage collection can no longer interrupt your sound. |