From dfdc243f03ec91678ca8ae87cbf26fbc06e2f925 Mon Sep 17 00:00:00 2001 From: Ednunp <29843396+Ednunp@users.noreply.github.com> Date: Sat, 15 Aug 2026 00:01:19 +0100 Subject: [PATCH] Latency slider: one slider now means one value, and a move arrives in seconds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs behind the 2026-08-14 field report ("the latency slider does nothing" — a WASAPI-only machine, auto-tune off, a clap test showing no change either way, while Ed's own two-slider rig audibly works). Both found by MEASURING, not reasoning: --latency-lab drives the real playout with realtime-paced sender/device shapes and records actual buffered depth once a second. 1. The slider wrote to a value nothing read. ReconcileReplicasLocked tags every session with an OUTPUT LANE (WasapiLane/AsioLane — never Mixed while a device is ticked), and the render path resolved its target from that lane. But MainForm.MaxLatencyBoxRoute sends the single slider to RenderRoute.Mixed in EVERY non-BothIndependent mode. So in classic modes the slider — and the auto-tune with it — updated a value the audio never consumed, leaving the real target on LaneLatency's 30 ms default for the whole session; raise AND lower equally inert (lower also matched on route, so it skipped every playing session). Only BothIndependent worked, because there the slider writes WasapiLane, which IS what its sessions read. That's precisely "works on mine, dead on his". Fix: the lane tag says which DEVICE a session renders through, not which knob governs it. One slider now means ONE value (sharedLatency) read by every session whatever its lane; two sliders only in BothIndependent, seeded from the shared value on entry so audio doesn't jump. Arming also uses the session's own route target (matters in BothIndependent). Measured, classic path: -0.15ms/s (STALLED) -> +2.07ms/s (GROWS). 2. Even correctly wired, a raise crawled. Steady-state depth feedback is capped at 0.3% (~3ms of catch-up per second) AND only recomputed at the 10s drift-window boundary, so a 400 ms raise took over two minutes and still read as a dead slider (Ed: "it needs to change the value quickly not over minutes"). A raise can't be met by dropping audio, only by banking it, so a DELIBERATE move (the hard setter — never the auto-tune's soft one, leaving its parked descent behaviour untouched) now engages a fast approach: 5% bias, recomputed 5x/sec, self-clearing within 15 ms of target. Audible glide, no gap, no click — the user asked for the change and hears it happen. Measured: 49ms -> 324ms within 10s, then holds. Gate: new step pins the invariant fast and deterministically — what the slider sets IS what the session's route reads, a lower reaches the session, and two-slider mode keeps its lanes separate. 74/74 + relay. --latency-lab (six scenarios) / --latency-lab classic (the faithful app path) stay in the tree as the measuring harness. NOT released. Test build for the reporting user: D:\Dropbox\remsound-latency-slider-test.zip Co-Authored-By: Claude Opus 5 --- RemSound-mini-logs-2026-08-14-234223.zip | Bin 0 -> 99009 bytes lab-classic-after.txt | 18 ++ lab-classic-before.txt | 18 ++ lab-classic-err.txt | 0 lab-classic-err2.txt | 0 lab-classic-fast.txt | 18 ++ lab-err3.txt | 0 latency-lab-err.txt | 0 latency-lab-out.txt | 113 ++++++++++ src/RemSound.App/CommandLine.cs | 5 + src/RemSound.App/LatencyLab.cs | 275 +++++++++++++++++++++++ src/RemSound.App/SelfTest.cs | 54 +++++ src/RemSound.Receiver/AudioReceiver.cs | 10 +- src/RemSound.Receiver/PlayoutEngine.cs | 83 +++++-- src/RemSound.Receiver/SessionPlayout.cs | 76 ++++++- 15 files changed, 638 insertions(+), 32 deletions(-) create mode 100644 RemSound-mini-logs-2026-08-14-234223.zip create mode 100644 lab-classic-after.txt create mode 100644 lab-classic-before.txt create mode 100644 lab-classic-err.txt create mode 100644 lab-classic-err2.txt create mode 100644 lab-classic-fast.txt create mode 100644 lab-err3.txt create mode 100644 latency-lab-err.txt create mode 100644 latency-lab-out.txt create mode 100644 src/RemSound.App/LatencyLab.cs diff --git a/RemSound-mini-logs-2026-08-14-234223.zip b/RemSound-mini-logs-2026-08-14-234223.zip new file mode 100644 index 0000000000000000000000000000000000000000..4256b2d2a77a156afaf50cf4f1fe1373b92799f6 GIT binary patch literal 99009 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%0DW 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 literal 0 HcmV?d00001 diff --git a/lab-classic-after.txt b/lab-classic-after.txt new file mode 100644 index 0000000..780adac --- /dev/null +++ b/lab-classic-after.txt @@ -0,0 +1,18 @@ +RemSound latency lab - measuring depth-target convergence with the shipped playout code. +Raise under test: 30ms -> 330ms. Expected growth at full depth-bias: ~3ms/s. + +--- T3 classic app path: WASAPI lane active, slider drives Mixed --- + session route after arrival: WasapiLane (slider writes to: Mixed) + settled at slider 30ms: buffered=70ms + t+10s buffered= 48ms + t+20s buffered= 66ms + t+30s buffered= 114ms + t+40s buffered= 125ms + t+50s buffered= 165ms + t+60s buffered= 194ms + RAISE via slider: 70ms -> 194ms in 60s = 2.07ms/s GROWS (slider works) + LOWER via slider: back to 30ms -> buffered=48ms after 3s + + +=== SUMMARY === + T3 classic app path: WASAPI lane active, slider drives Mixed: raise 2.07ms/s [GROWS (slider works)], lower -> 48ms diff --git a/lab-classic-before.txt b/lab-classic-before.txt new file mode 100644 index 0000000..4b6b8f8 --- /dev/null +++ b/lab-classic-before.txt @@ -0,0 +1,18 @@ +RemSound latency lab - measuring depth-target convergence with the shipped playout code. +Raise under test: 30ms -> 330ms. Expected growth at full depth-bias: ~3ms/s. + +--- T3 classic app path: WASAPI lane active, slider drives Mixed --- + session route after arrival: WasapiLane (slider writes to: Mixed) + settled at slider 30ms: buffered=39ms + t+10s buffered= 19ms + t+20s buffered= 39ms + t+30s buffered= 38ms + t+40s buffered= 29ms + t+50s buffered= 20ms + t+60s buffered= 30ms + RAISE via slider: 39ms -> 30ms in 60s = -0.15ms/s STALLED (the field bug) + LOWER via slider: back to 30ms -> buffered=1ms after 3s + + +=== SUMMARY === + T3 classic app path: WASAPI lane active, slider drives Mixed: raise -0.15ms/s [STALLED (the field bug)], lower -> 21ms diff --git a/lab-classic-err.txt b/lab-classic-err.txt new file mode 100644 index 0000000..e69de29 diff --git a/lab-classic-err2.txt b/lab-classic-err2.txt new file mode 100644 index 0000000..e69de29 diff --git a/lab-classic-fast.txt b/lab-classic-fast.txt new file mode 100644 index 0000000..4c96566 --- /dev/null +++ b/lab-classic-fast.txt @@ -0,0 +1,18 @@ +RemSound latency lab - measuring depth-target convergence with the shipped playout code. +Raise under test: 30ms -> 330ms. Expected growth at full depth-bias: ~3ms/s. + +--- T3 classic app path: WASAPI lane active, slider drives Mixed --- + session route after arrival: WasapiLane (slider writes to: Mixed) + settled at slider 30ms: buffered=49ms + t+10s buffered= 324ms + t+20s buffered= 335ms + t+30s buffered= 339ms + t+40s buffered= 301ms + t+50s buffered= 305ms + t+60s buffered= 306ms + RAISE via slider: 49ms -> 306ms in 60s = 4.28ms/s GROWS (slider works) + LOWER via slider: back to 30ms -> buffered=55ms after 3s + + +=== SUMMARY === + T3 classic app path: WASAPI lane active, slider drives Mixed: raise 4.28ms/s [GROWS (slider works)], lower -> 55ms diff --git a/lab-err3.txt b/lab-err3.txt new file mode 100644 index 0000000..e69de29 diff --git a/latency-lab-err.txt b/latency-lab-err.txt new file mode 100644 index 0000000..e69de29 diff --git a/latency-lab-out.txt b/latency-lab-out.txt new file mode 100644 index 0000000..7fbcfb0 --- /dev/null +++ b/latency-lab-out.txt @@ -0,0 +1,113 @@ +RemSound latency lab - measuring depth-target convergence with the shipped playout code. +Raise under test: 30ms -> 330ms. Expected growth at full depth-bias: ~3ms/s. + +--- T1 clean: write 10ms/10ms, read 10ms/10ms --- + settled at target 30ms: buffered=38ms ratio=1.001000 updates=1 + t+ 5s buffered= 10ms ratio=1.000101 updates=2 + t+10s buffered= 34ms ratio=1.000101 updates=2 + t+15s buffered= 49ms ratio=1.000070 updates=3 + t+20s buffered= 74ms ratio=1.000070 updates=3 + t+25s buffered= 78ms ratio=1.000049 updates=4 + t+30s buffered= 103ms ratio=1.000049 updates=4 + t+35s buffered= 98ms ratio=1.000635 updates=5 + t+40s buffered= 140ms ratio=1.000635 updates=5 + t+45s buffered= 132ms ratio=0.999845 updates=6 + t+50s buffered= 148ms ratio=0.999845 updates=6 + t+55s buffered= 184ms ratio=0.999892 updates=7 + t+60s buffered= 200ms ratio=0.999892 updates=7 + RAISE result: 38ms -> 200ms in 60s = 2.70ms/s GROWS (mechanism working) + LOWER result: back to target 30ms -> buffered=69ms after 3s + +--- T1 opus-frame: write 20ms/20ms, read 10ms/10ms --- + settled at target 30ms: buffered=76ms ratio=0.998000 updates=1 + t+ 5s buffered= 58ms ratio=0.999200 updates=2 + t+10s buffered= 97ms ratio=0.999200 updates=2 + t+15s buffered= 126ms ratio=0.998841 updates=3 + t+20s buffered= 127ms ratio=0.998841 updates=3 + t+25s buffered= 158ms ratio=1.000088 updates=4 + t+30s buffered= 153ms ratio=1.000088 updates=4 + t+35s buffered= 177ms ratio=1.000062 updates=5 + t+40s buffered= 202ms ratio=1.000062 updates=5 + t+45s buffered= 207ms ratio=0.999144 updates=6 + t+50s buffered= 237ms ratio=0.999144 updates=6 + t+55s buffered= 266ms ratio=1.000001 updates=7 + t+60s buffered= 271ms ratio=1.000001 updates=7 + RAISE result: 76ms -> 271ms in 60s = 3.25ms/s GROWS (mechanism working) + LOWER result: back to target 30ms -> buffered=59ms after 3s + +--- T1 gulpy device: write 20ms/20ms, read 21ms + 42ms gulp each ~1s --- + settled at target 30ms: buffered=43ms ratio=1.000400 updates=1 + t+ 5s buffered= 41ms ratio=0.999771 updates=2 + t+10s buffered= 59ms ratio=0.999771 updates=2 + t+15s buffered= 53ms ratio=1.000529 updates=3 + t+20s buffered= 85ms ratio=1.000529 updates=3 + t+25s buffered= 97ms ratio=1.000460 updates=4 + t+30s buffered= 130ms ratio=1.000460 updates=4 + t+35s buffered= 142ms ratio=0.999214 updates=5 + t+40s buffered= 140ms ratio=0.999214 updates=5 + t+45s buffered= 158ms ratio=1.000737 updates=6 + t+50s buffered= 189ms ratio=1.000737 updates=6 + t+55s buffered= 180ms ratio=0.999408 updates=7 + t+60s buffered= 238ms ratio=0.999408 updates=7 + RAISE result: 43ms -> 238ms in 60s = 3.25ms/s GROWS (mechanism working) + LOWER result: back to target 30ms -> buffered=59ms after 3s + +--- T1 jittery net: write 20ms +/-15ms, read 10ms/10ms --- + settled at target 30ms: buffered=70ms ratio=1.002000 updates=1 + t+ 5s buffered= 50ms ratio=1.001100 updates=2 + t+10s buffered= 39ms ratio=1.001100 updates=2 + t+15s buffered= 79ms ratio=1.001370 updates=3 + t+20s buffered= 47ms ratio=1.001370 updates=3 + t+25s buffered= 85ms ratio=1.000359 updates=4 + t+30s buffered= 89ms ratio=1.000359 updates=4 + t+35s buffered= 82ms ratio=0.999952 updates=5 + t+40s buffered= 117ms ratio=0.999952 updates=5 + t+45s buffered= 132ms ratio=1.001166 updates=6 + t+50s buffered= 162ms ratio=1.001166 updates=6 + t+55s buffered= 162ms ratio=0.999619 updates=7 + t+60s buffered= 189ms ratio=0.999619 updates=7 + RAISE result: 70ms -> 189ms in 60s = 1.98ms/s SLOW (partially working) + LOWER result: back to target 30ms -> buffered=69ms after 3s + +--- T2 engine-Mixed clean: write 20ms/20ms, read 10ms/10ms --- + settled at target 30ms: buffered=74ms ratio=1.001001 updates=1 + t+ 5s buffered= 34ms ratio=1.000401 updates=2 + t+10s buffered= 21ms ratio=1.000401 updates=2 + t+15s buffered= 29ms ratio=1.000281 updates=3 + t+20s buffered= 29ms ratio=1.000281 updates=3 + t+25s buffered= 30ms ratio=0.999897 updates=4 + t+30s buffered= 34ms ratio=0.999897 updates=4 + t+35s buffered= 37ms ratio=0.999928 updates=5 + t+40s buffered= 38ms ratio=0.999928 updates=5 + t+45s buffered= 29ms ratio=0.999949 updates=6 + t+50s buffered= 30ms ratio=0.999949 updates=6 + t+55s buffered= 39ms ratio=1.000864 updates=7 + t+60s buffered= 9ms ratio=1.000864 updates=7 + RAISE result: 74ms -> 9ms in 60s = -1.08ms/s STALLED (the field bug) + LOWER result: back to target 30ms -> buffered=29ms after 3s + +--- T2 engine-Mixed gulpy: write 20ms/20ms, read 21ms + 42ms gulp --- + settled at target 30ms: buffered=59ms ratio=0.998399 updates=1 + t+ 5s buffered= 56ms ratio=0.999568 updates=2 + t+10s buffered= 28ms ratio=0.999568 updates=2 + t+15s buffered= 25ms ratio=0.999818 updates=3 + t+20s buffered= 50ms ratio=0.999818 updates=3 + t+25s buffered= 44ms ratio=0.999962 updates=4 + t+30s buffered= 48ms ratio=0.999962 updates=4 + t+35s buffered= 39ms ratio=0.999465 updates=5 + t+40s buffered= 20ms ratio=0.999465 updates=5 + t+45s buffered= 35ms ratio=0.999116 updates=6 + t+50s buffered= 50ms ratio=0.999116 updates=6 + t+55s buffered= 53ms ratio=0.999471 updates=7 + t+60s buffered= 50ms ratio=0.999471 updates=7 + RAISE result: 59ms -> 50ms in 60s = -0.15ms/s STALLED (the field bug) + LOWER result: back to target 30ms -> buffered=62ms after 3s + + +=== SUMMARY === + T1 clean: write 10ms/10ms, read 10ms/10ms: raise 2.70ms/s [GROWS (mechanism working)], lower -> 69ms + T1 opus-frame: write 20ms/20ms, read 10ms/10ms: raise 3.25ms/s [GROWS (mechanism working)], lower -> 59ms + T1 gulpy device: write 20ms/20ms, read 21ms + 42ms gulp each ~1s: raise 3.25ms/s [GROWS (mechanism working)], lower -> 59ms + T1 jittery net: write 20ms +/-15ms, read 10ms/10ms: raise 1.98ms/s [SLOW (partially working)], lower -> 69ms + T2 engine-Mixed clean: write 20ms/20ms, read 10ms/10ms: raise -1.08ms/s [STALLED (the field bug)], lower -> 29ms + T2 engine-Mixed gulpy: write 20ms/20ms, read 21ms + 42ms gulp: raise -0.15ms/s [STALLED (the field bug)], lower -> 62ms diff --git a/src/RemSound.App/CommandLine.cs b/src/RemSound.App/CommandLine.cs index ae0e512..6e7e4fa 100644 --- a/src/RemSound.App/CommandLine.cs +++ b/src/RemSound.App/CommandLine.cs @@ -88,6 +88,11 @@ internal static class CommandLine return WithConsole(() => SelfTest.Run(args)); case "--perftest": case "--perf-test": return WithConsole(() => RunPerfTest(args)); + case "--latency-lab": + // Diagnostic harness (2026-08-14 latency-slider field report): drives the real + // playout with realtime-paced sender/device shapes and MEASURES whether the + // buffered depth converges to a raised target. Developer tool, ~8 min runtime. + return WithConsole(() => LatencyLab.Run(args)); case "--diagnostics": case "--diag": return WithConsole(() => RunDiagnostics(ValueAfter(args, raw))); case "--log": diff --git a/src/RemSound.App/LatencyLab.cs b/src/RemSound.App/LatencyLab.cs new file mode 100644 index 0000000..c2644c6 --- /dev/null +++ b/src/RemSound.App/LatencyLab.cs @@ -0,0 +1,275 @@ +using System.Diagnostics; +using System.Net; +using RemSound.Core; +using RemSound.Receiver; + +namespace RemSound.App; + +/// +/// Measured latency-slider lab (--latency-lab), built for the 2026-08-14 field report: +/// raising the receive-latency slider audibly lengthens the delay on one machine and does nothing +/// on another — both plain WASAPI, both ordinary consumer outputs, auto-tune off. The mechanism +/// under test is SessionPlayout's depth-feedback resampler bias (raise = play ≤0.3% slow until the +/// ring grows to target ≈ +3ms of depth per second). This drives the REAL playout objects with a +/// realtime-paced producer (sender-shaped writes) and consumer (device-shaped reads), samples the +/// actual buffered depth once a second, and prints growth rates — so the diagnosis rests on +/// numbers from the shipped code, not on reasoning about it (the standing audio rule). +/// +/// Scenarios vary the two things that differ between working and stalled rigs: the write shape +/// (10ms PCM-ish vs 20ms Opus-frame-ish vs jittery arrival) and the read shape (clean 10ms +/// callbacks vs chunky 21ms callbacks with periodic double-gulps). Tier 2 re-runs the key shapes +/// through PlayoutEngine's Mixed-route Read — the exact single-slider plumbing a fresh install +/// uses — in case the stall lives above SessionPlayout. +/// +internal static class LatencyLab +{ + private const int SampleRate = 48000; + private const int BytesPerFrame = 2 * sizeof(float); // stereo float mix bus + private const int StartTargetMs = 30; + private const int RaisedTargetMs = 330; + private const int SettleSeconds = 15; // let the 10s drift window engage before judging + private const int MeasureSeconds = 60; // growth window: expect ~+3ms/s => ~+180ms + + public static int Run() => Run([]); + + public static int Run(string[] args) + { + Console.WriteLine("RemSound latency lab - measuring depth-target convergence with the shipped playout code."); + Console.WriteLine($"Raise under test: {StartTargetMs}ms -> {RaisedTargetMs}ms. Expected growth at full depth-bias: ~3ms/s."); + Console.WriteLine(); + + var results = new List(); + + // T3 — the FAITHFUL classic-mode app path (the field bug). Wires the engine exactly as the + // running app does: CompositeRenderBackend marks the WASAPI lane active (a ticked WASAPI + // output, no ASIO), which tags every session RenderRoute.WasapiLane; the single latency + // slider in every non-BothIndependent mode drives RenderRoute.Mixed (MainForm.MaxLatencyBox- + // Route). Run this alone with: --latency-lab classic + if (args.Any(a => string.Equals(a, "classic", StringComparison.OrdinalIgnoreCase))) + { + RunClassicAppPath(results, "T3 classic app path: WASAPI lane active, slider drives Mixed"); + Console.WriteLine(); + Console.WriteLine("=== SUMMARY ==="); + foreach (var line in results) Console.WriteLine(" " + line); + return 0; + } + RunScenario(results, "T1 clean: write 10ms/10ms, read 10ms/10ms", writeMs: 10, writeJitterMs: 0, readMs: 10, gulpEvery: 0, engineMixed: false); + RunScenario(results, "T1 opus-frame: write 20ms/20ms, read 10ms/10ms", writeMs: 20, writeJitterMs: 0, readMs: 10, gulpEvery: 0, engineMixed: false); + RunScenario(results, "T1 gulpy device: write 20ms/20ms, read 21ms + 42ms gulp each ~1s", writeMs: 20, writeJitterMs: 0, readMs: 21, gulpEvery: 48, engineMixed: false); + RunScenario(results, "T1 jittery net: write 20ms +/-15ms, read 10ms/10ms", writeMs: 20, writeJitterMs: 15, readMs: 10, gulpEvery: 0, engineMixed: false); + RunScenario(results, "T2 engine-Mixed clean: write 20ms/20ms, read 10ms/10ms", writeMs: 20, writeJitterMs: 0, readMs: 10, gulpEvery: 0, engineMixed: true); + RunScenario(results, "T2 engine-Mixed gulpy: write 20ms/20ms, read 21ms + 42ms gulp", writeMs: 20, writeJitterMs: 0, readMs: 21, gulpEvery: 48, engineMixed: true); + + Console.WriteLine(); + Console.WriteLine("=== SUMMARY ==="); + foreach (var line in results) Console.WriteLine(" " + line); + return 0; + } + + /// The classic-mode reproduction: engine wired exactly as the shipped app wires it for a + /// plain WASAPI setup, then the slider raised through the very call MainForm makes. Measures the + /// buffered depth the same way as the other scenarios. + private static void RunClassicAppPath(List results, string name) + { + Console.WriteLine($"--- {name} ---"); + var endpoint = new IPEndPoint(IPAddress.Loopback, 47831); + var engine = new PlayoutEngine(new ReceiverDiagnostics()); + + // 1. CompositeRenderBackend.SetOutputDevices: one WASAPI output ticked, no ASIO. + engine.SetLaneActive(RenderRoute.WasapiLane, true); + engine.SetLaneActive(RenderRoute.AsioLane, false); + // 2. The slider's startup value, applied the way MainForm applies it in classic mode. + engine.SetMaxLatencyMs(RenderRoute.Mixed, StartTargetMs); + // 3. A peer's stream arrives — ReconcileReplicasLocked tags it with the active lane. + var session = engine.GetOrCreateSession(endpoint, 1, capacityBytes: 4 * 1024 * 1024); + Console.WriteLine($" session route after arrival: {session.Route} (slider writes to: {RenderRoute.Mixed})"); + + const int WriteMs = 20, ReadMs = 10; + var stop = false; + var writeBlock = new byte[WriteMs * SampleRate / 1000 * BytesPerFrame]; + FillSine(writeBlock); + + var producer = new Thread(() => + { + var sw = Stopwatch.StartNew(); + var nextDueMs = 0.0; + while (!Volatile.Read(ref stop)) + { + session.Write(writeBlock); + session.NoteFramesQueued(engine.TargetLatencyMs); // AudioReceiver's queued-callback lambda + nextDueMs += WriteMs; + var sleep = nextDueMs - sw.Elapsed.TotalMilliseconds; + if (sleep > 0) Thread.Sleep((int)sleep); + } + }) { IsBackground = true, Name = "lab-producer" }; + + var readFrames = ReadMs * SampleRate / 1000; + var readBytes = new byte[readFrames * BytesPerFrame]; + var consumer = new Thread(() => + { + var sw = Stopwatch.StartNew(); + var nextDueMs = 0.0; + while (!Volatile.Read(ref stop)) + { + engine.Read(readBytes, 0, readBytes.Length); + nextDueMs += ReadMs; + var sleep = nextDueMs - sw.Elapsed.TotalMilliseconds; + if (sleep > 0) Thread.Sleep((int)sleep); + } + }) { IsBackground = true, Name = "lab-consumer" }; + + producer.Start(); + Thread.Sleep(120); + consumer.Start(); + Thread.Sleep(SettleSeconds * 1000); + var settled = session.BufferedMs; + Console.WriteLine($" settled at slider {StartTargetMs}ms: buffered={settled}ms"); + + // 4. The user drags the slider up mid-stream — MainForm's exact call. + engine.SetMaxLatencyMs(RenderRoute.Mixed, RaisedTargetMs); + for (var s = 0; s < MeasureSeconds; s++) + { + Thread.Sleep(1000); + if (s % 10 == 9) Console.WriteLine($" t+{s + 1,2}s buffered={session.BufferedMs,4}ms"); + } + var after = session.BufferedMs; + var growth = (after - settled) / (double)MeasureSeconds; + var verdict = growth >= 2.0 ? "GROWS (slider works)" : growth >= 0.5 ? "SLOW" : "STALLED (the field bug)"; + Console.WriteLine($" RAISE via slider: {settled}ms -> {after}ms in {MeasureSeconds}s = {growth:F2}ms/s {verdict}"); + + engine.SetMaxLatencyMs(RenderRoute.Mixed, StartTargetMs); + Thread.Sleep(3000); + Console.WriteLine($" LOWER via slider: back to {StartTargetMs}ms -> buffered={session.BufferedMs}ms after 3s"); + Console.WriteLine(); + results.Add($"{name}: raise {growth:F2}ms/s [{verdict}], lower -> {session.BufferedMs}ms"); + + stop = true; + producer.Join(2000); + consumer.Join(2000); + } + + private static void RunScenario(List results, string name, int writeMs, int writeJitterMs, int readMs, int gulpEvery, bool engineMixed) + { + Console.WriteLine($"--- {name} ---"); + var endpoint = new IPEndPoint(IPAddress.Loopback, 47831); + var diagnostics = new ReceiverDiagnostics(); + PlayoutEngine? engine = null; + SessionPlayout session; + var target = StartTargetMs; + if (engineMixed) + { + engine = new PlayoutEngine(diagnostics); + engine.SetMaxLatencyMs(StartTargetMs); + session = engine.GetOrCreateSession(endpoint, 1, capacityBytes: 4 * 1024 * 1024); + } + else + { + session = new SessionPlayout(endpoint, 1, capacityBytes: 4 * 1024 * 1024); + } + + var stop = false; + var writeBlock = new byte[writeMs * SampleRate / 1000 * BytesPerFrame]; + FillSine(writeBlock); // real signal, not silence - keeps every probe honest + var rng = new Random(12345); + + // Producer: sender-shaped realtime writes, exactly the AudioReceiver wiring + // (Write then NoteFramesQueued with the CURRENT target, like the queued-callback lambda). + var producer = new Thread(() => + { + var sw = Stopwatch.StartNew(); + var nextDueMs = 0.0; + while (!Volatile.Read(ref stop)) + { + session.Write(writeBlock); + session.NoteFramesQueued(engineMixed ? engine!.TargetLatencyMs : Volatile.Read(ref target)); + nextDueMs += writeMs; + var jitter = writeJitterMs > 0 ? rng.Next(-writeJitterMs, writeJitterMs + 1) : 0; + var sleep = nextDueMs + jitter - sw.Elapsed.TotalMilliseconds; + if (sleep > 0) Thread.Sleep((int)sleep); + } + }) { IsBackground = true, Name = "lab-producer" }; + + // Consumer: device-shaped reads. Clean cadence, or chunky with a periodic double-gulp. + var readBlockFrames = readMs * SampleRate / 1000; + var readFloats = new float[readBlockFrames * 2 * 2]; // x2 room for the gulp read + var readBytes = new byte[readFloats.Length * sizeof(float)]; + var consumer = new Thread(() => + { + var sw = Stopwatch.StartNew(); + var nextDueMs = 0.0; + var n = 0; + while (!Volatile.Read(ref stop)) + { + var gulp = gulpEvery > 0 && ++n % gulpEvery == 0; + var frames = gulp ? readBlockFrames * 2 : readBlockFrames; + if (engineMixed) + { + engine!.Read(readBytes, 0, frames * BytesPerFrame); + } + else + { + session.ReadFloats(readFloats.AsSpan(0, frames * 2), frames, Volatile.Read(ref target), Volatile.Read(ref target), smoothness: 3); + } + nextDueMs += gulp ? readMs * 2 : readMs; + var sleep = nextDueMs - sw.Elapsed.TotalMilliseconds; + if (sleep > 0) Thread.Sleep((int)sleep); + } + }) { IsBackground = true, Name = "lab-consumer" }; + + producer.Start(); + Thread.Sleep(120); // pre-fill past the arming threshold so playback starts + consumer.Start(); + + // Phase A: settle at the low target so the 10s drift-measurement window engages. + Thread.Sleep(SettleSeconds * 1000); + var settled = session.BufferedMs; + Console.WriteLine($" settled at target {StartTargetMs}ms: buffered={settled}ms ratio={session.DriftResamplerRatio:F6} updates={session.DriftResamplerUpdates}"); + + // Phase B: RAISE - the shipped raise path (engine hard setter for tier 2; the value the + // reads/queued-callbacks see for tier 1). Then measure depth once a second. + if (engineMixed) engine!.SetMaxLatencyMs(RaisedTargetMs); else Volatile.Write(ref target, RaisedTargetMs); + var samples = new List(); + for (var s = 0; s < MeasureSeconds; s++) + { + Thread.Sleep(1000); + samples.Add(session.BufferedMs); + if (s % 5 == 4) + Console.WriteLine($" t+{s + 1,2}s buffered={session.BufferedMs,4}ms ratio={session.DriftResamplerRatio:F6} updates={session.DriftResamplerUpdates}"); + } + + // Growth rate over the measure window (simple end-to-end slope; the per-5s prints show shape). + var growthMsPerSec = (samples[^1] - settled) / (double)MeasureSeconds; + var verdict = growthMsPerSec >= 2.0 ? "GROWS (mechanism working)" + : growthMsPerSec >= 0.5 ? "SLOW (partially working)" + : "STALLED (the field bug)"; + Console.WriteLine($" RAISE result: {settled}ms -> {samples[^1]}ms in {MeasureSeconds}s = {growthMsPerSec:F2}ms/s {verdict}"); + + // Phase C: LOWER sanity - the drain path should snap back within a couple of seconds. + if (engineMixed) engine!.SetMaxLatencyMs(StartTargetMs); + else { Volatile.Write(ref target, StartTargetMs); session.DisarmAndRequestDrain(); } + Thread.Sleep(3000); + var afterLower = session.BufferedMs; + Console.WriteLine($" LOWER result: back to target {StartTargetMs}ms -> buffered={afterLower}ms after 3s"); + Console.WriteLine(); + + results.Add($"{name}: raise {growthMsPerSec:F2}ms/s [{verdict}], lower -> {afterLower}ms"); + + stop = true; + producer.Join(2000); + consumer.Join(2000); + session.Dispose(); + } + + /// A -12 dB 440 Hz sine so the ring carries real audio (probes and concealment + /// behave as in the field; silence would short-circuit none of them but costs nothing to avoid). + private static void FillSine(byte[] block) + { + var floats = System.Runtime.InteropServices.MemoryMarshal.Cast(block.AsSpan()); + for (var i = 0; i < floats.Length; i += 2) + { + var sample = (float)(0.25 * Math.Sin(2 * Math.PI * 440 * (i / 2) / SampleRate)); + floats[i] = sample; + floats[i + 1] = sample; + } + } +} diff --git a/src/RemSound.App/SelfTest.cs b/src/RemSound.App/SelfTest.cs index 3fbf135..61c90ee 100644 --- a/src/RemSound.App/SelfTest.cs +++ b/src/RemSound.App/SelfTest.cs @@ -129,6 +129,7 @@ internal static class SelfTest RunStep(results, "Service folder repair (reproduce wrong-owner lockout → detect → repair → verify)", ServiceAccessRepairLoop); RunStep(results, "Elevated verbs carry the real user (SID pass-through) + logs stay readable", ElevatedIdentityPassThrough); RunStep(results, "About box shows only the newest releases (screen-reader-safe size)", AboutBoxNotesTrimmed); + RunStep(results, "Latency slider reaches the streams it governs (one slider = one value)", LatencySliderReachesSessions); RunStep(results, "Long-run hygiene (log rotation, crash-report cap, priority-mode scope)", LongRunHygiene); RunStep(results, "Service startup volume (boot-once decision + settings round-trip)", ServiceStartupVolume); RunStep(results, "Update install window (same-day, wraparound, retry timing)", UpdateInstallWindow); @@ -2949,6 +2950,59 @@ internal static class SelfTest return $"shipped About text: 5 versions, {shown.Length} chars (was ~70,000 — the screen-reader crash)"; } + /// The latency slider must actually govern the streams that are playing. THE 2026-08-14 + /// FIELD BUG: incoming sessions are tagged with an output LANE (WasapiLane/AsioLane — never Mixed + /// while a device is ticked), but the single slider writes RenderRoute.Mixed, and the render path + /// resolved its target per-lane — so in every classic mode the slider (and the auto-tune) wrote a + /// value nothing read, leaving the real target on the 30 ms default for the whole session. Raise + /// and lower both inert; only BothIndependent worked. Measured end-to-end by + /// --latency-lab classic (stalled -0.15ms/s → +2.07ms/s after the fix); pinned here as the + /// fast, deterministic invariant: what the slider sets IS what the session's route reads, and a + /// lower reaches the session. Also checks the two-slider mode keeps its lanes genuinely separate. + private static string? LatencySliderReachesSessions() + { + var endpoint = new IPEndPoint(IPAddress.Loopback, 47832); + + // --- Single-slider mode (WasapiOnly and every other classic mode) --- + var engine = new RemSound.Receiver.PlayoutEngine(new RemSound.Receiver.ReceiverDiagnostics()); + engine.SetIndependentLaneLatency(false); + engine.SetLaneActive(RenderRoute.WasapiLane, true); // one WASAPI output ticked... + engine.SetLaneActive(RenderRoute.AsioLane, false); // ...no ASIO, exactly as CompositeRenderBackend reports + engine.SetMaxLatencyMs(RenderRoute.Mixed, 30); // MainForm's call in classic modes + var session = engine.GetOrCreateSession(endpoint, 1, capacityBytes: 1024 * 1024); + Check(session.Route == RenderRoute.WasapiLane, + $"a stream must land on the active output lane (got {session.Route}) — the mismatch the slider used to ignore"); + + engine.SetMaxLatencyMs(RenderRoute.Mixed, 330); // the user drags the slider up + Check(engine.TargetLatencyMsFor(session.Route) == 330, + $"the target the render path reads for this stream must BE the slider's value (got {engine.TargetLatencyMsFor(session.Route)}ms for a 330ms slider)"); + Check(engine.MaxLatencyMsFor(session.Route) == 330, "the max must follow the slider too"); + + // A LOWER must reach the session (disarm + drain). Arm it first, then lower and prove the + // very next read returns nothing — that IS the disarm, and it's what refills to the new depth. + var block = new byte[48000 * 8 / 2]; // 500ms stereo float — must exceed the 330ms target, or it never arms + session.Write(block); + session.NoteFramesQueued(engine.TargetLatencyMsFor(session.Route)); + var scratch = new float[960 * 2]; + Check(session.ReadFloats(scratch, 960, 330, 330) > 0, "the session must be armed and producing before the lower"); + engine.SetMaxLatencyMs(RenderRoute.Mixed, 30); + Check(session.ReadFloats(scratch, 960, 30, 30) == 0, + "lowering the slider must disarm+drain this stream — matching on the slider's route is what made 'lower' inert"); + + // --- Two-slider mode (BothIndependent): lanes stay genuinely separate --- + var indep = new RemSound.Receiver.PlayoutEngine(new RemSound.Receiver.ReceiverDiagnostics()); + indep.SetMaxLatencyMs(RenderRoute.Mixed, 250); // whatever the single slider last held... + indep.SetIndependentLaneLatency(true); // ...seeds both lanes, so audio doesn't jump + Check(indep.TargetLatencyMsFor(RenderRoute.WasapiLane) == 250 && indep.TargetLatencyMsFor(RenderRoute.AsioLane) == 250, + "entering two-slider mode must seed both lanes from the shared value (no jump at the changeover)"); + indep.SetMaxLatencyMs(RenderRoute.WasapiLane, 40); + indep.SetMaxLatencyMs(RenderRoute.AsioLane, 8); + Check(indep.TargetLatencyMsFor(RenderRoute.WasapiLane) == 40 && indep.TargetLatencyMsFor(RenderRoute.AsioLane) == 8, + "each lane must hold its own target in two-slider mode"); + + return "one slider now governs every stream (raise + lower reach it); two-slider mode keeps its lanes separate"; + } + /// Issue #23 boot self-heal decision core. Scenario: at the boot lock screen the machine's /// speakers audibly play (Windows tune, NVDA) but a capture attached in the first seconds of boot /// taps an engine mix the logon-session audio was never wired into — the endpoint's own METER shows diff --git a/src/RemSound.Receiver/AudioReceiver.cs b/src/RemSound.Receiver/AudioReceiver.cs index b6fb13a..aeb1cac 100644 --- a/src/RemSound.Receiver/AudioReceiver.cs +++ b/src/RemSound.Receiver/AudioReceiver.cs @@ -189,6 +189,10 @@ public sealed class AudioReceiver : IDisposable try { multiOutput.Stop(); } catch { /* ignore */ } try { multiOutput.Dispose(); } catch { /* ignore */ } multiOutput = new CompositeRenderBackend(mode, asioDriverName, playoutEngine, msg => diagnosticSink?.Invoke($"output: {msg}")); + // Two latency sliders exist in BothIndependent and nowhere else; every other mode has ONE + // slider, so one latency value governs every session (see PlayoutEngine.independentLanes — + // resolving it per output lane is what made the single slider inert, 2026-08-14). + playoutEngine.SetIndependentLaneLatency(mode == AudioMode.BothIndependent); if (wasRunning) multiOutput.Start(); } @@ -1116,7 +1120,11 @@ public sealed class AudioReceiver : IDisposable try { - newSession = new StreamSession(remote, streamId, format, sp, diagnostics, _ => sp.NoteFramesQueued(playoutEngine.TargetLatencyMs), decryptor); + // Arm against the target THIS session actually plays to (its own route's), not the + // engine-wide one — in BothIndependent those differ, and arming to the wrong one + // starts playback at the wrong depth. In single-slider mode every route resolves to + // the same shared value, so this is identical to the old call there. + newSession = new StreamSession(remote, streamId, format, sp, diagnostics, _ => sp.NoteFramesQueued(playoutEngine.TargetLatencyMsFor(sp.Route)), decryptor); } catch { diff --git a/src/RemSound.Receiver/PlayoutEngine.cs b/src/RemSound.Receiver/PlayoutEngine.cs index 15fafe5..848a0b2 100644 --- a/src/RemSound.Receiver/PlayoutEngine.cs +++ b/src/RemSound.Receiver/PlayoutEngine.cs @@ -86,7 +86,7 @@ internal sealed class PlayoutEngine : IWaveProvider public volatile int TargetMs = 30; public volatile int MaxMs = 80; } - private readonly LaneLatency mixedLatency = new(); + private readonly LaneLatency sharedLatency = new(); private readonly LaneLatency wasapiLaneLatency = new(); private readonly LaneLatency asioLaneLatency = new(); // Per-lane active flag — true when an output device is ticked for that lane in @@ -201,26 +201,59 @@ internal sealed class PlayoutEngine : IWaveProvider public WaveFormat WaveFormat { get; } = WaveFormat.CreateIeeeFloatWaveFormat(MixSampleRate, MixChannels); - /// Legacy property returning the Mixed route's target. Used by code paths that - /// don't care about per-route routing (every classic mode, plus diagnostics that report - /// "the" target latency in non-BothIndependent setups). - public int TargetLatencyMs => mixedLatency.TargetMs; - /// Legacy property returning the Mixed route's max. - public int MaxLatencyMs => mixedLatency.MaxMs; + /// True only in BothIndependent, where the UI genuinely shows two latency sliders + /// (WASAPI and ASIO) and each lane must hold its own target. FALSE in every single-slider mode, + /// and then there is exactly ONE latency value — — used by every + /// session whatever output lane it happens to be tagged with. + /// + /// This flag is the fix for the 2026-08-14 field report ("the latency slider does nothing"). + /// Sessions are always tagged with an OUTPUT LANE by ReconcileReplicasLocked (WasapiLane / + /// AsioLane — never Mixed while any output device is ticked), but the single slider wrote to the + /// Mixed value, which the render path then never read. So in every classic mode the slider — and + /// the auto-tune with it — updated a value nothing consumed, while the real target sat on + /// LaneLatency's 30 ms default for the whole session: raise and lower equally inert. Only + /// BothIndependent worked, because there the slider writes WasapiLane, which IS what its sessions + /// read. Measured before/after by --latency-lab classic and pinned by the gate. The lane + /// tag says which DEVICE a session renders through; it was never meant to pick a latency knob. + private volatile bool independentLanes; + + /// Switch between one-slider and two-slider latency. Entering two-slider mode seeds both + /// lane values from the shared one so the audio doesn't jump at the changeover; leaving it hands + /// control back to the shared value. Called from AudioReceiver.SetAudioMode. + public void SetIndependentLaneLatency(bool independent) + { + if (independent && !independentLanes) + { + wasapiLaneLatency.TargetMs = asioLaneLatency.TargetMs = sharedLatency.TargetMs; + wasapiLaneLatency.MaxMs = asioLaneLatency.MaxMs = sharedLatency.MaxMs; + } + independentLanes = independent; + } + + /// The one latency in single-slider mode; in BothIndependent, the value diagnostics + /// report as "the" target (each lane's own is available via ). + public int TargetLatencyMs => sharedLatency.TargetMs; + /// The one max in single-slider mode (see ). + public int MaxLatencyMs => sharedLatency.MaxMs; /// Per-route target accessor. In BothIndependent the WASAPI and ASIO routes have /// independent targets so each lane can settle at its native latency without the other - /// pulling it. In classic modes only Mixed is meaningful; the other two routes return - /// their defaults. + /// pulling it. In every single-slider mode all routes resolve to the one shared value. public int TargetLatencyMsFor(RenderRoute route) => LatencyFor(route).TargetMs; public int MaxLatencyMsFor(RenderRoute route) => LatencyFor(route).MaxMs; - private LaneLatency LatencyFor(RenderRoute route) => route switch + /// The latency a route reads. With one slider that is ALWAYS the shared value — see + /// for why resolving this per-lane made the slider inert. + private LaneLatency LatencyFor(RenderRoute route) { - RenderRoute.WasapiLane => wasapiLaneLatency, - RenderRoute.AsioLane => asioLaneLatency, - _ => mixedLatency, - }; + if (!independentLanes) return sharedLatency; + return route switch + { + RenderRoute.WasapiLane => wasapiLaneLatency, + RenderRoute.AsioLane => asioLaneLatency, + _ => sharedLatency, + }; + } /// Aggregate buffered ms across all active sessions. Used by the App's diagnostic /// snapshot row. Per-session levels are not currently exposed (single number is enough for @@ -345,15 +378,27 @@ internal sealed class PlayoutEngine : IWaveProvider var previousTarget = lane.TargetMs; lane.MaxMs = clamped; lane.TargetMs = clamped; - if (clamped < previousTarget && drainOnLower) + if (clamped == previousTarget) return; + // Which sessions does this change actually govern? With one slider that's EVERY session + // (they all read the shared value, whatever output lane they're tagged with) — matching on + // the route here is what made "lower the slider" inert in classic modes, since the slider's + // route is Mixed and no playing session is ever tagged Mixed. + var snap = sessionsSnapshot; + foreach (var s in snap) { - // Only drain sessions on THIS route — leaves other-route sessions playing. - var snap = sessionsSnapshot; - foreach (var s in snap) + if (independentLanes && s.Route != route) continue; + if (clamped < previousTarget && drainOnLower) { - if (s.Route != route) continue; s.DisarmAndRequestDrain(); } + else if (drainOnLower) + { + // drainOnLower marks the HARD setter — a deliberate move of the user's slider (the + // auto-tune uses the soft one). A raise can't be met by dropping audio, only by + // banking it, so converge in seconds instead of the steady-state crawl; without this + // a 400 ms raise takes over two minutes to arrive and still reads as a dead slider. + s.RequestFastLatencyApproach(); + } } } diff --git a/src/RemSound.Receiver/SessionPlayout.cs b/src/RemSound.Receiver/SessionPlayout.cs index dcde6a2..e8b73d9 100644 --- a/src/RemSound.Receiver/SessionPlayout.cs +++ b/src/RemSound.Receiver/SessionPlayout.cs @@ -179,6 +179,10 @@ internal sealed class SessionPlayout : IDisposable // true drift. private double smoothedRateRatio = 1.0; private bool resamplerActivelyTracking; + // Fast-approach state (see the FastDepthBias block). Set from the UI thread via + // RequestFastLatencyApproach, cleared on the audio thread once the ring reaches the new target. + private volatile bool fastApproachUntilOnTarget; + private long lastFastApplyTicks; private long resamplerUpdatesTotal; // Scratch buffer for reading from the ring buffer in float form. Sized lazily based on @@ -236,6 +240,21 @@ internal sealed class SessionPlayout : IDisposable // ≤0.3 % pitch nudge, never a click. private const double DepthCorrectionSec = 15.0; private const double MaxDepthBias = 0.003; + // === Fast approach after a DELIBERATE latency change (2026-08-14) === + // The steady-state numbers above are deliberately sleepy: 0.3% of realtime is ~3 ms of catch-up + // per second, and the correction is only recomputed at the 10 s drift-window boundary. That's + // right for silently absorbing clock drift, and hopeless for a user who just dragged the slider + // 400 ms and wants to HEAR the delay change — it would take over two minutes to arrive, which + // reads as "the slider does nothing" even once the slider's value reaches the right place. + // So a user-initiated change (the hard setter — never the auto-tune's soft one, which keeps the + // sleepy numbers and its parked descent behaviour) engages a fast approach: a bigger rate bias, + // recomputed several times a second, until the ring is near the new target. The audio slows or + // speeds by up to FastDepthBias while it converges — an audible, deliberate glide (that's the + // point: the user asked for the change and can hear it happen), never a gap or a click. + private const double FastDepthCorrectionSec = 3.0; + private const double FastDepthBias = 0.05; // 5% => ~50 ms of catch-up per second + private const double FastApplyIntervalSec = 0.2; // recompute 5x/sec while converging + private const double FastApproachDoneMs = 15.0; // close enough — hand back to steady state // Number of stereo frames each side of a splice point that get blended when a drop or // repeat fires. Cosine crossfade over this window smooths the discontinuity into an audio // DriftDropFramesTotal / DriftRepeatFramesTotal accessors removed 2026-05-23 alongside @@ -468,6 +487,16 @@ internal sealed class SessionPlayout : IDisposable drainRequested = true; } + /// The user just moved the latency slider: converge on the new depth in seconds rather + /// than minutes (see the FastDepthBias block). Deliberate changes only — the auto-tune's soft + /// setter must NOT call this, so its gentle, parked descent behaviour is untouched. + public void RequestFastLatencyApproach() + { + fastApproachUntilOnTarget = true; + var mir = mirrors; + for (var i = 0; i < mir.Length; i++) mir[i].fastApproachUntilOnTarget = true; + } + public void Dispose() { // AudioRingBuffer is managed; nothing to free explicitly. Method present for symmetry @@ -651,6 +680,16 @@ internal sealed class SessionPlayout : IDisposable prevDriftSampleTicks = nowTicks; UpdateDriftResamplerRateIfDue(nowTicks, targetLatencyMs); + // While converging on a freshly-moved slider, re-apply the (much larger) depth correction + // several times a second instead of waiting for the 10 s drift window — otherwise the first + // seconds after the user's move do nothing at all, which is exactly what "the slider doesn't + // work" felt like. Self-clearing once the ring is within FastApproachDoneMs of target. + if (fastApproachUntilOnTarget && resamplerActivelyTracking + && (nowTicks - lastFastApplyTicks) / (double)Stopwatch.Frequency >= FastApplyIntervalSec) + { + lastFastApplyTicks = nowTicks; + ApplyDepthCorrectedRate(targetLatencyMs); + } // Read through the resampler and apply concealment on full underruns. ReadThroughResampler(output, outFrames); @@ -667,6 +706,30 @@ internal sealed class SessionPlayout : IDisposable return outFrames; } + /// Set the resampler rate to the feed-forward clock ratio plus the depth-feedback bias + /// that walks the ring toward . Shared by the steady-state + /// 10 s window and the fast approach after a deliberate slider move — the ONLY difference is how + /// hard it's allowed to pull (see the FastDepthBias block), so both paths stay one piece of + /// arithmetic rather than two that can drift apart. + private void ApplyDepthCorrectedRate(int targetLatencyMs) + { + var depthFrames = playout.BufferedBytes / MixBytesPerFrame; + var targetFrames = targetLatencyMs * MixSampleRate / 1000; + var depthError = depthFrames - targetFrames; + var fast = fastApproachUntilOnTarget; + if (fast && Math.Abs(depthError) <= FastApproachDoneMs * MixSampleRate / 1000) + { + fastApproachUntilOnTarget = false; // arrived — back to the sleepy steady-state numbers + fast = false; + } + var spreadSec = fast ? FastDepthCorrectionSec : DepthCorrectionSec; + var bias = fast ? FastDepthBias : MaxDepthBias; + var depthCorrection = Math.Clamp(depthError / (spreadSec * MixSampleRate), -bias, bias); + var appliedRatio = smoothedRateRatio + depthCorrection; + driftResampler.SetRates(MixSampleRate * appliedRatio, MixSampleRate); + Interlocked.Increment(ref resamplerUpdatesTotal); + } + /// /// If the current drift-measurement window has expired, compute the new sender-to- /// receiver rate ratio from the bytes-written and bytes-output counters, smooth it @@ -728,18 +791,7 @@ internal sealed class SessionPlayout : IDisposable // faster; < 0 biases down to refill. Clamped + spread over DepthCorrectionSec so it's a // gentle, inaudible pitch trim, not a per-sample discontinuity. No-op until the // feed-forward has a valid measurement (smoothedRateRatio is meaningless before then). - if (resamplerActivelyTracking) - { - var depthFrames = playout.BufferedBytes / MixBytesPerFrame; - var targetFrames = targetLatencyMs * MixSampleRate / 1000; - var depthError = depthFrames - targetFrames; - var depthCorrection = Math.Clamp( - depthError / (DepthCorrectionSec * MixSampleRate), - -MaxDepthBias, MaxDepthBias); - var appliedRatio = smoothedRateRatio + depthCorrection; - driftResampler.SetRates(MixSampleRate * appliedRatio, MixSampleRate); - Interlocked.Increment(ref resamplerUpdatesTotal); - } + if (resamplerActivelyTracking) ApplyDepthCorrectedRate(targetLatencyMs); // Anchor the next window. resamplerWindowStartTicks = nowTicks;