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
| Property | Type | Description |
|---|---|---|
CurrentTimestamp | double | Current position in seconds. |
CurrentSamplePosition | long | Current position in samples (lock-free read). |
SampleRate | int | Sample rate in Hz. |
Channels | int | Channel count. |
Mode | ClockMode | Rendering mode: Realtime, Offline, NetworkServer, NetworkClient. |
IsNetworkControlled | bool | True when driven by a remote network server. |
Methods
// 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().
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
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.
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:
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:
| Zone | Drift Range | Action |
|---|---|---|
| Green | < SyncTolerance (5ms default) | No correction â within acceptable range. |
| Yellow | SyncTolerance âĻ SoftSyncTolerance (5â25ms) | Soft sync: gradual tempo adjustment up to Âą2%. |
| Red | > SoftSyncTolerance (25ms) | 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.
Adaptive Tolerance
The sync engine monitors red-zone hit frequency and automatically widens the tolerance thresholds on hardware that cannot sustain the strict defaults. No configuration is required â the system self-tunes at runtime.
| AdaptiveScale | Green Zone | Yellow Zone | Meaning |
|---|---|---|---|
1.0 | 5 ms | 25 ms | Optimal â strict defaults active. |
2.0 | 10 ms | 50 ms | Moderate load â tolerances doubled. |
4.0 | 20 ms | 100 ms | High load â fully relaxed (original values). |
The scale increases by 0.5 steps when âĨ 5 red-zone hits occur within a 3-second window, and decreases by 0.5 steps after 8 consecutive seconds in the green zone.
A scale above 1.0 means the engine is compensating for processing pressure. Use SyncDiagnostics to detect this condition â it may indicate a bug or performance issue in a custom effect or processing pipeline.
SyncDiagnosticsSnapshot diag = fileSource.SyncDiagnostics;
if (diag.IsRelaxed)
{
Console.WriteLine($"Adaptive tolerance active â Scale: {diag.AdaptiveScale:F1}x");
Console.WriteLine($"Green zone: {diag.EffectiveSyncToleranceMs:F1} ms (baseline: 5 ms)");
Console.WriteLine($"Yellow zone: {diag.EffectiveSoftSyncToleranceMs:F1} ms (baseline: 25 ms)");
Console.WriteLine($"Red zone hits in window: {diag.RedZoneHitsInWindow}");
}SyncDiagnosticsSnapshot is a readonly struct â zero heap allocation, safe to call from any thread.
Offline / File Rendering
Set ClockMode.Offline to render audio deterministically to a file without dropouts:
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();