P4: Phase B — framed_model re-transcribed to the confirmed chain (NOTES:199-226)

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.
This commit is contained in:
2026-08-20 17:16:22 +03:00
parent 8f64539328
commit f8ecf6a1af
3 changed files with 90 additions and 49 deletions
+63 -48
View File
@@ -2,6 +2,7 @@
#include "twin.hpp" #include "twin.hpp"
#include "freqpath.hpp" #include "freqpath.hpp"
#include "rt_mask_tables.hpp" #include "rt_mask_tables.hpp"
#include "rt_weights.hpp"
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
#include <algorithm> #include <algorithm>
@@ -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_0x1a0 = 2048.0; // cell base (scale divisor)
constexpr double C_0x540888 = 1.0; // attack coeff (dry/wet step 8) 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_0x540874 = 1.0; // dry/wet mix basis
constexpr double C_0x54087c = 1.0; // band blend mix constexpr double C_MIX = 1.0; // 0x54087c band blend mix (constructor 0.5? live=1.0)
constexpr double C_BLEND08 = 0.8; // DAT_1824c3e28 (blend offset) 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): // 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) // 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 // A/B are live tables; B = 1 - A. Forward sweep only.
// mirror for the bidirectional pass).
void iir_leaky(const double* A, const double* B, float* x, int n) { void iir_leaky(const double* A, const double* B, float* x, int n) {
double acc = 0.0; double acc = 0.0;
for (int i = 0; i < n; i++) { 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; // 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 // 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 // resolve to 1 (VA-linear dump). Live state 112 => fVar30 == 1.0 deterministically.
// 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.
double prng_fvar30(int& state) { double prng_fvar30(int& state) {
constexpr unsigned M = 0x8000007f; constexpr unsigned M = 0x8000007f;
unsigned s = static_cast<unsigned>(state & 0xffffffff); unsigned s = static_cast<unsigned>(state & 0xffffffff);
@@ -47,20 +53,17 @@ double prng_fvar30(int& state) {
return x; return x;
}; };
s = fix(s + 0x3cdca); // line 515 s = fix(s + 0x3cdca); // line 515
unsigned s0 = s; s = fix(s + 0x140236); // line 526
s = fix(s + 0x140236); // line 526 (s1 for iVar20 second term) s = fix(s + 0x10d56); // line 538
s = fix(s + 0x10d56); // line 538 (s2 -> iVar19)
s = fix(s + 0xdf6b6); // line 549 (s3 -> fVar30) s = fix(s + 0xdf6b6); // line 549 (s3 -> fVar30)
unsigned s3 = s; unsigned s3 = s;
(void)s0;
// fVar30 line554 = (int)(f32(f32(LUT[s3+1])*f32(LUT[s3])) + 0.001f)
int lu = static_cast<int>(s3 & 0x7f); int lu = static_cast<int>(s3 & 0x7f);
float a = static_cast<float>(kPRNGLut[lu]); float a = static_cast<float>(kPRNGLut[lu]);
float b = static_cast<float>(kPRNGLut[(lu + 1) & 0x7f]); float b = static_cast<float>(kPRNGLut[(lu + 1) & 0x7f]);
float prod = a * b + 0.001f; float prod = a * b + 0.001f;
int fv = static_cast<int>(prod); // cvttss2si truncation int fv = static_cast<int>(prod); // cvttss2si truncation
state = static_cast<int>(s3); state = static_cast<int>(s3);
if (fv < 1) fv = 1; // guard (observed always >=1) if (fv < 1) fv = 1;
return static_cast<double>(fv); return static_cast<double>(fv);
} }
@@ -78,6 +81,7 @@ void FramedDetector::setParams(const std::vector<DetectorBand>& bands) {
size_t half = nfft_ / 2; size_t half = nfft_ / 2;
res_.clear(); res_.clear();
track_.clear(); track_.clear();
f6f8_.assign(half + 1, 0.0f);
for (const auto& b : bands_) { for (const auto& b : bands_) {
std::vector<float> r(half + 1, 1.0f); std::vector<float> r(half + 1, 1.0f);
@@ -128,16 +132,14 @@ void FramedDetector::processFrame(const std::complex<double>* spectrum, float* m
double fVar30 = prng_fvar30(prng_state_); double fVar30 = prng_fvar30(prng_state_);
// Per-band mask chain (FUN_180529fe0 mono path, 0x5408b8==0). // Per-band mask chain (FUN_180529fe0 mono path, 0x5408b8==0).
// 0x540678[band] is the working mask; 0x5407c8[band] is the accumulator // Root source: /tmp/consumers_out.txt:638-1111 + NOTES_LEVEL:199-226 (2026-08-19e).
// (tracker state). Transcribed per decomp /tmp/consumers_out.txt:638-1111. std::vector<float> scratch(nfft_, 1.0f);
std::vector<float> scratch(half + 1); std::vector<float> diff(nfft_, 0.0f);
for (size_t k = 0; k <= half; k++) mask[k] = 1.0f; for (size_t k = 0; k <= half; k++) mask[k] = 1.0f;
for (size_t b = 0; b < bands_.size(); b++) { for (size_t b = 0; b < bands_.size(); b++) {
// 1. level = twin response x smoothed amp, scaled (0x9be0 buf*=scalar): // 1. scale: 0x540678[band] *= (fVar30/0x1a0)·0x540870·0x54088c (:656)
// mask *= (fVar30/0x1a0) * 0x540870 * 0x54088c // Level = am / res (res = |2B/A| is minimal at band centre).
// 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++) { for (size_t k = 0; k <= half; k++) {
double level = am_[k] / std::max(static_cast<double>(res_[b][k]), 1e-12) * double level = am_[k] / std::max(static_cast<double>(res_[b][k]), 1e-12) *
static_cast<double>(bands_[b].level_scale); static_cast<double>(bands_[b].level_scale);
@@ -146,53 +148,66 @@ void FramedDetector::processFrame(const std::complex<double>* spectrum, float* m
} }
int n = static_cast<int>(half) + 1; int n = static_cast<int>(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); 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); iir_leaky(kIIR_A2, kIIR_B2, scratch.data(), n);
// 4. blend + bigkernel exp2 (0x26b820): // mirror 0x540678[band] lower->upper (0x11940, :817).
// b = freqaxis*(1-mix) + mix*0.8 (= 0.8 at mix=1.0) mirror_half(scratch.data(), n, static_cast<int>(nfft_));
// mask = exp2(-mask) * b (vectorized exp2 of x blend;
// verified from the SIMD loop: the // 3. mono online blend + bigkernel exp2 (0x26b820):
// exp2 result is multiplied by the // 0x5406f8 = 0x540698·(1mix) + mix·0.8 (0x6e40 + 0x15060, :814-823)
// blend buffer ymm11). // 0x540678[band] = exp2(0x540678)·0x5406f8 (bigkernel, :832)
for (size_t k = 0; k <= half; k++) { for (size_t k = 0; k <= half; k++) {
scratch[k] = std::exp2(-static_cast<double>(scratch[k])) * C_BLEND08; double bl = C_AXIS * (1.0 - C_0x54087c) + C_0x54087c * C_BLEND08;
f6f8_[k] = static_cast<float>(bl);
scratch[k] = static_cast<float>(std::exp2(-static_cast<double>(scratch[k])) *
static_cast<double>(f6f8_[k]));
} }
// 5. combine / accumulator (0x5407c8[band]): // 4. combine (0x8d60/0x3c40/0x5a20, :843-885):
// acc = mask - b (0x8d60 sub, b = blend buffer) // track[band] = 0x540678 0x5406f8 (sub)
// mirror halves (0x11940) // += w_att·0x5406f8_upper (stride4) (0x5406c8 = kRTAtt)
// acc += w_att * upper (0x3c40 stride4) // += w_rel·0x5406f8_lower (0x5406e8 = kRTRel)
// acc += w_rel * lower (0x3c40) // += 0x540678 (0x5a20)
// acc += mask (0x5a20)
// Then step 6-8 operate on 0x540678[band] (the exp2 mask), NOT acc.
for (size_t k = 0; k <= half; k++) { for (size_t k = 0; k <= half; k++) {
double m = static_cast<double>(scratch[k]); double m = static_cast<double>(scratch[k]);
double d = m - C_BLEND08; double bl = static_cast<double>(f6f8_[k]);
double upper = (m > track_[b][k]) ? d : d; // mirror handled by caller double d = m - bl;
double track = track_[b][k] + d; diff[k] = static_cast<float>(d);
track_[b][k] = static_cast<float>(track); }
(void)upper; const float* wA = kRTAtt;
// mask carries on to warp/dry-wet below. const float* wR = kRTRel;
std::vector<float>& tr = track_[b];
for (size_t k = 0; k <= half; k++) {
size_t wi = std::min(k, size_t(half));
double val = static_cast<double>(diff[k]) +
static_cast<double>(wA[wi]) * static_cast<double>(f6f8_[k]) +
static_cast<double>(wR[wi]) * static_cast<double>(f6f8_[k]) +
static_cast<double>(scratch[k]);
tr[k] = static_cast<float>(val);
} }
// 6. warp tilt (0x8700 dst*=src, applied twice): // 5. WARP tilt: 0x540678 *= 0x540768[band] (band mult), then *= 0x5406a8
// mask *= 0x540768[band] (per-band mult table) // (0x8700, :938/:947).
// mask *= 0x5406a8 (warp / freqpath tilt)
for (size_t k = 0; k <= half; k++) { for (size_t k = 0; k <= half; k++) {
int wi = std::min(static_cast<size_t>(k), size_t(2048)); int wi = std::min(static_cast<size_t>(k), size_t(2048));
scratch[k] *= static_cast<float>(kBand768[wi]); scratch[k] *= static_cast<float>(kBand768[wi]);
scratch[k] *= static_cast<float>(kWarp[wi]); scratch[k] *= static_cast<float>(kWarp[wi]);
} }
// 7. IIR3 leaky (A3/B3) twice. // 6. IIR3 leaky twice (states 0x3c0510/0x440510, :970-1110).
// 8. dry/wet: mask = mask*(fVar30*0x540888) + (1-fVar30).
// (fVar30 = 0x540874 - rnd; 0x540888=1.0, 0x540874=1.0.)
iir_leaky(kIIR_A3, kIIR_B3, scratch.data(), n); iir_leaky(kIIR_A3, kIIR_B3, scratch.data(), n);
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) + (1fVar30).
// fVar30 = 0x540874 rnd; 0x540888=1.0, 0x540874=1.0. (identity at live consts)
for (size_t k = 0; k <= half; k++) { for (size_t k = 0; k <= half; k++) {
double g = static_cast<double>(scratch[k]); double g = static_cast<double>(scratch[k]);
g = g * (C_0x540874 * C_0x540888) + (1.0 - C_0x540874); g = g * (C_0x540874 * C_0x540888) + (1.0 - C_0x540874);
+1
View File
@@ -45,5 +45,6 @@ private:
std::vector<DetectorBand> bands_; std::vector<DetectorBand> bands_;
std::vector<std::vector<float>> res_; // per band, per bin |2B/A| std::vector<std::vector<float>> res_; // per band, per bin |2B/A|
std::vector<float> am_; // smoothed per-bin amplitude std::vector<float> am_; // smoothed per-bin amplitude
std::vector<float> f6f8_; // shared 0x5406f8 blend buffer (IIR1 out)
std::vector<std::vector<float>> track_; // per band, per bin accumulator 0x5407c8 std::vector<std::vector<float>> track_; // per band, per bin accumulator 0x5407c8
}; };
+25
View File
@@ -923,3 +923,28 @@ swept 800..1200, refs t1kq_only1_<fc> = true single-band) tracks the reference s
Remaining under-cut off-center: real soothe reduces ~16-22 dB broadly across 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. 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). 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·(1mix) + 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 = (maskblend) + 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=(dBA)/(BA) 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.