111d47530e734cd5520ac054a6957096bd5af6ae
soothe2-re
Обратный инжиниринг DSP-ядра oeksound soothe2 (VST3, Windows x64) → проверяемая численная модель и реконструкция на C++.
Цель — разобраться, как именно плагин считает подавление резонансов, и воспроизвести его поведение (в идеале бит-точно, но пока — с точностью ~0.1 dB на формах полос).
Статус (B.10, август 2026)
Детектор расшифрован структурно и проверен численно на двух независимых наборах измерений (31 точка, совместный фит rmse = 0.109 dB).
- Freq-path (форма полосы) — близнец
FUN_180535880/FUN_180536f90(резонатор 2-го порядка). Коэффициенты case8 (mode=1 исполняется как case8!):w0=2π·fc/fs; p=(sin(w0)·0.5)/Q; A=[p·gain+1, −2cos(w0), 1−p·gain]; B=[p/gain+1, −2cos(w0), 1−p/gain]; res(f)=|2·B/A|(f). - Level-path (глубина) — per-band маска
= depth·tilt(f)·D0·(L0/res(f))^p, гдеtilt(f)=1−w(f)— per-bin спектральный вес изFUN_180530d30(w[i] = log10(1/exp(...·exp(2000/f_bin))...)),L0— уровень входа,p≈0.085. - Ключевые факты:
gain ≈ 10^(sens/20)(sens=12 → gain≈4.1);- частотная селективность — НЕ notch самой маски, а резонанс в знаменателе:
врез глубже там, где
L0/resбольше (на центре полосы); - «парадокс dual_b1q» (red@2000 > red@500 при низком Q) решён: это частотный
наклон
tilt(f)(растёт с частотой), а не ширина резонанса; - warp-ось
0x540748= сигмоида8.3 − 7/(1+exp((x·20000−120)·(−0.01))), а НЕf/(f+K), как ошибочно предполагалось раньше.
Подробная история, гипотезы и все промежуточные находки — в roadmap.md.
Структура репозитория
re-tools/
├── README.md ← вы здесь
├── roadmap.md ← журнал всех результатов/гипотез (B.1…B.10)
├── model_dual.py ← КАНОНИЧЕСКАЯ верифицированная модель (B.10)
│
├── dsp/ ← реконструкция DSP-пайплайна на C++17
│ ├── rotor_kernel.hpp ← бит-точное ядро ротора (близнец детектора)
│ ├── 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 ← быстрый exp/log (Cody–Waite)
│ ├── soothe_constants.hpp ← константы из дампа
│ ├── harness.cpp ← CLI: input.wav → output.wav (24-bit)
│ └── CMakeLists.txt
│
├── *.java ← Ghidra-скрипты (analyzeHeadless, пост-скрипты)
│ ├── ListFuns / DumpFuns / DumpList ← списки функций/декомпиляция
│ ├── DumpVtables / DumpDsp / DumpCandidates
│ ├── ImportRtti, SearchRefs, Diag ← RTTI/перекрёстные ссылки
│ └── ...
│
├── ghidra-proj/ ← Ghidra-проект (soothe2.gpr/.rep), вне git
├── soothe_mem.bin ← дамп памяти плагина (frida), вне git
├── regions/, regions_* ← регионы памяти из дампа
├── phase_table_1024.f64/.json← таблица фаз (FFT)
│
├── Измерение и фиты полосы:
│ ├── measure.py ← анализ WAV: глубина вреза, attack/release тау
│ ├── probe.py ← спектральный анализ тонов
│ ├── bandshape.py ← эмпирическая форма полосы из рендеров
│ ├── fit_bandshape.py ← фит формы полосы
│ ├── fit_curves.py ← фит кривых (глубина/частота)
│ ├── fit_maskaxis.py ← brute-force подбор формулы маски (B.7/B.8)
│ ├── model_dual.py ← ВЕРИФИКАЦИЯ модели (dual_b1q + t1kq)
│ └── notch.py ← расчёт/проверка notch
│
│ ── Поведенческие симуляторы:
│ ├── sim.py, sim_v5.py ← STFT-детектор (эволюция гипотез о архитектуре)
│ └── verify_sim.py
│
│ ── Инструменты REAPER-рендеров:
│ ├── sweep.py ← генерит .rpp с изменением параметров плагина
│ ├── run_sweep.py, tt_sweep.py, run_verif*.py
│ ├── patchparam.py ← патчит PARAM в base64-блоке состояния
│ └── addfx.lua ← добавление soothe2 в проект
│
│ ── Живая трассировка (yabridge-host + Frida):
│ ├── dump_soothe.py ← дамп всей памяти плагина → soothe_mem.bin
│ ├── rtall.py / rtscan.py / rtsig.py / rttbl.py / rtver.py
│ ├── rtone.py / rtsig.py / rtwin.py / rtdeep*.py / rtdump*.py
│ └── probe.py, procdump.py, ghidra_diag.py, mkbase.py
│
├── *.txt / *.log ← декомпиляции, лямбли-дампы, xrefs (архив)
├── summary.md, notes_giant_fft.md
└── curve_fits.npz, depthcurve.npy, consts*.txt
Тестовый корпус /home/m/soothe-bt/ (вне git)
~600 рендеров REAPER (.rpp + .wav), сгенерированы скриптами выше. Ключевые наборы:
| Префикс | Что измеряет |
|---|---|
burst500_b1 |
основной эталон: burst 500 Гц, band1 fc=500 mode=1 sens=12 depth=0.864 |
dual_b1q_* |
2 тона (500+2000), q полосы 0.1…10 → парадокс red2000>red500 |
t1kq_only1_* |
1 тон 1 кГц, fc-скан → форма полосы (rmse 0.047) |
t1k_b1f_*, t1kq_b1f_* |
fc-сканы, другие q/сенс |
tone1k, tone1kq, dual, n5_b1q_*, b2*, al_* |
уровни/частоты/сенсы |
Входы: dual.wav (500+2000 Гц, −7.1 dBFS), tone1k.wav (0 dBFS), tone1kq.wav (−18 dBFS).
Модель (B.10) в двух строках
red(f) = -20*log10(1 - C(f))
C(f) = depth * tilt(f) * D0 * (L0 / res(f))**p
# res(f) = |2B/A|(f; fc, Q, gain) — case8, tilt(f) = 1 - w(f) — веса 0x530d30
# p ≈ 0.085, gain ≈ 10**(sens/20), tilt ratio(2000/500) ≈ 1.27
Проверить: python3 model_dual.py (31 точка, rmse 0.109 dB).
Как всё это воспроизвести
- Достать бинарь: установить 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. - Анализ:
measure.py fx.wav dry.wav --fc 500→ глубина/тау;bandshape.py→ форма полосы;model_dual.py→ верификация модели.
Чего не хватает / следующие шаги
- Бит-точная сверка рендера: собрать
dsp/harness.cpp, прогнатьburst500_b1.wavи сверить байт-в-байт (сейчас модель — численная, а не побайтовая). - Семантика IAT-хелперов
0x181a14xxx(exp/log/pow) и AVX-ядер (FUN_18001a5a0и др.) — целевые адреса вне дампа. - Точная функция LUT
level→mask(криваяparam_1+0x188) вместо аппроксимации^p. - Полный конвейер WOLA/oversample (offline 3×) и sidechain/стерео-путь.
Description
Reverse engineering of oeksound soothe2 VST3 DSP core → bit-exact buildable C++
12 MiB
Languages
C++
59.3%
Python
36.7%
Java
3.7%
Lua
0.2%
CMake
0.1%