Files
soothe2-re/summary.md
T
Matiq bdf9f21a60 docs: AGENTS.md + refresh README/roadmap/summary/SESSION_HANDOFF to P4 canon; rm empty logs
- AGENTS.md: canonical build/test commands, honest trimmed tone metric, current
  bit-exact state (mask-apply chain), open gaps (PRNG/FFT-conv/exp2/SR-mismatch).
- README.md: status moved to P4 (C++ FramedDetector canon, framed_test), historical
  B.1..B.15 numerical model demoted to <details>; structure/doc table updated.
- roadmap.md: P4 phase marked progress, P4.1/P4.2/P4.3 milestones, top status refresh.
- summary.md: marked historical (behavioral v4).
- SESSION_HANDOFF.md: fixed stale "NOT DECODED" (now decoded), copy note done.
- Removed empty tmp_spec.txt/giant3.txt and untracked .log/__pycache__ clutter.
2026-08-20 15:50:07 +03:00

22 KiB
Raw Permalink Blame History

Objective

  • Реверс-DSP oeksound soothe2 v1.1.2 VST3 → математическая модель (STFT, детектор резонансов, нотич-синтез). Текущая фаза — поведенческий параметр-свип через рендер Reaper RPP + статический декомпил (без декрипты бинарника пока).

⚠️ ИСТОРИЧЕСКИЙ ДОКУМЕНТ (поведенческая модель v4, sim.py, ок. 2026-08-17). Актуальный канон (bit-exact, P4) — README.md, AGENTS.md, handoff/NOTES_LEVEL.md. Здесь — ранняя поведенческая фаза реверса, сохранена как справочник.

Key Facts

  • Пользователь русскоязычный; sudo нет. Объект: ~/.wine/.../soothe2_x64.vst3 (PE32+, base 0x180000000), yabridge 5.1.1, Reaper 7.78.
  • Frida невозможна (Seccomp). Дамп памяти — pread /proc/pid/mem от родителя. pkill-ловушки: paths без harneb/reaperpkill -9 -x reaper, pkill -9 -f '[y]abridge'.
  • Рендер-проект: RENDER_RANGE 1 0 0 0 1000, ITEM обязательно POSITION 0. Offline-рендер мгновенный → для живого дампа realtime (RENDER_1X 1) + testtone.wav.
  • VST-state = 92-байт JUCE-заголовок + base64 XML <SOOTHE2STATE> + 54 <PARAM id=... value=.../>; внутри — UI-state без DSP.

Breakthrough (исправлено в этой сессии)

  • Формат-адаптивность PARAM: плагин применяет <PARAM value> ТОЛЬКО если строка сериализована в том же формате, что оригинал:
    • full-precision (depth, band freq/q...) → %.16f (напр. "0.9000000000000000"). Короткие строки → схлопывание (fallback).
    • short-формат "X.0" (selectivity, mix, mode, oversample, resolution) → ЛОМАЕТСЯ от %.16f.
    • sweep.py/tt_sweep.py адаптивны: >4 десятичных в оригинале → %.16f, 1-4 → то же число знаков, иначе — как есть.

Синтез модели (поведенческая, v4 — реализована в sim.py, RMSE на settled ≤0.05dB по всем свипам)

PARADИГМА (реализована):  out(t) = x(t)  amount_b(t)·bp_b(t; fc, Q)   [субтрактивный нотч, не микш-параллельный]
  amount(total) = A(depth)·S(sens)·H(sharp)·M(mode)      — LUT из измерений (пересечение, не аналит. фит)
  A(depth): 0.5/0.864/1/2/3/5/10/20 → 0.560/0.588/0.598/0.667/0.727/0.820/0.939/0.989
  S(sens):  0/6/12/24 → 0.48/0.76/1.0/1.0 (floor при sens=0!)
  H(sharp): 1/3/5/10 → 0.20/0.59/0.84/1.0
  M(mode):  0 → 0.745 (mode0 слабее), 1.. → 1.0
  Q(sel):   = 3.0+0.36·sel   (2nd-order bandpass; реал. даёт более крутые борта — приближ.)
  env(t):   one-pole, α_attack=1e^(1/(T_a·fs)), α_release=1e^(1/(T_r·fs))
            T_a = 0.02·e^(a/1.955) (0→20мс, 5→258мс, 10→3.3c);  T_r LUT из спадов (0→27мс, 5→140мс, 10→15c)
  детектор:  лауднеss полосы |bp| smoothed 5мс > thr 0.01 → цель amt, иначе 0   (порог между probe 0.0005 и burst 0.2)
