# soothe2-re Обратный инжиниринг DSP-ядра **oeksound soothe2** (VST3, Windows x64) → проверяемая численная модель и реконструкция на C++. Цель — понять, как именно плагин считает подавление резонансов, и воспроизвести его поведение (в идеале бит-точно, но пока — с точностью ~0.03–0.18 dB на формах полос и уровнях). --- ## Статус (B.15, август 2026) **Полный STFT-рендер** пайплайна работает в `framed_render.py` (N=2048, hop=512, sqrt-Hann, per-bin twin-envelope tatt=11ms/trel=80ms). Модель: `C(f_k)=g·LUT(log10(A_k/res_k)) + w·warp(f_k)^a`, `gain=(1−C)·res^(rp0·Q^drp)`. - **Честная (trimmed) метрика** — длина выхода = длина входа (не-тримнутые замеры давали ложный сдвиг ~0.2 dB, исправлено в B.14). Базлайн для scalar-rp = 0.280 dB. - **Q-dependent rp** `rp(Q)=rp0·Q^drp` (rp0=0.0275, drp=0.2159) → **mean=0.175 dB**: q0.1 и q10 почти идеальны, боттлнек q1@2000 (−0.71 dB). - **JOINT free-knot LUT** (8 узлов Pchip, G/W/A/rp0/drp) → **dual mean≈0.027 dB**, q1@2000 закрыт до 0.000; остаток al_* lv24 dC=+0.10 (зона xv<−0.3). - **Декомп DSP-ядра в основном закрыт**: twin-резонатор, генератор case8, level-weight (0x530d30 — численно НЕ create tilt, подтверждено при N=2048), LUT-кривая FUN_180563440 (linear/power-law по флагу), IIR-трекеры FUN_180563ce0, FFT-conv 0x535a70 — всё размаплено, `.dis` в `handoff/nls_dasm/`. ```bash # Текущий канон (Q-dep rp, trimmed): python3 framed_render.py dual ``` --- ## Структура репозитория ``` re-tools/ ├── README.md ← вы здесь ├── roadmap.md ← журнал всех результатов/гипотез (B.1…B.15) ├── framed_render.py ← КАНОНИЧЕСКИЙ frame-рендер (STFT+LUT+warp+res_power) ├── render_parity.py ← dB-parity харнесс (Goertzel steady-state замер) ├── model_lut.py, model_dual.py ← исторические модели B.10/B.11 (collapse-фиты) ├── model_fir.py ← bridge-модель B.12 (C=g·LUT+w·warp^a) │ ├── handoff/ ← перекидка между сессиями │ ├── SESSION_HANDOFF.md ← инвентарь декомпа, трансляция, Phase-5 план │ ├── NOTES_TWIN.md ← twin-резонатор + caller + grid │ ├── NOTES_LEVEL.md ← level-path, LUT-нога, res_power протокол │ └── nls_dasm/ ← 120+ дизассемблей (f_563440, f_563ce0, f529fe0, twin…) │ ├── dsp/ ← реконструкция DSP-пайплайна на C++17 │ ├── twin{.hpp,.cpp} ← бит-точный twin-резонатор (FUN_180535880) │ ├── spectral.cpp/.hpp ← WOLA/STFT-обработчик │ ├── detect.cpp/.hpp ← детектор резонансов │ ├── filter.cpp/.hpp ← биквад-фильтры │ ├── fft*.cpp, twiddle_*.cpp/hpp, phase_table.* ← FFT-планы/твилдлы │ ├── ms.hpp ← encode/decode mid/side │ ├── cody_waite.hpp ← быстрый sin/cos (FUN_1801de760/1e3f20) │ ├── soothe_constants.hpp ← константы из дампа │ ├── harness.cpp ← CLI: input.wav → output.wav (24-bit) │ └── CMakeLists.txt │ ├── *.java ← Ghidra-скрипты (analyzeHeadless, пост-скрипты) ├── ghidra-proj/ ← Ghidra-проект (soothe2.gpr/.rep), вне git ├── soothe_mem.bin ← дамп памяти плагина (frida), вне git ├── rwin_{A0,A1,B0,C0}.npy, r_freqaxis.npy ← живые таблицы (48k) из runtime-снимков │ ├── Измерение и фиты полосы: measure.py, probe.py, bandshape.py, fit_*.py, │ model_lut.py, notch.py ├── Поведенческие симуляторы: sim.py, sim_v5.py, verify_sim.py ├── Инструменты REAPER-рендеров: sweep.py, run_sweep.py, tt_sweep.py, │ patchparam.py, addfx.lua ├── Живая трассировка (yabridge-host + Frida): dump_soothe.py, rtall/rtscan/ │ rtsig/rttbl/rtver/rtone/rtwin/rtdeep*.py, probe.py, procdump.py └── summary.md, notes_giant_fft.md ``` ### Тестовый корпус `/home/m/soothe-bt/` (вне git) ~600 рендеров REAPER (`.rpp` + `.wav`). Ключевые наборы: | Префикс | Что измеряет | |---------|-------------| | `dual_b1q_*` | 2 тона (500+2000), q 0.1…10 → **парадокс red2000>red500** (решён: аддитивный warp) | | `al_*` | уровневой свип (fc=1000) → прямое измерение LUT-кривой | | `t1kq_*`, `t1k_*` | 1 тон, fc-скан, уровни −18/0 dBFS → форма полосы | | `burst500_b1` | основной эталон: burst 500 Гц, band1 fc=500 mode=1 sens=12 | | `tone1k`, `tone1kq`, `dual` | входные тоны (0 / −18 / −7.1 dBFS) | --- ## Модель в трёх строках ```python # per-frame, per-bin (framed_render.py — канон) xv = log10(A_k / res_k) # A_k = 2|X_k|/wsum (twin-env), res = |2B/A| case8 C = G * LUT(xv) + W * warp(f_k)**A # additive mask (НЕ мультипликация warp·LUT) gain = max(1 - C, eps) * res_k**rp # rp = rp0 * Q**drp (res_power) ``` - LUT — Pchip, узлы из joint-фита (dual+al_*) **или** свободные 8 узлов (B.15 → mean 0.027); - warp — эмпирический `0.87·7.942·x/(7.942+x)`, x=f/2000 (аппроксимация runtime-вычисления FUN_180563440); - rp(Q)=0.0275·Q^0.2159 (Q-dep rp, B.14); - B.15: G/W/A/rp0/drp = 0.9752/0.3394/1.0222/0.0254/0.2231; LUT KX=[−0.8..1.0]. Проверить: `python3 framed_render.py dual` (mean 0.175 для Q-dep rp). --- ## Как всё это воспроизвести 1. **Достать бинарь**: установить soothe2 (Windows VST3) под yabridge, запустить `dump_soothe.py` → `soothe_mem.bin` (или взять `.vst3`, выдернуть секции). 2. **Декомпиляция**: `analyzeHeadless soothe_x64 -process soothe_mem.bin -noanalysis -postScript .java` (image_base=0, блок `ram`=[0x180000000,…], адреса = RVA; смещение в файле = VA−0x180000000). 3. **Рендеры**: `sweep.py` генерит `.rpp`, `reaper -renderproject` выдаёт `.wav`. 4. **Анализ**: `render_parity.py` / `framed_render.py` сравнивают модель с реальными рендерами (steady-state tone-замер, тримнутая метрика). --- ## Чего не хватает / следующие шаги - **Структурная LUT FUN_180563440** вместо Pchip: закрыть остаток al_* lv24 (dC≈+0.10, зона xv<−0.3) и q1@500 (+0.06). - **Бит-точная сверка**: собрать `dsp/harness.cpp`, прогнать `burst500_b1.wav` и сверить байт-в-байт (сейчас модель — численная, rmse 0.03–0.18 dB). - C++ порт res_power + Q-dep rp + свободных LUT-узлов (тривиально: `gain *= pow(res, rp0·Q^drp)`). - Семантика IAT-хелперов `0x181a14xxx` (exp/log/pow) и AVX-ядер — целевые адреса вне дампа. - Полный конвейер WOLA/oversample (offline 3×) и sidechain/стерео-путь.