Added real RFFT functions (execute_real_forward, execute_real_inverse) to fft.hpp/cpp. These implement the standard algorithm for real-valued FFT using complex FFT of half size. Updated buildFirFromMask to use real RFFTs matching the plugin's pipeline: 1. log(mask) → negate 2. forward real RFFT (opB) 3. EXP in-place 4. inverse real RFFT (opC) 5. Window 6. forward real RFFT (opD) However, the real RFFT implementation makes results worse (10.377 dB vs 1.825 dB default). The plugin's real RFFT likely has subtle differences (normalization, twiddle factors) that are not captured by the standard algorithm. The default path (no FIRCONV) remains the best approach with 1.825 dB TOTAL error. Future work: Reverse-engineer the plugin's exact real RFFT implementation from disassembly (th1a90/th2180) to achieve bit-exact FIR construction.
soothe2-re
Обратный инжиниринг DSP-ядра oeksound soothe2 (VST3, Windows x64) → проверяемая реконструкция на C++ с bit-exact целью.
Цель — понять, как именно плагин считает подавление резонансов (уровневый детектор,
маска, фильтр), и воспроизвести это дословно. Текущий активный канон — C++
FramedDetector (dsp/framed_model.cpp): real mask-apply цепь с live-таблицами.
Канонные команды и метрика — в AGENTS.md.
Статус (24kk2, 2026-08-25)
Цель — bit-exact реверс (гейт смены канона зафиксирован пользователем: только
после прослеживания всех параметров до декомпа и схождения корпуса в шумовой пол).
Декомпиляция DSP-ядра закрыта (~95%); дизассемблы в handoff/nls_dasm/ (~140).
Применение декодировано до формул (сессия 24j…24kk2):
mask(b) = 10^(−cut_D(b)/20) ← вещественная, per-bin multiply кадра
cut_D(b) = α·ln(1+lvl_raw(b)/β)+c [+Δ у вторых пиков]
lvl_raw = am/res·scale (наш детектор-фронтенд, float-parity ✓)
слой = STFT БЕЗ синтез-окна (RT_SYN=1)
Калибровки формы (три независимых семейства, rms ≤0.016 дБ): α/β/c зависят от контента (α удваивается с числом тонов — частотное смешение шаблонно-локальное), q НЕ влияет на закон, sens входит линейно через lvl_raw.
- dual-семейство решено: корпус 22 случая mean 0.193 / max 0.438 dB
(канон 3.264). Флаги:
RT_VLAW=1 RT_SYN=1 RT_NOWARP=1 RT_NOIIR3=1 RT_IIR12=0. - Канон не тронут: TOTAL 2.286 (env-gated эксперименты живут рядом).
- Буфер FIR@540668 = промежуточное мин.-фазовое представление
(
exp(s−i·H(s)), Гильберт по частоте) — аудио слышитexp(scratch)напрямую. - Тела bigkernel'ов найдены (рантайм-резолв IAT): 1803a06a0 / 180296c80 / 180323f20 / 1802dc0e0 — x87-трансценденты (exp-семейство).
Главное за 2026-08-24…25 (сессии 24j–24kk2)
- Применение = побиновный complex-multiply кадра на маску; «магический ×1.805» оказался произведением экспонент стадий построения буфера (0.984×1.8345).
- Закон уровня универсальной формы
α·ln(1+L/β)+c— подтверждён тремя независимыми калибровками; константы зависят от контента (число тонов) и слабо от fc. - Слой STFT: плагин НЕ домножает выход обратного FFT на окно
(
RT_SYN=1); WIN_WINDOW движка — фейд 0.5→0.8 ровно за 2049 сэмплов (=бинам кернела). - GUI/аудио разделение: LUT-строитель FUN_180563a60 — GUI-ветка; аудио-компрессия живёт в семантиках шагов 9–19 BLOCKMAP.
- Dataflow шагов 9–16 декодирован: vec6f8=bands−ACC; fma тройками (re,im,coef) с ATT/REL; th2000=поэлементное умножение массивов (не axpy!); шаг 12=COPY (исправлен старый BLOCKMAP).
- Инструменты: rendersnap2 v7 (мягкий STOP-снаппер со слотами+скалярами, RENDER_FILE-фикс), patchparam.py (правка VST-чанка RPP!), campaign.py (ячейка параметризации), disasm_func.py (capstone с RIP-константами), iat_name.py (рантайм-резолв импортов через PE-экспорты).
# Сборка и канонные команды:
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…24kk2).
Открытые bit-exact пробелы
Приоритет №1 — каскадный симулятор шагов 9–19: dataflow декодирован
(24hh/24ii: vec6f8=bands−ACC, fma-тройки att/rel, ×track, ×warp, центрирование −1),
тела bigkernel'ов найдены по рантайм-адресам — осталось сложить оп-за-опом и
проверить на датасетах sc_* (rms каскада сейчас ~0.42 на угаданных формах).
Далее: k-маппинг фронтенда (twin/am формулы), Δ-правило вторых пиков из pre-combine.
Полный список — AGENTS.md, BITEXACT_PLAN.md, NOTES_LEVEL.md (24bb→24kk2).
Исторический блок (поведенческая/численная модель B.1…B.15,
framed_render.py, Pchip LUT, res_power) — см.roadmap.md; самодостаточен как справочник, но не канон.
Было (B.15 — историческая численная модель)
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/.
python3 framed_render.py dual # (исторический канон B.15)
Структура репозитория
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 | Старт для агента: сборка, метрика, bit-exact пробелы, структура |
| BITEXACT_PLAN.md | Путь к bit-exact: порядок работ (8 шагов), критерии, риски, точка входа |
| roadmap.md | Журнал всех результатов/гипотез (B.1…B.15, P4), статус по фазам, риски |
| handoff/SESSION_HANDOFF.md | Инвентарь декомпа (§0), трансляция/ключевые адреса (§2), Phase-5 план (§6) |
| handoff/NOTES_LEVEL.md | Level-path/mask-цепь, live-таблицы, bit-exact протокол (самый актуальный) |
| handoff/NOTES_TWIN.md | Twin-резонатор (FUN_180535880/536f90), caller, grid/oversample |
| handoff/NOTES_CAPTURE.md | Live-захват таблиц (registry heartbeat, SR 48000) |
| handoff/nls_dasm/ | 134 дизассембла (f_563440, f_563a60, f529fe0, twin, iface, fft) |
| notes_giant_fft.md | FFT-планировщики/ядра/twiddle/Cody-Waite |
| 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, см. выше)
# 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].
Как всё это воспроизвести
- Достать бинарь: установить soothe2 (Windows VST3) под yabridge, запустить
dump_soothe.py→soothe_mem.bin(или взять.vst3, выдернуть секции). - Декомпиляция:
analyzeHeadless <proj> soothe_x64 -process soothe_mem.bin -noanalysis -postScript <X>.java(image_base=0, блокram=[0x180000000,…], адреса = RVA; смещение в файле = VA−0x180000000). - Рендеры:
sweep.pyгенерит.rpp,reaper -renderprojectвыдаёт.wav. - Анализ:
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).