Step 3 of 8

Audio Sources

Everything that makes a sound. Namespace: OwnaudioNET.Sources

There are four kinds of source and one wrapper. They all implement IAudioSource, so learning one teaches you the rest.

TypeReach for it when…
FileSourceYou have a file. Streamed from disk, seekable, tempo- and pitch-shiftable.
SampleSourceThe audio is already a float[] in memory β€” short clips, one-shots.
StreamingSourceYou want to generate audio β€” a synth, a metronome, a network stream.
InputSourceThe audio is arriving live from a mic or line input.
SourceWithEffectsNot a source β€” a wrapper that gives any of the above its own effect chain.

What they all share

PropertyTypeDefaultDescription
IdGuidautoUnique identifier for this source.
StateAudioStateStoppedCurrent playback state.
Volumefloat1.0Track volume (0.0 – 20.0). Values above 1.0 amplify.
Panfloat0.0Stereo pan (-1.0 left … 0.0 center … +1.0 right), equal-power.
LoopboolfalseLoop when reaching end of stream.
Positiondoubleβ€”Current playback position in seconds (read-only).
Durationdoubleβ€”Total duration in seconds (read-only).
IsEndOfStreamboolβ€”Whether the source has reached its end.
Tempofloat1.0Playback speed multiplier, clamped to 0.8–1.2. Goes straight to the native track β€” no buffer clear, safe to set from a slider. FileSource only Wired only on FileSource; SampleSource/StreamingSource/InputSource store the value but do not apply it (backward-compatibility surface).
PitchShiftfloat0.0Pitch shift in semitones, clamped to -12 … +12. Goes straight to the native track β€” no buffer clear, safe to set from a slider. FileSource only Wired only on FileSource; other sources store the value but do not apply it (backward-compatibility surface).
OutputLevels(float left, float right)β€”Real-time output levels for VU metering (0.0–1.0).
OutputChannelMappingint[]?nullHardware channel routing. See Channel Routing.
C# β€” Common methods
source.Play();
source.Pause();
source.Stop();
source.Seek(double positionInSeconds);  // returns bool
source.RouteToChannels(params int[] channels); // fluent helper
source.Dispose();

Common Events

EventArgsDescription
StateChangedAudioStateChangedEventArgsPlayback state changed.
ErrorAudioErrorEventArgsAn error occurred during playback.
PositionChangedEventArgsPosition changed significantly (throttled >50ms).

FileSource

Plays audio from a file with background decoding, circular buffer, real-time pitch/tempo, and master clock synchronization. Decoding runs entirely in the native Rust engine. Supported formats out of the box: MP3, FLAC, WAV (PCM/ADPCM), AAC, ALAC, MP4/M4A, OGG/Vorbis and AIFF β€” no external codecs required. Exotic formats can additionally be handled by FFmpeg 8+ as an optional legacy fallback when it is installed on the system.

Constructor

C#
var source = new FileSource(
    filePath: "track.mp3",
    bufferSizeInFrames: 8192,   // internal circular buffer (default: 8192)
    targetSampleRate: 48000,    // auto-resampling; 0 = use file's rate
    targetChannels: 2           // auto-channel conversion; 0 = use file's channels
);

FileSource-Specific Properties

PropertyTypeDefaultDescription
StartOffsetdouble0.0Timeline start position in seconds for master clock sync.
IsAttachedToClockboolfalseWhether attached to a master clock (read-only).
SyncTolerancedouble0.005Green zone threshold: drift below this needs no correction (5ms).
SoftSyncTolerancedouble0.025Yellow zone threshold: triggers gradual tempo correction (25ms).
SoftSyncMaxTempoAdjustmentdouble0.02Maximum tempo drift during soft sync correction (2%).

Tempo & Pitch Control

ℹ️

Setting .Tempo / .PitchShift and calling SetTempoSmooth() / SetPitchSmooth() do the same thing: the value is handed to the native track, with no buffer clear and no silence gap. The smooth variants only read better at slider call sites. Under a master clock use SetTempoSynced() once the slider has settled β€” it reseeks to the current position so leftover old-tempo audio cannot leave a permanent drift.

