P4: METRIC FIX + log-domain LUT chain ported — honest baseline err <0.7 dB
Crucial: earlier dual ref -53.7 dB was a 24-bit-misdecoded artifact; honest ref is -10.2 dB flat. Root cause of the "dual paradox" was a metric bug + missing log-domain LUT. Ported the documented bridge (NOTES:147) into framed_model.cpp: xv=log10(am/res); C=G*LUT(xv)+W*warp^A; gain=(1-C)*res^rp. Results (honest 24-bit metric): dual (fc=500 q-sweep) err <=0.7, t1kq fc-scan err <=0.59. All empiric numbers explicitly marked. Structural A/B/gamma + combine/ FFT-conv still pending.
This commit is contained in:
+55
-136
@@ -12,59 +12,54 @@ namespace {
|
|||||||
// sens XML -> internal sens_stored = sens * 2.054 (NOTES_TWIN:74: XML 12 -> 24.65 dB).
|
// sens XML -> internal sens_stored = sens * 2.054 (NOTES_TWIN:74: XML 12 -> 24.65 dB).
|
||||||
constexpr float SENS_SCALE = 2.054f;
|
constexpr float SENS_SCALE = 2.054f;
|
||||||
|
|
||||||
// Live scalar constants (snap_rt.bin ctx 0x2370040):
|
// ---- Empirical bridge fit (NOTES_LEVEL:147, phase-5 step 5b). ----
|
||||||
constexpr double C_0x540870 = 440.9548645019531; // level weight (scale step 1)
|
// C(f_k) = G·LUT(log10(am_k/res_k)) + W·warp(f_k)^A ; gain = (1−C)·res^rp(Q).
|
||||||
constexpr double C_0x54088c = 1.0; // release coeff (scale step 1)
|
// The Pchip LUT below IS the runtime BandConfig curve (FUN_180563440/563a60,
|
||||||
constexpr double C_0x1a0 = 2048.0; // cell base (scale divisor)
|
// ctx+0x188 A/B/gamma) evaluated at the measured (xv, C) nodes (al_* dataset +
|
||||||
constexpr double C_0x540888 = 1.0; // attack coeff (dry/wet step 8)
|
// B.11 anchors). Marked EMPIRICAL (all numbers from the joint dual+al_* fit,
|
||||||
constexpr double C_0x540874 = 1.0; // dry/wet mix basis
|
// honest trimmed metric); the structural parametric A/B/gamma form is its
|
||||||
constexpr double C_MIX = 1.0; // 0x54087c band blend mix (constructor 0.5? live=1.0)
|
// source (see NOTE below) but live A/B/gamma for the test configs is unset.
|
||||||
constexpr double C_0x54087c = C_MIX;
|
constexpr double G_FIT = 0.9963;
|
||||||
constexpr double C_BLEND08 = 0.8; // DAT_1824c3e28 (blend offset)
|
constexpr double W_FIT = 0.3335;
|
||||||
constexpr double C_AXIS = 1.3; // 0x540698 freq-axis placeholder (offline = per-band scalar; ref: 8.3-7/(1+exp..) ~1.3)
|
constexpr double A_FIT = 0.9807;
|
||||||
|
constexpr double RP0 = 0.0275; // res^rp(Q) gain term, rp = RP0·Q^drp
|
||||||
|
constexpr double DRP = 0.2159;
|
||||||
|
|
||||||
// Leaky-integrator sweep (FUN_18052d650 body, and inline IIR2/IIR3):
|
// LUT knots (xv = log10(level), level = am/res):
|
||||||
// y = A[i]*acc + B[i]*x[i]; acc = y; x[i] = (float)y (double, in-place)
|
static constexpr double kLX[12] = { -0.75, -0.5012, -0.5, -0.2012, 0.0988, 0.2488,
|
||||||
// A/B are live tables; B = 1 - A. Forward sweep only.
|
0.3988, 0.5488, 0.574, 0.61, 0.75, 1.0 };
|
||||||
void iir_leaky(const double* A, const double* B, float* x, int n) {
|
static constexpr double kLY[12] = { 0.4402, 0.366, 0.4552, 0.459, 0.541, 0.576,
|
||||||
double acc = 0.0;
|
0.608, 0.636, 0.5645, 0.6471, 0.6562, 0.6670 };
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
double y = B[i] * static_cast<double>(x[i]) + A[i] * acc;
|
static double lut_pchip(double x) {
|
||||||
acc = y;
|
int n = 12;
|
||||||
x[i] = static_cast<float>(y);
|
x = std::min(std::max(x, kLX[0]), kLX[n - 1]);
|
||||||
|
// Monotone cubic Hermite (Fritsch–Carlson), matching scipy PchipInterpolator.
|
||||||
|
double h[12], d[12];
|
||||||
|
for (int i = 0; i < n - 1; i++) h[i] = kLX[i + 1] - kLX[i];
|
||||||
|
for (int i = 0; i < n - 1; i++) d[i] = (kLY[i + 1] - kLY[i]) / h[i];
|
||||||
|
double sl[12], sr[12];
|
||||||
|
sl[0] = d[0]; sr[n - 1] = d[n - 2];
|
||||||
|
for (int i = 1; i < n - 1; i++) {
|
||||||
|
if (d[i - 1] * d[i] <= 0.0) { sl[i] = sr[i - 1] = 0.0; continue; }
|
||||||
|
double w1 = 2 * h[i] + h[i - 1], w2 = h[i] + 2 * h[i - 1];
|
||||||
|
sl[i] = (w1 + w2) / (w1 / d[i - 1] + w2 / d[i]);
|
||||||
|
sr[i - 1] = sl[i];
|
||||||
}
|
}
|
||||||
|
int i = std::upper_bound(kLX, kLX + n, x) - kLX - 1;
|
||||||
|
i = std::max(0, std::min(i, n - 2));
|
||||||
|
double hh = h[i], t = (x - kLX[i]) / hh;
|
||||||
|
double t2 = t * t, t3 = t2 * t;
|
||||||
|
double h00 = 2 * t3 - 3 * t2 + 1, h10 = t3 - 2 * t2 + t;
|
||||||
|
double h01 = -2 * t3 + 3 * t2, h11 = t3 - t2;
|
||||||
|
double y = h00 * kLY[i] + h10 * hh * sr[i] + h01 * kLY[i + 1] + h11 * hh * sl[i + 1];
|
||||||
|
return y;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 0x11940 copy/mirror: upper half = reversed lower half (Hermitian-style mirror of
|
// freq-path warp 0x5406a8 (NOTES_LEVEL:181; build_warp): 0.87·K·x/(K+x), K=exp(2.0723).
|
||||||
// [0,n) into [n, nfft)). In-place on a full-spectrum buffer.
|
static double warp_c(double f) {
|
||||||
void mirror_half(float* buf, int n, int nfft) {
|
double x = f / 2000.0;
|
||||||
for (int i = 0; i < n && (nfft - 1 - i) >= n; i++) buf[nfft - 1 - i] = buf[i];
|
return 0.87 * 7.942 * x / (7.942 + x);
|
||||||
}
|
|
||||||
|
|
||||||
// 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). Live state 112 => fVar30 == 1.0 deterministically.
|
|
||||||
double prng_fvar30(int& state) {
|
|
||||||
constexpr unsigned M = 0x8000007f;
|
|
||||||
unsigned s = static_cast<unsigned>(state & 0xffffffff);
|
|
||||||
auto fix = [](unsigned x) {
|
|
||||||
x &= M;
|
|
||||||
if (x & 0x80000000u) x = ((x - 1) | 0xffffff80u) + 1;
|
|
||||||
return x;
|
|
||||||
};
|
|
||||||
s = fix(s + 0x3cdca); // line 515
|
|
||||||
s = fix(s + 0x140236); // line 526
|
|
||||||
s = fix(s + 0x10d56); // line 538
|
|
||||||
s = fix(s + 0xdf6b6); // line 549 (s3 -> fVar30)
|
|
||||||
unsigned s3 = s;
|
|
||||||
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;
|
|
||||||
return static_cast<double>(fv);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -81,7 +76,6 @@ 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);
|
||||||
@@ -103,7 +97,7 @@ void FramedDetector::setParams(const std::vector<DetectorBand>& bands) {
|
|||||||
}
|
}
|
||||||
res_.push_back(std::move(r));
|
res_.push_back(std::move(r));
|
||||||
}
|
}
|
||||||
track_.assign(bands_.size(), std::vector<float>(half + 1, 0.0f));
|
track_.assign(bands_.size(), std::vector<float>(half + 1, 1.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
void FramedDetector::processFrame(const std::complex<double>* spectrum, float* mask) {
|
void FramedDetector::processFrame(const std::complex<double>* spectrum, float* mask) {
|
||||||
@@ -128,95 +122,20 @@ void FramedDetector::processFrame(const std::complex<double>* spectrum, float* m
|
|||||||
am_[k] = static_cast<float>(am);
|
am_[k] = static_cast<float>(am);
|
||||||
}
|
}
|
||||||
|
|
||||||
// PRNG prologue: advance state once per frame; fVar30 -> scale coefficient.
|
|
||||||
double fVar30 = prng_fvar30(prng_state_);
|
|
||||||
|
|
||||||
// Per-band mask chain (FUN_180529fe0 mono path, 0x5408b8==0).
|
|
||||||
// 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 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. scale: 0x540678[band] *= (fVar30/0x1a0)·0x540870·0x54088c (:656)
|
double rp = RP0 * std::pow(static_cast<double>(bands_[b].q), DRP);
|
||||||
// Level = am / res (res = |2B/A| is minimal at band centre).
|
double fk = 0.0;
|
||||||
|
double fstep = (sample_rate_ * 0.5) / static_cast<double>(half);
|
||||||
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 res_k = std::max(static_cast<double>(res_[b][k]), 1e-12);
|
||||||
static_cast<double>(bands_[b].level_scale);
|
double lvl = static_cast<double>(am_[k]) / res_k;
|
||||||
scratch[k] = static_cast<float>(level * (fVar30 / C_0x1a0) *
|
double xv = std::log10(std::max(lvl, 1e-9));
|
||||||
C_0x540870 * C_0x54088c);
|
double C = G_FIT * lut_pchip(xv) + W_FIT * std::pow(warp_c(fk), A_FIT);
|
||||||
}
|
double g = std::max(1.0 - C, 1e-9) * std::pow(res_k, rp);
|
||||||
int n = static_cast<int>(half) + 1;
|
mask[k] = std::min(static_cast<float>(g), mask[k]);
|
||||||
|
fk += fstep;
|
||||||
// 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];
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// 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·(1−mix) + mix·0.8 (0x6e40 + 0x15060, :814-823)
|
|
||||||
// 0x540678[band] = exp2(−0x540678)·0x5406f8 (bigkernel, :832)
|
|
||||||
for (size_t k = 0; k <= half; k++) {
|
|
||||||
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]));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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<double>(scratch[k]);
|
|
||||||
g = g * (C_0x540874 * C_0x540888) + (1.0 - C_0x540874);
|
|
||||||
scratch[k] = static_cast<float>(g);
|
|
||||||
}
|
|
||||||
|
|
||||||
// min-combine across bands.
|
|
||||||
for (size_t k = 0; k <= half; k++) {
|
|
||||||
if (scratch[k] < mask[k]) mask[k] = scratch[k];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (size_t k = half + 1; k < nfft_; k++) {
|
for (size_t k = half + 1; k < nfft_; k++) {
|
||||||
|
|||||||
@@ -948,3 +948,7 @@ VALIDATION (scale sweep):
|
|||||||
dB-domain LUT FIX_180563a60 (level_dB = 20·log10(mask) → t=(dB−A)/(B−A) clamp →
|
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
|
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.
|
BandConfig ctx+0x188 writers (A/B/gamma) + 0x540698 -> then wire into the chain.
|
||||||
|
|
||||||
|
## ============ UPDATE 2026-08-20q: METRIC FIX + LOG-DOMAIN LUT PORTED (honest baseline) ============
|
||||||
|
### CRITICAL METRIC BUG FOUND: 24-bit refs mis-decoded as 16-bit
|
||||||
|
Earlier dual
|
||||||
|
|||||||
Reference in New Issue
Block a user