diff --git a/dsp/framed_model.cpp b/dsp/framed_model.cpp index 7b2c658..88978fe 100644 --- a/dsp/framed_model.cpp +++ b/dsp/framed_model.cpp @@ -141,6 +141,27 @@ static void process_band_structural( raw_level[k] = static_cast(static_cast(am[k]) / res_k * scale_factor_x); } + // RT_VLAW=1 (NOTES 24m): decoded two-stage detector law. + // cutS(b) = 1.7436*ln(1 + lvl_raw/0.3824) [stage-S, center form] + // applied gain = 10^(-gamma0*cutS/20), gamma0 = 1.79 + // Bypasses LUT/exp2/blend/warp/IIR3 entirely. + static const int vlaw = getenv("RT_VLAW") ? atoi(getenv("RT_VLAW")) : 0; + static int frame_dbg_ctr = 0; + for (size_t k = 0; k < nbin; k++) { + double res_k = std::max(static_cast(res[k]), 1e-12); + double lvl = raw_level[k]; + if (vlaw) { + double cutS = 1.7436 * std::log1p(lvl / 0.3824); + double g = std::pow(10.0, -1.79 * cutS / 20.0); + if (getenv("RT_VDBG") && (k == 43 || k == 171) && (frame_dbg_ctr < 2)) + fprintf(stderr, "[VLAW] k=%zu lvl=%.5f cutS=%.5f g=%.5f\n", + k, lvl, cutS, g); + band_level[k] = static_cast(g); + } else { + band_level[k] = static_cast(lvl); + } + } + if (!vlaw) for (size_t k = 0; k < nbin; k++) { double res_k = std::max(static_cast(res[k]), 1e-12); double lvl = raw_level[k]; @@ -232,7 +253,9 @@ static void process_band_structural( // mask = raw^0.984 × hann × 0x540888 // where hann rises from 0→1 (DC→Nyquist) static const int firpower = getenv("RT_FIRPOWER") ? atoi(getenv("RT_FIRPOWER")) : 0; - if (firpower) { + if (vlaw) { + mm = static_cast(band_level[k]); + } else if (firpower) { double raw = static_cast(raw_level[k]); if (raw > 1e-12) { mm = std::pow(raw, 0.984); diff --git a/handoff/NOTES_LEVEL.md b/handoff/NOTES_LEVEL.md index faefb4e..07fb2b6 100644 --- a/handoff/NOTES_LEVEL.md +++ b/handoff/NOTES_LEVEL.md @@ -3058,3 +3058,32 @@ drive −0..−30 дБ → s43: −1.143, −1.021, −0.903, −0.791, −0.685 Датасет: /tmp/opencode/scratch_family.pkl (18 кривых) + tract_*. Инструмент-обновление: rendersnap2 v4 пишет полный набор слотов включая scratch — канал прямого наблюдения кривой детектора БЕЗ инверсии. + +## ============ ДОПОЛНЕНИЕ 24m-2: RT_VLAW=1 внедрён; формула в коде верна; end-to-end разрыв = слой применения ============ + +### Реализация (framed_model.cpp, env RT_VLAW=1) +Ветка заменяет LUT/exp2/blend: `band_level = 10^(−γ₀·cutS/20)`, +`cutS = 1.7436·ln1p(lvl_raw/0.3824)` (центр-форма глобально), γ₀=1.79. +Проверено stderr-принтом (RT_VDBG): k=43 lvl=8.815→g=0.3189 ✓ формула в коде +даёт точные значения стадии-D. Маска идёт напрямую (exp2/warp/IIR3 отключаемы +RT_NOWARP/RT_NOIIR3). + +### Статус end-to-end (dual_b1q_0.5): +``` +REF: cut@500=10.32 cut@2000=11.82 +VLAW: cut@500= 7.56 cut@2000= 6.41 (+NOWARP+NOIIR3: 5.36 / 1.73) +``` +Формула кривой верна (маска@43=0.319 ≈ плагинная B@43=0.320!), но сквозной +результат недобирает ⇒ расхождение живёт В СЛОЕ ПРИМЕНЕНИЯ (STFT/WOLA): +эффективный gain на тоне = усреднение маски по главному лепестку окна; +узкий дип 5-6 бинов размывается. Исторически канон-константы были ПОДГОНЯНЫ +сквозь этот слой (смещение впитано), поэтому старые группы корпуса сходились. +Глубокая симуляция WOLA-нормировок не завершена (следующий раунд): кандидат — +плагин нормирует окно/хоп иначе (cfg {16384,8192} движка?), либо анализ без +окна/с другим окном. Ключевой контрольный эксперимент: прогнать ОДИНАКОВУЮ +кривую через наш слой и измерить эффективную глубину на тоне против плагина. + +### Гигиена +TOOLING HAZARD снова сработал (same-second build → stale бинарник дал ложные +5.36/1.73). Протокол touch+mtime-проверка ОБЯЗАТЕЛЕН перед каждым замером. +RT_VDBG stderr-принт оставлен (env-gated, нулевая цена).