201 lines
14 KiB
Markdown
201 lines
14 KiB
Markdown
# soothe2-re
|
||
|
||
Обратный инжиниринг DSP-ядра **oeksound soothe2** (VST3, Windows x64) → проверяемая
|
||
реконструкция на C++ с **bit-exact** целью.
|
||
|
||
Цель — понять, как именно плагин считает подавление резонансов (уровневый детектор,
|
||
маска, фильтр), и воспроизвести это дословно. Текущий активный канон — C++
|
||
`FramedDetector` (`dsp/framed_model.cpp`): real mask-apply цепь с live-таблицами.
|
||
Канонные команды и метрика — в [`AGENTS.md`](AGENTS.md).
|
||
|
||
---
|
||
|
||
## Статус (P5, 2026-08-22)
|
||
|
||
**Цель — bit-exact реверс.** Декомпиляция DSP-ядра закрыта (~95%): twin-резонатор,
|
||
генератор case8, level-path, mask-apply (FUN_180529fe0), FFT-conv — расшифрованы;
|
||
дизассемблы в `handoff/nls_dasm/` (~140).
|
||
|
||
**Активный канон — C++ `FramedDetector`** (`dsp/framed_model.cpp`): структурная цепь
|
||
```
|
||
level=am*res*scale -> IIR1/IIR2 leaky -> mask=exp2(-level)*blend -> combine/acc
|
||
-> warp(mask*=0x540768, *=warp) -> IIR3x2 -> dry/wet -> FFT-conv
|
||
```
|
||
- **Level-tracker = би-направленный leaky-IIR** `y=A[i]·acc+B[i]·x` (B=1−A), live-таблицы
|
||
в `dsp/rt_mask_tables.{hpp,cpp}`, `dsp/rt_weights.{hpp,cpp}`.
|
||
- **Корпус (62 случая)**: bridge mean 1.594 dB; структурная цепь TOTAL 2.286,
|
||
но comb **6.12** и res **0.44** — лучше bridge.
|
||
|
||
### Главное за 2026-08-21…22 (сессии 21a–22i)
|
||
|
||
1. **«LUT-кривая» FUN_180563440/563a60 — GUI-only!** Весь кластер кривых питается от
|
||
GUI-timer vtables; аудио-метод FUN_180529fe0 BandConfig не читает (22b). Live BandConfig
|
||
у всех конфигов одинаковый: A=−24/B=28/γ=1. LUT-константы в модели помечены EMPIRICAL.
|
||
2. **Пол редукции найден живьём и решён алгебраически** (22d/e): hot-тон упирается в жёсткий
|
||
пол gain=−20.72 dB = `20·log10(blend·ln10/20)` с точностью 0.0055 dB;
|
||
`floor_dB(sens) ≈ −18.78 − (sens−6)/3` (якорь sens6 = ln10/20 ровно).
|
||
3. **Разрыв локализован в детекторном фронте** (22g/i): модель теряет ×2.8 уровня на
|
||
изолированных пиках (lvl_raw 3.01 → 1.06 после IIR1-разведения), реальный плагин
|
||
доводит пост-IIR lvl до ~3.12. Не форма кривой — амплитудная цепочка am/res/scale.
|
||
4. **Инфраструктура**: официальный параметр-мост REAPER (`setparam.lua`/`dump_params.lua`),
|
||
XML `<PARAM>` в RPP = декоративная копия; live-capture BandConfig (`scripts/step7_capture.py`);
|
||
env-солверы констант (`RT_LUT_A/B/G/MULT`, `RT_LUT_OFF`, `RT_LVL_CAP`).
|
||
5. Оффлайн-гипотезы Phase B (pooling/temporal/scalar-ρ) — все опровергнуты (22a);
|
||
clipping-теория пола опровергнута (float-домен, 22g); mix = чистый dry/wet кроссфейд.
|
||
|
||
```bash
|
||
# Канон сборки и рендера:
|
||
cmake -S dsp -B dsp/build && cmake --build dsp/build --target framed_test render48k
|
||
./dsp/build/render48k /home/m/soothe-bt/tone1kq.wav /tmp/o48.wav 1000,0.99999785,12
|
||
python3 scripts/corpus_structural.py --vs-bridge scripts/baseline_bridge.json
|
||
```
|
||
Детальная метрика и история — в `AGENTS.md`, `BITEXACT_PLAN.md`, `handoff/NOTES_LEVEL.md`
|
||
(апдейты 20j…22i).
|
||
|
||
### Открытые bit-exact пробелы
|
||
**Приоритет №1 — детекторный фронт**: амплитудная нормировка am, форма res_k, сила
|
||
IIR1/2 вдоль частоты (модель теряет уровень на пиках). Далее: PRNG-пролог (LCG→fVar30),
|
||
FFT-conv сглаживание, бит-экзактный exp2, combine/acc консюмер, SR-mismatch
|
||
(внутренний DSP 48000/N=4096 против хоста). Полный список — `AGENTS.md`, `BITEXACT_PLAN.md`
|
||
(Шаг 9), `NOTES_LEVEL.md`.
|
||
|
||
> Исторический блок (поведенческая/численная модель B.1…B.15, `framed_render.py`,
|
||
> Pchip LUT, res_power) — см. `roadmap.md`; самодостаточен как справочник, но не канон.
|
||
|
||
<details><summary>Было (B.15 — историческая численная модель)</summary>
|
||
|
||
`framed_render.py` (N=2048, hop=512, sqrt-Hann, twin-env 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) метрика: Q-dep rp → mean 0.175 dB; B.15 joint free-knot LUT → dual 0.027 dB.
|
||
- Декомп DSP-ядра закрыт, `.dis` в `handoff/nls_dasm/`.
|
||
|
||
```bash
|
||
python3 framed_render.py dual # (исторический канон B.15)
|
||
```
|
||
</details>
|
||
|
||
---
|
||
|
||
## Структура репозитория
|
||
|
||
```
|
||
re-tools/
|
||
├── README.md ← вы здесь
|
||
├── AGENTS.md ← гайд для агентов: сборка, метрика, bit-exact пробелы
|
||
├── roadmap.md ← журнал всех результатов/гипотез (B.1…B.15, P4)
|
||
├── framed_render.py ← историческая численная модель (B.15, не канон)
|
||
├── 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, mask-цепь, live-таблицы (САМЫЙ АКТУАЛЬНЫЙ)
|
||
│ ├── NOTES_CAPTURE.md ← live-захват таблиц (registry heartbeat)
|
||
│ └── nls_dasm/ ← 134 дизассембла декомпа (f_563440, f_563a60, f529fe0, twin, fft…)
|
||
│
|
||
├── dsp/ ← реконструкция DSP-пайплайна на C++17
|
||
│ ├── framed_model{.cpp,.hpp} ← ГЛАВНЫЙ: mask-apply цепь (P4, активный канон)
|
||
│ ├── framed_test.cpp ← CLI рендер входа (N=2048, SR 44100) + метрика
|
||
│ ├── rt_mask_tables{.hpp,.cpp}, rt_weights{.hpp,.cpp} ← live-таблицы (IIR A/B, warp…)
|
||
│ ├── twin{.hpp,.cpp} ← бит-точный twin-резонатор (FUN_180535880)
|
||
│ ├── levelpath.cpp/.hpp ← LUT-кривая FUN_180563440/563a60 + combine-ядра
|
||
│ ├── freqpath.cpp/.hpp ← warp FUN_180530850 (0.87·x/(1+x/K))
|
||
│ ├── fftconv.cpp/.hpp ← FFT-conv (0x535a70)
|
||
│ ├── spectral.cpp/.hpp ← WOLA/STFT-обработчик
|
||
│ ├── 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 ← дамп памяти плагина, вне 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 (исторические, B-модели)
|
||
├── Поведенческие симуляторы: 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
|
||
├── Параметр-мост и live-capture: setparam.lua, dump_params.lua,
|
||
│ play.lua, scripts/step7_capture.py, scripts/rpp_setparam.py,
|
||
│ scripts/corpus{,_structural}.py, scripts/phaseA/B*.py
|
||
└── summary.md, notes_giant_fft.md
|
||
```
|
||
|
||
## Документация
|
||
|
||
| Документ | Содержание |
|
||
|---|---|
|
||
| [AGENTS.md](AGENTS.md) | **Старт для агента**: сборка, метрика, bit-exact пробелы, структура |
|
||
| [BITEXACT_PLAN.md](BITEXACT_PLAN.md) | **Путь к bit-exact**: порядок работ (8 шагов), критерии, риски, точка входа |
|
||
| [roadmap.md](roadmap.md) | Журнал всех результатов/гипотез (B.1…B.15, P4), статус по фазам, риски |
|
||
| [handoff/SESSION_HANDOFF.md](handoff/SESSION_HANDOFF.md) | Инвентарь декомпа (§0), трансляция/ключевые адреса (§2), Phase-5 план (§6) |
|
||
| [handoff/NOTES_LEVEL.md](handoff/NOTES_LEVEL.md) | **Level-path/mask-цепь, live-таблицы, bit-exact протокол** (самый актуальный) |
|
||
| [handoff/NOTES_TWIN.md](handoff/NOTES_TWIN.md) | Twin-резонатор (FUN_180535880/536f90), caller, grid/oversample |
|
||
| [handoff/NOTES_CAPTURE.md](handoff/NOTES_CAPTURE.md) | Live-захват таблиц (registry heartbeat, SR 48000) |
|
||
| [handoff/nls_dasm/](handoff/nls_dasm/) | 134 дизассембла (f_563440, f_563a60, f529fe0, twin, iface, fft) |
|
||
| [notes_giant_fft.md](notes_giant_fft.md) | FFT-планировщики/ядра/twiddle/Cody-Waite |
|
||
| [summary.md](summary.md) | **Историческая** сводка поведенческой модели (v4, sim.py) |
|
||
|
||
### Тестовый корпус `/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) |
|
||
|
||
---
|
||
|
||
## Историческая численная модель (B-фазы; канон теперь — C++ FramedDetector, см. выше)
|
||
|
||
```python
|
||
# per-frame, per-bin (framed_render.py — исторический канон B.15)
|
||
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].
|
||
|
||
---
|
||
|
||
## Как всё это воспроизвести
|
||
|
||
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-замер, тримнутая метрика).
|
||
|
||
---
|
||
|
||
## Чего не хватает / следующие шаги (bit-exact)
|
||
|
||
Текущий фокус — **детекторный фронт** (Шаг 9 плана): амплитудная нормировка am,
|
||
форма res_k, сила IIR1/2 вдоль частоты — модель теряет ×2.8 уровня на изолированных
|
||
пиках относительно реального плагина (NOTES_LEVEL 22g/22i). Открытое:
|
||
- **Детекторный фронт** (приоритет №1, Шаг 9) — см. выше.
|
||
- **PRNG-пролог** FUN_180529fe0 (LCG+LUT → fVar30) — per-frame рандомизация scale/dry-wet.
|
||
- **FFT-conv** (0x535a70) — сглаживание маски перед FIR (P3).
|
||
- **Бит-экзактный exp2** (0x26b820) вместо `std::exp2`.
|
||
- Точная обратная связь combine/аккумулятора `0x5407c8` (консюмер не найден).
|
||
- **SR-mismatch**: внутренний DSP 48000/N=4096 против хоста 44100/2048.
|
||
- Стерео-путь M8 (link/balance/LR-vs-MS), межполосное суммирование (Шаг 8). |