Skip to content

Commit 96b111d

Browse files
authored
Merge pull request #13 from Sendspin/task/unify-deadband-thresholds
Unify deadband thresholds and remove obsolete properties
2 parents d813df3 + a5525d1 commit 96b111d

File tree

3 files changed

+18
-88
lines changed

3 files changed

+18
-88
lines changed

src/Sendspin.SDK/Audio/SyncCorrectionCalculator.cs

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -259,19 +259,9 @@ private bool UpdateCorrectionInternal(double smoothedMicroseconds)
259259
var absError = Math.Abs(smoothedMicroseconds);
260260

261261
// Thresholds from options
262-
var deadbandThreshold = _options.EntryDeadbandMicroseconds;
262+
var deadbandThreshold = _options.DeadbandMicroseconds;
263263
var resamplingThreshold = _options.ResamplingThresholdMicroseconds;
264264

265-
// Use exit deadband (hysteresis) if we're currently correcting
266-
if (_currentMode != SyncCorrectionMode.None && absError < _options.ExitDeadbandMicroseconds)
267-
{
268-
_currentMode = SyncCorrectionMode.None;
269-
_targetPlaybackRate = 1.0;
270-
_dropEveryNFrames = 0;
271-
_insertEveryNFrames = 0;
272-
return HasChanged(previousMode, previousDrop, previousInsert, previousRate);
273-
}
274-
275265
// Tier 1: Deadband - error is small enough to ignore
276266
if (absError < deadbandThreshold)
277267
{

src/Sendspin.SDK/Audio/SyncCorrectionOptions.cs

Lines changed: 11 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,6 @@ namespace Sendspin.SDK.Audio;
2929
/// filter jitter and prevent oscillation. Proportional correction prevents overshoot
3030
/// by adjusting rate based on error magnitude rather than using fixed rate steps.
3131
/// </para>
32-
/// <para>
33-
/// <b>Note:</b> The <see cref="EntryDeadbandMicroseconds"/>, <see cref="ExitDeadbandMicroseconds"/>,
34-
/// and <see cref="BypassDeadband"/> properties are retained for backward compatibility but are no
35-
/// longer used.
36-
/// </para>
3732
/// </remarks>
3833
/// <example>
3934
/// <code>
@@ -48,36 +43,15 @@ namespace Sendspin.SDK.Audio;
4843
public sealed class SyncCorrectionOptions
4944
{
5045
/// <summary>
51-
/// Gets or sets the error threshold to START correcting (microseconds).
52-
/// </summary>
53-
/// <remarks>
54-
/// <para>
55-
/// <b>Deprecated:</b> This property is no longer used. The sync correction now uses
56-
/// fixed discrete thresholds matching the JS library: 1ms deadband, 8ms for 1% correction,
57-
/// 15ms for 2% correction.
58-
/// </para>
59-
/// <para>
60-
/// Retained for backward compatibility. Default: 2000 (2ms).
61-
/// </para>
62-
/// </remarks>
63-
[Obsolete("No longer used. Discrete thresholds are now fixed to match JS library.")]
64-
public long EntryDeadbandMicroseconds { get; set; } = 2_000;
65-
66-
/// <summary>
67-
/// Gets or sets the error threshold to STOP correcting (microseconds).
46+
/// Gets or sets the deadband threshold (microseconds).
47+
/// Sync errors below this are ignored — no correction is applied.
6848
/// </summary>
6949
/// <remarks>
70-
/// <para>
71-
/// <b>Deprecated:</b> This property is no longer used. The hysteresis deadband has been
72-
/// removed in favor of EMA smoothing and discrete rate steps, which provide more stable
73-
/// correction without oscillation.
74-
/// </para>
75-
/// <para>
76-
/// Retained for backward compatibility. Default: 500 (0.5ms).
77-
/// </para>
50+
/// Matches the JS/CLI spec: 1ms deadband. Errors within this range are
51+
/// considered "in sync" and do not trigger rate adjustment or frame manipulation.
52+
/// Default: 1000 (1ms).
7853
/// </remarks>
79-
[Obsolete("No longer used. Hysteresis replaced by EMA smoothing.")]
80-
public long ExitDeadbandMicroseconds { get; set; } = 500;
54+
public long DeadbandMicroseconds { get; set; } = 1_000;
8155

8256
/// <summary>
8357
/// Gets or sets the maximum playback rate adjustment (0.0 to 1.0).
@@ -194,23 +168,6 @@ public sealed class SyncCorrectionOptions
194168
/// </remarks>
195169
public long ScheduledStartGraceWindowMicroseconds { get; set; } = 10_000;
196170

197-
/// <summary>
198-
/// Gets or sets whether to bypass the deadband entirely.
199-
/// </summary>
200-
/// <remarks>
201-
/// <para>
202-
/// <b>Deprecated:</b> This property is no longer used. The sync correction now uses
203-
/// EMA-smoothed error values with discrete rate steps, which eliminates the need for
204-
/// deadband bypass. The EMA filter provides natural smoothing that handles small
205-
/// errors without requiring continuous micro-adjustments.
206-
/// </para>
207-
/// <para>
208-
/// Retained for backward compatibility. Default: false.
209-
/// </para>
210-
/// </remarks>
211-
[Obsolete("No longer used. EMA smoothing replaces the need for deadband bypass.")]
212-
public bool BypassDeadband { get; set; }
213-
214171
/// <summary>
215172
/// Gets the minimum playback rate (1.0 - MaxSpeedCorrection).
216173
/// </summary>
@@ -227,25 +184,11 @@ public sealed class SyncCorrectionOptions
227184
/// <exception cref="ArgumentException">Thrown when options are invalid.</exception>
228185
public void Validate()
229186
{
230-
if (EntryDeadbandMicroseconds < 0)
231-
{
232-
throw new ArgumentException(
233-
"EntryDeadbandMicroseconds must be non-negative.",
234-
nameof(EntryDeadbandMicroseconds));
235-
}
236-
237-
if (ExitDeadbandMicroseconds < 0)
238-
{
239-
throw new ArgumentException(
240-
"ExitDeadbandMicroseconds must be non-negative.",
241-
nameof(ExitDeadbandMicroseconds));
242-
}
243-
244-
if (ExitDeadbandMicroseconds >= EntryDeadbandMicroseconds && !BypassDeadband)
187+
if (DeadbandMicroseconds < 0)
245188
{
246189
throw new ArgumentException(
247-
"ExitDeadbandMicroseconds must be less than EntryDeadbandMicroseconds to create hysteresis.",
248-
nameof(ExitDeadbandMicroseconds));
190+
"DeadbandMicroseconds must be non-negative.",
191+
nameof(DeadbandMicroseconds));
249192
}
250193

251194
if (MaxSpeedCorrection is <= 0 or > 1.0)
@@ -311,8 +254,7 @@ public void Validate()
311254
/// <returns>A new instance with the same values.</returns>
312255
public SyncCorrectionOptions Clone() => new()
313256
{
314-
EntryDeadbandMicroseconds = EntryDeadbandMicroseconds,
315-
ExitDeadbandMicroseconds = ExitDeadbandMicroseconds,
257+
DeadbandMicroseconds = DeadbandMicroseconds,
316258
MaxSpeedCorrection = MaxSpeedCorrection,
317259
CorrectionTargetSeconds = CorrectionTargetSeconds,
318260
ResamplingThresholdMicroseconds = ResamplingThresholdMicroseconds,
@@ -321,7 +263,6 @@ public void Validate()
321263
StartupGracePeriodMicroseconds = StartupGracePeriodMicroseconds,
322264
ScheduledStartGraceWindowMicroseconds = ScheduledStartGraceWindowMicroseconds,
323265
ReconnectStabilizationMicroseconds = ReconnectStabilizationMicroseconds,
324-
BypassDeadband = BypassDeadband,
325266
};
326267

327268
/// <summary>
@@ -338,8 +279,7 @@ public void Validate()
338279
/// </remarks>
339280
public static SyncCorrectionOptions CliDefaults => new()
340281
{
341-
EntryDeadbandMicroseconds = 2_000,
342-
ExitDeadbandMicroseconds = 500,
282+
DeadbandMicroseconds = 1_000,
343283
MaxSpeedCorrection = 0.04, // 4% vs Windows 2%
344284
CorrectionTargetSeconds = 2.0, // 2s vs Windows 3s
345285
ResamplingThresholdMicroseconds = 15_000,

src/Sendspin.SDK/Audio/TimedAudioBuffer.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,7 +1080,7 @@ private void CalculateSyncError(long currentLocalTime)
10801080
/// Implements a tiered correction strategy:
10811081
/// </para>
10821082
/// <list type="bullet">
1083-
/// <item>Error &lt; 1ms (deadband): No correction, playback rate = 1.0</item>
1083+
/// <item>Error &lt; deadband (default 1ms): No correction, playback rate = 1.0</item>
10841084
/// <item>Error 1-15ms: Proportional rate adjustment (error / targetSeconds), clamped to max</item>
10851085
/// <item>Error &gt; 15ms: Frame drop/insert for faster correction</item>
10861086
/// </list>
@@ -1125,12 +1125,12 @@ private void UpdateCorrectionRate()
11251125
// Use smoothed error for correction decisions (filters measurement jitter)
11261126
var absError = Math.Abs(_smoothedSyncErrorMicroseconds);
11271127

1128-
// Thresholds for correction tiers
1129-
const long DeadbandThreshold = 1_000; // 1ms - no correction below this
1130-
const long ResamplingThreshold = 15_000; // 15ms - above this use drop/insert
1128+
// Thresholds for correction tiers (from options)
1129+
var deadbandThreshold = _syncOptions.DeadbandMicroseconds;
1130+
var resamplingThreshold = _syncOptions.ResamplingThresholdMicroseconds;
11311131

11321132
// Tier 1: Deadband - error is small enough to ignore
1133-
if (absError < DeadbandThreshold)
1133+
if (absError < deadbandThreshold)
11341134
{
11351135
LogCorrectionModeTransition(SyncCorrectionMode.None);
11361136
SetTargetPlaybackRate(1.0);
@@ -1143,7 +1143,7 @@ private void UpdateCorrectionRate()
11431143
// Rate = 1.0 + (error_µs / target_seconds / 1,000,000)
11441144
// This calculates the rate needed to eliminate the error over the target time.
11451145
// Example: 10ms error with 3s target → rate = 1.00333 (0.33% faster)
1146-
if (absError < ResamplingThreshold)
1146+
if (absError < resamplingThreshold)
11471147
{
11481148
// Log transition from drop/insert mode back to resampling (effectively None for drop/insert)
11491149
LogCorrectionModeTransition(SyncCorrectionMode.Resampling);

0 commit comments

Comments
 (0)