C#
// Direct set β€” mirrored onto the native track right away
source.Tempo      = 1.1f;   // 10% faster  (range: 0.8 – 1.2)
source.PitchShift = 2.0f;   // 2 semitones up  (range: -12.0 – +12.0)

// Same thing, named for slider call sites
source.SetTempoSmooth(1.1f);
source.SetPitchSmooth(2.0f);

// Clock-synced tracks: call this once the slider settled, it reseeks away the drift
source.SetTempoSynced(1.1f);

Complete Example

C#
int sr = OwnaudioNet.Engine!.Config.SampleRate;
int ch = OwnaudioNet.Engine!.Config.Channels;

var source = new FileSource("backing.mp3",
    bufferSizeInFrames: 4096,
    targetSampleRate: sr,
    targetChannels: ch);

source.Volume      = 0.8f;
source.Pan         = -0.3f;                // slightly left
source.Tempo       = GlobalTempo / 100f;  // e.g. 100 β†’ 1.0f
source.PitchShift  = 0;
source.StartOffset = 0.0;

source.StateChanged += (_, e) =>
{
    if (e.NewState == AudioState.EndOfStream)
        Console.WriteLine("Track finished.");
};

mixer.AddSource(source);

SampleSource

Plays audio from a pre-loaded float[] array. Ideal for sound effects, short clips, or synthesized audio.

C#
// Static sample
float[] samples = LoadSamplesFromFile("click.wav");
var click = new SampleSource(samples, OwnaudioNet.CreateDefaultConfig());
mixer.AddSource(click);
click.Play();

// Dynamic β€” submit samples in real time
var synth = new SampleSource(bufferSizeInFrames: 2048, OwnaudioNet.CreateDefaultConfig());
synth.AllowDynamicUpdate = true;

// Submit new samples as they are generated
synth.SubmitSamples(newSamples); // ReadOnlySpan<float>
synth.Clear();                   // flush buffer

StreamingSource

An endless source whose audio is produced by your own callback. Where SampleSource serves a fixed buffer, StreamingSource generates audio continuously β€” ideal for synthesizers, metronomes, test tones, procedural audio, or streaming from a network buffer. A parameter change takes effect within the look-ahead window (~120 ms) without reloading or restarting anything.

Constructor

C#
var source = new StreamingSource(
    render: MyGenerator,                    // AudioRenderCallback
    config: OwnaudioNet.CreateDefaultConfig()
);

The Render Callback

C#
public delegate void AudioRenderCallback(
    Span<float> buffer,    // interleaved destination, exactly frameCount Γ— Config.Channels long
    int frameCount,        // frames to produce
    long framePosition     // absolute frame index of the first requested frame
);

framePosition counts from the start of the timeline and is reset by Seek() and Stop(). Generators that must stay locked to a grid β€” a metronome, an LFO β€” should derive their phase from it rather than from an internal counter, so a seek repositions them exactly.

ℹ️

The callback runs on the source's own pump thread, never on the audio thread, so taking a lock is safe. Keep it allocation-free so the feed stays ahead of playback.

Behaviour

MemberValueNotes
Durationdouble.PositiveInfinityA generator has no end.
IsEndOfStreamfalseNever runs out of audio.
PositiondoubleSeconds since the last seek target.
Seek(seconds)trueDrops the queued look-ahead and moves the render cursor. Negatives clamp to 0.
Stop()β€”Stops and rewinds the render cursor to frame 0.

The callback is not invoked until Play() is called, so constructing a source is cheap. While paused or stopped the pump thread sleeps on a wake handle and costs no CPU.

Complete Example β€” sine generator

C#
var config = OwnaudioNet.CreateDefaultConfig();
float frequency = 440f;   // live-adjustable from the UI thread

void RenderSine(Span<float> buffer, int frameCount, long framePosition)
{
    int ch = config.Channels;
    double step = 2.0 * Math.PI * frequency / config.SampleRate;

    for (int f = 0; f < frameCount; f++)
    {
        // phase comes from the absolute position β€” a seek stays in phase
        float sample = (float)Math.Sin((framePosition + f) * step) * 0.2f;
        for (int c = 0; c < ch; c++)
            buffer[f * ch + c] = sample;
    }
}

var tone = new StreamingSource(RenderSine, config);
tone.Volume = 0.8f;
mixer.AddSource(tone);
tone.Play();