выход: x_out = mix/100·… — линейный кроссфейд: (100mix)/100·x + mix/100·fully_reduced
       (mix=100 = полная обработка = ref; mix=0 → сухой; проверено на s_corr_mix: монот. линейно)

Верификация (settled, RMSE по reduction dB на burst-окне):

параметр реал sim RMSE параметр реал sim RMSE
дефолт 7.70 7.70 0.002 sharp=1 1.11 1.11 0.002
depth=2 9.56 9.56 0.001 sharp=3 3.72 3.71 0.006
depth=5 14.90 14.89 0.008 attack=5 5.68 5.90 0.360
depth=10 24.27 24.25 0.018 mode=0 5.03 5.00 0.023
depth=20 39.43 39.40 0.054 mode=2 7.70 7.70 0.002
sens=0 2.89 2.89 0.001 sens=24 7.70 7.70 0.002
Q-профиль (probes-зонд): sim sel1 500→680@540→0.62@700 vs real 8.03→4.54→1.13; 2nd-order не покрывает
крутые борта реала (реал резче ~×3): допустимо как 1-я итерация, RMSE макс по профилю ~1.6dB.
Release-трассы: r0/r1/r2/r5 совпадают (sim против real 1.6s:3.5/4.3, 1.7s:1.5/1.9, 1.9s:0.3/0.3).

Q/width-зонд (probes500/probes500b, drive 500Hz amp0.2 + справки 0.02)

  • Методика: длинный single-FFT на окне 1.7..2.9s (разрешение ~0.7Hz), справки на 500,505,510,520,540, 555,575,600,700 (b-set) / 500,505,515,530,560,640,780 (a-set). Драйв-тон держит детект-клок, справки зондируют передаточную характеристику в установившемся режиме.
  • Данные (a-set) red@freq: sel1: 500:8.0 510:7.9 520:7.7 540:4.5 555:2.7 575:1.6 600:0.7 (плавный хвост) sel8: 500:8.7 510:9.1 520:6.6 540:1.3 555:0.1 575:0.0 600:0.0 (резкий срез) — Q уверенно растёт с sel.
  • Центраئة нотча на 500..510 чуть выше 500 (fs на 505..510) — трекинг слегка субадио.
  • Значения на дип-тонах 530/560/640 в b-наборе — из мусора (нет справки) и исключены; a-набор чистый.
  • ВАЖНО: dry-файлы дважды перезаписывались провальными dry-рендерами (RENDER_FILE не патчился по .wav напр. burst500L_byp → overwritten probes). ФИКС: патчить RENDER_FILE явно в sed до рендера.
  • data: оба svипа предсказуемо монотонны; таблица в summary выше.

Верифицированные данные (все свипы с корректным форматом)

  • depth (mir): монотонно distinct (vs-ref 496k→441k→...→347k@0.85→384k@1.0) — работает.
  • selectivity: 0/1/2/5 distinct (476k/466k/458k/461k vs ref); 10==20==50==100 (сатурация ≥10).
  • oversample: 0==ref; 1 distinct; 2==3==4 (сатурация на 2).
  • resolution: 0 distinct; 1==ref; 2 distinct (все distinct); 3==4.
  • mix: 0/25/50/75/100 всё distinct монотонно; 100==ref.
  • mode: 0 distinct; 1==2==3==4==ref.
  • band1 freq: tt_b1f_678 (дефолт banda freq) == tt_ref бит-в-бит; свип 100..8000 отрендерен.

