v5.9: the latency control governs the streams it's supposed to, and arrives in seconds

Confirmed working on Ed's two machines before shipping (his log: slider 500 with 17ms buffered
at 07:10:31 -> 214ms at +3s -> 445 at +9s -> settled ~490 by +15s; auto-tune then walked it down
in 5ms steps with the buffer tracking; a drop to 20ms took effect immediately). Both directions,
auto-tune on and off.

Third fix in this batch, found while answering "is ASIO testing essential before we ship": the
ASIO slider only exists in BothIndependent, but MainForm pushes its persisted value at startup in
EVERY mode (and its auto-tune can tick). Now that all routes resolve to the shared value in
single-slider mode, that write would silently overwrite the visible slider with a hidden control's
number. SetMaxLatencyMs now ignores AsioLane writes when !independentLanes — in single-slider mode
the ASIO box governs nothing. Gate covers it, plus the ASIO half of the wiring (a stream on the
ASIO lane reads the ASIO slider; moving one lane doesn't disturb the other) — provable without an
ASIO device, since the change is about WHICH value a lane reads, not the driver path (untouched).

Docs: manual gains an honest paragraph on changing latency mid-listen (immediate down, a few
seconds up, the slight stretch is the change happening, no gap or click); About + RELEASE_NOTES
for 5.9; MANUAL regenerated. Version 5.9. Gate 74/74 + relay 7.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Ednunp
2026-08-15 07:15:31 +01:00
co-authored by Claude Opus 5
parent dfdc243f03
commit 1ee510bf58
7 changed files with 50 additions and 14 deletions
+2
View File
@@ -736,6 +736,8 @@ Separately from the controls above — which manage the cushion against _network
The **Audio latency** control tells the receiving side how much sound to keep in reserve as a cushion against uneven network timing. A bigger cushion means more delay but fewer clicks. A smaller cushion means less delay but more clicks when the network wobbles.
**You can change it while you're listening.** Move the control and the delay follows within a few seconds — there's no gap or click while it changes. Lowering it takes effect straight away. Raising it can't happen instantly, because the extra cushion has to be built up out of the sound still arriving, so RemSound plays very slightly slow for a moment while it banks the difference: a big jump takes a few seconds to arrive and you can hear it stretch out as it goes. That's the change happening, not a fault.
Setting| Best for| Trade-off
---|---|---
510 ms| Local network, same computer.| Crackles on any internet connection with even modest jitter.
+10 -12
View File
@@ -1,18 +1,16 @@
# RemSound v5.8
# RemSound v5.9
A repair for the lock-screen service's settings folder.
The audio latency control now works properly while you're listening.
Some machines ended up with the service's settings folder locked so tightly that nothing could use it — saving the service profile failed with "access denied", the service's log files wouldn't open even in Notepad, and on some machines the service itself couldn't start. It came from a permissions bug in a recent release, and reinstalling didn't clear it.
On most setups, moving the latency control while sound was playing did nothing at all — neither up nor down. It only ever took effect if you set it before connecting, which made it look like the setting was simply ignored.
This release fixes the cause and heals affected machines automatically:
Two separate things were wrong, and both are fixed:
- The service applies the fix on its own when it installs this update — for most people that's it, nothing to do.
- RemSound also checks the folder every time it starts, and offers a one-click repair if it finds a problem.
- And there's a "Repair service folder access" item in the Service menu you can run any time. One administrator prompt, and saving the service profile and reading the logs work again.
- The control was sending its value somewhere the audio never looked, so the receiving side quietly stayed on its starting value for the whole session.
- Even once that reached the right place, the change crept in so slowly that a large move took over two minutes to arrive — still indistinguishable from nothing happening.
## Also in this release
Move it now and the delay follows within a few seconds. There's no gap and no click while it changes. Lowering takes effect straight away; raising can't be instant, because the extra cushion has to be built out of the sound still arriving, so RemSound plays very slightly slow for a moment while it banks the difference — you can hear it stretch, and that's the change happening.
- The service's log files are readable from every account on the machine again, so you can always open them in Notepad if you need to look at one or send it in. (The service's settings stay protected as before.)
- If a service action fails, the message now says what actually went wrong instead of showing a bare error code, and the reason is recorded in the service's own log.
- The repair, and the folder protection itself, now always apply to the account that's actually using RemSound — even on PCs where a different account's password is typed at the administrator prompt.
- The About box now shows the newest five releases instead of the entire version history, which had grown large enough to upset some screen readers. The full history stays here on the releases page.
Automatic latency tuning was affected by the same fault, so it now takes effect too.
Setups using separate WASAPI and ASIO latency controls were the one case that already worked. They keep their two independent settings, unchanged.
+2
View File
@@ -776,6 +776,8 @@ Audient USB Audio ASIO Driver &mdash; Pair 3 (channels 5/6): Loop-back 1 (L) / L
<p>The <strong>Audio latency</strong> control tells the receiving side how much sound to keep in reserve as a cushion against uneven network timing. A bigger cushion means more delay but fewer clicks. A smaller cushion means less delay but more clicks when the network wobbles.</p>
<p><strong>You can change it while you're listening.</strong> Move the control and the delay follows within a few seconds &mdash; there's no gap or click while it changes. Lowering it takes effect straight away. Raising it can't happen instantly, because the extra cushion has to be built up out of the sound still arriving, so RemSound plays very slightly slow for a moment while it banks the difference: a big jump takes a few seconds to arrive and you can hear it stretch out as it goes. That's the change happening, not a fault.</p>
<table>
<tr><th>Setting</th><th>Best for</th><th>Trade-off</th></tr>
<tr><td>5&ndash;10 ms</td><td>Local network, same computer.</td><td>Crackles on any internet connection with even modest jitter.</td></tr>
+8
View File
@@ -20,6 +20,14 @@ internal sealed class AboutDialog : Form
/// updates" path.</summary>
private const string ReleaseNotes =
"""
RemSound v5.9
The audio latency control now works properly while you're listening.
On most setups, moving the latency control while sound was playing did nothing at all neither up nor down. It only took effect if you set it before connecting. Two things were wrong: the control was writing its value where the audio never looked, and even when it landed, a change crept in so slowly that a big move took over two minutes to arrive. Both are fixed. Move it now and the delay follows within a few seconds, with no gap or click raising it stretches the sound very slightly for a moment while the extra cushion builds up, which is the change happening. Automatic latency tuning was affected by the same fault and now works too.
Setups with separate WASAPI and ASIO controls were the one case that already worked, and they keep their two independent settings exactly as before.
RemSound v5.8
A repair for the lock-screen service's settings folder.
+1 -1
View File
@@ -18,7 +18,7 @@
tag_name on the latest GitHub release; bump it on every public release. The
AssemblyVersion / FileVersion default to this value, and Assembly.GetName().Version
is what the About dialog and the updater both read. -->
<Version>5.8</Version>
<Version>5.9</Version>
</PropertyGroup>
<ItemGroup>
+22 -1
View File
@@ -2989,6 +2989,15 @@ internal static class SelfTest
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");
// The hidden ASIO slider must not touch the visible one. It only exists in BothIndependent,
// but the app pushes its persisted value at startup in EVERY mode (and its auto-tune can
// tick) — now that all routes resolve to the shared value, an unguarded write would overwrite
// the user's real slider with a hidden control's number.
engine.SetMaxLatencyMs(RenderRoute.Mixed, 120);
engine.SetMaxLatencyMs(RenderRoute.AsioLane, 8);
Check(engine.TargetLatencyMsFor(session.Route) == 120,
$"an ASIO-lane write must be ignored in single-slider mode (got {engine.TargetLatencyMsFor(session.Route)}ms, expected the visible slider's 120ms)");
// --- 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...
@@ -3000,7 +3009,19 @@ internal static class SelfTest
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";
// An ASIO-lane stream must read the ASIO slider — and moving one slider must not disturb the
// other lane. This is the ASIO half of the wiring, provable without an ASIO device: the change
// is about WHICH value a lane reads, not about the driver path (untouched).
indep.SetLaneActive(RenderRoute.WasapiLane, false);
indep.SetLaneActive(RenderRoute.AsioLane, true);
var asioSession = indep.GetOrCreateSession(new IPEndPoint(IPAddress.Loopback, 47833), 2, capacityBytes: 1024 * 1024);
Check(asioSession.Route == RenderRoute.AsioLane, $"with only ASIO ticked a stream must land on the ASIO lane (got {asioSession.Route})");
Check(indep.TargetLatencyMsFor(asioSession.Route) == 8, "an ASIO-lane stream must read the ASIO slider's value");
indep.SetMaxLatencyMs(RenderRoute.AsioLane, 60);
Check(indep.TargetLatencyMsFor(asioSession.Route) == 60, "moving the ASIO slider must reach its own stream");
Check(indep.TargetLatencyMsFor(RenderRoute.WasapiLane) == 40, "...without disturbing the WASAPI lane");
return "one slider governs every stream (raise + lower reach it, hidden ASIO box ignored); two-slider mode keeps WASAPI and ASIO separate";
}
/// <summary>Issue #23 boot self-heal decision core. Scenario: at the boot lock screen the machine's
+5
View File
@@ -373,6 +373,11 @@ internal sealed class PlayoutEngine : IWaveProvider
/// </summary>
public void SetMaxLatencyMs(RenderRoute route, int value, bool drainOnLower = true)
{
// The ASIO slider only exists in BothIndependent. Elsewhere it's hidden, but the app still
// pushes its persisted value at startup (and its auto-tune can tick), which — now that every
// route resolves to the one shared value — would silently overwrite the visible slider with a
// hidden control's number. Ignore it: in single-slider mode the ASIO box governs nothing.
if (!independentLanes && route == RenderRoute.AsioLane) return;
var clamped = Math.Clamp(value, 1, 500);
var lane = LatencyFor(route);
var previousTarget = lane.TargetMs;