Files
soothe2-re/roadmap.md
T

159 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Roadmap: bit-exact реверс DSP-ядра oeksound soothe2 (v1.1.2)
## Цель
Воспроизвести DSP-путь soothe2 в виде **собираемого standalone C++** с **побайтово
идентичным** выходом (bit-exact) на верификационных свипах, с пониманием логики
и подписанными функциями.
## Результат
`re-tools/dsp/``*.cpp/*.h` (классы DSP, подписаны), `CMakeLists.txt`, `harness.cpp`
(WAV16 → рендер → WAV24), `dsp_notes.md` (карта функция↔смысл, константы, LUT),
`verify_bit_exact.py` (прогон всех свипов, побайтовое сравнение).
## Текущий статус (2026-08-19)
- **Статический декомп DSP-ядра — закрыт (~90%)**: twin-резонатор, генератор case8,
level-path (0x530d30/0x529fe0), LUT-кривая (FUN_180563440), IIR-трекеры (FUN_180563ce0),
FFT-conv (0x535a70) декодированы; `.dis` в `handoff/nls_dasm/`. Остаток — runtime-значения
(окно 0x540658, sens-источник, IIR update-loop).
- **Численная модель = frame-рендер `framed_render.py`** (STFT N=2048, hop=512, sqrt-Hann,
twin-env tatt=11ms/trel=80ms): `C(f)=G·LUT(xv)+W·warp(f)^A`, `gain=(1C)·res^(rp0·Q^drp)`.
- **Честная тримнутая метрика** (длина выхода = длина входа): Q-dep rp → **mean=0.175 dB**
(q0.1/q10 идеальны, боттлнек q1@2000=0.71); joint free-knot LUT (B.15) → **dual mean≈0.027 dB**.
- **Цель bit-exact (Phase C) — в работе**: twin-транскрипция в `dsp/` готова, порт
res_power/Q-dep-rp/LUT — TODO.
- **ВАЖНО про метрику**: ранние записи "mean=0.160/0.144" считались на выходе НЕтримнутой
длины (`nfr*HOP+N`) и были артефактом; честный базлайн для scalar `rp=0.0169` = **0.280**.
Все числа в этом файле — на ТРИМНУТОЙ метрике.
## Модель в двух строках
```python
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; warp — `0.87·7.942·x/(7.942+x)`, x=f/2000 (аппроксимация runtime-вычисления FUN_180563440);
- Q-dep rp: `rp0=0.0275, drp=0.2159` (mean 0.175); B.15 joint-free-knot LUT: G/W/A/rp0/drp =
`0.9752/0.3394/1.0222/0.0254/0.2231` (dual mean 0.027).
- Запуск канона: `python3 framed_render.py dual`.
## Статус по фазам
- **Фаза 0** — сделано (OCR стр.30 manual → `pipeline_ocr.txt`, топология ниже).
- **Фаза A (статический RE)** — закрыта ~90%. Карта «что декодировано → где детали»:
| Компонент | Функция | Детали |
|---|---|---|
| Twin-резонатор | FUN_180535880/536f90 | `handoff/NOTES_TWIN.md`, `dsp/twin.*` |
| Генератор case8 | FUN_180533ec0 (RBJ bell, middle=2cos w0) | `handoff/SESSION_HANDOFF.md §4` |
| Writer коэффициентов | FUN_1805316e0 (17 case, mode→тип полосы) | `NOTES_LEVEL.md`, B.7 |
| Level-weight | FUN_180530d30 (численно НЕ даёт tilt) | `NOTES_LEVEL.md`, B.9/B.14 |
| LUT-кривая | FUN_180563440 (linear/power-law, 1024 бин) | `handoff/nls_dasm/f_563440.dis`, B.13 |
| IIR level-трекеры | FUN_180563ce0 (init: 341 бин, α=0.1) | `handoff/nls_dasm/f_563ce0.dis`, B.13 |
| Mask-применитель | FUN_180529fe0 (PRNG, depth, mask 1C) | `handoff/nls_dasm/f529fe0.dis` |
| FFT / twiddle / Cody-Waite | FUN_180008140 / FUN_180039b00 / 1801de760 | `notes_giant_fft.md`, `dsp/fft*` |
- **Фаза B (численная модель)** — пройдена до B.15: от B.10 (rmse 0.109 dB) до full frame-рендера
Q-dep rp (0.175) и joint free-knot LUT (dual 0.027). Канон — `framed_render.py`.
- **Фаза C (C++ bit-exact)** — in progress: `dsp/twin.*` готов и проходит float-parity (1.27e-5);
остальное построено, но res_power/Q-dep-rp/LUT/verify_bit_exact — TODO.
## Ключевые находки (итог)
- **Маска аддитивная**: `C = g·LUT(xv) + w·warp^a`, НЕ мультипликация warp·LUT (та проваливается >10 dB).
warp-terм ≈ `warp^3.14·w` — кратный каскад freq-axis ∏0x540688/FFT-conv.
- **res_power**: `gain = (1C)·res^rp` — закрывает 500Hz-residual для q0.1/q1 (envRmse 0.64→0.05 dB).
- **0x530d30 дважды NEGATIVE**: спектральные веса численно ≈0 (10⁻⁴⁰…10⁻²⁰⁰) → тилт 2000-vs-500
из них НЕ идёт (подтверждено при N=1024 и N=2048).
- **Форма LUT**: Pchip-эмпирика была немонотонна (провал при xv≈−0.5); настоящая кривая
FUN_180563440 — гамма/монотонная; при замене на монотонную (free-knot или структурную)
q1@2000 закрывается до 0.000.
- **Константы из дампа**: SCALE=1/1024, ONE=1.0, TWO=2.0, NEG1=1.0, HALF=0.5, DEPTH_SCALE=4.0,
DB_CONV=8.6859, FLOOR=6.9078, IIR α=0.1, LCG_OFFSET=0x3cdca.
## Контракт из manual (`soothe2_ManualFAQ.pdf`, v1.0.0)
| # | Факт | Следствие для DSP |
|---|---|---|
| M1 | Soft/Hard: режим меняет всё; depth 3.0 в soft ≠ hard (стр.7) | mode = глобальный препроцессор детектора |
| M2 | Depth — референтный dB (−18..18), реальная глубина до 60dB (стр.8) | вход depthcurve-LUT; clip 60dB |
| M3 | Sharpness: выше = глубже и уже (стр.8) | Q_notch растёт с sharp |
| M4 | Selectivity: 0 = «everything must go» (стр.9) | отбор/порог пиков, число нотчей НЕ лимитирует |
| M5 | Attack/Release: реальные времена частотно-зависимы (стр.11) | Env НЕ один one-pole; нужен частотный масштаб |
| M6 | Oversample = расчёт reduction-фильтра в высшем разрешении (стр.11) | интерполяция детекторной сетки → фильтра |
| M7 | Resolution = частота обновления детекции/фильтра (стр.11) | период пересчёта коэффициентов |
| M8 | Stereo: link 100% = сумма каналов; 0% = dual mono; balance; L/R vs M/S | детектор на sum; коррекция симметрична/раздельна |
| M9 | Trim регулирует ТОЛЬКО wet; Delta = xwet; Mix 0..100% (стр.14) | порядок: detect→notch→trim→mix/delta/bypass |
| M10 | Банды: 2 cut + 4 general; general: peak/shelf/reject/tilt (стр.17-19) | `FilterGraph<float,6,0x400>`, `DigitalFilter` мультитипный |
| M11 | Кривая = inverse EQ, сумма band-кривых = белая (стр.17) | `red(f)=base+ΣW_i·H_i` |
| M12 | Sidechain; Input trim — скрытый предусилитель анализа (стр.26) | отдельный вход/гейн анализа |
## Топология пайплайна (Приложение A, стр.30 — `pipeline_ocr.txt`)
```
main inputs ──┐
sidechain ────┤ (sidechain enabled?)
input trim ──► analysis (детекция; depth/sharpness/selectivity; EQ из per-band balances)
stereo apply + balance (channel link + global balance)
ms encode? ──► processing (нотч-синтез в M/S) ──► ms decode?
mix ──► bypass? ──► output
```
- mid/side ручка: обработка в M/S-области — стерео-путь подтверждён диаграммой.
- GUI/visualisation — побочная ветвь, в DSP-ядро не входит (кроме параметров).
## Журнал вех (детали — в handoff/ и git log)
| Веха | Дата | Суть / ключевые числа |
|---|---|---|
| B.1 | 08-17 | C++ каркас `dsp/*` (twin, spectral, filter, detect, ms, fft, cody, twiddle, harness) |
| B.2 | 08-17 | `libsoothe2_dsp.so` + harness WAV16→WAV24; фиксы WAV-алиасинга / in-place |
| B.3 | 08-17 | TODO: twiddle-loader FUN_18014ec20 (Dekker+Cody-Waite), bit-reverse 18003b6c0, транскрипция fft.cpp |
| B.4 | 08-17 | burst500: corr 0.995, SNR 19.8 dB, diff 0.065 dB; детектор = уровнезависимый floor |
| B.5 | 08-17 | FUN_180535880/536f90 — реальный SSE-код в дампе; .vst3 совпадает с дампом в `.text` |
| B.6 | 08-17 | Dispatch-модель исправлена (у каждого стаба своя таблица); полный twin-алгоритм; артефакты `det_*.dis` |
| B.7 | 08-18 | band-структура (+0x800 mode/0x804 freq/0x808 sens/0x80c Q), writer 5316e0 (17 case); XML mode=1 → case8 (RBJ bell) |
| B.8 | 08-18 | Коррекция middle=2cos(w0); `mask(f)=|2B/A|(fc,Q,gain)`; freq-path 530850 + warp, level-path 56e3e0 |
| B.9 | 08-18 | Level-path замкнут (потребитель 529fe0, аккумулятор 0x5407c8); формула 530d30; warp-ось sigmoid; конструктор 535ae0 (0x24=40000) |
| B.10 | 08-18 | Единая модель `C=depth·tilt·D0·(L0/res)^p`; парадокс dual_b1q решён (tilt-наклон); rmse 0.109 dB (31 pt); `model_dual.py` |
| B.11 | 08-18 | LUT-кривая извлечена (36 pt на одну кривую); PCHIP 10 узлов rmse 0.0718 dB; колено x≈0.58; `model_lut.py` |
| B.12 | 08-18 | Bridge-модель `C=g·LUT(xv)+w·warp^a` (a≈π, аддитивно), rmse 0.236; FFT-conv размаплен; freq-axis offline const; `model_fir.py` |
| Ph.5 | 08-18 | `render_parity.py`; dB-parity 36 pt rmse 0.268 dB (рефит 0.203); остаток структурный (0x540658) |
| B.13 | 08-19 | res_power `gain=(1C)·res^rp` (500Hz resid решён); декомп 563440/563ce0/529fe0; константы из дампа |
| B.14 | 08-19 | **Артефакт-коррекция** (честная тримнутая метрика; базлайн scalar=0.280); Q-dep rp → mean 0.175; 530d30 повторно NEGATIVE (N=2048) |
| B.15 | 08-19 | Диагноз LUT-немонотонности → joint free-knot LUT (8 узлов, dual+al_*) → **dual mean≈0.027 dB**; остаток al_* lv24 dC=+0.10 |
## Риски
- **FFT bit-exact**: DSP-STFT FFT найден и транскрибирован; главный нерешённый риск — порядок
double-операций в dispatch-ядрах при побайтовом сравнении.
- **Хост-зависимость**: точный размер буфера/фрейма (рендеры при разных RENDER_RANGE должны
давать одинаковые байты).
- **Stereo**: все текущие свипы mono; для bit-exact графа M8 нужны стерео-рендеры (link/balance/ms).
- **Честная метрика**: тримнутая длина выхода обязательна (артефакт B.14); fit-цели проверять на
`render_trim`/`framed_render.synthe`.
## Открытые вопросы
1. **Форма LUT в зоне xv<0.3**: joint-fit даёт dual≤0.07, но al_* lv24 dC=+0.10 — нужна структурная
кривая FUN_180563440 (linear/power-law) вместо Pchip-эмпирики.
2. **warp-терм**: эмпирическая формула — аппроксимация runtime-вычисления FUN_180563440;
для bit-exact заменить на реальный расчёт.
3. **Стерео-верификация (M8)** — приоритет mono-путь или сразу стерео-граф.
4. **0x540658 window** — статически невидим; живые копии `rwin_*.npy` (48k) требуют warp-нормализации
под 44.1k рендеры.
## Где лежат детали
- `handoff/SESSION_HANDOFF.md` — инвентарь декомпа (§0), трансляция/ключевые адреса (§2), Phase-5 план (§6).
- `handoff/NOTES_TWIN.md` — twin-резонатор, caller, grid/oversample.
- `handoff/NOTES_LEVEL.md` — level-path, LUT-нога, res_power протокол, спектральные веса.
- `handoff/nls_dasm/` — 120+ дизассемблей (f_563440, f_563ce0, f529fe0, twin, iface, fft).
- `notes_giant_fft.md` — FFT-планировщики/ядра/twiddle/Cody-Waite.
- `git log` — полная журнальная хронология с номерами коммитов.