From 2f854cd1dae0baedc10346d75f81f0550ac44460 Mon Sep 17 00:00:00 2001 From: Matiq Date: Wed, 2 Sep 2026 23:19:34 +0300 Subject: [PATCH] spectral: vectors instead of new[]; vlaw: extract law + vlaw_check target MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - spectral.cpp: window_/buf_/tmp_buf_/fir_buf_/fir_freq_ as std::vector (no exception-leak in ctor, destructor = default) - framed_model.hpp: extract vlaw_cut/vlaw_mask inline (BLOCKMAP:314 softplus) - dsp/vlaw_check.cpp: unit test for law (monotonic, zero-level, delta, ref, comb-neutral) — PASS - CMake: add vlaw_check target - Guard: corpus --compare d=+0.000, fn529fe0_check PASS, twin_check PASS --- dsp/CMakeLists.txt | 2 + dsp/framed_model.cpp | 7 ++-- dsp/framed_model.hpp | 12 ++++++ dsp/spectral.cpp | 30 ++++++--------- dsp/spectral.hpp | 10 ++--- dsp/vlaw_check.cpp | 55 +++++++++++++++++++++++++++ handoff/NOTES_LEVEL.md | 73 +++++++++++++++++++++++++++++++++++- handoff/NOTES_LEVEL_INDEX.md | 3 +- 8 files changed, 163 insertions(+), 29 deletions(-) create mode 100644 dsp/vlaw_check.cpp diff --git a/dsp/CMakeLists.txt b/dsp/CMakeLists.txt index 0714031..13c67ea 100644 --- a/dsp/CMakeLists.txt +++ b/dsp/CMakeLists.txt @@ -42,11 +42,13 @@ add_executable(vlog_check vlog_check.cpp) add_executable(leveltrack_check leveltrack_check.cpp) add_executable(levelpath_check levelpath_check.cpp) add_executable(exp2_check exp2_check.cpp) +add_executable(vlaw_check vlaw_check.cpp) add_executable(fn529fe0_check fn529fe0_check.cpp) target_link_libraries(twin_check soothe2_dsp) target_link_libraries(framed_test soothe2_dsp) target_link_libraries(render48k soothe2_dsp ${SAMPLERATE}) target_link_libraries(exp2_check soothe2_dsp) +target_link_libraries(vlaw_check soothe2_dsp) target_link_libraries(fn529fe0_check soothe2_dsp) target_link_libraries(tables_check soothe2_dsp) target_link_libraries(fftconv_check soothe2_dsp) diff --git a/dsp/framed_model.cpp b/dsp/framed_model.cpp index fdd8eb8..775affa 100644 --- a/dsp/framed_model.cpp +++ b/dsp/framed_model.cpp @@ -316,10 +316,9 @@ static void process_band_structural( if (delta_state) { } for (size_t k2 = 0; k2 < nbin; k2++) { - double cs = vlaw_alpha * std::log1p(static_cast(raw_level[k2]) / vlaw_beta) - + vlaw_c - + (delta_mark[k2] ? vlaw_delta : 0.0); - band_level[k2] = static_cast(std::pow(10.0, -cs / 20.0)); + band_level[k2] = static_cast(vlaw_mask( + static_cast(raw_level[k2]), vlaw_alpha, vlaw_beta, + vlaw_c, delta_mark[k2] ? vlaw_delta : 0.0)); } frame_dbg_ctr++; } else diff --git a/dsp/framed_model.hpp b/dsp/framed_model.hpp index 8c3109e..d3f209c 100644 --- a/dsp/framed_model.hpp +++ b/dsp/framed_model.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include #include "fn529fe0.hpp" @@ -45,6 +46,17 @@ inline double lut_parametric(double x, double A, double B, double gamma) { return A + (B - A) * 0.5 * (1.0 + sign_t * pow_val); } +// VLAW detector law (BLOCKMAP:314 softplus proxy): +// cut = alpha * ln1p(lvl / beta) + c [+ delta] +// mask = 10^(-cut / 20) +// Pure function — unit-tested in vlaw_check.cpp. +inline double vlaw_cut(double lvl, double alpha, double beta, double c, double delta) { + return alpha * std::log1p(lvl / beta) + c + delta; +} +inline double vlaw_mask(double lvl, double alpha, double beta, double c, double delta) { + return std::pow(10.0, -vlaw_cut(lvl, alpha, beta, c, delta) / 20.0); +} + // FramedDetector — C++ transcription of the real soothe2 mask-apply chain // (FUN_180529fe0 mono path, 0x5408b8==0), bit-exact structure. diff --git a/dsp/spectral.cpp b/dsp/spectral.cpp index 6798e60..45de8d5 100644 --- a/dsp/spectral.cpp +++ b/dsp/spectral.cpp @@ -12,13 +12,13 @@ SpectralProcessor::SpectralProcessor(size_t nfft, size_t hop, float sample_rate) : nfft_(nfft), hop_(hop), frame_count_(0), output_pos_(0), detector_(nfft, sample_rate) { - window_ = new double[nfft_]; + window_.resize(nfft_); computeWindow(); fft::init_plan(&plan_, static_cast(std::log2(nfft_))); - buf_ = new std::complex[nfft_]; - tmp_buf_ = new std::complex[nfft_]; - fir_buf_ = new std::complex[nfft_]; - fir_freq_ = new std::complex[nfft_]; + buf_.resize(nfft_); + tmp_buf_.resize(nfft_); + fir_buf_.resize(nfft_); + fir_freq_.resize(nfft_); overlap_.resize(nfft_, 0.0f); mask_.resize(nfft_, 1.0f); @@ -30,13 +30,7 @@ SpectralProcessor::SpectralProcessor(size_t nfft, size_t hop, float sample_rate) } } -SpectralProcessor::~SpectralProcessor() { - delete[] window_; - delete[] buf_; - delete[] tmp_buf_; - delete[] fir_buf_; - delete[] fir_freq_; -} +SpectralProcessor::~SpectralProcessor() = default; void SpectralProcessor::setDetectorParams(const std::vector& bands) { detector_.setParams(bands); @@ -63,8 +57,8 @@ void SpectralProcessor::stftFrame(const float* in, std::complex* out) { } void SpectralProcessor::istftFrame(std::complex* in, float* out, float* overlap) { - memcpy(tmp_buf_, in, nfft_ * sizeof(std::complex)); - fft::execute_inverse(&plan_, tmp_buf_); + memcpy(tmp_buf_.data(), in, nfft_ * sizeof(std::complex)); + fft::execute_inverse(&plan_, tmp_buf_.data()); static bool wola_computed = false; static float wola_norm = 1.0f; // RT_SYN: 0=synthesis window = analysis window (WOLA), 1=none @@ -279,9 +273,9 @@ void SpectralProcessor::processBlock(float* in, float* out, size_t num_samples, for (size_t f = 0; f < nframes; f++) { size_t offset = f * hop_; if (offset + nfft_ > num_samples) break; - stftFrame(in + offset, buf_); + stftFrame(in + offset, buf_.data()); - detector_.processFrame(buf_, mask_.data()); + detector_.processFrame(buf_.data(), mask_.data()); if (firconv == 3) { // RT_FIRCONV=3 (NOTES 24k): plugin application law decoded live: @@ -296,7 +290,7 @@ void SpectralProcessor::processBlock(float* in, float* out, size_t num_samples, // RT_FIRCONV=2: Full FIR construction pipeline (52b550-52b8bb). // mask → reciprocal (1/mask) → window → normalize → complex multiply. // This replicates the plugin's FFT-conv FIR design path. - buildFirFromMask(mask_.data(), fir_freq_, nfft_); + buildFirFromMask(mask_.data(), fir_freq_.data(), nfft_); // Complex multiply FIR × audio spectrum for (size_t i = 0; i < nfft_; i++) { buf_[i] *= fir_freq_[i]; @@ -317,6 +311,6 @@ void SpectralProcessor::processBlock(float* in, float* out, size_t num_samples, } } - istftFrame(buf_, out + offset, overlap_.data()); + istftFrame(buf_.data(), out + offset, overlap_.data()); } } diff --git a/dsp/spectral.hpp b/dsp/spectral.hpp index 35b0537..535076e 100644 --- a/dsp/spectral.hpp +++ b/dsp/spectral.hpp @@ -21,12 +21,12 @@ public: private: size_t nfft_; size_t hop_; - double* window_; + std::vector window_; FFTPlan plan_; - std::complex* buf_; - std::complex* tmp_buf_; - std::complex* fir_buf_; - std::complex* fir_freq_; + std::vector> buf_; + std::vector> tmp_buf_; + std::vector> fir_buf_; + std::vector> fir_freq_; std::vector fir_window_; std::vector overlap_; std::vector mask_; diff --git a/dsp/vlaw_check.cpp b/dsp/vlaw_check.cpp new file mode 100644 index 0000000..a85196e --- /dev/null +++ b/dsp/vlaw_check.cpp @@ -0,0 +1,55 @@ +#include +#include +#include "framed_model.hpp" + +// Unit check for the VLAW detector law (BLOCKMAP:314 softplus proxy): +// cut = alpha * ln1p(lvl/beta) + c [+ delta] +// mask = 10^(-cut/20) +// Reference values hand-computed from the dual-calibrated constants +// (alpha=3.2193, beta=0.4927, c=0.5423, delta=6.9177 — README.md:26). +int main() { + int fail = 0; + + // --- law monotonicity: higher level -> stronger cut -> smaller mask --- + double m0 = vlaw_mask(0.01, 3.2193, 0.4927, 0.5423, 0.0); + double m1 = vlaw_mask(1.0, 3.2193, 0.4927, 0.5423, 0.0); + double m2 = vlaw_mask(10.0, 3.2193, 0.4927, 0.5423, 0.0); + bool mono = (m0 > m1) && (m1 > m2); + std::printf("vlaw monotonic: m(0.01)=%.4f m(1)=%.4f m(10)=%.4f (%s)\n", + m0, m1, m2, mono ? "OK" : "MISMATCH"); + if (!mono) fail = 1; + + // --- zero level: cut = c => mask = 10^(-c/20) --- + double mz = vlaw_mask(0.0, 3.2193, 0.4927, 0.5423, 0.0); + double ez = std::pow(10.0, -0.5423 / 20.0); + bool zok = std::fabs(mz - ez) < 1e-9; + std::printf("vlaw zero-level: mask=%.6f expect=%.6f (%s)\n", + mz, ez, zok ? "OK" : "MISMATCH"); + if (!zok) fail = 1; + + // --- delta branch adds cut -> deeper mask --- + double md = vlaw_mask(1.0, 3.2193, 0.4927, 0.5423, 6.9177); + bool dok = md < m1; + std::printf("vlaw delta: mask+delta=%.4f < %.4f (%s)\n", + md, m1, dok ? "OK" : "MISMATCH"); + if (!dok) fail = 1; + + // --- numeric reference: lvl=1.0, dual params --- + // cut = 3.2193 * ln(1 + 1/0.4927) + 0.5423 + double cut_ref = 3.2193 * std::log1p(1.0 / 0.4927) + 0.5423; + double mref = std::pow(10.0, -cut_ref / 20.0); + bool rok = std::fabs(m1 - mref) < 1e-9; + std::printf("vlaw ref: mask=%.6f expect=%.6f cut=%.4f (%s)\n", + m1, mref, cut_ref, rok ? "OK" : "MISMATCH"); + if (!rok) fail = 1; + + // --- comb neutrality: alpha=0.05 beta=5.0 c=0 -> mask ~ 1 for lvl=0 --- + double mc = vlaw_mask(0.0, 0.05, 5.0, 0.0, 0.0); + bool cok = std::fabs(mc - 1.0) < 1e-9; + std::printf("vlaw comb-neutral: mask(0)=%.6f expect=1.0 (%s)\n", + mc, cok ? "OK" : "MISMATCH"); + if (!cok) fail = 1; + + std::printf("vlaw_check %s\n", fail ? "FAIL" : "PASS"); + return fail; +} diff --git a/handoff/NOTES_LEVEL.md b/handoff/NOTES_LEVEL.md index b0c5e5e..ef696e9 100644 --- a/handoff/NOTES_LEVEL.md +++ b/handoff/NOTES_LEVEL.md @@ -1,4 +1,4 @@ -# NOTES_LEVEL — живой журнал детектора/маски (голова 24mm5+, 2026-08-28) +# NOTES_LEVEL — живой журнал детектора/маски (голова 25a+, 2026-09-02) > **Архив 2026-08-18 — 2026-08-23 (4024 строки) → [`handoff/archive/NOTES_LEVEL_2026-08-18_2026-08-23.md`](archive/NOTES_LEVEL_2026-08-18_2026-08-23.md).** > Оглавление по датам/темам → [`handoff/NOTES_LEVEL_INDEX.md`](NOTES_LEVEL_INDEX.md) (единственный навигатор). @@ -395,3 +395,74 @@ winetrace_casc/chain_samples.pkl, kind∈{CIN,COUT,AIN,AOUT,COPY,EXP,DF0}). - w ≈ 0.015 (scalar, НЕ 0.977) - Каскад STATEFUL: bands_curve сохраняется между кадрами - 5407a8 = accumulator, НЕ нулевой при рекуррентности + +## ============ 25a (2026-09-02): chain_9_19 staged, IIR4-генератор, k-mapping dedup, audit fixes ============ + +### Сессия-контекст +Workflow `/next-task` (Q1=Local handoff, Q2=All, Q3=Implemented/Deployed). +Задачи взяты из рейтинга `BITEXACT_PLAN.md` + аудита. Всё gated `RT_*`, +guard `corpus.py --compare baseline_bridge.json --tol 0.25` держит d=+0.000. + +### 25a1. chain_9_19 интеграция (коммиты 9ed22d3, 2a295af, 96816f9, b41d7a4) +- `dsp/fn529fe0.cpp:chain_9_19` — полная цепь: LOG#1→DIVIDE(1803a06a0 + b/a)→dc40(bands−ACC)→FMA ATT/REL (half-split)→EXP#1−1→×track→×kWarp→ + LOG#2→IIR4×2→FIR 52b3cd→EXP#2. `track` — новый параметр (ctx+0x540768). +- **IIR4 = DOUBLE precision** (дизасм `f529fe0_full.dis:1191-1201`: + `movsd/mulsd/addsd/cvtpd2ps`; down@0x340510, up@0x3c0510, acc@0x440510). +- `generate_iir4_coefs()` — генератор FUN_180533340 (BLOCKMAP:135-150): + freq-warp `g = (i<=fc_norm) ? fc_norm/i : pow(fc_norm/i,p)`; live-константы + `DAT_1824c3d8c=0.9994880557060242`, `DAT_1824c46b8=0.9991304874420166`. + ВАЖНО: `up[i] = exp(−c·g·tau/mult)` (знак МИНУС, иначе Inf), `down=1−up`. + Smoke: down0=1, up0=0, down1=0.6292. Gated `RT_IIR4_GEN=1` + (env: RT_IIR4_C/TAU/P/MULT/SR, дефолты 1000/1200/0.5/360/48000). +- Интеграция: `framed_model.cpp` `RT_CASC=1` gate; вход — `raw_level` + (am/res·scale). `process_band_structural` получил `track` параметр. +- **Результаты**: bridge 1.594 (gated OFF, не тронут); structural 2.689 + (RT_CASC=0); RT_CASC=1 → 31.6 (НЕКАЛИБРОВАНО, ожидаемо). +- **fn529fe0_check**: добавлены chain smoke + generate_iir4 smoke; PASS. + Попутно починен тест haar_one_pass boundary (8.0 не 8.5). + +### 25a2. VLAW per-fc: continuous вместо дискретной решётки (f2cc0ae) +- `framed_model.cpp:283-298`: 800→1200 q<1 теперь lerp `t=(fc−800)/400` + (5.0/0.4 → 4.5/0.35) вместо дискретных веток. +- sens-эффект теперь ADDITIVE offset (12→0, 6→−0.48α, 24→+0.5α) вместо + перезаписи, стиравшей fc-выбор (баг старой решётки: sens всегда побеждал). +- Gates не сдвинулись (1.594/2.689) — полный перефит требует campaign.py + (8 мин/ячейка, `ph*.npz`) — вне scope. + +### 25a3. k-mapping dedup (197f5ed) +- `k_mapping_factor(fc,q,sens)` — вынос двух идентичных блоков + (lvl_in + raw_level) в helper. `k_fc=1.0` + opt-in `RT_KMAP_FC` (W_eq). +- RT_KMAP=1 не влияет на TOTAL (2.689) — ожидаемо, т.к. калибровка + α/β уже поглощает k на sens=12. + +### 25a4. Аудит `/audit-project` — Phases 1+2 применены (e4c5348) +- Убран dead conditional `if (pool_w > 0 && !lut_off == false) {}`. +- `DBG_CASC` fprintf в per-frame цикле → gated `RT_DBG_CASC`. +- Убран дублирующий zero-loop в `spectral.cpp buildFirFromMask`. +- Комментарии о разнице шкал FFT (1/N canonical vs 2/half plugin). +- Документирован blend 0.8 (декомп 0x5406f8, xmm10@1824c3e28). +- `assert(spectrum != nullptr)` в processFrame. +- НЕ применено (осознанно): thread_local→context (конфликт с golden rule — + chain нужен reentrant caller), SpectralProcessor new[]→vector, Config + struct для getenv — оставлено как техдолг. + +### 25a5. Блокеры (без изменений) +- ph*.npz нет на этой машине; live-dump не работает (маск-цепь при загрузке, + BLOCKMAP:285; wine DR EIO). Chain калибровка ждёт capture при INIT. +- prd.md создан (карта проекта, 293 строки). +- Structural baseline в README/prd: 2.689 (L/R), исторический 0.341 + недостижим на HEAD после M/S→L/R (e343b0a). + +### Коммиты сессии +``` +e343b0a prd.md + render48k L/R baseline +9ed22d3 chain_9_19: IIR4 double + FIR, haar fix +2a295af chain integrate RT_CASC gate +96816f9 chain: IIR4 generator + audio path +2201a57 docs: baseline 2.689 +f2cc0ae vlaw continuous per-fc + additive sens +b41d7a4 chain: RT_IIR4_GEN wire + smoke tests +197f5ed k_mapping_factor dedup +e4c5348 audit fixes Phases 1+2 +``` diff --git a/handoff/NOTES_LEVEL_INDEX.md b/handoff/NOTES_LEVEL_INDEX.md index df9cc70..163307d 100644 --- a/handoff/NOTES_LEVEL_INDEX.md +++ b/handoff/NOTES_LEVEL_INDEX.md @@ -1,6 +1,6 @@ # NOTES_LEVEL — оглавление журнала -> **Навигатор:** `NOTES_LEVEL.md` — живая голова (24mm5+, 2026-08-28, 389 строк). Архив 2026-08-18—2026-08-23 → [`handoff/archive/NOTES_LEVEL_2026-08-18_2026-08-23.md`](archive/NOTES_LEVEL_2026-08-18_2026-08-23.md) (4024 строки). Статус TOTAL → [`README.md:13`](../README.md). +> **Навигатор:** `NOTES_LEVEL.md` — живая голова (25a+, 2026-09-02, ~470 строк). Архив 2026-08-18—2026-08-23 → [`handoff/archive/NOTES_LEVEL_2026-08-18_2026-08-23.md`](archive/NOTES_LEVEL_2026-08-18_2026-08-23.md) (4024 строки). Статус TOTAL → [`README.md:13`](../README.md). ## Живая голова (`handoff/NOTES_LEVEL.md`) @@ -15,6 +15,7 @@ | 24mm12 | Детекторный каскад vt+0x28=180529c60 | Оркестратор 5300f0, vtable карта, 0x281 байт | | 24mm13 (+доп) | Хелперы каскада 529c60 | 5355d0→16140, 530080, 20f0/1850/1a00, рекуррентия Haar | | 24mm14 | Каскад декодирован — 3 фазы | `|z|` → Haar×2 → peak/sin/w/blend 5407a8 | +| 25a (2026-09-02) | chain_9_19 staged, IIR4-генератор, k-mapping dedup, аудит | IIR4 double (movsd), generate_iir4_coefs, RT_CASC/RT_IIR4_GEN gates, baseline 2.689 | ## Архив (`handoff/archive/NOTES_LEVEL_2026-08-18_2026-08-23.md`) — по периодам