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
+64 -49
View File
@@ -2,6 +2,7 @@
#include "twin.hpp"
#include "freqpath.hpp"
#include "rt_mask_tables.hpp"
#include "rt_weights.hpp"
#include <cmath>
#include <cstring>
#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_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<unsigned>(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<int>(s3 & 0x7f);
float a = static_cast<float>(kPRNGLut[lu]);
float b = static_cast<float>(kPRNGLut[(lu + 1) & 0x7f]);
float prod = a * b + 0.001f;
int fv = static_cast<int>(prod); // cvttss2si truncation
state = static_cast<int>(s3);
if (fv < 1) fv = 1; // guard (observed always >=1)
if (fv < 1) fv = 1;
return static_cast<double>(fv);
}
@@ -78,6 +81,7 @@ void FramedDetector::setParams(const std::vector<DetectorBand>& bands) {
size_t half = nfft_ / 2;
res_.clear();
track_.clear();
f6f8_.assign(half + 1, 0.0f);
for (const auto& b : bands_) {
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_);
// 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<float> scratch(half + 1);
// Root source: /tmp/consumers_out.txt:638-1111 + NOTES_LEVEL:199-226 (2026-08-19e).
std::vector<float> scratch(nfft_, 1.0f);
std::vector<float> 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<double>(res_[b][k]), 1e-12) *
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;
// 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<int>(nfft_));
// 3. mono online blend + bigkernel exp2 (0x26b820):
// 0x5406f8 = 0x540698·(1mix) + 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<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]):
// 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<double>(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<float>(track);
(void)upper;
// mask carries on to warp/dry-wet below.
double bl = static_cast<double>(f6f8_[k]);
double d = m - bl;
diff[k] = static_cast<float>(d);
}
const float* wA = kRTAtt;
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):
// 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<size_t>(k), size_t(2048));
scratch[k] *= static_cast<float>(kBand768[wi]);
scratch[k] *= static_cast<float>(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) + (1fVar30).
// 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<double>(scratch[k]);
g = g * (C_0x540874 * C_0x540888) + (1.0 - C_0x540874);
@@ -207,4 +222,4 @@ void FramedDetector::processFrame(const std::complex<double>* spectrum, float* m
for (size_t k = half + 1; k < nfft_; k++) {
mask[k] = mask[nfft_ - k];
}
}
}
+1
View File
@@ -45,5 +45,6 @@ private:
std::vector<DetectorBand> bands_;
std::vector<std::vector<float>> res_; // per band, per bin |2B/A|
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
};