Synchronization

Sample-accurate multi-track timeline synchronization. Namespace: OwnaudioNET.Synchronization

MasterClock

Every AudioMixer owns one MasterClock. Attach FileSource instances to it for drift-free multi-track playback. The clock advances automatically as the mixer processes audio frames.

Properties

PropertyTypeDescription
CurrentTimestampdoubleCurrent position in seconds.
CurrentSamplePositionlongCurrent position in samples (lock-free read).
SampleRateintSample rate in Hz.
ChannelsintChannel count.
ModeClockModeRendering mode: Realtime, Offline, NetworkServer, NetworkClient.
IsNetworkControlledboolTrue when driven by a remote network server.

Methods

C#
// Seek the timeline
mixer.MasterClock.SeekTo(double timestamp);   // position in seconds
mixer.MasterClock.Reset();                    // seek to 0.0

// Advance manually (Offline mode only)
mixer.MasterClock.Advance(int frameCount);

// Convert between units
long   samples = mixer.MasterClock.TimestampToSamplePosition(5.0);  // 5 seconds → samples
double seconds = mixer.MasterClock.SamplePositionToTimestamp(240000L);

Attaching Sources to the Clock

FileSource implements both IMasterClockSource and ISynchronizable. Attach before calling Play().

C#
var mixer   = new AudioMixer(OwnaudioNet.Engine!.UnderlyingEngine, 1024);
var vocals  = new FileSource("vocals.wav",  targetSampleRate: 48000, targetChannels: 2);
var backing = new FileSource("backing.mp3", targetSampleRate: 48000, targetChannels: 2);

// 1. Attach to clock (BEFORE Seek and Play)
vocals.AttachToClock(mixer.MasterClock);
backing.AttachToClock(mixer.MasterClock);

// 2. Optional: timeline offsets (backing starts 2.5 seconds into the session)
backing.StartOffset = 2.5;

// 3. Seek and play
mixer.MasterClock.SeekTo(0.0);
vocals.Seek(0);
backing.Seek(0);
vocals.Play();
backing.Play();

mixer.AddSource(vocals);
mixer.AddSource(backing);
mixer.Start();

Detach on Stop

C#
vocals.Stop();
vocals.DetachFromClock();   // allows independent playback or reuse

mixer.RemoveSource(vocals.Id);
mixer.MasterClock.SeekTo(0.0);

ISynchronizable Interface

Sources that support sample-accurate position tracking implement ISynchronizable. Use this for precise position display or external sync.

C#
if (source is ISynchronizable sync)
{
    long   samplePos = sync.SamplePosition;
    double posInSec  = samplePos / (double)OwnaudioNet.Engine!.Config.SampleRate;

    // Force hard resync (jumps buffer to match clock position)
    sync.ResyncTo(targetSamplePosition);
}

Accurate Position Display

Interpolate between timer ticks for smooth UI updates at 30+ FPS without polling the audio thread too often:

C#
private double _lastEnginePos;
private double _lastEnginePosAt;
private readonly Stopwatch _watch = Stopwatch.StartNew();

// Update at 30 Hz (33ms timer)
private void OnPositionTimer()
{
    double enginePos = mixer.MasterClock.CurrentTimestamp;
    double nowSec    = _watch.Elapsed.TotalSeconds;

    if (enginePos != _lastEnginePos)
    {
        _lastEnginePos   = enginePos;
        _lastEnginePosAt = nowSec;
    }

    // Interpolate between engine updates
    double displayPos = _lastEnginePos + (nowSec - _lastEnginePosAt);
    CurrentPositionSeconds = displayPos;
}

Drift Correction Zones

When a source is attached to the master clock, its position is compared to the clock on every buffer. Correction is automatic:

ZoneDrift RangeAction
Green< SyncTolerance (20ms default)No correction — within acceptable range.
YellowSyncTolerance â€Ļ SoftSyncTolerance (20–100ms)Soft sync: gradual tempo adjustment up to Âą2%.
Red> SoftSyncTolerance (100ms)Hard sync: skip/fill buffer to realign immediately.
â„šī¸

The TrackDropout event on AudioMixer is raised when a Red Zone correction occurs, so you can log or react to sync issues.

Offline / File Rendering

Set ClockMode.Offline to render audio deterministically to a file without dropouts:

C#
mixer.RenderingMode = ClockMode.Offline;
mixer.StartRecording("render.wav");
mixer.Start();
// In Offline mode the mix thread blocks until all sources provide data
// — no dropouts, deterministic output
mixer.Stop();
mixer.StopRecording();