diff --git a/dsp/CMakeLists.txt b/dsp/CMakeLists.txt index 62da042..7623b5a 100644 --- a/dsp/CMakeLists.txt +++ b/dsp/CMakeLists.txt @@ -25,6 +25,7 @@ add_library(soothe2_dsp SHARED exp2.cpp leveltrack.cpp framed_model.cpp + fnfaith.cpp fn529fe0.cpp rt_weights.cpp rt_mask_tables.cpp diff --git a/dsp/fnfaith.cpp b/dsp/fnfaith.cpp new file mode 100644 index 0000000..e5c275d --- /dev/null +++ b/dsp/fnfaith.cpp @@ -0,0 +1,105 @@ +#include "fnfaith.hpp" +#include +#include + +namespace fnfaith { + +Params params_from_env() { + Params p{}; + p.scale = getenv("RT_FAITH_SCALE") ? atof(getenv("RT_FAITH_SCALE")) : 1.0; + p.kappa = getenv("RT_FAITH_KAPPA") ? atof(getenv("RT_FAITH_KAPPA")) : 0.0094; + p.p = getenv("RT_FAITH_P") ? atof(getenv("RT_FAITH_P")) : 0.822; + p.tau1 = getenv("RT_FAITH_TAU1") ? atof(getenv("RT_FAITH_TAU1")) : 19.03; + p.mult1 = getenv("RT_FAITH_MULT1") ? atof(getenv("RT_FAITH_MULT1")) : 360.0; + p.tau3 = getenv("RT_FAITH_TAU3") ? atof(getenv("RT_FAITH_TAU3")) : 10.68; + p.mult3 = getenv("RT_FAITH_MULT3") ? atof(getenv("RT_FAITH_MULT3")) : 2400.0; + p.tau4 = getenv("RT_FAITH_TAU4") ? atof(getenv("RT_FAITH_TAU4")) : 10.68; + p.mult4 = getenv("RT_FAITH_MULT4") ? atof(getenv("RT_FAITH_MULT4")) : 2400.0; + p.C_hz = getenv("RT_FAITH_C") ? atof(getenv("RT_FAITH_C")) : 1000.0; + return p; +} + +namespace { + +struct FaithCoefs { + std::vector up, down; +}; + +// FUN_180533340 transcription (constants from dump: DAT_24c3d8c=0.5, DAT_24c46b8=-2pi) +FaithCoefs gen_coefs(size_t nbin, double sr, double tau, double mult, double p, + double C_hz, double kappa) { + FaithCoefs c; + c.up.assign(nbin, 0.0); + c.down.assign(nbin, 1.0); + const double srh = sr * 0.5; + const double fcnorm = (C_hz / srh) * (double)nbin; + for (size_t i = 1; i < nbin; i++) { + const double r = fcnorm / (double)i; + const double g = ((double)i <= fcnorm) ? r : std::pow(r, p); + const double cd = 1.0 / (g * tau / mult + 1.0); + double up = std::exp(cd * g * tau * kappa * (-6.283185307179586)); + if (up > 1.0) up = 1.0; + if (up < 0.0) up = 0.0; + c.up[i] = up; + c.down[i] = 1.0 - up; + } + return c; +} + +// FUN_18052d650: acc=0; forward all bins; backward n-2..1, acc persists into bwd. +void bidir(std::vector& accst, const FaithCoefs& cf, float* x, size_t nbin) { + double acc = 0.0; + accst[0] = 0.0; + for (size_t i = 0; i < nbin; i++) { + acc = cf.up[i] * acc + cf.down[i] * (double)x[i]; + x[i] = (float)acc; + } + for (size_t i = (size_t)((long long)nbin - 2); i >= 1; i--) { + acc = cf.up[i] * acc + cf.down[i] * (double)x[i]; + x[i] = (float)acc; + } +} + +} // namespace + +void band_mask_faithful(const float* am, const float* res, size_t nbin, + float sample_rate, float scale_factor, const Params& pr, + float* mask_out) { + static std::vector cache; + static size_t cached_nbin = 0; + static double cached_sr = 0.0; + static Params cached_pr{}; + if (cache.empty() || cached_nbin != nbin || cached_sr != sample_rate || + cached_pr.kappa != pr.kappa || cached_pr.p != pr.p || + cached_pr.tau1 != pr.tau1 || cached_pr.mult1 != pr.mult1 || + cached_pr.tau3 != pr.tau3 || cached_pr.mult3 != pr.mult3 || + cached_pr.tau4 != pr.tau4 || cached_pr.mult4 != pr.mult4 || + cached_pr.C_hz != pr.C_hz) { + cache.clear(); + cache.push_back(gen_coefs(nbin, sample_rate, pr.tau1, pr.mult1, pr.p, pr.C_hz, pr.kappa)); + cache.push_back(gen_coefs(nbin, sample_rate, pr.tau3, pr.mult3, pr.p, pr.C_hz, pr.kappa)); + cache.push_back(gen_coefs(nbin, sample_rate, pr.tau4, pr.mult4, pr.p, pr.C_hz, pr.kappa)); + cached_nbin = nbin; + cached_sr = sample_rate; + cached_pr = pr; + } + + // band curve: lvl_raw scaled + std::vector m(nbin); + for (size_t k = 0; k < nbin; k++) { + double res_k = res[k] > 1e-12 ? (double)res[k] : 1e-12; + double lvl = (double)am[k] / res_k * (double)scale_factor; + m[k] = (float)(std::exp2(-(lvl * pr.scale)) ); + } + + std::vector accst(1, 0.0); + bidir(accst, cache[0], m.data(), nbin); // #1 + bidir(accst, cache[0], m.data(), nbin); // #2 + bidir(accst, cache[1], m.data(), nbin); // #3 + bidir(accst, cache[2], m.data(), nbin); // #4a + bidir(accst, cache[2], m.data(), nbin); // #4b + + for (size_t k = 0; k < nbin; k++) mask_out[k] = m[k]; +} + +} // namespace fnfaith diff --git a/dsp/fnfaith.hpp b/dsp/fnfaith.hpp new file mode 100644 index 0000000..5fd45f6 --- /dev/null +++ b/dsp/fnfaith.hpp @@ -0,0 +1,45 @@ +#pragma once +// FAITHFUL mask-shaping chain (22w) — transcription of FUN_180529fe0 per +// handoff/BLOCKMAP_529fe0.md (raw asm). Replaces process_band_structural +// when RT_FAITHFUL=1. +// +// band = lvl * s (s = RT_FAITH_SCALE, decomp: /0x1a0*0x870*0x88c) +// mask = exp2(-band) (bigkernel exp2 family) +// bidir(mask, A); bidir(mask, A) // states reset each pass (52d650 + inline) +// bidir(mask, B) // pass #3 +// bidir(mask, C); bidir(mask, C) // pass #4 x2 +// mirror upper half; dry/wet identity. +// +// Bidirectional one-pole per FUN_18052d650: acc = up[i]*acc + down[i]*x[i], +// forward over all bins, backward n-2..1 with persistent acc, reset per call. +// Coefficients per FUN_180533340 (freq-warped): fc_bin = C/(sr/2)*nbin; +// g = fc/i below crossover, powf(fc/i, p) above; c = 1/(g*tau/mult+1); +// up[i] = exp(-2*pi*kappa*c*g*tau), down[i] = 1-up[i]. +// +// Decoded defaults (FUN_180530b60 log-interp @ live 0x540878/87c = 0.5): +// setA/B tau=19.03 mult=360 ; set3/set4 tau=10.68 mult=2400 ; p=0.822 ; +// C=1000 Hz ; kappa = 1/T8 ~ 0.0094 (T8 unknown -> env). + +#include +#include + +namespace fnfaith { + +struct Params { + double scale; // RT_FAITH_SCALE + double kappa; // RT_FAITH_KAPPA + double p; // RT_FAITH_P + double tau1, mult1; // state A (#1,#2) + double tau3, mult3; // state B (#3) + double tau4, mult4; // state C (#4 x2) + double C_hz; // crossover (1000) +}; + +Params params_from_env(); + +// Compute lower-half mask [0, nbin) for one band from lvl_raw = am/res*scale_factor. +void band_mask_faithful(const float* am, const float* res, size_t nbin, + float sample_rate, float scale_factor, const Params& pr, + float* mask_out /* nbin */); + +} // namespace fnfaith diff --git a/dsp/framed_model.cpp b/dsp/framed_model.cpp index 239f9d6..50d4ccd 100644 --- a/dsp/framed_model.cpp +++ b/dsp/framed_model.cpp @@ -4,6 +4,7 @@ #include "rt_mask_tables.hpp" #include "rt_weights.hpp" #include "fn529fe0.hpp" +#include "fnfaith.hpp" #include #include #include @@ -414,10 +415,22 @@ void FramedDetector::processFrame(const std::complex* spectrum, float* m for (size_t k = 0; k <= half; k++) mask[k] = 1.0f; if (is_internal_grid(nfft_, sample_rate_)) { + // RT_FAITHFUL=1 (NOTES 22w): BLOCKMAP_529fe0 transcription path + static const int faithful = getenv("RT_FAITHFUL") ? atoi(getenv("RT_FAITHFUL")) : 0; + static const fnfaith::Params fparams = faithful ? fnfaith::params_from_env() + : fnfaith::Params{}; + // same scale_factor as process_band_structural (line ~79) + constexpr float sf = 15.0f * 440.95f / 2048.0f; for (size_t b = 0; b < bands_.size(); b++) { std::vector band_mask(nfft_, 1.0f); + if (faithful) { + fnfaith::band_mask_faithful(am_.data(), res_[b].data(), half + 1, + sample_rate_, sf, fparams, + band_mask.data()); + } else { process_band_structural(am_.data(), res_[b].data(), bands_[b], band_mask.data(), nfft_, sample_rate_); + } for (size_t k = 0; k <= half; k++) { mask[k] = std::min(band_mask[k], mask[k]); } diff --git a/handoff/NOTES_LEVEL.md b/handoff/NOTES_LEVEL.md index 33bc3fc..2038445 100644 --- a/handoff/NOTES_LEVEL.md +++ b/handoff/NOTES_LEVEL.md @@ -2113,3 +2113,39 @@ res-кривые на сетке 0.25 Гц — а детекция может и ### Инструментарий lawfit22r.py: +iir_c параметр (бидир-IIR по бинам в реплее), +res в npz; sim_errors кэширует по (Q,rp,iir_c). + +## ============ UPDATE 2026-08-23d (22w): FAITHFUL-ЦЕПЬ v1 — ИНФРАСТРУКТУРА ГОТОВА, ДЕТЕКТОР ГЛАВЕН ============ + +Инфраструктура (dsp/fnfaith.{hpp,cpp}, env RT_FAITHFUL=1 в render48k): +транскрипция маско-формирующей цепи по BLOCKMAP_529fe0: band=lvl·s → exp2(-band) +→ бидир-IIR ×2 (setA tau19/mult360) → бидир (setB tau10.7/mult2400) → ×2 (setC). +Коэффициенты — точная транскрипция FUN_180533340: частотный варп +fc_bin=(1000/(sr/2))·nbin (=85 @48k/4096!), g=fc/i ниже кросса, степенной выше, +up=exp(-2π·κ·c·g·tau), down=1-up. Константы из дампа: DAT_24c46b8=-2π, +24c3d8c=0.5 (sr→Найквист), mult 1200/800/360, тау-интерполяция 15↔9 и 9↔180. +Все параметры — env (RT_FAITH_*). Combine acc в v1 опущен (нет консюмера, 22u). + +### Смоук-свипы (t1kq1000 / dual500 / dual2000 / al12; err dB) +- κ=0.05,s=8: t1kq −25 / dual500 −58 / **dual2000 −2.84** — тон-конкурент ВПЕРВЫЕ + дотягивается до реала, но центральный тон улетает в перерез. +- κ=0.2..0.5: насыщение (up→0 на низких бинах), баланса пары нет нигде; + Δerr(500−2000) ≈ 15-20 дБ во всех точках (в реале ~1.5). +- Корпус (κ=0.02, s=2): TOTAL 5.315 (t1kq 1.35 / t1k 1.55 / al 0.89 / + res 2.87 / dual 11.52 / comb **2.57** — лучший comb в истории проекта, + базлайн 4.34!). Ресурс мультиполосной механики виден. + +### ВЫВОД (решающий) +Форма маски НЕ может сбалансировать dual: контраст lvl между тонами создаётся +САМИМ ДЕТЕКТОРОМ (am/res из STFT), а не пост-обработкой. Реальный детектор +даёт почти равные уровни драйва на 500 и 2000 Гц при q=0.5 ⇒ гипотеза (а) +из 22u — ПРИОРИТЕТ №1: уровни считаются во ВРЕМЕННОЙ области (банк twin- +фильтров 536300 строит res-кривые на сетке 0.25 Гц — вероятно, и уровни тоже +через них), либо тяжёлый кросс-бин пулинг ДО кривой полосы. +Смена канона НЕТ; faithful-путь остаётся за флагом для следующих экспериментов. + +### NEXT +1. Детектор: декод FUN_180536300 до конца (scratch-лейаут известен, NOTES_TWIN) + → понять, откуда берутся bands[] кривые на входе 529fe0. +2. comb 2.57 при живом faithful — разобраться после детектора. +3. RT_DUMP_BIN-трасса живого плагина на dual-конфиге (покадрово mask/lvl) — + прямой ответ на вопрос «какой lvl реально у тона 2000».