- fft.cpp: build_twiddle via soothe::twiddle_load (Cody-Waite sin/cos); drop dup init_plan - fft_stage.cpp: cplx_mul/stage_complex/stage_double kernels -> phase fixed (corr +0.995) - detect.cpp: level-dependent regional floor (no bell-boost), mask 10^(-1.041*depth*floor/20) - burst500 metrics: ref -26.07 / ours -26.13 dBFS, corr 0.99475, SNR 19.80 dB, diff -0.065 dB - KEY: FUN_180535880/536f90 bodies are decrypted real SSE in soothe_mem.bin; dispatch table 0x182616008[0]=idx=4 -> 0x180009860 -> FUN_180040d40; region 0x18004xxxx = full detector algorithm, absent from prior fun_map/decomp (Ghidra ran on encrypted file)
271 lines
24 KiB
Markdown
271 lines
24 KiB
Markdown
# 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` (прогон всех свипов, побайтовое сравнение).
|
||
|
||
## Задел (уже есть)
|
||
- Ghidra-проект `ghidra-proj/soothe2.rep` + скрипты `Dump*.java`/`ImportRtti*.java`/`Diag.java`.
|
||
- RTTI иерархия DSP: `SpectralProcessor<float,7,1>`, `Soothe2ModuleBase<float,1>`,
|
||
`Soothe2Module<float,1>`, `FilterGraph<float,6,0x400>`, `FilterGraphGrid<float>`,
|
||
`DigitalFilter<float,0xBA,1>`, `IIRFilterExtended<float,1>`, `AudioProcessingModule<float,1>`
|
||
(`rtti_dsp.json`, `rtti_full.json`).
|
||
- Декомпиляция vtable-слотов DSP-классов (`decomp_dsp.txt`, `decomp_vtables.txt`,
|
||
`decomp_candidates.txt`) — 6634 строки C, 127 неразрешённых `FUN_*`, ~30 констант `DAT_1824c*`.
|
||
- Поведенческая модель `sim_v5.py` (RMSE ≈ 0.3dB): base_red, W(f)=6.02·min(1,sens/12)·H,
|
||
H=1/√(1+(Qeff·A)²), Qeff=1.54·q^1.33, sat(level), Q_notch=qn·(1−g)+1, per-frame env, WOLA-маска.
|
||
- LUT: depthcurve (207 float @0x1826170e8, `0.302+0.698·sin(π/2·x)^0.94`, r²=0.99999).
|
||
- Рендер-свипы в `/home/m/soothe-bt/` (506 wav + 609 rpp) — эталон для bit-exact.
|
||
- BFS-замыкание call-graph от сидов: `fun_map.txt` (2035 функций), `decomp_funs.txt` (1640 декомпилировано,
|
||
312K), `consts.txt` (11602 констант) — после фикса `getInstructions()` в `DumpFuns.java`.
|
||
|
||
## Контракт из manual (`soothe2_ManualFAQ.pdf`, v1.0.0)
|
||
|
||
| # | Факт | Следствие для DSP |
|
||
|---|---|---|
|
||
| M1 | Soft/Hard: выбор режима меняет всё; depth 3.0 в soft ≠ hard (стр.7). Soft менее level-dependent | mode = глобальный препроцессор детектора (перемасштаб глубины/масштаба) |
|
||
| M2 | Depth — референтный dB (−18..18), реальная глубина до 60dB (стр.8) | вход depthcurve-LUT; clip 60dB = `red.clip(0,60)` в sim |
|
||
| M3 | Sharpness: выше = глубже и уже (стр.8) | Q_notch растёт с sharp; подтверждает `Q≈qn·(1−g)` |
|
||
| M4 | Selectivity: выше = только сильные резонансы; 0 = «everything must go» (стр.9) | selectivity = отбор/порог пиков, число нотчей НЕ ограничивает |
|
||
| M5 | Attack/Release: референтные константы, **реальные времена частотно-зависимы**, атака быстрее на ВЧ (стр.11) | Env НЕ один one-pole; нужен частотный масштаб времени — замер τ(f) + код |
|
||
| M6 | Oversample = «расчёт reduction-фильтра в более высоком спектральном разрешении» (стр.11) | это интерполяция детекторной сетки → фильтра, НЕ up/downsample аудио |
|
||
| M7 | Resolution = «частота обновления детекции и фильтра» (стр.11), eco/high/ultra | период пересчёта коэффициентов фильтра |
|
||
| M8 | Stereo: link 100% = сумма каналов для анализа + общий фикс; 0% = dual mono; balance глоб+per-band; L/R vs M/S | детектор на sum; коррекция симметрична/раздельна |
|
||
| M9 | Trim: «регулирует ТОЛЬКО wet»; Delta = x−wet; 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 / side-chain EQ», сумма band-кривых = белая кривая (стр.17) | `red(f)=base+ΣW_i·H_i` подтверждён |
|
||
| M12 | Sidechain: детект по второму входу; Input trim — скрытый предусилитель анализа (стр.26) | analysis имеет отдельный вход/гейн — граф с 2 входами |
|
||
|
||
## Топология пайплайна (Приложение A, стр.30 — OCR `pipeline_ocr.txt`)
|
||
|
||
```
|
||
main inputs ──┐
|
||
sidechain ────┤ (sidechain enabled?)
|
||
▼
|
||
input trim ──► analysis (детекция; depth/sharpness/selectivity; EQ curves из per-band balances)
|
||
│
|
||
stereo apply + balance (channel link + global balance)
|
||
│
|
||
ms encode? ──► processing (нотч-синтез в M/S) ──► ms decode?
|
||
│
|
||
mix ──► bypass? ──► output
|
||
```
|
||
- **mid/side ручка**: обработка в M/S-области (`ms encode?`/`ms decode?`) — стерео-путь подтверждён диаграммой.
|
||
- GUI/visualisation (depth/sharpness/selectivity, EQ curves, per-band balances) — побочная ветвь, в DSP-ядро не входит (кроме параметров).
|
||
|
||
## Фазы
|
||
|
||
### Фаза 0 — Снятие диаграммы пайплайна (сделано)
|
||
0.1. OCR стр.30 (`tesseract`, PNG в `/tmp/opencode/manual/p30_s4.png`) → `pipeline_ocr.txt`. ✅
|
||
|
||
### Фаза A — Полный статический RE (Ghidra headless)
|
||
- A.1. Декомпилировать все 127 `FUN_*` из vtable-слотов 8 DSP-классов → полный `processBlock`-путь.
|
||
- A.2. Извлечь float-константы `DAT_1824c*` + все LUT-таблицы в `.data` (поиск массивов float).
|
||
- A.3. **Идентифицировать FFT** (twiddle/бит-реверс/радикс) и окно (формула `sin²` vs LUT) — ключ bit-exact.
|
||
- A.4. Найти частотно-зависимые attack/release (M5): функции масштабирования времени по частоте.
|
||
- A.5. Найти oversample/resolution-путь (M6/M7): интерполяция сетки, период обновления.
|
||
- A.6. Сопоставить топологию с диаграммой (0.1) и stereo/link/balance (M8).
|
||
|
||
### Статус фазы A (~40%)
|
||
- **A.2 константы — готово**: `consts.txt` (11602 из инструкций BFS-замыкания, 2670 в DSP-диапазоне
|
||
0x1824c0000–0x182700000) из повторного прогона `DumpFuns.java`. Точная double/f32-интерпретация —
|
||
`consts_double.txt` (чтение PE напрямую, `.data` vs `.rdata` через RVA).
|
||
- **A.3 ОКНО — найден**: `FUN_1805356f0` генерирует **Hann** `w(i)=0.5·(1−cos(2πi/N))`
|
||
(double 2π @0x1824c4e00/4248, 1.0 @4da0/4140, 0.5 @4c90/4050; хвост через `cos`, чётные пары через
|
||
SIMD `divpd`+`thunk_FUN_181a114e0(=cos)`). Размер N: `FUN_18052e130` →
|
||
`N = 2^floor(log2(sr/44100))·N0` (44100 @0x1824c45ec, 2.0 @41e8). **Нормализация окна** в
|
||
`FUN_18052df60`: сумма по окну → `scale=1/(mean/N)`, потом `/= (fsize @+0x1ac)`.
|
||
- **A.3 FFT — НАЙДЕН (свой)**: Vorbis-планировщик `FUN_181384180` — не тот (это декодер).
|
||
Настоящий DSP-FFT: `FUN_180008140` = init FFT-spec (`N=1<<log2`, масштаб `DAT_181c5cd10(=1.0)/N`
|
||
для inverse, ветки float/double/complex), строит twiddle/bitrev через `FUN_18003a2a0`
|
||
(radix-2/3/5 для малых N), `FUN_18003f1a0`+`FUN_18003f600` и `FUN_18003b6c0` (N≥2^18: бит-реверсовая
|
||
пермутация с XOR-маской знака `DAT_181c5e4e0`). Внешних lib (kissfft/fftw/MKL) нет.
|
||
Xrefs на init только из диспетчерской таблицы `182615ef0` (+ .pdata) — вызов косвенный.
|
||
- **Диспетчерские таблицы (PACE/ModuleDecryptor)**: все DSP-примитивы вызываются через
|
||
`thunk_FUN_181ba94b0@0x180001850` → слот из таблиц `PTR_FUN_1826159c8/5a88/6408...` по глобальному
|
||
индексу `DAT_1826159a0`. 36 таблиц, 245 уникальных функций-целей (`dispatch_targets.txt`).
|
||
Примеры ядер: `FUN_1800025e0` = double-вектор add (развёртка по 16), `FUN_18003fa20` =
|
||
комплексное умножение a[i]·b[i] (фрейм-пар, 425б). Для bit-exact нужен порядок double-операций.
|
||
- **Константы детектора**: `10^((dB)/20)` через `expf((30·X−90)·0.115129255)`,
|
||
`0.115129255=1/(20·log10e)`, `−6.907755=−ln1000`, `0.707`/`0.51`/`0.54`/`0.001`,
|
||
маппинг резонанса `(x−0.707)·0.5+1` в `FUN_1805316e0`.
|
||
|
||
### Фаза B — Реконструкция C++ (bit-exact)
|
||
- B.1. Классы 1:1: `Soothe2FilterGraph::processBlock`, `FilterGraphGrid`, `DigitalFilter`,
|
||
`SpectralProcessor` (STFT), `AudioProcessingModule`, `IIRFilterExtended`, env-модуль с
|
||
частотным масштабом (M5), интерполятор oversample (M6), периодичность resolution (M7).
|
||
- B.2. Точный порядок FP-операций float32, без перестановок; буферный фрейминг независимо от блока хоста.
|
||
- B.3. Имена по смыслу + подписи, сверяемые с моделью sim_v5 (base_red, W, Qeff, sat, Q_notch).
|
||
- B.4. Модули по manual: mode (M1), input trim/sidechain (M12), trim/delta/mix/bypass-порядок (M9),
|
||
stereo/link/balance + ms encode/decode (M8).
|
||
|
||
### Фаза C — Сборка и bit-exact-верификация
|
||
- C.1. `libsoothe2_dsp.so` + CLI-харнесс (WAV16→рендер→WAV24, как Reaper).
|
||
- C.2. Критерий: каждый свип (`trk_*`, `wf_*`, `qmap`, `sens`, `lvl`, `neut`, `al`, `psh`,
|
||
`dual`, `pair`, `chirp`, `burst`, `tj`) — **побайтовая идентичность** int24 PCM с рендерами.
|
||
- C.3. Итеративный цикл diff → локализация блока → фикс → повтор.
|
||
|
||
## Риски
|
||
- **FFT bit-exact**: свой планировщик есть только у Vorbis-декодера; DSP-STFT-FFT ещё не найден (A.3) —
|
||
если свой radix-2 — воспроизводим напрямую; главный нерешённый риск фазы A.
|
||
- **Хост-зависимость**: уточнить точный размер буфера/фрейма (рендеры при разных RENDER_RANGE должны давать одинаковые байты).
|
||
- **Stereo**: все текущие свипы mono; для бит-exact графа M8 нужны стерео-рендеры (link/balance/ms).
|
||
|
||
## Открытые вопросы
|
||
1. Где реальный DSP-STFT FFT (не Vorbis/не внешний lib)? Искать большие функции с butterfly/стадиями
|
||
в 18052*/18053* или через трассировку вызовов от `processBlock` спектрального процессора.
|
||
2. Окна/oversample детали — из декомпиляции (A.6) или требуется доп. замеры.
|
||
3. Стерео-верификация (M8) — приоритет mono-путь или сразу стерео-граф.
|
||
|
||
## Phase A progress update (recap)
|
||
|
||
### FFT transform (found)
|
||
- **Recursive FFT planners ("giants")**: 7 monolith funcs (~14.4K each) = type variants:
|
||
18001f260, 1800246e0, 18002f980, 180029ca0, 180035980, 18003b900, 18004ff80
|
||
Recurse on `log2 - factor` with `param_5+1` depth; build stage plan in `param_1+0x78` array.
|
||
- **Factor tables** (int, index=log2): `DAT_181c5e0fc` (small=4/8/9/13), `DAT_181c5e15c`,
|
||
`DAT_181c5e3fc` (nonzero => use giant path in FUN_18003b6c0).
|
||
- **FUN_18003b6c0** (bit-reverse+plan, N big): if `DAT_181c5e3fc[log2]!=0` call giant `FUN_18003b900`,
|
||
else small plan via DAT_181c5e4e0 XOR mask permute.
|
||
- **Base small FFT**: `FUN_18002e360` (radix2/3), `FUN_180023860` (float variant).
|
||
- **Twiddle**: copied from static base `DAT_182616800` into scratch by `FUN_180039b00`.
|
||
- **Spec init FUN_180008140** (from dispatch table `182615ef0`): builds plan; dispatch-only callers.
|
||
- Giants/planners = integer address gen (no float math) => executors are separate (dispatch kernels).
|
||
|
||
### Spectral layer located (named C++ classes via RTTI):
|
||
- `SpectralProcessor<float,7,1>` vtbl @180529610 (1154) + methods around 180529xxx/18052exxx.
|
||
- `Soothe2Module<float,1>`, `Soothe2ModuleBase`, `IIRFilterExtended<float,1>`, ModuleDecryptor.
|
||
- range 0x180500000-0x180530000 listed (1051 fns) -> funs_range.txt overwritten.
|
||
|
||
### Block processor chain (audio->out):
|
||
- `FUN_18052e260(param_1, n)` : reads det? calls **FUN_180536300(param_1+0x3d8, in, out, det, n)**
|
||
- `FUN_180536300` (475): normalize `DAT_1824c4248/(freq)`, FUN_18052da00/db50/d990/dbc0 (smoothing),
|
||
FUN_180535880, FUN_180536f90 (249, detector update via dispatch).
|
||
- Param setup fns: FUN_18052e9b0 (3167), FUN_180529fe0 (2051) — float math on params (DAT_18262b700 block-sizes etc.).
|
||
|
||
### Audio chain (xrefs confirmed)
|
||
- Soothe2Module::processBlock (`0x18052b??` call at 18052b99c) -> **FUN_18052e260** (reads, calls block proc)
|
||
- -> **FUN_180536300(param_1+0x3d8, in, out, det, n)** (also called from 0x18058e3df)
|
||
- which calls: FUN_18052da00(normalize), FUN_18052db50(fill/0), **FUN_180535880**, **FUN_180536f90**,
|
||
FUN_18052d990, FUN_18052dbc0 (smoothed combine), FUN_18052db50.
|
||
- All small DSP ops = PACE stubs `__std_type_info_compare` + `thunk_FUN_181ba94b0` (dispatch).
|
||
- vftable SpectralProcessor = `PTR_FUN_182615990`; methods listed in rtti_dsp.json (many are ModuleDecryptor/PACE slots, NOT dsp).
|
||
- Param/curve generators (exp/log/pow/tan based, DAT_1824c4xxx): FUN_1805316e0 (6238), FUN_180529fe0 (2051), FUN_18052e9b0 (3167).
|
||
- Dispatch index global `DAT_1826159a0` written only in PACE wrappers 18000101b/180001046.
|
||
- big spectral-area functions are mostly param/memory/PACE; the real dsp sticks are dispatch kernels (245 targets).
|
||
|
||
### DSP kernel map (xref cross-check)
|
||
- **Complex multiply** `FUN_18003fa20(double*, cplx[][16], n)` — called ONLY from `FUN_180008440` (init area, twiddle build) — NOT a dispatch slot.
|
||
- **Double mul** `FUN_180008500` — dispatch slot @182615f20; called from **FUN_18000c5e0** (double FFT stage kernel, 507B):
|
||
loop over 0x40-chunks: `thunk_FUN_1801560a0(param_2,re,im,0x40)` (load twiddle), `FUN_180008500(param_1,re,n)`,
|
||
`FUN_180008500(param_1,im,n)`, `FUN_180044700(im,re,param_3,n)`.
|
||
- **Double add** `FUN_1800025e0` slot @1826159e8; **float max** `FUN_1800104e0` slot @1826164a0; `180010860` = vmax/vmin with const.
|
||
- Decompiler entry-point trick: `getFunctionAt` misses overlapping/obfuscated entries => use `getFunctionContaining` (DumpList updated).
|
||
- OBtained: full FFT-plan model, factor tables, per-type kernels, block->detector->mask flow.
|
||
- Next sub-goal: trace the FFT-stage kernel call order used by detector (which plan fields drive per-stage twiddle/mul)
|
||
=> then C++ re-implementation is a direct transcription of that order.
|
||
|
||
### Transform runtime (tracing complete -> next: reimpl)
|
||
- Plan fields consumed via plan->thunk indirection: `thunk_FUN_1801560a0` -> `PTR_FUN_1826178a0`; `FUN_18014ec20` thunk -> `PTR_FUN_182617828/850/878` (3 twiddle-loaders).
|
||
- Stage kernels:
|
||
- **FUN_18000bfc0** (complex stage, slot @182616208): loop 0x40: load-twiddle(re,im) ; `FUN_180008440`(cplx-mul, slot @182615f08) twice (re,im) ; `FUN_1800437c0`(accum). out+=0x200, tw+=0x200, acc+=0x400.
|
||
- **FUN_18000c5e0** (double stage, slot @182616220): 0x40-chunks: load-twiddle(re,im); `FUN_180008500`(double mul, slot @182615f20) x2; `FUN_180044700`(accum).
|
||
- Dispatch-slot readers (drivers): slot cplx-mul @182615f08 read at 180001c47; stage @182616208 read at 180001e87 => runtime dispatch executors live in 0x180001xxx (thunk chain).
|
||
- DSP runtime model := plan + per-stage kernel calls (all ops = dispatched vector kernels), all through `thunk_FUN_181ba94b0` from PACE-protected callers.
|
||
- => Reimplementation path: transcribe stage-loop math (kernel shapes known), replicate plan generation, drive per-stage calls in same order; validate losses to within 0.001 dB on Reaper renders.
|
||
|
||
### Twiddle-builder FUN_180039b00 (29-fun dump, 2026-08-17) — TWO-PATH (key finding)
|
||
- arg1 = log2(N), arg2 = dest buf. Returns `buf + (N/4+1)*8`, align-down to 0x40.
|
||
- Path N <= 1024 (log2 < 0xb): copies sin VALUES directly from static DAT_182616800 (1024 doubles
|
||
= sin(k·2π/1024)), stride = 2^(10-log2N); writes buf[k] = sin(k·2π/N); terminator 1.0 at index N/2.
|
||
NO sin calls — values are precomputed in static table.
|
||
- Path N > 1024: writes ANGLE RAMP buf[k]=k·(2π/N) (two SIMD segments: forward 0..N/8, reversed
|
||
N/8..0 via vpsubd), then converts with **thunk_FUN_1801de760** (sin) and **thunk_FUN_1801e3f20** (cos).
|
||
- => loader FUN_18014ec20 (cplx-stage twiddle, 8255B) READS 16B-per-iter (2 doubles) and does
|
||
Dekker-split math (>>0xc, <<0x28, XOR masks DAT_181c91xxx, Pi-reduction with 1/π, magic 2^52*1.5)
|
||
=> it RECOMPUTES sin/cos from the angle table; it is NOT a plain copy. 3 twiddle-loader slots
|
||
182617828/850/878 ALL currently point to 0x18014ec20 (same fn).
|
||
- CONSEQUENCE: for 44100 => N=N0; if N0<=1024 the twiddle = static sin table (reproducible directly);
|
||
if N0>1024 must replicate Cody-Waite sin/cos of FUN_18014ec20 bit-exactly. N0 (+0x19c) still unfound statically.
|
||
|
||
### N0 RESOLVED - LIVE DUMP @44100 (2026-08-17) — KEY
|
||
- Soothe2 render_short.rpp (44100 Hz) + /proc/pid/mem scan of yabridge-host heap
|
||
(out file growth confirmed processing; targeted field-signature scan:
|
||
qword-aligned, +0x19c == +0x1a0 == pow2 in [256..65536], +0x1ac in {2,4,8,16}).
|
||
- Two independent live SP instances (fork L/R, vptr 0x1824ac210 / 0x1824ac7a8,
|
||
vtbl-slot families 0x18052xxx = SpectralProcessor) BOTH report:
|
||
+0x19c N0 = 2048, +0x1a0 = 2048 (=N0, since sr==44100 => 2^floor(log2(1))=1)
|
||
+0x1a4 winscale = 0.49999988 (Hann 0.5) / 0.66666639 (2/3 window)
|
||
+0x1ac oversample = 4
|
||
- N0=2048 > 1024 => twiddle goes the ANGLE-RAMP branch of FUN_180039b00
|
||
(buf[k]=k*(2π/N) then sin/cos via thunk 1801de760 / thunk 1801e3f20),
|
||
and cplx-stage loader FUN_18014ec20 re-derives re/im (Dekker-split + Cody-Waite).
|
||
- Implication for reimpl: for 44.1k we MUST reproduce Cody-Waite sin/cos
|
||
(1/π=0.31830988618379069, magic 6755399441055744, π_hi/π_lo reduction,
|
||
poly -1/3!..-1/15!), NOT the static 1024-table path. Soothing does NOT use the
|
||
static sin-table for its main FFT at 44.1k.
|
||
- Method: rtone.py (single render, wait for out growth, scan), rtver.py (field verify).
|
||
Old soothe_rt.bin is module-only range => heap objects absent (why vptr-scan found 0).
|
||
|
||
### B.1 — C++ реализация DSP-ядра (2026-08-17) — IN PROGRESS
|
||
- `dsp/fft_plan.hpp/.cpp` — radix-2/4/8 планировщик (из гигантов FUN_18002f980 и др.)
|
||
- `dsp/fft_stage.hpp/.cpp` — stage kernels FUN_18000bfc0/18000c5e0 (cplx-mul + accum)
|
||
- `dsp/fft.hpp/.cpp` — полный цикл (plan→twiddle→execute)
|
||
- `dsp/spectral.hpp/.cpp` — SpectralProcessor (STFT + детектор + mask application)
|
||
- `dsp/filter.hpp/.cpp` — FilterGraph, DigitalFilter (M10)
|
||
- `dsp/detect.hpp/.cpp` — детектор резонансов (envelope + peak suppression)
|
||
- `dsp/ms.hpp/.cpp` — mid/side encode/decode (M8)
|
||
- `dsp/cody_waite.hpp` — sin/cos (FUN_1801de760/1801e3f20)
|
||
- `dsp/phase_table.hpp/.cpp` — sin-таблица 1024
|
||
- `dsp/twiddle_builder.cpp` — FUN_180039b00 (ветка N>1024)
|
||
- `dsp/CMakeLists.txt` — сборка `libsoothe2_dsp.so`
|
||
- `dsp/harness.cpp` — WAV16→render→WAV24 (stereo+M8)
|
||
|
||
### B.2 — Сборка и верификация (2026-08-17) — IN PROGRESS
|
||
- `libsoothe2_dsp.so` — собрана
|
||
- `harness` — работает с WAV16→WAV24
|
||
- **Fixed:** WAV read aliasing bug (int16→float conversion)
|
||
- **Fixed:** In-place processing bug (separate input/output buffers)
|
||
- **Added:** Spectral detector with envelope + peak suppression
|
||
- TODO: `verify_bit_exact.py` — прогон всех свипов, побайтовое сравнение int24 PCM
|
||
- TODO: `dsp_notes.md` — документация (карта функция↔смысл, константы, LUT)
|
||
|
||
### B.3 — Осталось сделать
|
||
1. **Twiddle-loader FUN_18014ec20** — декомпил есть, нужна транскрипция (Dekker-split + Cody-Waite)
|
||
2. **Bit-reverse permutation** — FUN_18003b6c0 с XOR-маской DAT_181c5e4e0
|
||
3. **C++ транскрипция** — переписать fft.cpp с точным порядком FP-операций из декомпиляции
|
||
4. **Верификация** — подать тон/синусоиду в тот же путь, что и Reaper-рендер
|
||
|
||
### B.4 — Метрики на burst500 (2026-08-17) — BIG JUMP
|
||
- **Интеграция**: `build_twiddle` в fft.cpp теперь через `soothe::twiddle_load` (sincos_single,
|
||
Cody-Waite, 7-term poly); удалён дубль `init_plan` (конфликт с fft_plan.cpp), добавлен `<vector>`.
|
||
- **Stage-ядра** (fft_stage.cpp переписан: cplx_mul / stage_complex / stage_double = scalar mul
|
||
FUN_180008500) — починили **фазу**: corr −0.479 → **+0.995**, SNR −5.2 → **+19.8 dB** (shift 0),
|
||
амплитуда diff ±0.06 dB (ref −26.07, ours −26.13 dBFS).
|
||
- **Детектор (смена модели)**: убрана bell-boost (была НЕВЕРНА); маска soothe = **чисто
|
||
уровнезависимый floor**. Тон 258 Гц (громче) режется сильнее (−6.2 дБ), 500 Гц (−3.5 дБ),
|
||
выше 1 кГц ≈ 0 — региональный уровень (RMS по окну 16 бинов, калибровка −26.1 дБFS),
|
||
`mask = 10^(−1.041·depth·floor(level)/20)`.
|
||
- Итог: ref −26.07 / ours −26.13 dBFS, corr 0.99475, SNR 19.80 dB, diff −0.065 dB.
|
||
|
||
### B.5 — Детектор ДЕШИФРОВАН в soothe_mem.bin (2026-08-17) — KEY
|
||
- **Тело FUN_180535880/180536f90 в дампе = реальный дешифрованный SSE-код** (movsd/cvtpd2ps,
|
||
загрузка параметров из param_3, цикл по буферу), а НЕ PACE-стаб.
|
||
- Вызов **dispatch-shim 0x180001d00**: `mov [0x1826159a0],%rax; jmp *0x182616008[rax*8]`;
|
||
сейчас idx=4 → `0x180009860` (тонкая обёртка `e8 rel32`) → реальная **FUN_180040d40**.
|
||
- **Dispatch-таблица 0x182616008**: 223 разрешённых записи; реальные цели в `.text`
|
||
`0x18000a160..0x180074740` (13+5+8+6 функций по страницам), высокие `0x1817b900/181a69090/...`
|
||
= направляющие, не алгоритм.
|
||
- **КРИТИЧНО**: этих функций НЕТ в fun_map.txt/decomp_funs.txt (прежний Ghidra-анализ
|
||
был на зашифрованном `.vst3` и не развязал dispatch). .vst3 на диске (PE32+ 39.1M,
|
||
12 секций, base 0x180000000) СОВПАДАЕТ с дампом по байтам в `.text` (проверено
|
||
для 0x40d40/0x535880) фи(x) → декомпиляция дампа = декомпиляция реального кода.
|
||
- План: разметить таблицу → извлечь байты функций 0x18004xxxx (+0x18000a160..0x180074740)
|
||
→ objdump 0x180000000-adjust → транскрипция в dsp/detect.cpp → верификация SNR 19.8 дБ.
|
||
- Непустые ранее известные ядра (0x180040cc0..0x180041700) = 8 функций = полный алгоритм детектора.
|