From 8f64539328ff06091490f3ea92d24f0047b38f18 Mon Sep 17 00:00:00 2001 From: Matiq Date: Thu, 20 Aug 2026 16:25:00 +0300 Subject: [PATCH] =?UTF-8?q?P4:=20CRITICAL=20fix=20=E2=80=94=20level=20=3D?= =?UTF-8?q?=20am/res=20(not=20am*res);=20fc-resonance=20shape=20now=20corr?= =?UTF-8?q?ect?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit res=|2B/A| is minimal at band centre (not maximal). Model uses xv=log10(A_k/res_k) => level = am / res. Old am*res inverted the fc-response (cut more off-center). Fixed; fc-scan (tone1kq, band fc 800..1200 via t1kq_only1_) now tracks the reference: scale=42 mean|err| 2.46 dB, intact shape (deepest at fc==tone). Was flat+inverted before. Remaining under-cut off-center -> FFT-conv smoothing/gaps. --- dsp/framed_model.cpp | 6 ++++-- handoff/NOTES_LEVEL.md | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/dsp/framed_model.cpp b/dsp/framed_model.cpp index 8b55f6f..af2631f 100644 --- a/dsp/framed_model.cpp +++ b/dsp/framed_model.cpp @@ -134,10 +134,12 @@ void FramedDetector::processFrame(const std::complex* spectrum, float* m for (size_t k = 0; k <= half; k++) mask[k] = 1.0f; for (size_t b = 0; b < bands_.size(); b++) { - // 1. level = twin |2B/A| x smoothed amp, scaled (0x9be0 buf*=scalar): + // 1. level = twin response x smoothed amp, scaled (0x9be0 buf*=scalar): // mask *= (fVar30/0x1a0) * 0x540870 * 0x54088c + // Level uses xv = log10(A_k / res_k): res=|2B/A| is MINIMAL at band + // centre (a notch), so am/res is maximal there -> deepest cut at fc. for (size_t k = 0; k <= half; k++) { - double level = am_[k] * static_cast(res_[b][k]) * + double level = am_[k] / std::max(static_cast(res_[b][k]), 1e-12) * static_cast(bands_[b].level_scale); scratch[k] = static_cast(level * (fVar30 / C_0x1a0) * C_0x540870 * C_0x54088c); diff --git a/handoff/NOTES_LEVEL.md b/handoff/NOTES_LEVEL.md index 29c8dc7..91c1c4f 100644 --- a/handoff/NOTES_LEVEL.md +++ b/handoff/NOTES_LEVEL.md @@ -905,3 +905,21 @@ Verified constants at raw=RVA: 0x24c3c58=0.001, 0x24c3e28=0.8, 0x24c4674=-0.7, ### framed_model.cpp: scale coeff now computed via prng_fvar30() (prng_state_=112), not a hardcoded constant. t1kq still -0.43 dB at level_scale=600 (unchanged). + +## ============ UPDATE 2026-08-20o: CRITICAL — level = am / res (not am * res) ============ +Found + fixed a sign/direction bug in framed_model.cpp level computation. + +res = |2B/A| (twin) is MINIMAL at band centre (0.117), NOT maximal. The model uses +xv = log10(A_k / res_k), i.e. LEVEL = am / res. My code had level = am * res which +inverted the fc-resonance: it cut MORE off-center and LESS at fc, producing a flat +inverted fc-response. + +Fix: level = am_ / res_[b][k] (with 1e-12 floor). Now the fc-scan (tone1kq, band fc +swept 800..1200, refs t1kq_only1_ = true single-band) tracks the reference shape +(deepest at fc==tone): + scale=42: fc900 +0.3, fc950 +1.3, fc1000 +0.9, fc1100 +0.2; mean|err| 2.46 dB, + max 5.3 dB (worst at edges fc800 +3.9, fc1200 +5.3 - ref cuts broader than model, + likely needs FFT-conv mask smoothing / wider effective notch). +Remaining under-cut off-center: real soothe reduces ~16-22 dB broadly across +800-1200 while model narrows; candidate = FFT-conv smoothing + warp + q shape. +DEFAULT level_scale for am/res path ~= 35-45 (was 600 for the wrong am*res path).