Прочие находки

  • 60-сек рендер p60_ref/p60_dep0/p60_dep1 подтвердил collapse класса при коротких строках.
  • rtdump.py живые дампы (регионы по VA, RA/WA) — DSP-кластер .data меняется между глубинами; раньше не совпали по layout (артефакт noise).

Work State

Completed

  • Свип-инфраструктура: sweep.py + tt_sweep.py (адаптив формата), рендер, сверочный анализ (spectrum.py медленный, нужно numpy).
  • Тест-тон: testtone.wav (2s, multi-tone 110Hz14kHz с AM) для изоляции резонансов; tt_ref==tt_678 (0 diff).
  • Базовые свипы по всем основным параметрам.

Active

  • Извлечена статическая depth-кривая: LUT 207 float @0x1826170e8..0x182617420, форма 0.302 + 0.698·sin(π/2·x)^0.94 (r²=0.99999), saved depthcurve.npy. ИДЕНТИЧНА у ref/dep0/dep1 → вшита в бинарь (build-time), не меняется от параметра = внутренний «depth→amount» маппинг.
  • Адаптивная динамика (burst-тест, band1@500Hz, q≈1, depth 0.864): burst500.wav (2s: 0.5s тишины → 1.0s 500Hz burst amp 0.2 → 0.5s тишины, + 1000Hz amp 0.05 фон). burst500_b1 (fx) vs burst500_byp (dry):
    • ATTACK: плавное включение нотч-ослабления, τ≈18 мс (фит на экспоненту A·(1e^t/τ), stable 17.6/17.7/18.8/18.8), установка −7.7dB бай-в-бай за ~100мс.
    • RELEASE: хвост после снятия бурста спадает за τ≈5 мс (после 1.5s fx-энергия 24→0 за ~30мс) — быстрый трелинг, симметрия фола не классическая (вероятно фильтр-транзиент, не медленный rel).
    • Глубина ослабления НЕ зависит от «порога» — это фикс. нотч на частоте band, amount задаётся curve(depth).
    • Рендерится через те же tt-файлы; РЕЗУЛЬТАТЫ в wav — 24-bit (sampwidth=3), читать через 24-bit decode (или любой np.frombuffer под sw), не 16-bit!
    • τ-инвариантность (burst-тест, band1@500Hz, win 10ms, Goertzel, фит к A(1e^t/τ)):
      • depth 0→0.86→1: A=6.39→7.70→7.91 dB; τ=18.3→16.8→16.5 ms (τ почти const) → depth контролирует amount, НЕ скорость.
      • selectivity: τ РАСТЁТ монотонно 12.3→16.8 ms при sel 0→10 (12.3/12.4/12.6/12.9/13.0/13.4/14.3/16.8) → Q/selectivity = time-const антреккинга.
      • depth-amount таблица (settled, band1@500): sel 0..6 монотонно 7.79→−9.08 dB, затем dip sel8=8.79, sel10=7.71 (non-monotonic у высоких sel, нюанс трекера).
      • «650Hz notch» в раннем FFT-анализе — АРТЕФАКТ деления на near-zero dry; реальные числа = ratio dry/fx только там, где dry>5% пика (pure 500Hz tone → окно вокруг 500).
  • secret свипа: patchparam.py обязателен для формат-сохранения: state-блок в RPP = base64, wrapped по 128-симв/строка с 2-sp indent и НОВОЙ строкой перед '>'. Ключевое: глубина пишется ТОЛЬКО %.16f (короткая 0.0 → collapse!), поэтому CLI depth=0.0! форсит полную точность.
  • Float-поиск по живому дампу: depth 301 hits (множество копий по bands), selectivity/sharpness → общий бакет (10.015, 9.997), mix → 4 копии 100.0, band freqs (678.76, 8242.67) → 0 точных (хранятся иначе, напр. Hz→binidx или через npf).
  • Живой diff dep0 vs dep1 в .data (0x182622130, 0x182622350, 0x1826223c0, 0x18262aca8, 0x182672fe4) — флоаты-состояние меняются; но регион содержит много мусора/nan (это .data буферы рендера, не коэфф).

