From b3558448478758e5e8d0b40996ba546bb9ba748b Mon Sep 17 00:00:00 2001 From: Joe DiPrima Date: Thu, 13 Aug 2026 19:54:34 -0500 Subject: [PATCH] cadence census item 3: footstep smoother intake clocked at 28Hz (gotcha 32) -- the authored olympic windows breathe at their authored width again. The N=30/15 fill-0 windows (AUDWTHR.h:457) are sized for one sample per POD frame; the port's watcher poll fed one per poll, and the bench measured the poll path at ~141-147/s (MORE than render -- the wall window was 0.2s, not even 0.5s). Fix: only the Add is clocked (per-instance 28Hz wall clock, catch-up repeats the current sample capped at the largest authored window 30); the downstream forward still runs every poll with the held average, so watcher semantics are untouched; N untouched. BT_AUD_SMOOTH_HZ override (=0 restores per-poll). Measured: avgUpdates 70.6 -> 19-20/s, envelope wobble -26-45%, ramp90 0.55s -> 1.23s (the authored 30-sample/28Hz ~ 1.07s window restored in wall time), steady levels unchanged (0.852 vs 0.863 -- smoothing only, no level shift). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_016bw71WVsccjwW7uKRg4aWD --- engine/MUNGA/AUDCMP.cpp | 50 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/engine/MUNGA/AUDCMP.cpp b/engine/MUNGA/AUDCMP.cpp index 9c2c3b9..6f2db9c 100644 --- a/engine/MUNGA/AUDCMP.cpp +++ b/engine/MUNGA/AUDCMP.cpp @@ -1,4 +1,5 @@ #include +#include // (BT411) the 28Hz smoother-intake clock (cadence census item 3) #include "munga.h" #pragma hdrstop @@ -958,7 +959,54 @@ void if (control_ID == controlID) { - audioControlAverage.Add(control_value); + // (BT411 cadence census item 3, gotcha 32) SAMPLE INTAKE AT 28 Hz: + // the authored olympic windows (N=30/15, fill 0) are SIZED for one + // sample per POD frame (28 Hz -- AUDWTHR.h:457); the port's watcher + // poll feeds one per RENDER frame (~59), which halves the smoothing + // window in wall time and feeds a noisier operand. Clock ONLY the + // Add (the smoother's own intake) on a per-instance 28 Hz wall + // clock; the forward below still runs EVERY poll with the held + // average, so downstream watcher semantics are untouched. During a + // hitch the pod would have taken ~28 samples/s of the stalled + // value -- catch-up repeats the current sample, capped at the + // largest authored window (30), heat.cpp #119 pattern. + // BT_AUD_SMOOTH_HZ= overrides (=0 restores per-poll intake). + { + static Scalar s_hz = -2.0f; + if (s_hz < -1.0f) + { + const char *hz = getenv("BT_AUD_SMOOTH_HZ"); + s_hz = (hz != 0 && *hz != '\0') ? (Scalar)atof(hz) : 28.0f; + } + if (s_hz > 0.0f) + { + static std::map s_nextMs; + const DWORD now = GetTickCount(); + const DWORD tick = (DWORD)(1000.0f / s_hz); // 35 ms at 28 + DWORD &next = s_nextMs[this]; + if (next == 0) + { + audioControlAverage.Add(control_value); + next = now + tick; + } + else if ((long)(now - next) >= 0) + { + int added = 0; + while ((long)(now - next) >= 0 && ++added <= 30) + { + audioControlAverage.Add(control_value); + next += tick; + } + if ((long)(now - next) >= 0) + next = now + tick; // hitch: resync + } + // else: off-tick poll -- hold; the average is forwarded below + } + else + { + audioControlAverage.Add(control_value); // legacy per-poll + } + } if (getenv("BT_AUDIO_SPATIAL")) { static int s_sa=0; if ((controlID == 100 || controlID == 101) && s_sa++ < 3000)