frequency = 880f;   // takes effect within ~120 ms, no restart needed
ℹ️

StreamingSource works with SourceWithEffects and Channel Routing exactly like every other source.

InputSource

Captures audio from an input device (microphone, line-in) and routes it into the mixer.

C#
// Enable input in AudioConfig first
var config = OwnaudioNet.CreateDefaultConfig();
config.EnableInput = true;
await OwnaudioNet.InitializeAsync(config);
OwnaudioNet.Start();

var mixer = new AudioMixer(OwnaudioNet.Engine!.UnderlyingEngine);
mixer.Start();

var mic = new InputSource(OwnaudioNet.Engine!, bufferSizeInFrames: 8192);
mixer.AddSource(mic);
mic.Play();

// Monitor input peak levels
var (leftLevel, rightLevel) = mic.GetInputLevels();

SourceWithEffects

Wraps any IAudioSource to add a per-track effect chain. Effects run in the order they are added.

C#
var source   = new FileSource("vocal.wav");
var trackFx  = new SourceWithEffects(source);

// Build effect chain
trackFx.AddEffect(new CompressorEffect { Threshold = 0.7f, Ratio = 4f });
trackFx.AddEffect(new ReverbEffect { RoomSize = 0.4f, Mix = 0.2f });
trackFx.AddEffect(new EqualizerEffect());

// Add the wrapper to the mixer β€” NOT the raw source
mixer.AddSource(trackFx);

// Manage the chain
trackFx.RemoveEffect(effect);   // returns bool
trackFx.ClearEffects();
int count           = trackFx.EffectCount;
IEffectProcessor[] fx = trackFx.GetEffects();
⚠️

If you add effects to a source that is already in the mixer, remove the raw source and add the SourceWithEffects wrapper: mixer.RemoveSource(source.Id); mixer.AddSource(trackFx);

Channel Routing

Route individual sources onto specific channels of a multi-channel output. Routing is done entirely with the per-source OutputChannelMapping: it places each source onto the channels of the mixer bus, and the bus's Config.Channels channels map straight through to the audio interface in order (bus channel 0 β†’ physical 0, 1 β†’ 1, …).

C#
// Open a 4-channel output β€” the 4 bus channels drive physical channels 0–3 in order
var config = new AudioConfig
{
    Channels = 4
};

// Route music to channels 0+1, metronome to channels 2+3
var music     = new FileSource("music.mp3");
var metronome = new FileSource("click.wav");

music.OutputChannelMapping     = new[] { 0, 1 };
metronome.OutputChannelMapping = new[] { 2, 3 };

// Fluent style
music.RouteToChannels(0, 1);
metronome.RouteToChannels(2, 3);

mixer.AddSource(music);
mixer.AddSource(metronome);
ℹ️

The OutputChannelMapping array length must equal the source's Config.Channels. Channels are zero-indexed.

Channel Conversion (Upmix / Downmix)

OutputChannelMapping maps source channels 1-to-1 to output channels β€” it does not change the channel count of the source itself. To split a mono source across two channels, or sum a stereo source to mono, use the targetChannels parameter on FileSource. The decoder performs the conversion before mixing, and Config.Channels reflects the converted count.

ScenarioHowResult
Mono file β†’ stereo outputtargetChannels: 2L and R are identical (duplication)
Stereo file β†’ mono outputtargetChannels: 1(L + R) Γ— 0.5 β€” equal power sum
C# β€” Stereo file used as mono (downmix)
// Stereo file loaded as mono β€” decoder sums L+R to a single channel
var source = new FileSource("stereo.wav", targetChannels: 1);
// source.Config.Channels == 1

source.OutputChannelMapping = new[] { 0 }; // route the mono signal to output channel 0
mixer.AddSource(source);
C# β€” Mono file spread across two output channels (upmix)
// Mono file loaded as stereo β€” decoder duplicates the signal to both channels
var source = new FileSource("mono.wav", targetChannels: 2);
// source.Config.Channels == 2

source.RouteToChannels(2, 3); // spread to output channels 2+3
mixer.AddSource(source);

Next

⚠️

Setting OutputChannelMapping to an array whose length does not match Config.Channels throws ArgumentException. Always set targetChannels first if you need a different channel count.