diff --git a/dsp/framed_model.cpp b/dsp/framed_model.cpp index a375d0b..a44e37c 100644 --- a/dsp/framed_model.cpp +++ b/dsp/framed_model.cpp @@ -102,10 +102,40 @@ static void process_band_structural( // hypothesis: audio path has NO LUT (FUN_180563a60 was GUI-only, 22b); // mask = blend*exp2(-lvl_raw) directly. static const int lut_off = getenv("RT_LUT_OFF") ? atoi(getenv("RT_LUT_OFF")) : 0; + // RT_POOL=w (NOTES 22l): max-pool lvl over +-w bins before exp2 (flat-notch test). + // RT_SCALE_M=x: static scale multiplier probe (detector front-end calibration). + static const int pool_w = getenv("RT_POOL") ? atoi(getenv("RT_POOL")) : 0; + static const double scale_mult = getenv("RT_SCALE_M") ? atof(getenv("RT_SCALE_M")) : 1.0; + const double scale_factor_x = scale_factor * scale_mult; + + std::vector lvl_in(nbin); + for (size_t k = 0; k < nbin; k++) { + double res_k = std::max(static_cast(res[k]), 1e-12); + lvl_in[k] = static_cast(static_cast(am[k]) / res_k * scale_factor_x); + } + if (pool_w > 0 && !lut_off == false) {} + if (pool_w > 0) { + std::vector pooled(nbin); + for (size_t k = 0; k < nbin; k++) { + size_t lo = (k > (size_t)pool_w) ? k - pool_w : 0; + size_t hi = std::min(nbin - 1, k + (size_t)pool_w); + float mx = 0.0f; + for (size_t j = lo; j <= hi; j++) mx = std::max(mx, lvl_in[j]); + pooled[k] = mx; + } + lvl_in.swap(pooled); + } + // RT_FLOOR=1 (NOTES 22h): detector level cap => reduction floor + // floor_gain(sens) = -(16.78+sens/3)/6.0174*6.0174 dB => lvl_cap below + static const int floor_on = getenv("RT_FLOOR") ? atoi(getenv("RT_FLOOR")) : 0; + if (floor_on) { + float cap = (16.78f + band.sens / 3.0f) / 6.0174f; + for (size_t k = 0; k < nbin; k++) if (lvl_in[k] > cap) lvl_in[k] = cap; + } for (size_t k = 0; k < nbin; k++) { double res_k = std::max(static_cast(res[k]), 1e-12); - double lvl = static_cast(am[k]) / res_k * scale_factor; + double lvl = lvl_in[k]; if (!lut_off) { // dB-domain LUT (FUN_180563a60) on LEVEL before IIR/exp2: keeps both // quiet (t1kq) and loud (t1k) inputs inside the LUT domain [A,B], diff --git a/handoff/NOTES_LEVEL.md b/handoff/NOTES_LEVEL.md index 6240ab2..3e991bd 100644 --- a/handoff/NOTES_LEVEL.md +++ b/handoff/NOTES_LEVEL.md @@ -1732,3 +1732,31 @@ HF-rolloff −1.5@8k (вероятно WIN_freq окно FFT-conv — табли 1. Декомп амплитудной цепочки: wsum/am нормировка (spectral.cpp), масштаб окна. 2. Twin-адаптация: проверить динамику res_k по длине тона (рендеры 0.5/2/8 c). 3. WIN_freq rolloff: добавить окно FFT-conv в модель (registry [01] 0.5→1.0). + +## ============ UPDATE 2026-08-22l: POOL+SCALE+FLOOR — тон точен до 0.02 дБ, корпус 5.9 ============ + +Новая архитектура (все за env-флагами, канон не тронут — санити TOTAL 2.286 ✓): + RT_LUT_OFF=1 RT_IIR12=0 RT_NOWARP=1 RT_NOBLEND=1 RT_NOIIR3=1 + RT_POOL=5 RT_SCALE_M=1.144 RT_FLOOR=1 + lvl_in = pool_max(lvl_raw, ±5 бин) · 1.144; cap = (16.78+sens/3)/6.0174 + mask = exp2(−min(lvl_in,cap)) + +Результаты: +- TONE floor: **−20.74 dB vs real −20.72** (Δ0.02!) при pool∈{4,5}, scale 1.144. +- White noise: mean|err| 0.63 dB (форма плоская ✓), но перерез центра полосы + (−3.54 vs −1.04 @678). +- Корпус 62: TOTAL **5.905** (t1k 60→7.2 благодаря полу; t1kq 5.57 — тихие случаи + теперь перерезаются scale/pool). Пока ХУЖЕ канона 2.286/bridge 1.594. + +### Интерпретация +Архитектурно верно (пол/пин/форма тона), но константы (pool width, scale 1.144, +cap-закон) сняты с ОДНОГО случая (hot tone trim+12) и не обобщаются на тихие. +Масштаб 1.144 требует источника в декомпе (золотое правило) — кандидаты: wsum +(sqrt-Hann vs др.), формула scale_factor, sens внутри twin. + +### NEXT +1. Систематический фит (pool_w, scale, cap-параметры a/b в (a+b·sens)) по МИНИМИЗАЦИИ + corpus TOTAL — офлайн через render48k прогонки (быстро, ~2 мин/конфиг). +2. Источник ×1.144: декоп wsum/am нормировки детектора (spectral.cpp vs decomp). +3. Разобраться с перерезом центра полосы на шуме (pool слишком широкий для шума? + может, pooling по МАКСИМУМУ неверно — попробовать contrast-vs-pool комбинацию).