Провалы/уроки

  • ASLR между отдельными rtdump-запусками сдвигает раскладку регионов → прямого VA-сравнения НЕТ (dep0b 10 регионов, dep1b 9). Только те адреса, что реально в 0x18260a000-0x18267c000 (совпадают) сравнимы.

Relevant Files

  • /home/m/re-tools/sim.py — поведенческий симулятор (LUT amount + 2nd-order bp Q(sel) + env attack/release + mix/mode). simulate(x, fc, depth, sens, sharp, sel, mode, attack, release, mix). RMSE settled ≤0.05dB (см. таблицу выше).
  • /home/m/re-tools/verify_sim.py — RMSE-сверка симулятора против рендеров (mag@500 трасса). Использование: verify_sim.py <fx.wav> --params depth=5 attack=5.
  • /home/m/re-tools/fit_curves.py — фит LUT (остаётся как аналитический эквивалент; LUT в sim.py приоритетнее).
  • probes500/probes500b_wav+byp — зонды ширины нотча; prb_/prb2_ — рендеры.
  • synth_multi.py (AM-комб 200–3000Hz резонансный), burst500.wav + burst500_b1.rpp/.wav + burst500_byp.rpp/.wav (burst-атак/релиз band1@500Hz), burst_dep0/dep1/sel0/sel2/sel5.rpp/.wav (τ-sweep), comb.wav + comb_b1_*.rpp/wav, comb_dep_{0..1}, depthcurve.npy. Рендеры Reaper = 24-bit — читать с sampwidth=3, не 16-bit!
  • /home/m/re-tools/patchparam.py — патчер PARAM в base64 state с формат-сохранением (128-wrap, depth=...! → %.16f).
  • /home/m/re-tools/spec.py, probe.py, notch.py, synth.py, mkbase.py, tt_sweep.py.
  • /home/m/soothe-bt/tone1kq.wav (чистый 1k, -18dBFS), dual.wav (500+2000Hz), resonant.wav (500Hz-резонанс).
  • /home/m/soothe-bt/t1kq_*.rpp/wav, res_only1_*.rpp/wav, dual_b1_*.rpp/wav.
  • /home/m/re-tools/sweep.py — адаптивный генератор RPP (tpl фиксирован, для смены файла использовать mkbase + tt_sweep).
  • /home/m/soothe-bt/tt_base.rpp (template: testtone + дефолтные парамы), tt_ref.wav.
  • /home/m/soothe-bt/s_corr_{sel,os,res,mix,mode}*.wav — верифицированные свипы на resonant.wav.
  • /home/m/soothe-bt/render_v5.rpp, s_ref2.wav (бит-в-бит оригинал).

Фаза 3: Трекинг подтверждён (aug 16) — ARCHITECTURE REVISION

  • Вход-зависимость: ред@1k растёт с уровнем сигнала (band1=1000, тон 1k): -27→-21→-15→-12→-9→-6→-3 dBFS5.80→7.75→9.91→11.05→12.21→13.40→14.60 dB. amount растёт с уровнем детектируемого резонанса (не бинарный порог).
  • Трекинг нотча (fresh renders trk_b1_*, вход tone1k 1k, band1∈{500,1000,2000}, длинная FFT): нотч ВСЕГДА на fc=тон=1k, band-частота НЕ двигает позицию режектора:
    • band=500 → 11.91dB, band=1000 → 15.82dB, band=2000 → 11.91dB, band1 off → 9.80dB, sens=0 → 9.80dB.
    • Единственный вклад band-EQ — ВЕС глубины детекции в области (совпадение band~тон даёт +4dB; off/sens0 2dB).
  • Мульти-пики: dual (500+2000), оба нотча активны одновременно при sel=1..20 (~10dB оба) → детектор находит НЕСКОЛЬКО резонансов; selectivity НЕ регулирует число нотчей на явных тонах.
  • Пользователь (иерархия): детектор оценивает каждую частоту спектра; band-EQ формирует его ВХОД (усиление/ослабление детекции по областям, у band2 sens может быть 12); selectivity = отбор пиков; sharpness = форма нотча; depth = глобальная глубина.
  • Reaper-ловушка: tt_sweep с ОТНОСИТЕЛЬНЫМ out_wav создаёт ПОДДИРЕКТОРИЮ → всегда абсолютный путь в RENDER_FILE.
  • Файлы: trk_base.rpp, trk_b1_{500,1000,2000,eqoff,sens0}.rpp/.wav, sel_base.rpp, sel_{1,3,10,20}.rpp/.wav, lvl_{03..27}db.wav + lvl_t_*.rpp/.wav.

