From f8ecf6a1af0596584be64cb3bbc27050b027bdf4 Mon Sep 17 00:00:00 2001 From: Matiq Date: Thu, 20 Aug 2026 17:16:22 +0300 Subject: [PATCH] =?UTF-8?q?P4:=20Phase=20B=20=E2=80=94=20framed=5Fmodel=20?= =?UTF-8?q?re-transcribed=20to=20the=20confirmed=20chain=20(NOTES:199-226)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes structural divergences: IIR1 into shared 0x5406f8 buffer + bridge to band mask (FUN_18052d650, 0x5160); IIR2 on band mask; Hermitian mirror (0x11940); blend step f6f8=axis(1-mix)+mix*0.8, mask=exp2(-mask)*f6f8; real combine via kRTAtt/kRTRel weights (0x5406c8/6e8); warp, IIR3x2, dry/wet. Validation: t1kq only1 fc1000 -21.1 vs -22.1 (OK); fc-scan shape intact; dual @500 matches (s30 -51.5 vs -53.7), @2000 gap -4..-13 vs -29.6 remains. That gap = dB-domain band LUT (FUN_180563a60) not yet in chain -> Phase A. --- dsp/framed_model.cpp | 113 +++++++++++++++++++++++------------------ dsp/framed_model.hpp | 1 + handoff/NOTES_LEVEL.md | 25 +++++++++ 3 files changed, 90 insertions(+), 49 deletions(-) diff --git a/dsp/framed_model.cpp b/dsp/framed_model.cpp index af2631f..47f6f7b 100644 --- a/dsp/framed_model.cpp +++ b/dsp/framed_model.cpp @@ -2,6 +2,7 @@ #include "twin.hpp" #include "freqpath.hpp" #include "rt_mask_tables.hpp" +#include "rt_weights.hpp" #include #include #include @@ -17,13 +18,14 @@ constexpr double C_0x54088c = 1.0; // release coeff (scale step constexpr double C_0x1a0 = 2048.0; // cell base (scale divisor) constexpr double C_0x540888 = 1.0; // attack coeff (dry/wet step 8) constexpr double C_0x540874 = 1.0; // dry/wet mix basis -constexpr double C_0x54087c = 1.0; // band blend mix -constexpr double C_BLEND08 = 0.8; // DAT_1824c3e28 (blend offset) +constexpr double C_MIX = 1.0; // 0x54087c band blend mix (constructor 0.5? live=1.0) +constexpr double C_0x54087c = C_MIX; +constexpr double C_BLEND08 = 0.8; // DAT_1824c3e28 (blend offset) +constexpr double C_AXIS = 1.3; // 0x540698 freq-axis placeholder (offline = per-band scalar; ref: 8.3-7/(1+exp..) ~1.3) // Leaky-integrator sweep (FUN_18052d650 body, and inline IIR2/IIR3): // y = A[i]*acc + B[i]*x[i]; acc = y; x[i] = (float)y (double, in-place) -// A/B are 2049-double live tables; B = 1 - A. Forward sweep only (the callers -// mirror for the bidirectional pass). +// A/B are live tables; B = 1 - A. Forward sweep only. void iir_leaky(const double* A, const double* B, float* x, int n) { double acc = 0.0; for (int i = 0; i < n; i++) { @@ -33,11 +35,15 @@ void iir_leaky(const double* A, const double* B, float* x, int n) { } } +// 0x11940 copy/mirror: upper half = reversed lower half (Hermitian-style mirror of +// [0,n) into [n, nfft)). In-place on a full-spectrum buffer. +void mirror_half(float* buf, int n, int nfft) { + for (int i = 0; i < n && (nfft - 1 - i) >= n; i++) buf[nfft - 1 - i] = buf[i]; +} + // FUN_180529fe0 PRNG prologue (:515-583). LCG state advances by round offsets; // fVar30 (scale coeff) = (int)(LUT[s+1]*LUT[s]+0.001). CONSTANTS b5c8/b704/b700 -// resolve to 1 (VA-linear dump; the earlier 0.4552/0.6089/0.6070 came from a bad -// file offset). Live state 112 => fVar30 == 1.0 deterministically over 300 frames. -// Returns the scale coefficient fVar30 and advances the state for the next call. +// resolve to 1 (VA-linear dump). Live state 112 => fVar30 == 1.0 deterministically. double prng_fvar30(int& state) { constexpr unsigned M = 0x8000007f; unsigned s = static_cast(state & 0xffffffff); @@ -47,20 +53,17 @@ double prng_fvar30(int& state) { return x; }; s = fix(s + 0x3cdca); // line 515 - unsigned s0 = s; - s = fix(s + 0x140236); // line 526 (s1 for iVar20 second term) - s = fix(s + 0x10d56); // line 538 (s2 -> iVar19) + s = fix(s + 0x140236); // line 526 + s = fix(s + 0x10d56); // line 538 s = fix(s + 0xdf6b6); // line 549 (s3 -> fVar30) unsigned s3 = s; - (void)s0; - // fVar30 line554 = (int)(f32(f32(LUT[s3+1])*f32(LUT[s3])) + 0.001f) int lu = static_cast(s3 & 0x7f); float a = static_cast(kPRNGLut[lu]); float b = static_cast(kPRNGLut[(lu + 1) & 0x7f]); float prod = a * b + 0.001f; int fv = static_cast(prod); // cvttss2si truncation state = static_cast(s3); - if (fv < 1) fv = 1; // guard (observed always >=1) + if (fv < 1) fv = 1; return static_cast(fv); } @@ -78,6 +81,7 @@ void FramedDetector::setParams(const std::vector& bands) { size_t half = nfft_ / 2; res_.clear(); track_.clear(); + f6f8_.assign(half + 1, 0.0f); for (const auto& b : bands_) { std::vector r(half + 1, 1.0f); @@ -128,16 +132,14 @@ void FramedDetector::processFrame(const std::complex* spectrum, float* m double fVar30 = prng_fvar30(prng_state_); // Per-band mask chain (FUN_180529fe0 mono path, 0x5408b8==0). - // 0x540678[band] is the working mask; 0x5407c8[band] is the accumulator - // (tracker state). Transcribed per decomp /tmp/consumers_out.txt:638-1111. - std::vector scratch(half + 1); + // Root source: /tmp/consumers_out.txt:638-1111 + NOTES_LEVEL:199-226 (2026-08-19e). + std::vector scratch(nfft_, 1.0f); + std::vector diff(nfft_, 0.0f); for (size_t k = 0; k <= half; k++) mask[k] = 1.0f; for (size_t b = 0; b < bands_.size(); b++) { - // 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. + // 1. scale: 0x540678[band] *= (fVar30/0x1a0)·0x540870·0x54088c (:656) + // Level = am / res (res = |2B/A| is minimal at band centre). for (size_t k = 0; k <= half; k++) { double level = am_[k] / std::max(static_cast(res_[b][k]), 1e-12) * static_cast(bands_[b].level_scale); @@ -146,53 +148,66 @@ void FramedDetector::processFrame(const std::complex* spectrum, float* m } int n = static_cast(half) + 1; - // 2. IIR1 leaky (attack ramp A1/B1) — FUN_18052d650 in-place. + // 2. IIR1 leaky into the SHARED 0x5406f8 buffer (:668 FUN_18052d650), + // then copy into the band's 0x540678 (0x5160 bridge, :731). iir_leaky(kIIR_A1, kIIR_B1, scratch.data(), n); + for (size_t k = 0; k <= half; k++) f6f8_[k] = scratch[k]; + for (size_t k = 0; k <= half; k++) scratch[k] = f6f8_[k]; - // 3. IIR2 leaky (slow release A2/B2) — inline in-place. + // IIR2 leaky (slow release A2/B2, states 0x3404f8/0x2c04f8) — applied to + // 0x540678[band] in-place (:739-818). iir_leaky(kIIR_A2, kIIR_B2, scratch.data(), n); - // 4. blend + bigkernel exp2 (0x26b820): - // b = freqaxis*(1-mix) + mix*0.8 (= 0.8 at mix=1.0) - // mask = exp2(-mask) * b (vectorized exp2 of x blend; - // verified from the SIMD loop: the - // exp2 result is multiplied by the - // blend buffer ymm11). + // mirror 0x540678[band] lower->upper (0x11940, :817). + mirror_half(scratch.data(), n, static_cast(nfft_)); + + // 3. mono online blend + bigkernel exp2 (0x26b820): + // 0x5406f8 = 0x540698·(1−mix) + mix·0.8 (0x6e40 + 0x15060, :814-823) + // 0x540678[band] = exp2(−0x540678)·0x5406f8 (bigkernel, :832) for (size_t k = 0; k <= half; k++) { - scratch[k] = std::exp2(-static_cast(scratch[k])) * C_BLEND08; + double bl = C_AXIS * (1.0 - C_0x54087c) + C_0x54087c * C_BLEND08; + f6f8_[k] = static_cast(bl); + scratch[k] = static_cast(std::exp2(-static_cast(scratch[k])) * + static_cast(f6f8_[k])); } - // 5. combine / accumulator (0x5407c8[band]): - // acc = mask - b (0x8d60 sub, b = blend buffer) - // mirror halves (0x11940) - // acc += w_att * upper (0x3c40 stride4) - // acc += w_rel * lower (0x3c40) - // acc += mask (0x5a20) - // Then step 6-8 operate on 0x540678[band] (the exp2 mask), NOT acc. + // 4. combine (0x8d60/0x3c40/0x5a20, :843-885): + // track[band] = 0x540678 − 0x5406f8 (sub) + // += w_att·0x5406f8_upper (stride4) (0x5406c8 = kRTAtt) + // += w_rel·0x5406f8_lower (0x5406e8 = kRTRel) + // += 0x540678 (0x5a20) for (size_t k = 0; k <= half; k++) { double m = static_cast(scratch[k]); - double d = m - C_BLEND08; - double upper = (m > track_[b][k]) ? d : d; // mirror handled by caller - double track = track_[b][k] + d; - track_[b][k] = static_cast(track); - (void)upper; - // mask carries on to warp/dry-wet below. + double bl = static_cast(f6f8_[k]); + double d = m - bl; + diff[k] = static_cast(d); + } + const float* wA = kRTAtt; + const float* wR = kRTRel; + std::vector& tr = track_[b]; + for (size_t k = 0; k <= half; k++) { + size_t wi = std::min(k, size_t(half)); + double val = static_cast(diff[k]) + + static_cast(wA[wi]) * static_cast(f6f8_[k]) + + static_cast(wR[wi]) * static_cast(f6f8_[k]) + + static_cast(scratch[k]); + tr[k] = static_cast(val); } - // 6. warp tilt (0x8700 dst*=src, applied twice): - // mask *= 0x540768[band] (per-band mult table) - // mask *= 0x5406a8 (warp / freqpath tilt) + // 5. WARP tilt: 0x540678 *= 0x540768[band] (band mult), then *= 0x5406a8 + // (0x8700, :938/:947). for (size_t k = 0; k <= half; k++) { int wi = std::min(static_cast(k), size_t(2048)); scratch[k] *= static_cast(kBand768[wi]); scratch[k] *= static_cast(kWarp[wi]); } - // 7. IIR3 leaky (A3/B3) twice. - // 8. dry/wet: mask = mask*(fVar30*0x540888) + (1-fVar30). - // (fVar30 = 0x540874 - rnd; 0x540888=1.0, 0x540874=1.0.) + // 6. IIR3 leaky twice (states 0x3c0510/0x440510, :970-1110). iir_leaky(kIIR_A3, kIIR_B3, scratch.data(), n); iir_leaky(kIIR_A3, kIIR_B3, scratch.data(), n); + + // 7. dry/wet: mask = mask·(fVar30·0x540888) + (1−fVar30). + // fVar30 = 0x540874 − rnd; 0x540888=1.0, 0x540874=1.0. (identity at live consts) for (size_t k = 0; k <= half; k++) { double g = static_cast(scratch[k]); g = g * (C_0x540874 * C_0x540888) + (1.0 - C_0x540874); @@ -207,4 +222,4 @@ void FramedDetector::processFrame(const std::complex* spectrum, float* m for (size_t k = half + 1; k < nfft_; k++) { mask[k] = mask[nfft_ - k]; } -} +} \ No newline at end of file diff --git a/dsp/framed_model.hpp b/dsp/framed_model.hpp index efdf01e..fb9fd60 100644 --- a/dsp/framed_model.hpp +++ b/dsp/framed_model.hpp @@ -45,5 +45,6 @@ private: std::vector bands_; std::vector> res_; // per band, per bin |2B/A| std::vector am_; // smoothed per-bin amplitude + std::vector f6f8_; // shared 0x5406f8 blend buffer (IIR1 out) std::vector> track_; // per band, per bin accumulator 0x5407c8 }; diff --git a/handoff/NOTES_LEVEL.md b/handoff/NOTES_LEVEL.md index 91c1c4f..a5d3875 100644 --- a/handoff/NOTES_LEVEL.md +++ b/handoff/NOTES_LEVEL.md @@ -923,3 +923,28 @@ swept 800..1200, refs t1kq_only1_ = true single-band) tracks the reference s 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). + +## ============ UPDATE 2026-08-20p: Phase B — framed_model RE-TRANSCRIBED ============ +Re-transcribed dsp/framed_model.cpp per confirmed chain (NOTES:199-226, source +/tmp/consumers_out.txt:638-1111). Structural divergences from the old approximation: +- IIR1 (FUN_18052d650, state 0x440518) writes into the SHARED 0x5406f8 buffer, + then bridges into the band's 0x540678 (:731 0x5160); IIR2 (0x3404f8/0x2c04f8 on + the band mask) kept separate. +- mirror 0x11940: upper half = reversed lower (Hermitian), full-nfft scratch. +- blend step: f6f8 = 0x540698·(1−mix) + mix·0.8; mask = exp2(−mask)·f6f8. C_AXIS=1.3 + placeholder (offline 0x540698 = per-band scalar, NOT the exp-formula; NOTES:354). +- combine (0x8d60/0x3c40/0x5a20): track = (mask−blend) + kRTAtt·blend + + kRTRel·blend + mask, using live weight tables kRTAtt/kRTRel (0x5406c8/6e8). +- warp (0x540768·0x5406a8), IIR3 (A3/B3)x2, dry/wet identity. + +VALIDATION (scale sweep): +- DUAL (band fc=500, tones 500+2000 at −7.23 dBFS): ref @500 −53.7/@2000 −29.6. + model @500 matches over scale (s30 −51.5, s42 −58.1); @2000 stuck ~−4..−13 (way + short). The DAZING far-field reduction (reduce 2000 by −29.6 when band is at 500) + does NOT come from the exp2(−linear·level) chain — exp2(am/res) collapses to ~0 + once res@2000 (1.344) ≫ res@500 (0.1175). +- T1KQ only1 fc1000: −21.1 vs ref −22.1 (OK). fc-scan shape intact. +=> Phase B confirms structurally: the missing stage for dual-far-field is the + dB-domain LUT FIX_180563a60 (level_dB = 20·log10(mask) → t=(dB−A)/(B−A) clamp → + t^γ), which COMPRESSES the exp2 collapse into a smooth log curve. NEXT = decode + BandConfig ctx+0x188 writers (A/B/gamma) + 0x540698 -> then wire into the chain.