Files
soothe2-re/roadmap.md
T

16 KiB
Raw Blame History

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-ядра — закрыт (~95%): twin-резонатор, генератор case8, level-path (0x530d30/0x529fe0), LUT-кривая (FUN_180563440), IIR-трекеры (FUN_180563ce0), FFT-conv (0x535a70), main render-loop (FUN_18052e260) декодированы; .dis в handoff/nls_dasm/. Остаток — runtime-значения (коэффициенты IIR level-tracker 0x4c0528/0x3c0510/0x2c04f8, скаляры 0x540870/874/87c/888/88c из RPP, цель P1.5 live-capture).
  • Численная модель = 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/ готова и проходит float-parity (1.27e-5); живые таблицы встроены (P1.1, tables_check ALL OK); FFT-conv расшифрован (P1.2: f_52d990 = memcpy, N=4096) и интегрирован (P1.3 fftconv.cpp); маска-канон подтверждена (P1.4, FUN_180529fe0) и level-tracker UPDATE реализован (P2, leveltrack.cpp). Остаток кода: twin-mask factory (FUN_18056e3e0), mask-accumulator 0x5407c8, render-loop 52e260, бит-экзактная base-CRT FFT, стерео M8, multiband combine 5316e0,
    • live-коэффициенты (P1.5). res_power/Q-dep-rp порт в C++ — ещё TODO.
  • ВАЖНО про метрику: ранние записи "mean=0.160/0.144" считались на выходе НЕтримнутой длины (nfr*HOP+N) и были артефактом; честный базлайн для scalar rp=0.0169 = 0.280. Все числа в этом файле — на ТРИМНУТОЙ метрике.

Модель в двух строках

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); dsp/{tables_data, fftconv, leveltrack}.cpp + dsp_ctx.hpp готовы (P1-P2 структурно); verify_bit_exact.py даёт базлайн (все DIFF — ожидаемо до подключения живой маски). Оставшийся код по фазам:

    Фаза Компонент Статус
    P1.5 live-коэффициенты level-tracker A[] + скаляры RPP TODO (нужен live-capture / дизасм FUN_18052d650)
    P2.5 twin-mask factory FUN_18056e3e0 (полн.) + mask-accum 0x5407c8 TODO (структурный фрагмент есть)
    P3 бит-экзактная base-CRT FFT (диспетчер 0x535a70) TODO (главный риск)
    P4 render-loop 52e260 + res_power/Q-dep-rp порт + связка mask→FFT-conv TODO (структура известна)
    P5 стерео M8 (link/balance/LR-vs-MS) + multiband combine 5316e0 + writer TODO (в скоупе, не начат)

    Оценка объёма оставшегося кода: ~60% (против ~40% готовых) — крупнейшие куски P3 (FFT bit-exact) и P5 (стерео M8); структурно всё размаплено, остаток — точные вычисления и живые константы.

Ключевые находки (итог)

  • Маска аддитивная: 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)=
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 ЗАКРЫТ 2026-08-19: live-захват через registry heartbeat ({u64 count, u64 ptr} run at psy. 0x29b06c0) во время offline-рендера. Окно = registry[01] (8193 f32, 0.5→1.0, сатурация), freq-axis = registry[0d] (0..23988 Hz, spacing 11.713 ⇒ внутренний SR=48000), веса [03][04][05][06] (WA/WB/WC/WD). Сохранено: handoff/rtwin_freq_44100.npy, rtfreqaxis_48000_internal.npy, rtwa/rtwb/rtwc/rtwd_*.npy. Инфра: rtsnap_fast.py, rtsnap2.py (двухточечный diff подтвердил стабильность таблиц), findctx.py. Полное описание: handoff/NOTES_CAPTURE.md. vptr/44100-marker объект-скан (rtobj) — тупик (производная vtable не адресуется статически; регистр-бикон — рабочий путь).

Где лежат детали

  • 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 — полная журнальная хронология с номерами коммитов.