Фаза 3b: amount зависИТ от уровня сигнала (не просто порог)

  • lvl_sweep (тон 1k, band1@1000, depth=0.864): ред@1k = 5.80 / 7.75 / 9.91 / 11.05 / 12.21 / 13.40 / 14.60 для 27..3 dBFS.
  • При depth=10: 21.67 / 24.15 / 26.63 / 27.86 / 29.09 / 30.33 / 31.56. Наклон d(ред)/d(level) ≈ 0.370.41 dB/dB обоих depth → amount ∝ уровень^p (монотонно с насыщением), НЕ бинарный порог. depth — масштаб, не floor.
  • Обе кривые монотонны и близки по форме (отношение ~2.2 при громком, ~3.7 при тихом → форма зависит от depth слабо).

Фаза 4 (aug 16): КАРТА EQ-ВЕСОВ ДЕТЕКТОРА

  • on полосы = enable детекторного веса; off-полоса её sens НЕ отдаёт в детекцию. Подтверждения:
    • eqT_b1_b2/b3/b4/b2b3b4 и trk_b1_500 (sens off-полос любая) ВСЕ = 11.91dB (только от b1 sens12); eq_off_sensfac (все off, factory sens) = 9.80dB = нейтраль.
    • eq1778_b2s_12 (b2 on=0) = 11.81, b1on12_b2s12 (b2 on=1) = 17.60 — ЕДИНСТВЕННАЯ разница band2 on.
    • Баг в раннем clear_bands сбрасывал b1 sens→0 (eq_b1_on был sens0=нейтраль 9.80; правильный net_b1on12 sens12 = 11.91).
  • S(sens) при совмещённом весе (band1@1k, тон 1k): sens 0/6/9/12/18/24 → boost над полом 0.00/3.16/4.65/6.02/6.02/6.02 dB. boost = 6.02·min(1, sens/12), насыщение +6dB при sens≥12 (не монотонно из-за сатурации детектора, не бага).
  • W(f) — колокол EQ-веса (тон 1k, одна полоса on sens12, варьирую band freq):
    • band1 (q≈1): 500→11.91(+2.11), 600→12.80(+3.00), 750→14.28(+4.48), 850→15.19(+5.39), 950→15.75(+5.95), 1000→15.82(+6.02), 1050→15.76, 1200→15.05(+5.25), 1500→13.44(+3.64), 2000→11.91(+2.11), 2500→11.16(+1.36). Симметричный широкий колокол (первый октав хвост +2.1dB, не спадает к 0).
    • band2 (q≈4.5): 600→10.15(+0.35), 750→10.78(+0.98), 850→11.97(+2.17), 1000→15.80(+6.00), 1200→11.68(+1.88), 1500→10.34(+0.54) — РЕЗКИЙ колокол. Q полосы управляет шириной детекторного веса (q=4.5 на −1 октаве теряет ~полностью, q=1 теряет только ~66%).
  • Позиция нотча: band freq не двигает режектор (всегда на резонансе тона); EQ-веса модулируют ТОЛЬКО глубину.
  • Новый модельный блок: red(f_tone) = floor(level) + Σ_on boost_i, boost_i = 6.02·min(1,|sens_i|/12)·H_q(f_tone; fc_i, Q_i).
  • Файлы: wf_{600,750,...,2500}.wav (band1 q-sweep), ws_{6,9,18,24}.wav (aligned sens), b2q_{600..1500}.wav (band2 q=4.5 sweep), b1on12_b2{on12,s12}.wav.

