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 <noreply@anthropic.com>
19 lines
757 B
Plaintext
19 lines
757 B
Plaintext
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
|