Release v3.7: device-change smoothing, auto-tune spike rejection, wider mic detector, forensic logging

Coalesce capture-engine rebuilds (CompositeCaptureBackend): a swap-triggering
source change now arms a 250ms debounce timer and re-arms on each further
change, so a flap or quick reconfiguration produces ONE rebuild to the final
state instead of a burst (Andre's 16:40 four-rebuilds-in-33s crackle). In-place
updates still apply immediately; a pending rebuild whose target flaps back is
cancelled.

Auto-tune (TickRoute) now keys off the SECOND-highest arrival-gap/render-gap
second in the lookback window instead of the single worst, so a lone ~1s
OS/driver stall no longer balloons the buffer to the 200ms cap (the 16:51
trim burst); sustained jitter still reacts at full speed. Logs both gap-max
(true peak) and gap-used (value acted on).

Mic-privacy detector widened: also catches a per-app Deny aimed at this exe
under ConsentStore\microphone\NonPackaged\<exe>, the HKLM NonPackaged gate,
and the Group-Policy/MDM force-deny (AppPrivacy LetAppsAccessMicrophone=2) —
the block shapes that silence WASAPI capture while ASIO sails past, and that
the old three-value check missed.

Forensic instrumentation so the next log proves what happened: capPeak=
(loudest pre-encode sample, per lane, on the diag line), mic-privacy verdict
logged at startup, ui: capture tick/untick events, and device-event: lines for
Windows endpoint changes.

Docs: mic-privacy + auto-tune sections updated in readme.html, MANUAL.md
regenerated, About-box changelog and RELEASE_NOTES for v3.7.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Ednunp
2026-06-10 22:44:55 +01:00
co-authored by Claude Fable 5
parent 55bdcde0af
commit 9e247112cb
9 changed files with 213 additions and 51 deletions
+15
View File
@@ -76,6 +76,13 @@ internal sealed class SenderLane
public float TakeMaxPreEncodeStepCrossBuffer() => preEncodeStepProbe.TakeMaxCrossBuffer();
public float TakeMaxPreEncodeStepWithinBuffer() => preEncodeStepProbe.TakeMaxWithinBuffer();
// Loudest absolute sample seen on this lane's pre-encode buffer since the last drain (resets on
// read), surfaced on the diag line. ~0 = we are sending silence (mic blocked / muted / wrong
// endpoint); a clear non-zero = real audio is reaching the encoder. This is the signal level the
// log never had — which is exactly why a "mic sends silence" report couldn't be confirmed from it.
private float preEncodePeak;
public float TakeMaxPreEncodePeak() { var p = preEncodePeak; preEncodePeak = 0f; return p; }
// Which render route this lane announces in its format packets. The receiver reads the
// Lane byte on the wire and tags the matching SessionPlayout, which makes PlayoutEngine
// route the lane's audio to the corresponding per-route IWaveProvider surface (lane
@@ -190,6 +197,14 @@ internal sealed class SenderLane
// <see cref="preEncodeStepProbe"/> field comment for why this isn't shared with the
// other lane in BothIndependent.
preEncodeStepProbe.ScanStereo(span);
// Capture-level peak alongside the discontinuity probe — the loudest sample about to be sent.
var peak = preEncodePeak;
for (var s = 0; s < span.Length; s++)
{
var a = span[s] < 0f ? -span[s] : span[s];
if (a > peak) peak = a;
}
preEncodePeak = peak;
switch (owner.Codec)
{