145 lines
9.0 KiB
Markdown
145 lines
9.0 KiB
Markdown
# 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
|
||
```
|
||
|
||
## Документация
|
||
|
||
| Документ | Содержание |
|
||
|---|---|
|
||
| [roadmap.md](roadmap.md) | Журнал всех результатов/гипотез (B.1…B.15), статус по фазам, риски, открытые вопросы |
|
||
| [handoff/SESSION_HANDOFF.md](handoff/SESSION_HANDOFF.md) | Инвентарь декомпа (§0), трансляция/ключевые адреса (§2), Phase-5 план (§6) |
|
||
| [handoff/NOTES_TWIN.md](handoff/NOTES_TWIN.md) | Twin-резонатор (FUN_180535880/536f90), caller, grid/oversample |
|
||
| [handoff/NOTES_LEVEL.md](handoff/NOTES_LEVEL.md) | Level-path, LUT-нога, res_power протокол, спектральные веса |
|
||
| [handoff/nls_dasm/](handoff/nls_dasm/) | 120+ дизассемблей (f_563440, f_563ce0, f529fe0, twin, iface, fft) |
|
||
| [notes_giant_fft.md](notes_giant_fft.md) | FFT-планировщики/ядра/twiddle/Cody-Waite |
|
||
| [summary.md](summary.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 <proj> soothe_x64 -process soothe_mem.bin
|
||
-noanalysis -postScript <X>.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/стерео-путь. |