Фаза 5 (aug 16): STFT-параметры детектора (Stage 1)

  • Хоп/задержка: tone-jump зонд tj.wav (1k→1.5k в t=2.0s) → нотч пересаживается на новый резонанс за ~1 кадр ≈ 18мс (hop ≤ 512 сэмплов). Трекинг чирпа 400→2000Hz в реальном времени, dt≈0.03→0.05s (шум оценки FFT-бина, значимой задержки нет).
  • Разрешение по частоте: пары 1000+1040 → сливаются в один широкий нотч; 1000+1100 разделяются (центры ≈996/1104, но провал между ними всё ещё глубокий); 1000+1200 → два чётких отдельных нотча с полным восстановлением на 1100. → окно FFT ≈ 10242048 (бины 2143Hz), multiple-нотч подтверждён.
  • Энерготрекинг (fx@f по окну 2048, sync-Goertzel): до прыжка fx@1000 ≈ 24dB (ред ~14dB), после — fx@1500 ≈ −23.8dB. Двойной бурст (gap 10..100ms) → нотч на том же тоне не переоткрывается между бурстами (ред остаётся ~4dB floor) — bурст probe (осб) указывает на фиксированный window-центр, не пер-открытие.
  • Вывод для v5: STFT с окном ~2048, хоп ~5121024; window function Hann; детектор по |X| per-bin → локальные пики (selectivity); EQ-веса W(f) как множитель sensitivity per-bin; per-пик нотч (bandstop Q=sharpness).
  • Файлы: chirp_log_400_2000.wav, chirp_trk.rpp/.wav, pair_{1040,1100,1200}.wav + pair_trk_{40,100,200}, dburst_{10,20,50,100}.wav + dburst_trk_*, tj.wav + tj_trk.rpp/.wav.

Фаза 6 (aug 16): КОЛОКОЛ EQ-ВЕСА + SHARPNESS + level-floor (Stage 2 фит)

  • H(f) — колокол: H(f) = 1/sqrt(1 + (Qeff·A)²), A = f/fc fc/f. Qeff = 1.54·q^1.33 (q=1→1.54, 2→3.32, 3→5.64, 6→16.8). Воспроизводит кривую band1 (wf_*) с ошибкой ≤0.15dB по всему диапазону 500..2500.
  • Sharpness → Q нотча: psh_{1,3,5,10} (probes500, drive 500·0.2, band1@500): центр 505Hz red 1.3/4.3/6.8/8.7dB; ширина Q_notch≈1.15·sharp (sh10→~11, sh5→~6, sh3→~3, sh1→~1.2). Центр-глубина НЕ постоянна — растёт с sharp (1.3→8.7), т.е. sharp масштабирует amount И Q.
  • level-floor (нейтраль): neut_{24..3} (все полосы off/sens0, тон 1k): red = 2.22/3.36/4.85/5.71/6.65/7.65 dB при 24/18/12/9/6/3 dBFS rms. Монотонно, наклон ~0.31 dB/dB. al_{*} (b1@1000 sens12): 5.80/7.75/9.91/11.04/12.21/13.39 → boost = 3.59/4.39/5.06/5.33/5.56/5.74, насыщается к ~6dB с уровнем (не аддитивен в dB!).
  • Модель amount: red(f) = curv(level·G_eq(f)) с насыщением (не линейный сдвиг в dB). depth масштабирует (фаза 3b). Для sim: 2D LUT level×W.
  • Файлы: lvl_tone_lv{24,18,12,9,6,3}.wav, neut_{*}.rpp/.wav, al_{*}.rpp/.wav, psh_{1,3,5,10}.rpp/.wav.