diff --git a/.gitignore b/.gitignore index a177e9f..946a16e 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,8 @@ # --- расшифрованные дизассемблы декомпа: НУЖНО трекать (ценные артефакты) --- !handoff/ +!handoff/archive/ +!handoff/archive/*.md !handoff/nls_dasm/ !handoff/nls_dasm/*.dis !handoff/nls_dasm/*.bin diff --git a/AGENTS.md b/AGENTS.md index af05d71..b6401eb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,34 +1,8 @@ -# AGENTS.md — guide for AI agents working in this repo +# AGENTS.md — runbook for AI agents working in this repo -Bit-exact реверс DSP-ядра oeksound soothe2 (VST3) → транскрипция на C++18 в `dsp/`. -Полное журналирование — в `handoff/NOTES_LEVEL.md`, `handoff/NOTES_TWIN.md`, -`handoff/NOTES_CAPTURE.md`, `roadmap.md`. - -> **Текущая фаза (2026-08-27, v1.0):** ПРИМЕНЕНИЕ ДЕКОДИРОВАНО ФОРМУЛАМИ: -> аудио = побиновое умножение кадра на вещественную маску `10^(−cut_D/20)`, -> `cut_D = α·ln1p(lvl_raw/β)+c` (+Δ у вторых пиков). VLAW параметризация по (fc, q, sens): -> - res (fc=300-700, q=1.0): α=5.0, β=0.3 -> - t1kq (fc=800-1200, q<1.0): α=4.0, β=0.4 -> - t1k (q≥0.99, fc<1200): α=4.0, β=0.5 -> - t1k (q≥0.99, fc≥1200): α=4.5, β=0.4 -> - dual (fc=500, q=0.1-10.0): α=3.2193, β=0.4927, c=0.54, Δ=6.92 -> - al (sens<12): α=3.5, β=0.3; (sens≥24): α=4.5, β=0.4 -> -> **Результаты v1.0: TOTAL 0.870 dB** (bridge baseline 1.594 dB). Структурная цепь -> лучше bridge для t1k, res, dual, comb. Добавлен detector cascade 529c60 (Haar smoothing, -> magnitude, peak processing). Реализован exact ln/exp2 (log2_ln.hpp/cpp). -> Реализован real RFFT (fft.hpp/cpp), но FIR construction с real RFFT даёт 10.377 dB -> (плагин использует нестандартные twiddle операции). -> -> Ключевые факты: q НЕ влияет на закон (доказано 24kk); sens линейно через -> lvl_raw; далёкий контент не влияет — смешение шаблонно-локальное (24ll); -> аудио = exp(deepest-scratch)+const; буфер FIR@540668 = мин.-фазовое -> представление (exp(s−iH(s))) — не для транскрипции. Тела bigkernel'ов: -> 1803a06a0/180296c80/180323f20/1802dc0e0 (x87 exp-семейство, 24jj). -> -> ОСТАТОК: reverse-engineer plugin's exact twiddle operations (th1a90/th2180) -> для bit-exact FIR construction. Журнал: NOTES_LEVEL 24j–24kk2; -> карта метода: handoff/BLOCKMAP_529fe0.md. ЭТОТ ФАЙЛ ЧИТАЙ ПЕРВЫМ. +> **Статус → `README.md:13` (единственный источник TOTAL).** Этот файл — только runbook: сборка, метрика, env-флаги, tooling hazard. +> Полный журнал — `handoff/NOTES_LEVEL_INDEX.md` → `handoff/NOTES_LEVEL.md` (живая голова) + `handoff/archive/`. +> Карта метода — `handoff/BLOCKMAP_529fe0.md`. ЭТОТ ФАЙЛ ЧИТАЙ ПЕРВЫМ. ## Золотое правило (обязательно) 1. **Цель — bit-exact реверс кода**, НЕ эмпирическая подгонка кривых. Каждый параметр @@ -88,77 +62,21 @@ err = db(ta(out,1000)/ta(ref,1000)) # err в dB, цель <0.1 Тестовый корпус (`/home/m/soothe-bt/`, вне git): `tone1kq.wav`(вход), `t1kq_b1f_.wav` (рефы), `comb.wav`/`comb_ref.wav` (мультиполосный). -## Текущее состояние (2026-08-21, P4) - -**ДВА канона в `dsp/framed_model.cpp`:** - -1. **BRIDGE** (эмпирическая погона, путь framed_test 44.1k, NOTES:147): -``` -am_k: smoothed amp (2|X|/wsum, att~11ms/trel~80ms) -xv = log10(am_k / res_k) res_k = |2B/A| twin (min@band centre) -C = G·LUT(xv) + W·warp(f_k)^A G/W/A = 0.9963/0.3335/0.9807 (fit) -gain_k = (1−C) · res_k^rp(Q) rp(Q) = 0.0275·Q^0.2159 -LUT = monotone cubic (Fritsch–Carlson) через joint-fit узлы (al_* + B.11 anchors) -``` -2. **СТРУКТУРНАЯ цепь FUN_180529fe0** (`process_band_structural`, внутренняя сетка - 48000/4096 через `dsp/build/render48k`, NOTES_LEVEL:820-840 + апдейты 20j–21c): -``` -scale → LUT level-domain (t^γ·MULT, γ=0.344 decomp / MULT=4.2 placeholder) -→ IIR1 → IIR2 → mirror → blend(0.8)/exp2(−lvl) → combine(acc-update, без консюмера) -→ dual-warp(kBand768·kWarp·res^rp) → IIR3 bidirectional ×2 → dry/wet(identity) -``` -- **METRIC CRITICAL**: рефы 24-bit НЕЛЬЗЯ читать 16-bit кодеком (даёт phantom −53 dB); - `render_parity.load` (sw handling) + окно 3.5s — канон. dual honest ref = −10.2 dB FLAT. -- **Broadband-hypothesis ОПРОВЕРГНУТА** (NOTES:2026-08-20s): редукция per-bin. -- **Честные результаты**: bridge корпус mean 1.594 (comb 10.15 сломан); структурная цепь: - comb **6.12** и res **0.44** — лучше bridge; t1kq 0.77 / t1k 2.11 / al 0.99 / dual 3.26 — хуже. - Полная таблица: BITEXACT_PLAN §0 / NOTES_LEVEL:21c. -- **ГЛАВНЫЙ ОТКРЫТЫЙ РАЗРЫВ**: насыщение кривой редукции — реальная упирается в C_max≈0.70, - exp2(−lvl) не ограничен (dual@500: константный −6.7 dB при всех q). Лечится Шагом 7 - (live-захват A/B/γ) или статическим hunt'ом clamp'а в level-пути. Слепая подкрутка - γ/MULT исчерпана (свип 21c). -- **21d — закон редукции найден, канон НЕ сменён**: эффективное ослабление аффинно - в dB(lvl) (маска ≈ 0.8·lvl^−0.354; al-свееп ±0.13 dB). Scalar-семейство (X0,S,floor, - CMAX) не закрывает тон+шум одновременно → res/dual регресс у любой точки; ищем - контент-зависимый механизм. Инструментарий RT_DUMP_BIN / RT_DUMP_ALL в коде (opt-in). - -### Открытые пробелы (после 22z; детали — NOTES_LEVEL 22s–22z) -1. **Семантика входов `bands[]` в FUN_180529fe0** — главный вопрос. Отвергнуто: - lvl=f(am,res^α) с единым законом [22x, теорема], форма-постобработка маски [22w], - спрединг IDFT→окно→DFT [22z]. Живой захват: кривая редукции R=1/mask @слот - 0x5407f8 (пик следует за fc полосы), acc/f6f8 нули в стационаре. - Пути: (а) декод тел conv 0x1802a24c0 + complex-op шагов с буферами - 0x540548/550/598; (б) синхронная трасса буферов в аудио-колбеке. -2. dual-таблица по q (22x): cut@центра константен при любом q, Δcut скайрта ≈ - линейно по −ln res с b≈2.25 — геометрия twin-шаблона на сетке 0.25 Гц. -3. FFT-conv: последовательность вызовов декодирована (BLOCKMAP/22z), тело - 0x1802a24c0 (AVX2) и complex-op шаги — не расшифрованы. -4. Бит-экзактный exp2 (0x26b820); бит-точная DSP-FFT (P3); стерео M8 (P5) — отложены. -5. Источник констант RT_LAWAFFINE A/S в декомпе не найден (помечено EMPIRICAL). - ## Структура ключевых файлов -- `dsp/framed_model.{cpp,hpp}` — C++ порт mask-apply цепи: bridge-канон + структурная - `process_band_structural` (ГЛАВНЫЙ активный файл). -- `dsp/render48k.cpp` — пайплайн внутренней сетки 48000/4096 (resample → структурная цепь → resample). -- `dsp/framed_test.cpp` — CLI bridge-рендер входа (метрика см. выше). -- `scripts/corpus.py` — корпусный харнесс bridge + guard `--compare baseline_bridge.json`. -- `scripts/corpus_structural.py` — тот же корпус через render48k + режим `--vs-bridge`. -- `dsp/{twin,levelpath,freqpath,fftconv,vlog,leveltrack,fn529fe0}.cpp` — расшифрованные модули. -- `dsp/rt_mask_tables.{hpp,cpp}`, `rt_weights.{hpp,cpp}` — live-таблицы. -- `handoff/NOTES_LEVEL.md` — полный журнал уровня/маски (самый актуальный). -- `handoff/nls_dasm/` — 134 дизассембла декомпа. -- `/tmp/consumers_out.txt` — полный decomp `FUN_180529fe0` (уникален, вне git). -- `/tmp/snap_rt.bin` — live-снимок (ctx 0x2370040, SR 48000). -- `handoff/BLOCKMAP_529fe0.md` — полная блок-карта FUN_180529fe0 по raw asm (22v). -- `scripts/lawfit22r.py` — офлайн реплей-сим пайплайна (~2 c корпус), фиты законов. -- `scripts/resalpha.py` — сбор трактов + теорема об отсутствии α (22x). -- `scripts/dualtrace.py` + `play_loop.lua` — живой захват ctx при realtime-playback (22y). +- `dsp/framed_model.{cpp,hpp}` — C++ порт mask-apply цепи (ГЛАВНЫЙ файл). +- `dsp/render48k.cpp` — пайплайн 48000/4096 (resample → chain → resample). +- `dsp/twin.{cpp,hpp}` — twin FUN_180535880 (float-parity). +- `scripts/corpus.py` / `corpus_structural.py` — харнессы + guard `--compare`. +- `handoff/NOTES_LEVEL.md` — живой журнал (голова 24mm5+, хвост → `archive/`). +- `handoff/NOTES_LEVEL_INDEX.md` — оглавление журнала. +- `handoff/BLOCKMAP_529fe0.md` — блок-карта FUN_180529fe0. +- `handoff/nls_dasm/` — 134 дизассембла. -## Env-флаги экспериментов (24j–24kk2) +## Env-флаги экспериментов (актуальные) | флаг | действие | |------|----------| -| `RT_VLAW=1` | закон применённой стадии: mask=10^(−(α·ln1p(lvl/β)+c)/20) | +| `RT_VLAW=1` | закон применённой стадии: `mask=10^(−(α·ln1p(lvl/β)+c)/20)` | | `RT_SYN=1` | STFT БЕЗ синтез-окна (найденный слой плагина) | | `RT_WIN=0/1/2` | окно анализа: sym-hann / periodic / rect | | `RT_NOWARP=1` | отключить warp-модуляцию маски | @@ -166,26 +84,26 @@ scale → LUT level-domain (t^γ·MULT, γ=0.344 decomp / MULT=4.2 placeholder) | `RT_IIR12=0` | отключить частотные IIR1/2 (КРИТИЧНО с RT_VLAW — иначе размывают дипы) | | `RT_DUMP_BIN=` (+`RT_DUMP_FRAME=N`) | дамп тракта бина N: am/res/lvl_raw/band_level/prewarp/w | | `RT_VDBG=1` | stderr-печать vlaw-вычислений | +| `RT_FAITHFUL=1` | faithful-цепь `dsp/fnfaith.cpp` (детекторный каскад) | +| `RT_FIRCONV=1/3` | FIR-применение (1=complex-mul, 3=`1.019·mask^1.8345`) | Полный набор dual-решения: `RT_VLAW=1 RT_SYN=1 RT_NOWARP=1 RT_NOIIR3=1 RT_IIR12=0`. -## Инструменты сессии 24j–24kk2 +## Инструменты | скрипт | назначение | |--------|-----------| -| `scripts/rendersnap2.py [cap] [outdir]` | мягкий STOP-снаппер: слоты FIR/scratch/bands/R + скалярный банк + t_snap; НЕ убивает reaper; RENDER_FILE из rpp (не удалять чужие рефы!) | -| `scripts/campaign.py ` | ячейка параметризации: клоны rpp+рефы (~8 мин) | -| `scripts/disasm_func.py [len]` | capstone-дизасм с инлайн-резолвом RIP-констант | -| `scripts/iat_name.py` | рантайм-резолв импортов bigkernel'ов через PE-экспорты (SIGSTOP!) | -| `scripts/probe_states.py` / `probe_mem.py` / `dump_dispatch.py` | живые state-заголовки / память / таблицы диспатча | -| `scripts/hunt2.py` | перебор всех ctx-инстансов | -| `scripts/scan_pairs.py`, `scan_lutsub.py` | диагностика памяти по сигнатурам float-пар | +| `scripts/rendersnap2.py [cap] [outdir]` | мягкий STOP-снаппер (RENDER_FILE из rpp!) | +| `scripts/campaign.py ` | ячейка параметризации (~8 мин) | +| `scripts/disasm_func.py [len]` | capstone-дизасм с RIP-константами | +| `scripts/iat_name.py` | рантайм-резолв импортов (SIGSTOP!) | +| `scripts/probe_states.py` / `probe_mem.py` / `dump_dispatch.py` | live state/память/таблицы | +| `scripts/hunt2.py` | перебор ctx-инстансов | +| `scripts/scan_pairs.py`, `scan_lutsub.py` | диагностика памяти | -Датасеты кампании: `/tmp/opencode/sc_{q,sens,qmap,k,f,d}*` + `tract_*` + -`{level_pairs,f_series,q_series,k_series}.pkl/.npz` (см. NOTES 24bb–24kk2). +Датасеты: `/tmp/opencode/sc_{q,sens,qmap,k,f,d}*` + `tract_*` + `*.pkl/.npz` (см. `NOTES_LEVEL_INDEX.md`). +Открытые пробелы → `BITEXACT_PLAN.md`. ## Чистая работа -- Не коммитить: `*.bin`(дампы 112М), `*.wav/rpp`, `*.log`, `dsp/build/`, `ghidra-proj/`, - `dl/lib/bin/include/`, `regions*/`. См. `.gitignore`. -- После правки C++ — собрать (`cmake --build dsp/build --target framed_test`) и обновить - `handoff/NOTES_LEVEL.md`. Fasta метрику держать честной (тримнутая длина). +- Не коммитить: `*.bin`, `*.wav/rpp`, `*.log`, `dsp/build/`, `ghidra-proj/`, `dl/lib/bin/include/`, `regions*/`. См. `.gitignore`. +- После правки C++ — `touch` исходника + `cmake --build dsp/build --target framed_test` (hazard `AGENTS.md:66`) + `python3 scripts/corpus.py --compare scripts/baseline_bridge.json --tol 0.25`. diff --git a/BITEXACT_PLAN.md b/BITEXACT_PLAN.md index d9adb79..15b715b 100644 --- a/BITEXACT_PLAN.md +++ b/BITEXACT_PLAN.md @@ -1,270 +1,90 @@ # BIT-EXACT PLAN — путь от dB-приближения к побайтовой парности -Статус: **НЕ bit-exact**. Мы на уровне честной dB-параллели (полный корпус mean 1.594 dB, -comb 10 dB). Bit-exact ДОСТИЖИМ (F0 gate: плагин байт-детерминирован), но модель ещё не -воспроизводит реальный DSP-путь. Этот документ — план, как туда дойти, и точка отсчёта -для любой будущей сессии. **Читать вместе с `AGENTS.md` и `handoff/NOTES_LEVEL.md`.** +Статус: **НЕ bit-exact**. Канон — bridge `framed_test` 44.1k: **TOTAL 1.594 dB** (62 случая, trimmed 24-bit, `scripts/baseline_bridge.json`). Bit-exact достижим (F0 gate: рендеры байт-детерминированы `handoff/NOTES_LEVEL.md:1020`), но модель — погона. Читать вместе с `AGENTS.md` и `handoff/NOTES_LEVEL_INDEX.md`. -> **ОБНОВЛЕНИЕ 2026-08-22 (сессии 22a–22e):** -> 1. Phase-B оффлайн-гипотезы (pooling/temporal/scalar-ρ) — все ОПРОВЕРГНУТЫ (22a). -> 2. Шаг 7 выполнен → премиса опровергнута: BandConfig A/B/γ = −24/28/1 у ВСЕХ конфигов, -> но это GUI-timer кривые; аудио их не читает (22b). LUT-константы структурной цепи -> помечены EMPIRICAL. -> 3. Насыщение редукции НАЙДЕНО ЖИВЬЁМ и РЕШЕНО АЛГЕБРАИЧЕСКИ (22d/e): -> hot-тон упирается в ЖЁСТКИЙ пол gain=**−20.72 dB**, pin ≥24 dB драйва; -> `gain_floor = 20·log10(blend·ln10/20)` — совпадение 0.0055 dB! -> Константа ln10/20=0.11513 @0x1824c3cd4, аудио-юзеры FUN_180529c60/52baa0/bad0/bba0. -> 4. Инфраструктура: официальный параметр-мост (`setparam.lua`), XML `` в RPP = -> декоративная копия (не источник стейта); depth=±18dB/trim=±24dB; WAV bext/junk -> грабли подтверждены живьём. -> Новый приоритет №1 — Шаг 9 (ниже). -> -> **ОБНОВЛЕНИЕ 2026-08-23 (сессии 22s–22z):** Шаг 9 закрыт иначе — пол решён -> алгебраически ещё в 22e; фронт сместился. Отвергнуты: скалярные законы A/S -> (насыщение ~1.87–1.93, 22s/t), двухфакторные quad/resrp (22t), точечные -> lvl=f(am,res^α) — «теорема об отсутствии α» (22x), спрединг IDFT→окно→DFT (22z). -> Новый приоритет №1 — семантика входов `bands[]` (см. AGENTS.md и NOTES_LEVEL -> 22s–22z); карта метода — handoff/BLOCKMAP_529fe0.md. +> **2026-08-28 (24mm14): применение декодировано до формул.** `mask=10^(−cut_D/20)`, `cut_D=α·ln1p(lvl/β)+c [+Δ]` — три семейства, rms ≤0.016. `dual` 0.193 dB (env-gated `RT_VLAW=1 RT_SYN=1 RT_NOWARP=1 RT_NOIIR3=1 RT_IIR12=0`), детекторный каскад `180529c60` + RFFT до df0 бит-точны, `γ=1.760561` из цепи. **Гейт смены канона = bit-exact** (решение `24hh2`). Остаток: 3 шага ниже. -> **ОБНОВЛЕНИЕ 2026-08-25 (сессии 24j–24kk2):** ПРИМЕНЕНИЕ ДЕКОДИРОВАНО ДО ФОРМУЛ. -> Применённый фильтр = побиновное умножение кадра на вещественную маску -> `10^(−(α·ln1p(lvl/β)+c)/20)`; α/β/c — константы контент-семейства (таблица -> калибровок в NOTES 24ee); слой STFT без синтез-окна. dual-корпус 0.193 -> (флаги RT_VLAW/SYN/NOWARP/NOIIR3/IIR12). Гейт смены канона = BIT EXACT -> (решение пользователя 24hh2). Остаток до полного покрытия: (1) каскадный -> симулятор шагов 9–19 по dataflow BLOCKMAP 24hh/24ii + тела bigkernel -> 1803a06a0 и со. (24jj); (2) k-маппинг фронтенда (twin/am формулы); -> (3) Δ-правило вторых пиков. Отвергнуто: ×1.805-свёртка, клампы параметров, -> GUI-LUT в аудио-пути, B∝am, скалярные комбинации lvl/res. - -Цель фазы C (bit-exact): воспроизвести `FramedDetector` (= fn `FUN_180529fe0` mono-path) -настолько точно, что `verify_bit_exact.py` даёт побайтовое совпадение на рендерах -(`t1kq_*`, `dual_*`, `comb_*`). Ниже — конкретный порядок, что и зачем. +Цель фазы C: воспроизвести `FramedDetector` (= `FUN_180529fe0` mono-path) до `verify_bit_exact.py` побайтового совпадения (`t1kq_*`, `dual_*`, `comb_*`). --- -## 0. Текущее состояние (честная метрика, 24-bit, trimmed, 62 случая) +## 0. Текущее состояние — ЕДИНСТВЕННЫЙ ИСТОЧНИК `README.md:13` -| Группа | bridge mean|err| | bridge max | struct 48k mean|err| | struct max | -|--------|----------|-----|----------|---------| -| t1kq (fc-scan) | 0.226 | 0.500 | 0.768 | 1.013 | -| t1k (loud 0 dBFS) | 1.801 | 2.227 | 2.114 | 2.753 | -| al (level sweep) | 0.638 | 1.596 | 0.986 | 1.822 | -| res | 0.628 | 1.535 | **0.437** ✓ | 1.252 | -| dual (q-sweep) | 0.726 | 2.252 | 3.264 ✗ | 6.455 | -| comb (4-band) | **10.149** ✗ | 14.752 | **6.116** ✓ | 9.148 | -| **TOTAL** | **1.594** | 14.752 | 2.286 | 9.148 | +Честная trimmed метрика 62 случая (bridge vs structural 48k/4096 `corpus_structural.py`): -(struct = структурная цепь на внутренней сетке 48000/4096 через `dsp/build/render48k`, -прогон `scripts/corpus_structural.py`, NOTES_LEVEL:21c. Bridge = `framed_test` 44.1k.) +| Группа | bridge mean | bridge max | structural mean | VLAW env-gated mean | +|--------|-------------|------------|-----------------|---------------------| +| t1kq (fc-scan) | 0.226 | 0.500 | 0.768 | 0.426–0.852* | +| t1k (loud) | 1.801 | 2.227 | 2.114 | 0.577–0.930* | +| al (level) | 0.638 | 1.596 | 0.986 | 0.090–0.804* | +| res | 0.628 | 1.535 | **0.437** | 0.395* | +| dual (q-sweep) | 0.726 | 2.252 | 3.264 | **0.193** (max 0.438) ✓ | +| comb (4-band) | **10.149** | 14.752 | **6.116** | 2.678–4.335* | +| **TOTAL** | **1.594** | 14.752 | 2.286 | **0.870–1.931*** | -> **21d**: найден закон редукции — эффективное ослабление АФФИННО в dB(lvl): -> `x_exp2 = −0.6646 − 0.05877·dB` (маска ≈ 0.8·lvl^−0.354, γ≈decomp 0.344). -> На тонах al-свеепа линейность ±0.13 dB (42 dB диапазона!). Прекалибровка -> pre-IIR (X0=1.8/S=0.11) даёт TOTAL 2.633: t1kq/t1k/al/comb лучше, res/dual -> хуже. Grid-search семейства (X0,S,floor,CMAX) на валидированной траекторной -> модели: оптимум rms 0.75 dB, но НИ ОДНА точка семейства не закрывает тон+шум -> одновременно → канон не меняем (gate «без регресса групп»), ищем -> контент-зависимый механизм (детектор att/rel на флюктуациях / combine- -> консюмер). Инструментарий RT_DUMP_BIN/RT_DUMP_ALL оставлен opt-in. +`*` зависит от калибровки VLAW `α/β/c` (семейство dual vs fc-зависимая `AGENTS.md` больше не хранит таблицу — см. `README.md:13` и `handoff/NOTES_LEVEL.md:3919`). -**Корень проблемы**: активный канон — ЭМПИРИЧЕСКАЯ bridge-модель -`C = G·LUT(xv) + W·warp(f)^A; gain = (1−C)·res^rp` (xv=log10(am/res)). Это ПОГОНА, не -транскрипция. Структурная цепочка FUN_180529fe0 перенесена в C++ на внутренней сетке -(коммиты 7bf5a4a..b2cb923): уже ОБГОНЯЕТ bridge на comb (−4 dB) и res, но регрессирует -на однополосных t1kq/t1k/al и проваливает dual@500 (константный −6.7 dB при всех q — -отсутствие НАСЫЩЕНИЯ редукции: реальная кривая упирается в C_max≈0.70, exp2(−lvl) не -ограничен). Свип γ×MULT подтвердил: текущий оптимум (0.344/4.2) лучший, слепая -подкрутка исчерпана — проблема в ФОРМЕ кривой. - -> **КОРРЕКЦИЯ 22d/e**: «насыщение C_max≈0.70» старой модели = артефакт placeholder -> MULT=4.2 / Pchip cap 0.667, НЕ свойство плагина: живой плагин на ±24 dB trim -> редуцирует БЕЗ потолка (R 1.4→15.7 dB), а на горячем тоне (+6 dBFS) упирается в -> ЖЁСТКИЙ пол gain=−20.72 dB = **blend·ln10/20 точно** (Δ0.0055 dB). Механизм пола — -> в нетранскрибированном куске аудио-пути (след: FUN_180529c60, ln10/20-фактор), -> см. Шаг 9. +Канон = bridge `1.594` (эмпирика `C=G·LUT(xv)+W·warp^A, gain=(1−C)·res^rp`). Structural `2.286` обгоняет bridge на `comb/res`, регрессирует на однополосных — форма кривой редукции неверна. VLAW закрывает `dual` (0.193) — применённый слой и каскад декодированы. --- -## 1. Порядок работ (обязательный порядок; каждый шаг валидируется отдельно) +## 1. Порядок работ (3 шага, обязательный порядок; каждый валидируется отдельно) -> **ВАЛИДАЦИЯ (обязательно, НЕ пропускать).** Чтобы не повторить регресс Phase B, -> есть защитный харнесс `scripts/corpus.py` + зафиксированный bridge-базлайн -> `scripts/baseline_bridge.json` (62 случая, правильная 24-bit метрика): -> ```bash -> cmake --build dsp/build --target framed_test -> python3 scripts/corpus.py # полный корпус, текущая сборка -> python3 scripts/corpus.py --compare scripts/baseline_bridge.json --tol 0.25 -> ``` -> `--compare` фейлит (exit≠0), если любая группа регрессирует по mean|err| больше tol. -> Правило: структурная цепь (Шаг 1-2) должна НЕ регрессировать ниже bridge на -> однополосных (t1kq/t1k/al/res/dual) и ЖЕЛАТЕЛЬНО улучшать comb. Каждый под-шаг -> (IIR1 → blend → combine → warp → IIR3) коммитить отдельно и прогонять `--compare` — -> если конкретный под-шаг регрессирует, откатить именно его, а не всё сразу. -> Существующие `dsp/*_check.cpp` (twin/tables/leveltrack/levelpath/exp2/fftconv) — -> модульные чёрные проверки на бит-парность под-функций; тоже гонять: `cmake --build -> dsp/build` после правок. +> **Валидация:** `cmake --build dsp/build --target framed_test` + `python3 scripts/corpus.py` + `python3 scripts/corpus.py --compare scripts/baseline_bridge.json --tol 0.25` (фейлит при регрессе группы >tol). Структурная цепь валидируется `scripts/corpus_structural.py --vs-bridge`. Каждый под-шаг коммитить отдельно. +### Шаг 1 — Каскадный симулятор шагов 9–19 FUN_180529fe0 (ПРИОРИТЕТ №1) -### Шаг 1 — Реализовать полную структурную mono-цепочку FUN_180529fe0 (КОРЕНЬ) -Заменить bridge (эмпирическую погону) на точную транскрипцию. Реализовать в -`dsp/framed_model.cpp` (или отдельном `dsp/fn529fe0.cpp`) по NOTES_LEVEL:820-840: +Dataflow декодирован `handoff/BLOCKMAP_529fe0.md:22v` + `handoff/NOTES_LEVEL.md:4400`: ``` -1. scale: band_mask *= (fVar30/0x1a0)·0x540870·0x54088c (fVar30=1 из PRNG, locked) -2. IIR1: y[i]=A1[i]·acc+B1[i]·x[i] (kIIR_A1/B1, fast attack) -> f6f8 -3. copy f6f8 <- band -4. IIR2: inline, kIIR_A2/B2 (slow release) -5. mirror upper half = reversed lower (Hermitian) -6. blend: f6f8 = 0x540698·(1−mix)+mix·0.8; mask = exp2(−level)·f6f8 -7. combine: acc[band] = 0x540678 − 0x5406f8; mirror; - += 0x5406c8·upper; += 0x5406e8·lower; += 0x540678 -8. warp: mask *= kBand768[band]; mask *= kWarp (TWO warps) -9. IIR3: inline TWICE, kIIR_A3/B3 -10. dry/wet: mask = mask·(fVar30·0x540888)+(1−fVar30) (=identity сейчас) -11. FFT-conv (Шаг 4) +bands[i] → LOG#1 (140980) → 52d650 (bidir #1) → vec698*= (1−[54087c]) → vec6f8+= [54087c]·0.8 +→ DIVIDE vec6f8/bands (1803a06a0) → vec6f8=bands−ACC (dc40) → fma ATT/REL (1fa0/1940, тройки re,im,coef) +→ COPY (1b80) → mirror 9 + bigkernel 140b60 → EXP#1 (1409e0) + −1 → ×track (th2000) → ×warp (th2000) +→ LOG#2 (140980) → IIR4×2 (bidir, log-домен) → EXP#2 → bands_final +→ FIR-секция: DESIGN 1802a24c0 (conv_float_a24c0.dis) → copy → RFFT pair th2180/th1a90 (buf548/598) + → FIR[1..n/2]*=2.0 → EXP 1803831c0 → WINfreq· → FIR[0]=1 → df0 track⊗FIR → mask ``` -Критерий: на однополосных t1kq/t1k/al/res структурная цепочка должна НЕ регрессировать -ниже bridge (т.е. mean ≤ 0.6-0.8). ВАЖНО: прошлый регресс (F2) был из-за неверного -домена (пробовали combine в bridge-финале). Здесь combine/exp2 — в ЕГО собственном -домене (reduction/exp2), как в декомпе. +Тела bigkernel'ов резолвлены статически `handoff/NOTES_LEVEL.md:3944` (`1803a06a0` divide, `180296c80` expf, `1803831c0` табличная кривая, `1803a06a0` etc.; `24mm2` таблица). RFFT до df0 бит-точна `24mm11`. -### Шаг 2 — combine/аккумулятор 0x5407c8 (ПЕРЕ-СКОУП 2026-08-21) -Семантика декодирована на уровне thunk'ов (NOTES_LEVEL:21b, consumers_out.txt:833-885): -``` -f6f8 = mask − acc (0x8d60, dst=3-й аргумент; acc НЕ перезаписывается) -mirror f6f8 -f6f8_upper += kRTAtt·acc_upper ; f6f8_lower += kRTRel·acc_lower (0x3c40) -acc += mask (0x5a20, персистентный per-band аккумулятор) -``` -**КРИТИЧЕСКОЕ**: в online однополосном пути обновлённые f6f8/acc НЕ имеют консюмера до -warp/IIR3/dry-wet (проверено исчерпывающим grep). Combine НЕ может влиять на single-band -вывод сам по себе. Ожидалось, что combine закроет comb — частично закрыл уже сам -структурный каркас (comb 6.1 vs bridge 10.1 без wiring). ДАЛЬНЕЙШИЙ ШАГ: найти точку -потребления acc/f6f8 (межполосный уровень или FFT-conv каскад) и только потом wire. -НЕ изобретать track→mask feedback (эмпирика, запрещено золотым правилом). -ВНИМАНИЕ: `FUN_1805316e0` — это WRITER КОЭФФИЦИЕНТОВ полос (17 case), НЕ масковый -комбинер (ошибка в старой редакции этого плана и в Шаге 8). +**Делать:** оп-за-оп транскрипция `scripts/cascade_sim.py` (numpy-эквиваленты divide/expf с FMA-точностью) → валидация на `multi6` (6 нотчей один прогон, `handoff/NOTES_LEVEL.md:3809`) и `sc_*` дистанционной серии (rms сейчас 0.42 на угаданных формах). После структуры — C++ порт `dsp/fn529fe0.cpp` с точными полиномами. -### Шаг 3 — Bit-exact exp2 (F5b) -Заменить `std::exp2`/`exp2d::exp2_dsp` на точную табличную реализацию `0x26b820`: -таблицы уже извлечены (`dsp/exp2_tables.{hpp,cpp}`, 8×16 irr + серия kExp2_big). Нужно -транскрибировать body 1:1 (Cody-Waite hi/lo, vfmadd213sd-полином, спец-ветви subnormal/ -overflow). Пока не bit-exact — оставить `exp2d::exp2_dsp` (численно = std::exp2). -Критерий: `exp2_check` сравнивает протв захваченных пар точка-в-точку. +Критерий: каскад воспроизводит `B=exp(scratch)` из `multi6` на всех 6 пиках ±0.05 dB (сейчас `rms 0.42`), затем `dual` 0.193 сохраняется без per-семейной калибровки. -### Шаг 4 — FFT-conv 0x535a70 + FIR (ПРИОРИТЕТ ПОНИЖЕН 2026-08-21) -FFT-conv сглаживает маску перед FIR (0x540658 window / freqaxis). Это последний этап -mono-цепи. `dsp/fftconv.cpp` есть, но нужна точная блоковая обработка (overlap-save как -в декомпе 0x52b550-0x52b8b5), не текущий stand-in. Критерий: маска-гейн после conv -совпадает по форме с реальным ref (сглаживание нотча, двусторонний хвост). -**ПОНИЖЕНО до P3**: NOTES_LEVEL:18c — окно 0x540658 near-flat (0.8→1.0 plateau при -N/2≥2048), эффект построения FIR на форму маски минимален при N=4096. Последовательность -уже размечена (fwd → kill mirror → fill xmm13/xmm9 → inv → complex op → fwd → window -copy → inv → FIR[0]=1.0); остались неизвестные скаляры xmm13/xmm9 и complex-op шага 4. +### Шаг 2 — k-маппинг фронтенда (twin/am) -### Шаг 5 — Bit-exact DSP-FFT 0x140a70 (multi-week, P3) -Заменить `dsp/fft.cpp` (std::cos radix-2) на точный split-radix 2/4/8 по плану из -NOTES_LEVEL:1107-1116: -- twiddle: `DAT_182616800` sin-таблица, `sin(k·2π/1024)`, loader `FUN_180039b00` (stride 2^(10-m)) -- butterfly: `FUN_18000bfc0/18000c5e0` + 0x8440 elementwise mul + `FUN_1800437c0` acc -- plan-gen: `FUN_18002f980` (рекурсия + per-log2 фактор-таблицы) -Это НЕ блокирует Шаги 1-4 (bridge/структурная цепь рендерят и с численным FFT), нужен -только для побайтовой парности FFT-conv-пути. +`k = lvl_impl / lvl_ours` (`handoff/NOTES_LEVEL.md:3436`): `k(q≥2)=0.403` const, `k(sens)` экспонента (5.37 при s18, 22.0 при s24). При q=1.0 vs q=0.5 `k` идентичен — `q` НЕ влияет на закон (`24kk`), `sens` линейно через `lvl_raw` (`24kk`). Остаток — twin gain пол (`res` плагина ≥0.153 vs наш 0.0069) и амплитудная нормировка `am` (wsum/окно в `dsp/spectral.cpp` vs декомп `FUN_180535880` tail-calls). -### Шаг 6 — Внутренняя геометрия 48000/4096 vs 44100/2048 — ✅ ПАЙПЛАЙН СДЕЛАН (2026-08-21) -Внутренний DSP SR=48000/N=4096 (freqaxis spacing 11.713 Hz). Хост рендера 44100/2048. -F4-тест показал, что простая resample_poly НЕ закрывает (даже хуже). Нужно: детектор -гнать на 48000/4096 (IIR-таблицы индексированы 0..2048 = N/2+1 при N=4096), затем -свести к 44100. Ожидается закрытие хвостов t1k_b1f/al (+2.2/−1.6) — именно уровневой -зависимости. Критерий: корректное выравнивание бина и окна между двумя сетками. -**Статус**: пайплайн `dsp/render48k.cpp` (resample 44.1→48 → SpectralProcessor(4096,1024,48000) -→ resample обратно) реализован и гоняет полный корпус (`scripts/corpus_structural.py`, -коммиты 7659eb0/6924e53). Выравнивание бина/окна валидировано smoke; уровневые хвосты -t1k/al НЕ закрыты самим по себе — см. §0 и Шаг 7. Известный артефакт: zero-pad последнего -BLK-блока даёт спад am в ~последних 0.06s (косметика, на метрику почти не влияет). +**Делать:** декод `FUN_180535880` tail-calls + `FUN_180536300` caller (grid `NOTES_TWIN.md`) до точных `twin_coeff`/`am` формул, либо калибровка `k(q,sens,fc)` по `sc_*` + `tract_*` как таблицы с декомп-обоснованием. -### Шаг 7 — BandConfig A/B/γ (level-path ctx+0x188) live-захват под конкретные конфиги -> **СТАТУС 2026-08-22: ВЫПОЛНЕН → ПРЕМиса ОПРОВЕРГНУТА (NOTES_LEVEL 22b).** Захват по 7 -> конфигам дал идентичные A=−24/B=28/γ=1, но весь кластер FUN_180563440/563a60 — -> GUI-timer only; аудио FUN_180529fe0 BandConfig не читает. Насыщение кривой редукции -> искать в теле аудио-функции (см. NOTES_LEVEL 22b, выводы). -Структурная LUT-кривая `FUN_180563a60` (A/B/γ). Снято для render_long (A=−24/B=28/γ=1) -и t1kq (то же), но для остальных тестов не захвачено. Метод автоматизирован -(NOTES_CAPTURE.md). Захватить для t1k_b1f / al / dual-конфигов → реальные A/B/γ → это -закрывает уровневую зависимость, которую bridge-LUT не может (F1 closure). Критерий: -mean кап-нагрузки al/t1k ≤ 0.3 dB. +Критерий: cross-config `α/β/c` становятся вычисляемыми, а не фитовыми; `sens 6..24` перестаёт требовать per-групповой `α`. -### Шаг 8 — Стерео M8 + полный pipeline (P4/P5) -Финальный рендер stereo (link/balance/LR-vs-MS) по M8 и межполосное суммирование. -Все текущие рендеры mono. Для bit-exact графа нужны стерео-рендеры как мишени. -ВНИМАНИЕ: `FUN_1805316e0` = writer коэффициентов полос, НЕ комбинер масок — точка -межполосного суммирования масок/acc ещё не локализована (см. пере-скоуп Шага 2). -Критерий: `verify_bit_exact.py` — побайтовое совпадение данных-чанка WAV. +### Шаг 3 — Δ-правило вторых пиков (pre-combine) -### Шаг 9 — Механизм пола редукции blend·ln10/20 (НОВЫЙ ПРИОРИТЕТ №1, 2026-08-22) -Живой факт (22d): hot-тон упирается в ЖЁСТКИЙ пол gain=−20.72 dB = `20·log10(0.8·ln10/20)` -с точностью 0.0055 dB; колено между L=−6..0 (trim-шкала); холодный тон на ±24 trim -пола НЕ достигает. t-clamp в транскрибированной LUT НЕ даёт pin (grid MULT≤12/B≥18 — -NOTES 22e) ⇒ пол живёт в другом куске аудио-пути. +Второй контент-пик: `cut = α·ln1p(g·lvl/β)+c`, `g@2000≈12.15` vs `g@fc=1.0` (`handoff/NOTES_LEVEL.md:3890`), но `g` меняется между прогонами (12.15 vs 1.85 при той же геометрии) — зависит от `STATE=[ctx+0x540788]` (спектральная память, `24ii2`). Смешение шаблонно-локальное (`24ll`: далёкий тон@4000 не влияет), ширина провалов константа `24k-2`. -9a. Декодировать `FUN_180529c60` полностью: фактор - `expf((p87c·30 − 90)·ln10/20)` на band-буферы 0x540678[band] через - `FUN_1804d56b0` (f_529c60.dis:94-101) — что именно считает 1804d56b0 и куда - идёт произведение (это vtbl-метод рядом с аудио-entry). -9b. Найти сайт клампа: grep ln10/20-константы (0x1824c3cd4) по остальным юзерам - (18052baa0/bad0/bba0) и по f529fe0.dis/f_52d650.dis на предмет floor/clamp маски. -9c. Проверка предсказаний модели пола: (i) floor_gain(mix) сдвигается как - 20log10(blend′/0.8); (ii) пол частотно-зависит только через blend(freqaxis) - при mix<100; (iii) knee-позиция от sens/depth. Рендеры через setparam.lua. -9d. Порт клампа в `process_band_structural` → smoke → корпус с гейтом - (`corpus_structural.py --vs-bridge`; цель: dual@500 уходит с −6.7, res/dual/comb ≥ bridge). -Критерий: воспроизвести pin −20.72 dB в рендере модели + отсутствие регресса групп. +**Делать:** серия `campaign.py` с вариацией `STATE` (разный контент до основного тона) при фиксированной геометрии → формула `g(b)=G(геометрия, STATE)` + pre-combine шага `52a397` (`handoff/BLOCKMAP_529fe0.md:43`). + +Критерий: `d1500` серия `3.43→4.23` монотонна без отрицательных `w(d)` (`24mm`). --- -## 2. Что НЕ делать (подводные камни из дока) +## 2. Что НЕ делать -- **НЕ вводить эмпирию там, где есть декомп.** Каждый параметр — из декомп-адреса или - live-таблицы, иначе пометить EMPIRICAL и в «осталось» (золотое правило AGENTS). -- **НЕ делать combine в bridge-final-gain домене** — F2 показал регресс 4.68/5.22. - Combine/exp2 живут в reduction/exp2-домене реальной цепочки. -- **НЕ менять Pchip-LUT в bridge** — регрессирует весь корпус (F1, параметрич. хуже). - Bridge — только запасной вариант, пока структурная цепочка не пройдёт Шаг 1-2. -- **НЕ использовать битые 24-bit загрузчики** — метрика `render_parity.py:43` правильная - (x>=0x800000 => x−0x1000000). В тестовых скриптах использовать тот же код. -- **НЕ редактировать XML `` в RPP ради изменения звука** — это декоративная - UI-копия, плагин берёт стейт из бинарной части чанка (NOTES 22c). Только мост - `setparam.lua`. -- **НЕ читать WAV наивным readframes+reshape** — REAPER пишет bext/junk-чанки; только - каноничный `render_parity.load` (иначе фантомный шум/клиппинг, NOTES 22c). +- **НЕ вводить эмпирию где есть декомп** (золотое правило `AGENTS.md:33`). `LCP` и `MULT` без источника — помечать `EMPIRICAL`. +- **НЕ делать combine в bridge-final-gain домене** — F2 регресс 4.68/5.22 `handoff/archive/NOTES_LEVEL_2026-08-18_2026-08-23.md:1064`. +- **НЕ читать WAV наивно** — только `render_parity.load` (bext/junk, 24-bit `handoff/NOTES_LEVEL.md:1462`). +- **НЕ править XML `` в RPP** — декоративная копия, стейт в бинарной части (`setparam.lua`/`patchparam.py`). +- **НЕ свёртка/OLA поверх маски** — опровергнуто `24j` (`A=1.019·V^1.8345` = произведение экспонент, не свёртка). ## 3. Риски и время -- **Доминирующий риск**: дорогой структурный перенос (Шаг 1-2) может снова регрессировать - ниже bridge, как Phase B. Митигация: валидировать каждый под-шаг (IIR1 отдельно, blend - отдельно) на t1kq, не коммитить пока не ≥ bridge. -- **DSP-FFT (Шаг 5)** — multi-week само по себе. Но НЕ блокирует Шаги 1-4. -- **Стерео (Шаг 8)** — новая мишень-корпус, больше рендеров. -- **Оценка**: Шаги 1-4 (монопуть) — 1-2 недели. Шаг 5 — до 3 недель. Шаги 6-8 — 1-2 недели. - До полной байтовой парности — ориентировочно 1-2 месяца с аккуратным монофокусом. +- Доминирующий риск — каскад 9–19: 5700 строк AVX-512+FMA (`1803831c0`), 10 ядер. Митигация: numpy-симулятор до C++ порта, валидация на `multi6` самосогласованно. +- Twin/am — multi-week (dispatch ядра 2/4/8, plan-gen `FUN_18002f980`). Не блокирует Шаг 1. +- Стерео M8 — отложен (все рендеры mono). +- Оценка: Шаг 1 — 1–2 недели, Шаг 2 — 1–2 недели, Шаг 3 — дни. До байтов — 1–2 месяца. -## 4. Точка входа для следующей сессии +## 4. Точка входа -1. Прочитать `AGENTS.md`, затем NOTES_LEVEL **UPDATE 22s–22z** (журнал актуальной - фазы) и `handoff/BLOCKMAP_529fe0.md` (блок-карта FUN_180529fe0 по raw asm). -2. Собрать: `touch dsp/framed_model.cpp && cmake --build dsp/build --target render48k framed_test` - (TOOLING HAZARD: touch перед сборкой обязателен). -3. Бейзлайны: канон = `RT_LUT_OFF=1 RT_IIR12=0 RT_NOWARP=1 RT_NOBLEND=1 RT_NOIIR3=1 - RT_LAWAFFINE=7.4,1.85 python3 scripts/corpus_structural.py` → TOTAL 1.931; - bridge-гейт: `--vs-bridge scripts/baseline_bridge.json` (1.594). -4. **ПРИОРИТЕТ №1 — семантика входов `bands[]`**: - a. Статика: декод тел conv 0x1802a24c0 (float)/0x1802fa420 (double, - резолв ILT-стабов через idx@0x1826159a0, см. BLOCKMAP thunk-таблицу) и - complex-op шагов с буферами ctx+0x540548/550/598. - b. Динамика: синхронная трасса буферов bands[]/f6f8/acc в момент аудио- - колбека (`scripts/dualtrace.py` + `play_loop.lua`; между кадрами acc/f6f8 - нули — снапшот должен попадать В колбек, см. NOTES_CAPTURE 22y). -5. Инструменты: `scripts/lawfit22r.py` (офлайн реплей, 2 c корпус — ЛЮБОЙ закон), - `scripts/resalpha.py` (тракты+теорема α), live-capture `dualtrace.py`. -6. Факт для калибровки интуиции: dual-таблица по q в NOTES 22x (cut@центра - константен; Δскайрта ≈ линейно по −ln res, b≈2.25 дБ/e-fold). +1. `AGENTS.md` → `handoff/NOTES_LEVEL_INDEX.md` (живая голова `24mm5+`) + `handoff/BLOCKMAP_529fe0.md`. +2. `touch dsp/framed_model.cpp && cmake --build dsp/build --target render48k framed_test` (hazard `AGENTS.md:66`). +3. Бейзлайны: `python3 scripts/corpus.py --compare scripts/baseline_bridge.json --tol 0.25` (TOTAL 1.594); VLAW dual `RT_VLAW=1 RT_SYN=1 RT_NOWARP=1 RT_NOIIR3=1 RT_IIR12=0` → 0.193. +4. Инструменты: `scripts/cascade_sim.py` (структурная фаза), `scripts/rendersnap2.py`, `scripts/disasm_func.py`, `scripts/iat_name.py`, `scripts/campaign.py`. diff --git a/README.md b/README.md index 8caf608..d74a876 100644 --- a/README.md +++ b/README.md @@ -10,80 +10,49 @@ --- -## Статус (24kk2, 2026-08-25) +## Статус (24mm14, 2026-08-28) — ЕДИНСТВЕННЫЙ ИСТОЧНИК TOTAL -**Цель — bit-exact реверс** (гейт смены канона зафиксирован пользователем: только -после прослеживания всех параметров до декомпа и схождения корпуса в шумовой пол). -Декомпиляция DSP-ядра закрыта (~95%); дизассемблы в `handoff/nls_dasm/` (~140). +**Канон (bridge, `framed_test` 44.1k): TOTAL 2.286 dB** — честная 24-bit trimmed метрика, 62 случая (`scripts/baseline_bridge.json`). Guard: `python3 scripts/corpus.py --compare scripts/baseline_bridge.json --tol 0.25`. Детали — `handoff/NOTES_LEVEL_INDEX.md`. -**Применение декодировано до формул** (сессия 24j…24kk2): +**Цель — bit-exact** (гейт смены канона = все параметры прослежены до декомпа + корпус в шумовой пол). Декомп DSP-ядра ~95%, `handoff/nls_dasm/` ~140 `.dis`. + +**Применение декодировано до формул** (сессии 24j…24mm14, журнал — `handoff/NOTES_LEVEL.md` живая голова + `handoff/archive/`): ``` 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. +Калибровки (три независимых семейства, rms ≤0.016 дБ): α/β/c зависят от контента (α удваивается с числом тонов — частотное смешение шаблонно-локальное), q НЕ влияет на закон (`24kk`), sens линейно через lvl_raw (`24kk`). -- **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-семейство). +- **dual-семейство решено (env-gated)**: 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`. Канон не тронут. +- **Буфер FIR@540668** — промежуточное мин.-фазовое `exp(s−i·H(s))`; аудио = `exp(scratch)` напрямую. +- **Тела bigkernel'ов** (рантайм IAT): `1803a06a0 / 180296c80 / 180323f20 / 1802dc0e0` — x87 exp-семейство (`handoff/BLOCKMAP_529fe0.md:540`). -### Главное за 2026-08-24…25 (сессии 24j–24kk2) +### Главное за 2026-08-24…28 (24j–24mm14) -1. **Применение = побиновный complex-multiply кадра** на маску; «магический ×1.805» - оказался произведением экспонент стадий построения буфера (0.984×1.8345). -2. **Закон уровня универсальной формы** `α·ln(1+L/β)+c` — подтверждён тремя - независимыми калибровками; константы зависят от контента (число тонов) и слабо от fc. -3. **Слой STFT**: плагин НЕ домножает выход обратного FFT на окно - (`RT_SYN=1`); WIN_WINDOW движка — фейд 0.5→0.8 ровно за 2049 сэмплов (=бинам кернела). -4. **GUI/аудио разделение**: LUT-строитель FUN_180563a60 — GUI-ветка; аудио-компрессия - живёт в семантиках шагов 9–19 BLOCKMAP. -5. **Dataflow шагов 9–16 декодирован**: vec6f8=bands−ACC; fma тройками - (re,im,coef) с ATT/REL; th2000=поэлементное умножение массивов (не axpy!); - шаг 12=COPY (исправлен старый BLOCKMAP). -6. **Инструменты**: rendersnap2 v7 (мягкий STOP-снаппер со слотами+скалярами, - RENDER_FILE-фикс), patchparam.py (правка VST-чанка RPP!), campaign.py - (ячейка параметризации), disasm_func.py (capstone с RIP-константами), - iat_name.py (рантайм-резолв импортов через PE-экспорты). +1. **Применение = побиновный multiply** на вещественную маску; «×1.805» = `0.984×1.8345` (экспоненты стадий). +2. **Закон `α·ln1p(L/β)+c`** — три независимых калибровки; константы контент-зависимы. +3. **Слой STFT без синтез-окна** (`RT_SYN=1`); `WIN_WINDOW` 0.5→0.8 за 2049 сэмплов. +4. **GUI/аудио разделение**: `FUN_180563a60` — GUI-ветка; аудио-компрессия в шагах 9–19 `BLOCKMAP`. +5. **Dataflow 9–16**: `vec6f8=bands−ACC`, fma тройками `(re,im,coef)` ATT/REL, `th2000`=array-mul, шаг 12=COPY. +6. **Детекторный каскад `180529c60` (vt+0x28)**: `|z| → Haar [0.25,0.5,0.25]×2 → peak/sin → w=1/inner → blend 5407a8`. +7. **RFFT до df0 бит-точна** (`th1a90/th2180` + `buf548` scale 2^-12, `24mm9` 0.0065 dB). ```bash -# Сборка и канонные команды: +# Сборка и канонные команды (см. AGENTS.md — runbook): 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.py --compare scripts/baseline_bridge.json --tol 0.25 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). +### Открытые bit-exact пробелы (приоритет → `BITEXACT_PLAN.md`) +1. **Каскадный симулятор шагов 9–19** — dataflow готов (`24hh/24ii`), тела bigkernel'ов известны, rms ~0.42 на угаданных формах → оп-за-оп транскрипция + `sc_*` датасеты. +2. **k-маппинг фронтенда** (twin/am, `k=0.403 ∀q≥2`, `24x`). +3. **Δ-правило вторых пиков** из pre-combine. -> Исторический блок (поведенческая/численная модель 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/`. - -```bash -python3 framed_render.py dual # (исторический канон B.15) -``` -
+> Исторический блок B.1…B.15 (`framed_render.py`, Pchip, `res_power`) — см. `handoff/archive/NOTES_LEVEL_2026-08-18_2026-08-23.md` и `roadmap.md`; не канон. --- @@ -91,69 +60,52 @@ 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…) -│ +├── README.md ← вы здесь (ЕДИНСТВЕННЫЙ источник TOTAL) +├── AGENTS.md ← runbook: сборка, метрика, env-флаги, tooling hazard +├── BITEXACT_PLAN.md ← план к bit-exact (3 шага, критерии, риски) +├── roadmap.md ← журнал B-фаз (B.1…B.15, свёрнут; детали → archive) ├── 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) +│ ├── framed_model{.cpp,.hpp} ← ГЛАВНЫЙ: mask-apply цепь (канон) +│ ├── framed_test.cpp ← CLI bridge-рендер (N=2048, SR 44100) +│ ├── render48k.cpp ← структурная цепь 48k/4096 (resample → chain → resample) +│ ├── rt_mask_tables{.hpp,.cpp}, rt_weights{.hpp,.cpp} ← live-таблицы (IIR A/B, warp) +│ ├── twin{.hpp,.cpp} ← twin FUN_180535880 (float-parity) +│ ├── levelpath/freqpath/fftconv/vlog/leveltrack/fn529fe0 ← расшифрованные модули +│ ├── fft*.cpp, twiddle_*.cpp/hpp, phase_table.*, cody_waite.hpp │ └── 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 +├── handoff/ +│ ├── NOTES_LEVEL.md ← живой журнал (голова 24mm5+; хвост → archive/) +│ ├── NOTES_LEVEL_INDEX.md ← оглавление журнала по датам/темам +│ ├── BLOCKMAP_529fe0.md ← карта метода FUN_180529fe0 (актуальна) +│ ├── NOTES_TWIN.md ← twin краткая справка (детали → dsp/twin.cpp) +│ ├── NOTES_CAPTURE.md ← registry heartbeat, live-таблицы (сжато) +│ ├── archive/ ← история: NOTES_LEVEL_2026-08-18_2026-08-23.md, +│ │ SESSION_HANDOFF_2026-08-18.md, summary_v4.md +│ └── nls_dasm/ ← 134 дизассембла (f_563440, f_563a60, f529fe0, twin, fft) +├── scripts/ +│ ├── corpus.py / corpus_structural.py ← харнессы (guard --compare) +│ ├── rendersnap2.py, campaign.py, disasm_func.py, iat_name.py ← инструменты 24j+ +│ └── lawfit22r.py, resalpha.py, cascade_sim.py, wine_*trace.py +├── *.java ← Ghidra-скрипты (DumpFuns, ImportRtti …) +├── soothe_mem.bin ← дамп памяти (вне git, VA−0x180000000) +├── rwin_{A0,A1,B0,C0}.npy, r_freqaxis.npy ← живые таблицы (48k) +└── framed_render.py, model_*.py, sim.py ← исторические B-модели (не канон) ``` ## Документация | Документ | Содержание | |---|---| -| [AGENTS.md](AGENTS.md) | **Старт для агента**: сборка, метрика, bit-exact пробелы, структура | -| [BITEXACT_PLAN.md](BITEXACT_PLAN.md) | **Путь к bit-exact**: порядок работ (8 шагов), критерии, риски, точка входа | -| [roadmap.md](roadmap.md) | Журнал всех результатов/гипотез (B.1…B.15, P4), статус по фазам, риски | -| [handoff/SESSION_HANDOFF.md](handoff/SESSION_HANDOFF.md) | Инвентарь декомпа (§0), трансляция/ключевые адреса (§2), Phase-5 план (§6) | -| [handoff/NOTES_LEVEL.md](handoff/NOTES_LEVEL.md) | **Level-path/mask-цепь, live-таблицы, bit-exact протокол** (самый актуальный) | -| [handoff/NOTES_TWIN.md](handoff/NOTES_TWIN.md) | Twin-резонатор (FUN_180535880/536f90), caller, grid/oversample | -| [handoff/NOTES_CAPTURE.md](handoff/NOTES_CAPTURE.md) | Live-захват таблиц (registry heartbeat, SR 48000) | -| [handoff/nls_dasm/](handoff/nls_dasm/) | 134 дизассембла (f_563440, f_563a60, f529fe0, twin, iface, fft) | -| [notes_giant_fft.md](notes_giant_fft.md) | FFT-планировщики/ядра/twiddle/Cody-Waite | -| [summary.md](summary.md) | **Историческая** сводка поведенческой модели (v4, sim.py) | +| [AGENTS.md](AGENTS.md) | **Runbook**: сборка, метрика, env-флаги, tooling hazard | +| [BITEXACT_PLAN.md](BITEXACT_PLAN.md) | **План к bit-exact**: 3 шага, критерии, риски, точка входа | +| [handoff/NOTES_LEVEL_INDEX.md](handoff/NOTES_LEVEL_INDEX.md) | **Оглавление журнала** по датам/темам → `NOTES_LEVEL.md` / `archive/` | +| [handoff/NOTES_LEVEL.md](handoff/NOTES_LEVEL.md) | Живой журнал (голова 24mm5+; хвост → `archive/`) | +| [handoff/BLOCKMAP_529fe0.md](handoff/BLOCKMAP_529fe0.md) | Карта метода FUN_180529fe0 (актуальна) | +| [handoff/nls_dasm/](handoff/nls_dasm/) | 134 дизассембла декомпа | +| [roadmap.md](roadmap.md) | Журнал B-фаз (свёрнут, детали → archive) | +| [handoff/NOTES_TWIN.md](handoff/NOTES_TWIN.md) | Twin краткая справка (детали → `dsp/twin.cpp`) | +| [handoff/NOTES_CAPTURE.md](handoff/NOTES_CAPTURE.md) | Registry heartbeat, live-таблицы (сжато) | +| [handoff/archive/](handoff/archive/) | История: `NOTES_LEVEL_2026-08-18_2026-08-23.md`, `SESSION_HANDOFF_…`, `summary_v4.md` | ### Тестовый корпус `/home/m/soothe-bt/` (вне git) @@ -198,15 +150,8 @@ gain = max(1 - C, eps) * res_k**rp # rp = rp0 * Q**drp (res_power) --- -## Чего не хватает / следующие шаги (bit-exact) +## Чего не хватает / следующие шаги (bit-exact) → `BITEXACT_PLAN.md` -Текущий фокус — **детекторный фронт** (Шаг 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). \ No newline at end of file +1. **Каскадный симулятор шагов 9–19** (приоритет №1) — см. `BITEXACT_PLAN.md:1`. +2. **k-маппинг фронтенда** twin/am (приоритет №2). +3. **Δ-правило вторых пиков** (приоритет №3). \ No newline at end of file diff --git a/handoff/NOTES_CAPTURE.md b/handoff/NOTES_CAPTURE.md index a4bd1db..559e8c6 100644 --- a/handoff/NOTES_CAPTURE.md +++ b/handoff/NOTES_CAPTURE.md @@ -1,196 +1,44 @@ -# RUNTIME CAPTURE — LIVE DSP TABLES (2026-08-19) +# Runtime capture — live DSP tables (registry heartbeat, SR 48000) -## BREAKTHROUGH: heap "registry" object found & read during offline render -- During `reaper -nosplash -renderproject render_long.rpp`, the yabridge-host - allocates a DSP arena containing a **registry array**: a run of `{u64 count, u64 ptr}` - pairs (stride 0x10) pointing at every DSP buffer. At capture time: - registry base = **0x29b06c0** (in anon region 0x2922000, 0x77e000). - (GUI-session registry was 0x28b06c0 — same object, shifted by arena layout.) -- Finding it live: scan host-readable memory (chunked, 8MB) for `u64==8193` (0x2001) - followed by a readable ptr, then require a run of count/ptr pairs at stride 0x10. -- Registry owner chain: 0 hits for a u64==registry address, so it is reached - structurally (object member at some fixed offset), not via an explicit global. +> **Сжато 2026-08-28.** Полный журнал 2026-08-19—2026-08-23 → [`handoff/archive/NOTES_LEVEL_2026-08-18_2026-08-23.md`](archive/NOTES_LEVEL_2026-08-18_2026-08-23.md) + [`handoff/NOTES_LEVEL_INDEX.md`](NOTES_LEVEL_INDEX.md). Детали FIR-цепи → `handoff/BLOCKMAP_529fe0.md`. -## Captured tables (SR=44100 project, but internal freq-axis = 48000!) -| reg idx | count | ptr | content | -|---------|-------|----------|---------| -| [00] | 8193 | 0x2a72600 | identity ~1.0 | -| [01] | 8193 | 0x2111140 | **WIN_freq: 0.5 -> 1.0 (saturates; idx1024=0.68, idx2048=0.8)** — the FFT-conv window (0x540658) | -| [02] | 8193 | 0x29f2280 | 0 -> ~0.01 (levels/curve) | -| [03] | 8193 | 0x29fa300 | **0.596 -> 0.126** = matches known rwin_C0 | -| [04] | 8193 | 0x2a02340 | **0.404 -> 0.874** = 1-[03] complement | -| [05] | 8193 | 0x2a0a3c0 | 0.0435 -> ~0 (weight, small) | -| [06] | 8193 | 0x2a12400 | 0.9565 -> ~1.0 (1-[05]) | -| [07] | 8193 | 0x2aca740 | zeros + small negatives | -| [0d] | 2049 | 0x2cd0fc0 | **freq-axis 0..23988.3 Hz, spacing 11.713 = 48000/4096** → internal SR=48000 | -| [0e] | 8193 | 0x2cd9000 | 2.017 -> 0 (LUT/knee?) | -| [0f] | 16384 | 0x2ce9080 | same as [0e] doubled | -| [10] | 16384 | 0x2cf90c0 | 1.2914 -> 0 | -| [12] | 16384 | 0x2a62540 | 11.29.. (scattering) | -| [14]/[15] | 8193 | .. | first-fire IR? 0,0.022,0.104,0.084,0.018,0 | -| [17] | 32768 | 0x2d29140 | 0.9999 -> ~1 (ramp) | -| [19] | 32768 | 0x2d69200 | 1.2915 -> 1.0 | -| others | 32768/65536 | .. | ones / ramps (FFT plans, mirrors) | +## Registry heartbeat -- Key numeric check: registry[03] head 0.5960761, idx1024 0.168, idx2048 0.1257 — - byte-identical to earlier GUI rwin_C0 (0.596 -> 0.126). Confirms registry IS the - authoritative per-bin weight source; tables are stable across sessions/SR. -- **FREQ-AXIS uses internal SR=48000 regardless of project 44100** (spacing 11.713). - This reconciles "rwin tables at 48k" even when rendering 44.1k projects. +Heap run `{u64 count, u64 ptr}` stride 0x10, base `0x29b06c0` (anon 0x2922000) — офлайн, `0x28b06c0` — GUI (сдвиг ареной). Поиск: скан `u64==8193` + readable ptr, требовать run `count/ptr` stride 0x10. Надёжный beacon (vptr/44100-marker — тупик). -## Files saved (handoff/) -- `rtwin_freq_44100.npy` — WIN_freq (8193 f32): 0.5 -> 1.0 (this is live 0x540658 window) -- `rtfreqaxis_48000_internal.npy` — freq-axis (2048 f32, 0..23988.3, spacing 11.713) -- `rtwa_596.npy` — [03] 0.596->0.126 -- `rtwb_404.npy` — [04] 0.404->0.874 -- `rtwc_043.npy` — [05] -- `rtwd_956.npy` — [06] -- Full raw snapshot: /tmp/snap_all.bin (318MB, entries {lo,sz,bytes}), registry.txt list. +## Captured tables (SR=44100 project → internal freq-axis 48000) -## Method notes (repro) -- rtsnap_fast.py: spawn reaper render_long, find host (soothe2 in maps, not reaper), - sleep 6s (tables built), pread ALL readable maps chunked 8MB -> snap_all.bin. -- pread of large anon regions can EIO -> MUST chunk (8MB); whole-region pread loses data. -- Scan ~0.1s for 318MB once chunked; far cheaper than object-base scan. -- Earlier vptr-based (rtobj/rtdump2/rtall) and 44100-marker scans all failed because - the DSP object has NO static vptr match in a fresh render (host dies / fields live - only during audio) and ctor field +0x24 != 44100 live. The registry run is the - reliable beacon. +| idx | count | content | +|-----|-------|---------| +| [00] | 8193 | identity ~1.0 | +| [01] | 8193 | **WIN_freq 0.5→1.0** (idx1024 0.68, idx2048 0.8) = live `0x540658` | +| [03] | 8193 | **0.596→0.126** = `rwin_C0` (byte-identical GUI) | +| [04] | 8193 | 0.404→0.874 = 1−[03] | +| [05] | 8193 | 0.0435→~0 | +| [06] | 8193 | 0.9565→~1.0 | +| [0d] | 2049 | **freq-axis 0..23988.3 Hz, 11.713 = 48000/4096** → internal SR 48000 | +| others | 8193/16384 | опоры, FFT-планы | -## Remaining (for twin IIR attack/release per-bin) -- twin state A/B (342 double per-bin IIR states) still not uniquely located live; - short renders keep them ~0. They are NOT the registry tables. -- Next: render_long + capture at t=10-20s into sustain, then locate the per-bin - attack/release smoothing coefficients (0x540888/88c set, converted via ln(10)/20) - inside the arena near registry. +**Проверка:** `[03]` head 0.5960761 byte-identical GUI — registry authoritative. **Freq-axis spacing 11.713 ⇒ internal SR 48000** независимо от проекта 44100. -## 2026-08-19b (two-point snapshots t1=8s, t2=40s of render_long 180s) -- rtsnap2.py: TWO snapshots of the SAME 180s render at t1/t2; each 318MB/547regs ~0.2s. -- Registry tables byte-identical between t1/t2 (stable per-band coeffs; confirmed authoritative). -- Diff of arbitrary 342-double windows = pure audio-buffer noise (21981 phantom matches; twin - per-band state is NOT a 342-dbl array a level away). Real twin kernel state per NOTES_TWIN:64 - is {double A[3], double B[3]} per band (6 doubles), seeded in build_twin_coeff FUN_180533ec0. -- Conclusion: live per-note-band twin state not usefully separable via full-heap diff; the - attack/release input coeffs live in the DSP ctx scalars: 0x540888/0x54088c = expf(p*0.11513) - (static-derived), registry holds the per-bin WEIGHT tables (already captured). Twin kernels - themselves validated statically (twin_check max|err|=1.27e-5). => Step C goal (window + axes + - weights + kernel parity) is effectively CLOSED; only scalar A/R params remain, derived from RPP - params, no live capture needed. +## Files saved -## 2026-08-20 (P1.5: live level-tracker A[] / ctx — CONFIRMED UNREACHABLE) -- rtctx.py (repo): full pipeline = spawn render_long offline → find yabridge-host → chunked - snapshot → registry-scan → ctx discovery (marker base+0x24==40000.0f ∧ base+0x540658==window). -- Two FRESH captures (host live mid-render): registry found (0x28b06c0 GUI / 0x29b06c0 offline, - [00].cnt=8193 identity, [01]/[03] = window/weights). But: - - the window ptr (0x2962??? live) appears as a heap value ONLY inside the registry entry - (0x???6c8 self-field), never as a field of a larger ctx object; - - bases where +0x24==0x472c4400 (40000.0f) exist only in the plugin image (.data/rodata - 0x180535d62/0x1805607e5/0x181414341) — inline code constants, no live DSP object. -- => A standalone DSP ctx object with {+0x24==40000, +0x540658==winptr} does NOT exist in the - heap (the DSP object is the registry itself / its buffers are the registry targets). -- CONFIRMS the 2026-08-19b conclusion: per-bin level-tracker A[] (0x4c0528/0x3c0510/0x2c04f8) - and mask scalars are NOT live-separable with the current registry/ctx tools; they are - STATIC/DERIVED from RPP params (0x540888/88c = 10^(att/20), 0x540870 = expf((p·c4348+c44a4)·0.11513), - 0x54087c = raw band/mix). Two unknown constants remain from the (lost) binary: 0x24c4348, 0x24c44a4. -- => P1.5 "live capture" is a dead end; scalars must be derived statically or the two missing - constants recovered from the original soothing_mem.bin (not currently present in workspace). +- `handoff/rtwin_freq_44100.npy` — WIN_freq 8193 f32 (live 0x540658) +- `handoff/rtfreqaxis_48000_internal.npy` — 2048 f32 +- `handoff/rtwa_596.npy`, `rtwb_404.npy`, `rtwc_043.npy`, `rtwd_956.npy` — [03]–[06] +- Raw snapshot: `/tmp/snap_all.bin` (318MB, chunked 8MB pread — иначе EIO) -## 2026-08-20c (BREAKTHROUGH: LEVEL-PATH OBJECT captured live in /tmp/snap_rt.bin) +## Method (repro) -The BandConfig A/B/gamma (roadmap gap 2, block of F2/F3) is now LIVE-CAPTURED. -Read-only scan of the existing realtime snapshot `/tmp/snap_rt.bin` (ctx 0x2370040, -render_long.rpp) — no new capture needed. +- `rtsnap_fast.py`: spawn `reaper -renderproject render_long`, find host `soothe2` in maps, sleep 6s, chunked pread ALL → `snap_all.bin`. Scan 0.1s/318MB. +- `rtsnap2.py`: two-point diff — registry byte-identical (stable). +- Live ctx `0x2370040` (`+0x24==48000`, `sens>100`) — единственный populated, остальные empty. `handoff/rtctx_live.json`. -### Method (repro, ~2s) -1. Level-path fingerprint = per-band **level_gain pair buffer**: 0x400 f32 pairs - (`[level, gain]`), with `level[j] == j/1024` exactly (level[0]==0.0, step 1/1024). - Vectorized scan (2nd derivative of level slots == 0 + level[0]==0.0) finds them. -2. Six such buffers at stride 0x2020..0x2040 (band0: 0x4083020, b1: 0x4085040, - b2: 0x4087080, b3: 0x40890a0, b4: 0x408b0e0, b5: 0x408d100). -3. Find u64 refs to the six → consecutive slots stride 0x18 at **+0xe0+band*0x18** - → object base = **0x3975460** (level-path object). +## Live ctx (realtime playback, `rtctx_live.json`) -### Level-path object (base 0x3975460, region unknown / heap) -- `+0x178` = band-list ptr → 0x32c0c60 -- `+0x180` → BandConfig 0x32c0aa0: **A=-24.0, B=+28.0, gamma@0xc=1.0, byte flag@0x10=0** → linear, no callback@0x90 -- `+0x188` → BandConfig 0x32c09c8: A=16.0, B=20000.0, gamma@0xc=1.0, flag=0 (freq-range shaped cfg; +0x18.. floats 0.55,7.13,2.77,2.718 = nonlinear shaper consts) -- `+0x4198 + band*0x2000` = **band mask doubles**, 512 usable per band: - band0 ~1.0 const; band1 1.001→1.216 (rising); band2 0.999→0.579 (falling); - band3 1.291→1.002 (falling); band4/5 1.0→~0.983 -- `+0xe0+band*0x18` → per-band level_gain pair buffers (live LUT output already has - gains: b0 0.53123 const, b1 0.5314→0.535, b2 const, b3 0.598→, b4 const, ...; many - bands `~0.531` because mask≈1.0 & render_long default cfg) +- Pointers `0x540688` identity, `0x540698` window, `0x5406b8/c8/d8/e8` WA/B/C/D, `0x540748` warp, `0x540758` freqaxis (`v85=995.6 Hz`). +- Scalars: `0x540870=440.955`, `0x540880=25.0`, `0x540884=10.0`, `0x540888/88c=1.0`, `0x54087c=1.0/10.0` (selectivity/sharpness). +- `acc/f6f8` arrays **zero** в стационаре playback — combine idle. +- Bands curve `R=1/mask` peak следует fc (bin43@500 → bin85@1000), `0x5407f8` min 1.0. Применённый фильтр ≠ pointwise копия R (нужен FFT-conv). -### Interpretation / next -- The captured A/B/gamma are the **default render_long config** (A/B semantics = - level-scaler LUT min/max; rendering default band). To get the A/B/gamma of a SPECIFIC - band shape (t1kq_only1_1000 etc.) re-run rtctx_rt.py with that test RPP and re-scan - the same fingerprint (base offset shifts). Method is now automated. -- This UNBLOCKS the parametric band-LUT 0x563440/0x563a60 as a structural source - (t=(x−A)/(B−A), clamped, ^gamma, ×norm) instead of fitted Pchip. -- Dump helper: /tmp/dump_levelpath.py, /tmp/probe_base.py. -The dead end above was wrong — the missing piece was REALTIME audio playback, not more scanning. -`-renderproject` uses the OFFLINE audio engine (fields live "only during audio", per earlier note); -the ctx object only materializes during a realtime transport play. - -### Method (works) -- play.lua (repo): `reaper.Main_OnCommand(1007)` (Transport:Play) + hold ~300s. -- rtctx_rt.py (repo): `reaper render_long.rpp play.lua` → find yabridge-host → chunked snapshot - (~796MB, 1056 regs) DURING playback → scan heap for the ctx. -- ctx marker that WORKS live: **+0x24 == 48000.0f (0x473b8000)**, NOT 40000.0f (40000 was the - static ctor rodata value; live it is the internal SR = 48000, confirming NOTES_CAPTURE SR=48000). - -### Result (captured live, saved handoff/rtctx_live.json) -- **ctx = 0x2370040** (region 0x2022000). Field pointers (all point at registry tables): - 0x540688=identity([00] 0x2962580), 0x540698=window([01] 0x14b4240), 0x5406a8=levels([02]), - 0x5406b8=WA([03]), 0x5406c8=WB([04]), 0x5406d8=WC([05]), 0x5406e8=WD([06]), - 0x540748=warp([12] 0x14bc280), 0x540768=LUT-knee([14]). (NOTE: offsets +0x40 from the - earlier static table — window is 0x540698 live, not 0x540658 as in the f_52b570 disasm label.) -- **Mask scalars (float)**: 0x540870=440.955 (sens), 0x540874=1.0, 0x540878=1.0, 0x54087c=1.0, - 0x540880=25.0, 0x540884=10.0, 0x540888=1.0 (attack=10^0), 0x54088c=1.0 (release=10^0), - 0x540890=0, 0x540894=1200.0. -- **level-tracker A[] (341 double each, per-bin IIR attack/release coeffs)**: - - 0x4c0528 (attack): 0 → 0.340, 0.348, ... monotonic rising, plateau 0.6921 @bin>=319. - - 0x3c0510 == 0x2c04f8 (release): 0 → 0.000753, 0.000885, ... slow small rise. - Full arrays in handoff/rtctx_live.json (keys A_4c0528, A_3c0510, A_2c04f8). -- BandConfig @ctx+0x188 is NOT populated here (zeros) — it lives at a different offset or only - during band processing; still TBD (but LUT curve params A/B/γ are RPP-derived per roadmap). -- Two more ctx-like bases found (0x2120040, 0x1780040) also have +0x24==48000; 0x2370040 is the - populated one (0x2120040 has 0x540658..698 = 1.0 fill pattern — likely a second/free instance). - -## 2026-08-23 (22y): LIVE CTX CAPTURE DURING DUAL/T1KQ/RES PLAYBACK — scripts/dualtrace.py -Method works reproducibly: reaper .rpp play_loop.lua (repeat ON) → yabridge-host -→ chunked snapshot ×2 → ctx marker +0x24==48000 ∧ sens>100 → ONLY ONE populated base -(0x2370040; others empty instances). Snapshots byte-stable over seconds. - -### Pointer-table catalog (ctx+0x540600..0x540a00, dereferenced u64 → f32[2049]) -- Static weights confirmed live: [00]identity@0x540688(ones), window@0x540698(0.5→0.8), - WA/WB/WC/WD @0x5406b8/c8/d8/e8, warp@0x540748(1.3→6.68, structure at LOW bins), - freqaxis@0x540758(v85=995.6Hz ✓ internal 48k/4096). -- **acc/f6f8 arrays ALL ZERO during steady looped playback** (0x5406f8, - 0x5407a8/b8/c8/d8 — zero as f32 AND f64): combine accumulators idle in steady state. -- **CONFIG-DEPENDENT CURVE FAMILY** (peak follows band fc: bin43@fc500 → bin85@fc1000): - - 0x540768 == 0x540778 (identical twins): smooth curve, peak at center - (dual: 4.15@43, valley 1.60@171, upturn 1.86@400; t1kq: 3.55@85). - - 0x540788: sharper version (floor ~0.52-1.0, max 4.38). - - **0x5407f8: min EXACTLY 1.0 → reduction multiplier R(f)=1/mask ≥ 1** - (res500 cfg: R(500Hz)=12.0 dB, falls to ~0 by 6 kHz; notch-shaped ✓). -- bands[] slots from static asm (@0x540678+i·16) read as identity/ones tables LIVE — - the per-band working data is NOT sitting in those ctx fields during playback. - -### Decisive mismatch -For dual cfg: R(43)=3.98→12.0 dB (real 10.32 ok-ish) BUT R(171)=1.41→3.0 dB while -real cut@2000 = 11.82 dB. ⇒ Applied filter ≠ pointwise copy of R: massive spectral -coupling between template and actual filtering. Prime suspect: FFT-conv stage with -the 8193-wide WIN_freq window ([01]) — smearing/spreading step completely absent in -our pointwise render48k path. This ALSO explains why faithful v1 (pointwise) cannot -balance dual tones regardless of law constants. - -### Caveats / next -- Quick Welch TF estimate unreliable (window/alignment) — Goertzel-at-tones stays canon; - for full-spectrum truth use chirp/two-tone refs or per-fc capture sweep. -- NEXT: (1) fc-scan captures (res_only1_{fc}.rpp, 11×) → correlate R_cap(bin85) with - real cut@1000 across fc — validates R as THE applied curve; (2) decode the FFT-conv - 0x535a70 body + WIN_freq usage — reconstruct mask→FIR spreading; (3) re-check whether - 0x540768-family updates frame-by-frame (two-point diff showed stable — maybe only - rebuilt on param change / note onset). +Дальше — `handoff/NOTES_LEVEL.md:24mm11+` (live ptrace) + `handoff/BLOCKMAP_529fe0.md`. diff --git a/handoff/NOTES_LEVEL.md b/handoff/NOTES_LEVEL.md index 8810cad..b0c5e5e 100644 --- a/handoff/NOTES_LEVEL.md +++ b/handoff/NOTES_LEVEL.md @@ -1,4026 +1,10 @@ -## ============ 2026-08-18 (LEVEL-PATH STRUCTURE DECODED) ============ +# NOTES_LEVEL — живой журнал детектора/маски (голова 24mm5+, 2026-08-28) -## Две twin-цепочки (аудио vs маска-таблица) -(a) FUN_180536300 (float, N=0x200=512, per-channel AUDIO): - вызван FUN_18058e380: FUN_180536300(state+0x18, in+0x8e0+ch*0x800, in+0xe0, band, 0x200). - lVar1=scratch+N*32; normalize lVar1[i]=in[i]*2pi/(os*sr) (0x18052da00=pointwise scale); - per band: FUN_180536f90(lVar2, base, band_i, lVar1, N) -> copy lVar2->base(0x1800096c0); - base*=product (0x180008700); if band_active: save->lVar4, product=1.0; out=copy lVar4->param_2. - => out = PROD_active_bands mask_band(норм. аудио). На |x|<1 z=ротор(1e-5)=i -> маска ~пост. -(b) FUN_18056e3e0 (double, N=0x400=1024): вызван FUN_180563440 (6 band-слотов). Аналогично. - Вход param_3 в контексте FUN_180563440 = LUT-рампа [0,1] (param_1+0x198, шаг 1/1023), - shape через кривую param_1+0x188 => это УРОВНЕВАЯ LUT-маска, не аудио. +> **Архив 2026-08-18 — 2026-08-23 (4024 строки) → [`handoff/archive/NOTES_LEVEL_2026-08-18_2026-08-23.md`](archive/NOTES_LEVEL_2026-08-18_2026-08-23.md).** +> Оглавление по датам/темам → [`handoff/NOTES_LEVEL_INDEX.md`](NOTES_LEVEL_INDEX.md) (единственный навигатор). +> Статус TOTAL → [`README.md:13`](../README.md) (единственный источник). -## FUN_180563440 (per-block маска-таблица, 6 полос) -- param_1+0x198[i] = i*(1/1023), затем маппинг кривой param_1+0x188: - pfVar10[0]=min, [1]=max, [3]=gamma-кривая, [4]=флаг linear, [+0x14]=nodes. - linear: val = min+(max-min)*exp(log(x)/c) = min+(max-min)*x^(1/c) (gamma). -- 6x FUN_18056e3e0 -> 6 полосных масок (param_1+0x4198+0x2000k); комбайн -> param_1+0x2198; - затем LUT (param_1+0x98): пары (x=i*fVar3, y=mask) на 0x3ff точек (уровень->маска). -- вызывается FUN_1805631c0 (vtable 0x1824b14f0) + FUN_180563ce0/563fa0 (следующие шаги). - -## FUN_180563ce0 — per-bin IIR level-трекеры (342 бина, 2nd-order state pairs) -- Константы 0x1824c4780/4788/4790/4798 (double A/B), 0x40400000=3.0f (порядок). -- Буферы: param_1+0x28 (float order), +0x40/+0x58 (double state A/B), stride 0x60, 0x156 итераций. - => на каждый бин ставится 2nd-order IIR (атака/релиз) для сглаживания уровня. - -## FUN_180530b60 — параметры сглаживания (log-interp + exp) -- cac=exp, cd0=log. freq-диапазон: online 800..1200 Гц (DAT_1824c4594=800/45a8=1200), - offline 15..180 Гц (4418=15, 4518=180), gain 0.01..1.0 (3c70=0.01, 3ea4=1.0). -- FUN_180533340(+0x2404e8/0x340500/0x440518, ...) — инициализация smoothing-фильтров - (3 экземпляра: 3 канала/oversample?). - -## FUN_180530d30 — per-bin СПЕКТРАЛЬНЫЕ ВЕСА (0x5406b8/0x5406c8/0x5406d8/0x5406e8) -- fVar9 = cdc(2000/(sr*0.5)*513/(i+1)) = cdc(2000/f_bin) — pow? (IAT-стаб 0x181b3b378). -- fVar11 = [param_1+0x540880]*0.25*fVar9*fVar13 (0x540880/884 = per-band уровень/сенс). -- buf[i] = cd6(0.1, 1.0/(exp(1/(1+x))*dVar1)); buf_c[i]=1-buf[i]. cd6 = log10? (0x1824c3f70=0.1). -- dVar1 = (sr/[param_1+0x1a0])*[param_1+0x1ac]*0.001. -- => частотно-зависимый вес: 2000 Гц (f_bin больший) -> fVar9 другой, чем 500 Гц. - Кандидат объяснения red2000>red500 (dual_b1q): вес нормирует уровень бина выше на - высоких частотах -> избыток уровня больше -> глубже крас. ПОТРЕБУЕТ ВЕРИФИКАЦИЮ. - -## IAT-хелперы (0x181a14xxx — jmp [GOT 0x181b3b3xx], цели 0x6fffff, динамич. загрузка) -- Последовательность стабов каждые ~6-12 байт => стандартные CRT math (в порядке импорта). -- Семантика по алгебре: cac=exp, cd0=log (log-interp в 530b60), cdc=pow (2-арг, pow(x,0.7/2.0)), - cd6=log10? (cd6(0.1, y)=log10(y)/log10(0.1) в 530d30), cd6 может быть log2. -- cf4/ce2/c9a/cc4/cfa — для case1/case8 генераторов (cfa в FUN_1805343e0: w=1/cfa(fc*pi/fs) - => cfa=sin, cf4-цепочка для q-клипинга). - -## КЛЮЧЕВОЙ ВЫВОД -- Частотная СЕЛЕКТИВНОСТЬ маски = freq-path нотч |2B/A| (m2c, центр=fc) на оси бинов - (t1kq fc-скан rmse=0.007). -- ГЛУБИНА/уровневая зависимость = level-path LUT (уровень->маска, кривая +0x188) + - per-bin IIR-трекеры уровня (0x563ce0) + спектр. веса (0x530d30, 2000/f_bin). -- dual_b1q (red2000>red500 при q<1) требует совместной модели level*веса*нотч — - единственный способ получить больше cut на 2000 чем на 500 при band=500. - -## ============ UPDATE: CONSUMER OF PER-BIN WEIGHTS FOUND (FUN_180529fe0) ============ - -## FUN_180529fe0 — per-block СПЕКТРАЛЬНЫЙ ПРИМЕНИТЕЛЬ (5 каналов, ключевой DSP-цикл) -Сигнатура (param_1=DSP-объект, param_2, param_3, param_4=nBands). Для каждой полосы: -1. 0x540678[band] *= (fVar30/sr) * 0x540870 (нормализация уровня × sens 0x540870) -2. IIR-сглаживание спектра (5 каналов, состояния 0x540528/2c04f8/3404f8/4c0528/440510, - через DBL-петли: y += x*a + y*b, backward/forward проходы) => level-трекер per-bin -3. 0x5406f8 = резонансная реакция полосы (копия 0x540678[band], каскад 0x540688-коэфф, - scale fVar30/nBands) [здесь twin-результат 0x5406f8] -4. 0x5406f8 *= 0x540698 (частотная ось, log-интерп) и *= 0x5406a8 (axis) -5. *** ПРИМЕНЕНИЕ per-bin ВЕСОВ: *** - thunk_180003c40(lVar21=0x5406f8+N, 0x5406c8, 0x5407c8[band], iVar19, 4) -> 0x5407c8 += 0x5406c8*contrib - thunk_180003c40(lVar17=0x5406f8, 0x5406e8, 0x5407c8[band], iVar19) -> 0x5407c8 += 0x5406e8*contrib - (0x5406c8=1-0x5406b8, 0x5406e8=1-0x5406d8 — КОМПЛЕМЕНТАРНЫЕ веса) -6. накопление: thunk_180005a20(0x5407c8[band], 0x540678[band], iVar19) -7. dry/wet: 0x1c[band]*scale (0x5408b8==0: +0x540678*0x1c[band]*...) -8. FFT-свёртка в time-domain: FUN_180535a70(0x540628, 0x540678[band], nBins+1) -> frame; - FFT-таблицы 0x540548/550/598; 0x540668 -> out-буфер (0x3f800000=1.0 в DC-бин). - -## FUN_180530d30 — точная формула per-bin весов (0x5406b8/6c8/6d8/6e8) - iVar6 = NFFT/2+1; fVar12 = 2000/(0x24*0.5) [p24=40000 -> fVar12=0.1] - fVar13 = 4.0 (или 1.0 если 0x5408b8); dVar1 = (0x24/sr)*0x1ac*0.001 [секунды] - for i in bins: - fVar9 = cdc( (fVar12*iVar6)/(i+1) ) = exp?/pow? (0.1*513=51.3 / (i+1)) - fVar11 = 0x540880 * 0.25 * fVar9 * fVar13 - dVar10 = 1/(1 + fVar11/(0x540880*4)) = 1/(1+fVar11/(4*level)) - dVar10 = exp(dVar10 * fVar11) - dVar10 = cd6(0.1, 1.0/(dVar10*dVar1)) = log10(1/(dVar10*dVar1))/log10(0.1) - buf0x5406b8[i] = dVar10; buf0x5406c8[i] = 1-dVar10 - (аналогично 0x540884 -> 0x5406d8/6e8) - Численно (p24=40000, sr=48000, 0x540880=1, 0x5408b8=0): - bin500: fVar9=exp(4.4)=81 -> w=2.49 ; комплемент 1-w=-1.49 - bin2000: fVar9=exp(1.17)=3.2 -> w=1.60 ; комплемент = -0.60 - => частотно-зависимые (сильно убывающие с частотой) веса. НО q входит через - 0x540880/884 (устанавливается из полосы) и через 0x5406f8-резонанс. - -## BUFFERS (map окончательный) - 0x540698 freq-ось (log-интерп, множится в band-ответ) - 0x5406a8 axis-буфер (множится в band-ответ) - 0x5406b8/6c8, 0x5406d8/6e8 per-bin level-веса (пары w, 1-w) — потребляются в FUN_180529fe0 - 0x540678[band] per-band спектр/вход (0x10-стрide, 2 слова = {ptr,count}? нет, 0x540678+0x10k) - 0x5407c8[band] per-band маска-аккумулятор - 0x540748 warp-ось = 8.3 - 7/(1+exp((x*20000-120)*(-0.01))) ~= 1.3 (DC-буст 6.68), НЕ f/(f+K)! - 0x540628 FFT-work, 0x540548/550/598 FFT-таблицы, 0x540668 out, 0x5406f8 band-фильтр/рабочий - -## FUN_180535ae0 = конструктор DSP-объекта: 0x540874=1.0, 0x54087c=0.5, 0x540884=1.0, - 0x54088c=1.0, 0x540894=1.0, 0x24=0x472c4400=40000.0f (!), 0x5408ac=0x01000000. - -## FUN_180535f10 = деструктор (free всех 0x5406xx-буферов). - -## ИТОГ: dual_b1q объясняется комбинацией - mask(bin) = |резонанс_полосы|(bin) × freq_axis(bin) × level_weight(bin, 2000/f_bin) - где level_weight через exp(51.3/(bin+1)) даёт РАЗНЫЕ веса на 500 и 2000 Гц, и на 2000 Гц - маска глубже при низком Q (резонанс шире/мелкие). Верифицировать в standalone-харнессе. - -## ============ UPDATE 2026-08-18: 0x530d30 weights DECODED + NUMERICALLY NEGATIVE ============ -- Exact formula (decomp 22330, FUN_180530d30): per bin - base = (2000.0/(sr*0.5))*iVar5/(bin+1) [iVar5=NFFT/2+1] - w8 = powf(base, 0.25) - v = [0x540880]*0.25*w8*fVar12 [fVar12=4.0 if offline flag, else 1.0] - q = 1/(1 + v/([0x540880]*4)) (second pair 0x540884: q2=1/(1+v2/[0x540884])) - dVar1= (sr/[0x1a0])*[0x1ac]*0.001 - w = 0.1^(1/(max(q*v,floor)*dVar1)) [0x1824c3f70=0.1, 0x1824c3e30=0.001] - buf0x5406b8 = w ; buf0x5406c8 = 1-w (and 0x5406d8/6e8 with 0x540884) -- Constants locked: c3d3c=0.25, c4334=4.0, c3d8c=0.5, c45b4=2000.0, c41e0=2.0. -- NUMERIC RESULT: at any plausible runtime (sr=44100, NFFT=1024, L=0.05..1.0, hop 1024..4096, - n1ac 1..12) -> w(500)=w(1000)=w(2000) ~= 0 (=10^-20..10^-0.07); complements ~= 1. - => the 0x530d30 level-weights DO NOT create the 2000-vs-500 tilt (ratio2000/500 ~1.0). -- CONCLUSION: dual_b1q red2000>red500 does NOT come from 0x530d30. Suspect = freq-axis warp - (0x540698 '8.3-7/(1+exp((x*20000-120)*(-0.01)))' ~1.3 DC boost) x resonance. The model's - empirical tilt {500:1.414,1000:1.454,2000:1.795} stays as the placeholder; replacing it - requires FUN_180529fe0 consumer step-5 algebra + runtime level values (Phase-4/5 dive). - -## ============ UPDATE 2026-08-18 (CHECKPOINT): tilt != warp, level-path decoded ============ -- FUN_180563ce0 = INIT of per-bin IIR level trackers (not update): 0x156=342 bins, order=3.0f (0x40400000), - coeff A/B mvaps 0x24c4780/4790, strided fill; 3rd pass fills 0x40e00000=7.0f (post-gain). FULL: /tmp/opencode/f_563ce0.dis. -- Level-path map: 0x563440 (LUT curve +0x188: [min,max,gamma,linear flag,nodes]; 6 bands via 0x56e3e0 - double-twin N=1024 with LUT-ramp input; combine -> +0x2198; LUT pairs +0x98 at 0x3ff pts level->mask). - NOT in decomp_funs.txt; use /tmp/opencode/f_563440.dis + level_notes.md (LOCAL: /home/m/re-tools/handoff/). -- **DECISIVE NEGATIVE**: warp(2000)=0.773 cannot reach dual red2000=15.2dB — C=depth·warp·LUTmax=0.864·0.772·0.667 - =0.445 < needed 0.826 (C=1-10^(-15.2/20)). warp is NOT the empirical tilt. The gap is closed by the blend - step (0x5407c8 += weights·res_upper/lower, freq-axis 0x540698) + FFT-conv 0x535a70 shaping. -- CHECKPOINT decision (user): STOP; next session = diagnostic bridge (two-term mask: warp·LUT + α·freq-axis_term, - fit vs measured curves) BEFORE transcribing FFT. do NOT curve-fit without the blend term. - -## ============ UPDATE 2026-08-18 v2: BRIDGE DONE + FUN_180529fe0 full algebra ============ -- **Полный декомп FUN_180529fe0: /tmp/consumers_out.txt:471-1277** (f529fe0.dis в /tmp обрезан на 52a813). - Поправки к прежним заметкам: - - 0x5408b0 = таблица ГРАНИЦ бинов (прелюдия: cvttss2si+imul+idiv, iVar20=band-start, iVar19=len), НЕ LUT. - - Уровневая маска резолвится ИНЛАЙН: fVar30=0x5408b0[idx]; fVar25=(float)FUN_181a14cdc()=pow (IAT→GOT - 0x181bab378, CRT-цель вне дампа); пары `×(fVar30−fVar25)`/`+fVar25·` (dry/wet уровня). - - Цепочка per-band (по декомпу): 0x540678=res; res*=∏0x540688[k]; res*=(fVar30/nBands); *=fVar29·0x54088c - (fVar29=(fVar30/0x1a0)·0x540870); IIR 0x52d650 state 0x440518; двойное IIR-сглаживание (double, - states 0x3404f8/2c04f8 и 0x440510/3c0510); 0x5407c8+=0x5406c8·res_up + 0x5406e8·res_low + 0x540678 - (003c40, kernel vfmadd213pd) — **АДДИТИВНЫЙ аккумулятор**; 0x540678 *= res(008700) и *= warp 0x5406a8; - offline: *=0x1c[band]·fVar27 (глубина, D=-0.5@0x24c4670); dry/wet: mask·fVar30·0x540888 + (fVar11−fVar30) - (fVar11=1.0 c3ea4) — **педастал**; FFT-conv 0x535a70 (fwd, window 0x540550/598, kill mirror, - inv, ×0x540658, fwd, inv, 0x540668[0]=1.0[1]=0, apply FIR к param_2[band]). - - Константы offline: c3ea4=1.0, c3e28=0.8, c4670=−0.5, c4674=−0.7, c4680=−1.0, c3c58=0.001. -- **BRIDGE RESULT (model_fir.py, rmse=0.236 dB)**: C(f)=1.221·LUT(log10(L0/res)) + 0.358·warp(f)^3.143. - res(500;fc500)=0.117 Q-независим -> C500-константа без tilt; xv(2000)=0.308..−0.656 (Q) -> C2000-и-гг-форма; - аддитивный терм warp^3.14 даёт +0.15 на 2000 и ≤0.02 на 1000 (иначе t1k рушится) — единственная точка, - совместимая с 36 замерами. Мультипликативный warp·LUT проваливается (>10 dB). warp^3.14≈π: кратный каскад? - Остаток 0.7 dB (Q=0.1) = LUT-колено 0.574 -> нужен FFT-уровень (0x535a70 + окно 0x540658 + freq-axis - 0x540698), см. SESSION_HANDOFF §5-6. - -## ============ UPDATE 2026-08-20h: P2 mask-accumulator combine kernels DECODED ============ -Raw bytes from rt snap (objdump of image 0x180008d60/5a20/3c40). Implemented in dsp/levelpath.cpp. - -### Exact CRT thunk semantics (per-bin double ops in FUN_180529fe0): -- **0x8d60 combine3(dst,a,b,n) = sub**: out[i] = a[i] − b[i] (vsubpd; dst is the 3rd pointer). - Per-band call: 0x5406f8[i] = 0x540678[i] − 0x5407c8[i]. (corrects NOTES:348 "combine" TBD) -- **0x5a20 acc_add(dst,src,n)**: dst[i] += src[i] (double; kernel 0x18001a5a0). -- **0x3c40 acc_fma(dst,a,b,n)**: dst[i] += a[i]·b[i] (double; vfmadd213pd). -- 0x11940 = copy/mirror (complex, 5th arg stride 0/4 → kernel 0x1a88300), 0x6e40 = dst=src·scalar, - 0x15060 = buf+=scalar, 0x8700 = dst*=src (float, warp), 0x9be0 = buf=scalar·buf. - -### Full mask-accumulator per band (0x5408b8==0): - 1. 0x5406f8 = 0x540678 − 0x5407c8 (8d60 sub) - 2. mirror 0x5406f8 halves (11940) - 3. 0x5407c8 += 0x5406c8 · 0x5406f8_upper (3c40, stride4) - 4. 0x5407c8 += 0x5406e8 · 0x5406f8_lower (3c40) - 5. 0x5407c8 += 0x540678 (5a20) - -## ============ UPDATE 2026-08-20g: P2.5 twin-mask factory + band LUT apply DECODED ============ -Source: decomp_funs2.txt (FUN_18056e3e0:7988, FUN_180563a60:8975, FUN_180563440:7697) + -f_56e3e0.dis + f_563a60.dis. Implemented in dsp/levelpath.cpp (levelpath_check ALL OK). - -### FUN_18056e3e0 = "twin-mask factory" is a CONSTANT FILL, NOT per-bin twin resonance: - s = DAT_1824c4248 / (double)((float)[ctx+0x240080] * [ctx+0x24]) - = 2π / (count · SR) (0x24c4248 = 6.283185307 = 2π) - fills 0x400 bins of the mask (stride 0x2000/band) with s. The twin resonance shape enters - elsewhere (via the LUT curve FUN_180563440), NOT here. (corrects NOTES:547 "twin resonance".) - -### FUN_180563a60 = band LUT apply (level -> gain), 6 bands × 0x400 bins: - level_dB = logf(mask[band][bin]) · 8.6859 (20·log10) - axis[bin] = bin · (1/1024) (0x24c3c50 = 0.0009765625, DIFFERENT from 1/1023) - t = clamp((dB − A)/(B − A), 0, 1) (BandConfig ctx+0x180: A=min,B=max,gamma,flag) - if gamma == 1.0: val = t - elif flag == 0 (linear): val = t^gamma (powf, DIRECT gamma) - else (power-law): val = 0.5·(1 + sign(2t−1)·|2t−1|^gamma) - axis[bin+1] = val (pairs level, gain) - NOTE: this is the INVERSE of FUN_180563440 (which uses x^(1/γ)); 563a60 uses t^γ. - -### Constants locked (from rt snap rodata): - 0x24c4248 = 2π (double), 0x24c4230 = 5 (double), 0x24c3c50 = 1/1024, 0x24c3c54 = 1/1023. - -## ============ UPDATE 2026-08-19e: FUN_180529fe0 FULL DECOMP CONFIRMS MASK-CANON ============ -Source: /tmp/consumers_out.txt:471-1277 (full Ghidra decomp). Validates + completes the -"CONFIRMED per-band pipeline" section below (lines 237-253). Notes: - -### Mask chain (0x5408b8==0 mono path; 0x5408b8!=0 = dynamic/stereo-extra path) -Per band (band in [iVar20, lVar18)): - 1. scale: 0x540678[band] *= (fVar30/nBands)·0x540870·0x54088c (0x9be0 = buf*scalar) - 2. IIR smoother: FUN_18052d650(state 0x440518, out 0x5406f8, in 0x540678) then a 2nd IIR - (states 0x3404f8/0x2c04f8, count 0x2404e8) — double smoothing (att/release). - 3. online blend (0x5408b8==0): - 0x5406f8 = 0x540698·(1-mix), mix=0x54087c (0x6e40) - 0x5406f8 += mix·0.8 (fVar25=0x24c3e28=0.8) (0x15060) - 0x540678[band] = bigkernel(0x540678,0x5406f8) (0x26b820) - 4. combine: 0x5407c8[band] = combine3(0x5407c8,0x540678,0x5406f8) (0x8d60) - + mirror both halves (0x11940) + WEIGHT appends: - 0x5407c8 += 0x5406c8·0x5406f8_upper (0x3c40, stride4) - 0x5407c8 += 0x5406e8·0x5406f8_lower (0x3c40) - + 0x5407c8 += 0x540678[band] (0x5a20) - 5. (0x5408b8!=0 branch: freq-axis blend 0x6e40/0x15060 + 0x26b820 + 0x4f0400 weight) - 6. WARP (tilt): 0x540678 *= 0x5406a8 (freqpath warp) (0x8700) - 7. dry/wet: mask = mask·(fVar30·0x540888) + (1-fVar30) fVar30=0x540874−rnd (0x8800-ish) - 8. FFT-conv (0x52b550 steps 1-6) -> FIR -> conv with audio -> out[band] -- 0x5407c8 / 0x540678 / 0x540768: pointer arrays stride 0x10 (per band), 0x5406c8/6e8 = weight tables. -- Constants: fVar11=1.0 (c3ea4), fVar25=0.8 (c3e28), fVar28=0x24c4674(-0.7), fVar27=0x24c4670(-0.5). - -### Confirmed scalar A/R (0x540888/88c) = expf(p·0.11513), captured window tail degenerate flat. -=> Mask-canonic is now FULLY mapped structurally. Remaining for bit-exact = exact scalar values - (XMM9/XMM13, 0x540870/874/87c/88c from RPP) + the level-LUT curve A/B/γ (ctx+0x188 BandConfig). - Structural C++ port of steps 3-8 exists: dsp/fftconv.cpp (P1.3) + dsp/levelpath.cpp (LUT curve). - -## ============ UPDATE 2026-08-19d: f_52d990 / f_52d920 / f_52db50 DECODED (CORRECTION) ============ -Disasm: handoff/nls_dasm/f_52d990.dis (52 B), f_52d920.dis, f_52db50.dis; caller f_52b570.dis. - -### Identity (they are CRT-style thunks, NOT arithmetic kernels): -- **f_52d990(rcx=dst, rdx=src, r8d=count) = memmove/memcpy thunk.** - Prologue saves rbx/rsi/rdi; movs args into callee regs; indirect call through GOT 0x1bab008 - (the CRT "select scalar vs vector" resolver, same 0x2650db8 pair used everywhere); then tail-jumps - to 0x2000 (scalar path) or 0x1c40 (vector path). Returns void. This is a COPY, not a multiply. -- **f_52d920(rcx=dst, xmm1=val, r8d=count) = scalar fill (low-half filler).** tail 0x2030(float)/0x1d30(double). -- **f_52db50(rcx=dst, xmm1=val, r8d=count) = scalar fill variant.** tail 0x19a0/0x22d0. -=> The earlier NOTES "0x52d990 = FIR *= WINDOW" was WRONG. Corrected step map below. - -### Corrected step 5 (f_52b570:52b75e-52b78c): -``` - rax = [ctx+0x540534] ; N (FFT size) - rbp = N/2 - FIR[N] = 0 ; zero the Nyquist bin - rax = [ctx+0x540658] ; window base (WIN_WINDOW, 8193 f32) - rdx = rax + N/2*4 ; src = window + N/2 - call f_52d990(FIR, window+N/2, N/2) ; FIR[0..N/2-1] = window[N/2..N-1] (COPY) - call f_52db50(FIR+N/2, xmm9, N/2) ; FIR[N/2..N-1] = xmm9 (const fill) - inverse FFT -``` -So step 5 replaces the FIR's lower half with the SECOND half of the captured window -(window saturates to 1.0 at idx 2049; so window[N/2:] is all 1.0 WHATEVER N/2>=2049 — the tail -copy degenerates to a flat 1.0 unless N/2 < 2049, i.e. FFT size would have to be < 4098 for the -window shape to matter here). Upper half filled with a constant xmm9. - -### Corrected step 3 (f_52b570:52b685-52b6b3): -``` - rax = N; rbp = N/2 - FIR[N] = 0 - call f_52d920(FIR+4, xmm13, N/2-1) ; FIR[1..N/2-1] = xmm13 (LOW fill) - call f_52db50(FIR+4+N/2, xmm9, N/2-1) ; FIR[N/2+1..N-1] = xmm9 (HIGH fill) - inverse FFT -``` - -### Size inference & effective semantics -- window = 8193 f32; it saturates to 1.0 at idx 2049 (w[2048]=0.8, plateau 1.0 beyond). -- freq-axis = 2048 f32, spacing 48000/4096 = 11.718 (matches 4096 FFT half). => N≈4096, N/2≈2048. -- Then step 5 copies window[2048:4096] = {0.8 -> 1.0 (mostly flat)} into FIR[0:2048], - upper FIR[2048:4096]=xmm9(=0?), FIR[4096]=0. Effective = gentle low-pass ramp, near-flat. - This matches NOTES line 416: captured window adds almost nothing over empirical (it's essentially 1.0). - -### Remaining unknowns (now narrowed): -- xmm9 / xmm13 scalar values = still the FUN_180529fe0 prologue constants (need prologue read or live capture). -- window[N/2:] copy + xmm9 fill IS the whole "window" mechanism; the earlier "FIR *= WINDOW" - and "0x540658 is freq-shaped" speculation is replaced by: **FIR spectrum lower half = window tail, - upper half = const xmm9**. Window content already captured (rtwin_freq_44100.npy → WIN_WINDOW). - -## ============ UPDATE 2026-08-18c: FFT-CONV LOOP FULLY MAPPED (Phase 5 step 3 partial) ============ -Per-band FFT-conv loop disasm: /tmp/opencode/f_52b570.dis (0x52b550-0x52b8b5 == consumers :1195-1271). - -### What 0x535a70 / helpers ARE (they're dispatchers, real kernels in base-CRT): -- 0x535a70 = FFT fwd dispatcher -> 0x140a10 (scalar) / 0x140a70 (vector). NOT hand-transcribable, unnecessary. -- 0x540548/0x540550/0x540598 = FFT-PLAN table ptrs (plan 0x540530; +0x18/+0x20/+0x68), NOT windows. - Plan built by FUN_18052dc30(param_1+0x540530, [0x64]=FFT size, 0x540868). 0x540534 = plan+0x4 = N/2 usage. -- Per-band ops (dispatch variants collapsed): iVar20=N/2; r14=FIR(0x540668); - 1) 0x535a70(fftctx 0x540628, mask, N/2+1) forward FFT of mask - 2) 0x2210(0x540628, 0, r14, N/2+1) inv FFT into FIR - 3) fwd FFT (0x2180/0x1bb0) ; zero @[N] ; 0x52d920(r14+4, xmm13, N/2-1)=fill LOW half; - 0x52db50(r14+4+N/2, xmm9, N/2-1)=fill HIGH half; inv FFT (0x1a90/0x19d0) - 4) 0x140b30/0x140aa0(r14,r14,N/2+1) in-place complex op - 5) fwd FFT ; zero @[N] ; 0x52d990(r14, 0x540658+N/2, N/2) = FIR *= WINDOW 0x540658; - 0x52db50(r14+N/2, xmm9, N/2) = fill HIGH half; inv FFT - 6) FIR[0]=1.0f, FIR[1]=0; if 0x540890!=0: complex ops (0x1880/0x1ca0) + FIR *= 0x540888 over 2*N; - copy FIR->out (0x1df0/0x1f70). - -### Key unknowns (static analysis exhausted): -- **0x540658 window content**: ONLY 1 direct ref in whole dump (the read at 0x52b771). Not allocated in - consumers_out (:119-135 alloc 668/698/6a8/6b8/6c8/6d8/6e8/6f8/708/718/798), NOT set by 52dc30 (plan tables - only at +0x18/0x20/0x48/0x58/0x68). Written only indirectly -> needs runtime capture OR plan-internal - region. If 0x540658 is freq-shaped, it is the source of warp^3.14 (~π) & LUT-knee 0.574. -- xmm13/xmm9 fills = config constants from FUN_180529fe0 prologue (offline), unknown values. -- FFT size N = 2*0x540534; 0x1a0 = pow(2,floor(log2(SR/44100)))*412 (SR 44.1k -> 412); 0x540868=0x1a0*iVar9*8. - -### Conclusions: -- freq-axis NOT the boost source (offline const). FFT tables NOT windows. The whole freq-shaping funnels - through 0x540658 window in step 5 + the offline fill constants. To resolve: runtime capture of 0x540658 - (needs the plugin under a debugger) OR empirically back out its response from the model residual. -- Recommend: since bridge already at 0.236 and FFT window content is static-invisible, next high-value move = - full pipeline render (step 5) rather than more static FFT mining. - -## ============ UPDATE 2026-08-18b: FREQ-AXIS 0x540698 BUILDER (decomp :21735-21757) ============ -- Allocation+fill of freq-axis 0x540698 lives in the SAME function as the 0x540668 FIR window alloc - (decomp_funs.txt :21735-21757, the builder calls FUN_18052e190(param_1+0x540698, count=0x54086c)). -- OFFLINE branch (param_2=='\0'): the whole 0x540698 array is filled with ONE scalar - fVar29 = (DAT_18262b5c8 * LUT_BANDBOUNDS[uVar3+1] * DAT_18262b704 * LUT_BANDBOUNDS[uVar3] + fVar22) - * DAT_18262b700 - (DAT_18262b5c8/DAT_18262b704/DAT_18262b700 = Soothe2ModuleBase statics, uVar3 = 0x5408b0 boundary index). - => OFFLINE freq-axis is CONSTANT (per-band-band-width scalar), NOT the '8.3-7/(1+exp...)' formula. - The exp-formula suspicion was wrong for offline path. Any per-bin frequency dependence must enter via - WARP 0x5406a8 (freqpath.cpp, already transcribed) and the 0x540668 FIR (FFT-conv impulse). -- 0x540668 (FIR window, count 0x54086c*2) allocated right before; filled with 1.0f (0x3f800000) in offline - init at :21798-21804 (<=> flat impulse). So in offline the FFT-conv kernel starts as a pure del - a; the real kernel arrives from 0x535a70 processing (to transcribe next). -- 0x5406a0 = element count used by the fill loop (large, ~0x54086c). Buffer 0x5406b8..0x5406e8 - (level-weights caps) allocated at :21753-21757 with the same count. - -## ============ UPDATE 2026-08-18 (Phase 5 step 2): FUN_180529fe0 COMBINE ALGEBRA — FULL CHAIN DECODED ============ -Source of truth: FULL decomp in /tmp/consumers_out.txt:471-1277 (the decomp_funs.txt copy at :11425 is -TRUNCATED — Ghidra dropped the middle because of the noreturn/dispatch thunks `(*DAT_181bab008)(&..,&..)` -guarding small-vs-vectorized variants; the consumers_out.txt copy came from a previous session's -getFunctionContaining(0x52ac64) and has the complete per-band loop + FFT-conv). - -### Data-type map (from allocation sites, consumers_out :119-265) -- 0x5406f8 double[], size (0x540868)<<3 — resonance/freq-blend buffer (upper half = mirror) -- 0x5406a8 float[] size 0x54086c — WARP (the tilt!) -- 0x5406c8/6e8 float[] size 0x54086c — level-weights' complements (0x530d30 writer) -- 0x540678[band], 0x540768[band], 0x5407c8[band]: pointer arrays stride 0x10 (re/im or cur/prev) -- IIR states: 0x440518 (+0x4c0528), 0x3404f8, 0x3c0510/0x440510 - -### Thunk identity table (full decomp pairs; A = small stub, B = vectorized body) -- 009be0/00ffe0 : buf = scalar·buf (float; 004700-pair: double) BODY: mulss -- 008700/00fb60 : dst *= src (in-place, float, vmulss) ← WARP APPLY (dst=2nd arg) -- 011940/010860 : copy/mirror (double, complex stride via 5th arg==4; kernel 0x181a99c40) -- 006e40/004720 : dst = src·scalar (B double: vmovsd) ← freq-axis blend -- 015060/014c40 : buf += scalar (B double: vaddsd) ← dry offset -- 005a20/006840 : acc += src (kernel 0x18001a5a0, double) -- 003c40/00ee20 : acc += a·b (kernel 0x1800752e0: vfmadd213pd, DOUBLE) ← WEIGHT APPLY -- 008d60/00dc40 : 3-ptr combine, TBD (mask accumulate) -- 26b820/3a06a0 : big kernel (sets MXCSR; loads rodata 0x181f31680/34a80 tables) → likely exp/sat s.t. begin? TBD -- 2dc0e0/367980 : fills dst buffer with constant pattern 1.0/0.0 (vmovdqu rodata) — TBD (dry/wet?) - -### CONFIRMED per-band pipeline (lines ref 638-1269) for the rendered (online) stage - per band (band in [iVar20, lVar18)): - 1. scale: 0x540678[band] = ((fVar30/0x1a0)·0x540870 · 0x54088c) · 0x540678[band] (:656) - 2. IIR: FUN_18052d650(state=0x440518, out=0x5406f8, in=0x540678[band]) → smoother (:668) - 3. online: blend & weight into mask: - 0x5406f8 = 0x540698·(1−mix) 006e40 mix=0x54087c (:814) - 0x5406f8 += mix·0.8 015060 c3e28=0.8 (:823) - 0x540678[band] = bigkernel(0x540678[band],0x5406f8) 26b820 (:832) - 0x5407c8[band] = combine(0x5407c8[band],0x540678[band],0x5406f8) 00dc40 (:843) - mirror 0x5406f8 halves (011940, n and n+stride4) (:852 861) - WEIGHTS: 0x5407c8[band] += 0x5406c8·0x5406f8_upper 003c40(stride4) (:869) - 0x5407c8[band] += 0x5406e8·0x5406f8_lower 003c40 (:877) - 0x5407c8[band] += 0x540678[band] 005a20 (:885) - 4. WARP (TILT): 0x540678[band] *= 0x5406a8 008700 (:938,:947) - 5. dry/wet: 0x540678[band] = mask·(fVar30·0x540888) + (1−fVar30) fVar30=0x540874−rnd (:1184,:1193) - 6. FFT-conv: FUN_180535a70(0x540628, mask, n+1) fwd-FT → window×0x540658 (52d990) → inv-FT - → impulse 0x540668, DC=(1.0,0.0) (:1245) → ×0x540888 → conv(impulse, audio param_2) → out - -### THE TILT (replaces empirical {500:1.414,1000:1.454,2000:1.795}) - warp 0x5406a8 = 0.87·x/(1+x/K), K=exp(2.0723267)≈7.942, x = f_bin/2000 [c3e50=0.87, c4208, c45b4=2000] - warp(500)=0.211 warp(1000)=0.409 warp(2000)=0.773 (linear depth ratio 2000/500 = 3.67) - applied directly to the FLOAT mask before FFT-conv ⇒ enters the dB via the impulse response. - NOTE: 3.67 linear is ≫ the fitted tilt ratio (1.27) ⇒ the rest comes from the LUT-level flattening - (step 5 dry/wet + 0x540888) and the level sidechain 0x563xxx — that is the Phase-5 step-4 integration. - -## ============ 2026-08-18e (STEP4: runtime-values, TIME-DOMAIN from renders) ============ - -## Атака уровня: fast (≤ ~1 STFT frame / ~20ms) — ИЗМЕРЕНО на рендерах -- Goertzel-огибающая 2000Hz по dual_b1q_*.wav (100ms/20ms окна): env ПЛОСКАЯ 0.18 (q0.1), - 0.30 (q1), 0.32 (q10) уже на 0.2s; пик 0.208/0.344/0.364 (атака, ~первые 50ms меньше подавления). -- red(dB) по времени (вход dual 500+2000, канал 2000): t=0: 13.2 dB -> t=0.02s: 15.2 dB (steady). - => reduction выходит на стационар к ~20ms. 0.540658-окно/level-smooth = быстрые → для parity - доминирует steady-state C (B.12, 0.268 dB); атака нужна лишь как короткий onset (~1 frame). -- Релиз НЕ наблюдается на tone->silence (out=G*in, in=0→out=0): для релиза нужен burst-тест с - second tone-активностью между бёрстами; dburst-огибающая зашумлена 50ms-окном поверх 100ms бёрстов. - -## Runtime-константы (FUN_180537e90, quality-setter, decomp ≈:8534) -- 0x1ac = oversample-фактор ПО КАЧЕСТВУ: quality1->4, 2->8, 3->0x10(16), else->2. -- 0x1a4 = fVar6 / (float)0x1ac (см. строку 482: есть деление на 0x1ac => 0x1a4 = analysis-res". -- 0x1a0 = pow(2,floor(log2(SR/44100)))*412 (@44.1k = 412) — база cell-count геометрии. - -## Коэф-таблицы IIR 0x24c4780/0x24c4790 (soothe_mem.bin offset 0x24c4780) -- floats: {1,0,0,0,-1,0,0,0,0,1,0,0,1,1,0,0} (A) / {-1,0,...} (B) — шаблон блочный, НЕ чистая - attack/decay константа; doubles: 0.0078125=1/128 на 4-й/6-й слотах. => отложить, не декодить. -- Более информативна ctor-инициализация 0x540880=0x540884=1.0f (movl 0x3f800000, disasm 0x535d19). - -## ============ 2026-08-18f (STEP5b: FULL FRAME-RENDER PILOT - WORKS) ============ -## framed_render.py: STFT(2048/512, sqrt-Hann) -> per-bin amp 2|X|/wsum (att 11ms/rel 80ms) -## -> B.12 mask C=g*LUT(xv)+w*warp^a, gain=1-C -> OLA. VALID: dynamics match, steady ~B.12. -## al_* DATASET (центр band fc=1000 sens12, tone1000, level-свип 0..-24dBFS): -## lvl 0,-3,-6,-9,-12,-18,-24 dBFS -> red -104,-6.1,-7.9,-9.7,-11.6,-15.4,-19.5 dB. -## xv=-0.269..0.931 (res_center=0.1171) => REAL LUT-нога КРУЧЕ frozen-узлов (cap 0.667): -## на xv=0.931 C->1 (клиф), у B.12 только -9.5. КОНФЛИКТ с t1k fc-scan (15.6@0dBFS там же) -## => премьюза fc-scan рендеров неверна (вход/настройки) - ПЕРЕСОГЛАСОВАТЬ. -## al_* = калибровочный датасет для замены LUT-узлов B.11 (structural 0.540658-piece). - -## ============ 2026-08-18g (STEP5c: DATASET INCONSISTENCIES DECODED) ============ -## КЛЮЧ: RPP несёт base64 XML "" — декодер: -## b64 = join всех строк-бейс64 (>40 chars) из .rpp; decode utf8; regex id/value. -## 1) t1k/t1kq _b1f_* = МУЛЬТИБАНД: band1 on sens=12 + band2 on sens=-12 (fc=1778.7, q=4.5) -## + band3/4 on (defaults) => НЕ single-band. B.12 фит впитал multi-band depth-split -## => LUT_obs у b1f сидит на ДРУГОЙ кривой (0.07-0.17 vs 0.38-0.63 у al_/dual). -## 2) t1kq_only1_* = НАСТОЯЩИЙ single-band fc-скан (band2 on=0). НО в RPP НЕТ 'depth' => -## default depth (не 0.8639736) => нельзя мешать с al_/dual без нормализации depth. -## 3) tone_amp АБСОЛЮТНАЯ калибровка НЕСОСТОЯТЕЛЬНА (t1k: 1.058 vs true 0.215; t1kq: 0.284 vs 0.058). -## Для xv использовать FFT-амплитуду: seg=1..4s, Hann, A=4|X|/N (проверено: lv12 0.17759==peak). -## 4) ЧИСТЫЕ single-band датасеты (depth=0.8639736, band1 один): -## dual_b1q (22) + al_* (6) — СОВМЕСТИМЫ ~0.05 LUT_obs: -## dual@2000 xv -0.66..0.31 -> LUTobs 0.434..0.547 (гладкая S-кривая) -## dual@500 xv=0.574 -> 0.564 -## al_* xv -0.42..0.63 -> 0.381..0.626 (круче внизу, ~0.05-0.08 выше dual@2000) -## Остаток dual@2000 vs al_ на низких xv => семантика уровня: per-band bandpass-энергия -## (включает ликование др. тонов) vs per-bin амплитуда — НЕ РЕШЕНО. Кандидат-тест Q=10 -## (рес-широкая, ликование 500-тона в band1 при измерении 2000). -## 5) b2_* семейство (band2 fc=1778.7, sens=12): b2only_12, b2q_*, b2_b1s0/on, b1on12_b2on12 -## (band1 500 + band2 1778, оба sens=12) — датасет multi-band depth-split для проверки. -## 6) ПЛАН (следующая сессия): (а) refit single-band LUT по dual_b1q+al_ (не b1f!); -## (б) уточнить depth-контекст t1kq_only1 перед микшированием; (в) решить семантику уровня -## через dual Q=10; (г) влить в framed_render.py. - -## ============ 2026-08-18h (STEP5c: REFIT FAILS - LEVEL SEMANTICS UNRESOLVED) ============ -## Совместный LUT-фит dual_b1q+al_ (9-узловой PCHIP + g,w,a): -## g=1.1895 w=0.3749 a=2.0 (на границе!) -> TOTAL 0.474 dB, dual 0.26, al 0.894. -## АЛ-точки не ложатся на dual-кривую: xv>0.3 al хочет +1.3..-1.0 dB сверху. -## КЛЮЧЕВОЙ ФАКТ (оба = тон в ЦЕНТРЕ полосы, res=0.117, depth/sens одинаковы): -## dual@500 (A=0.4397, вход=ДВА тона) xv=0.574 -> red 10.22 -## al_3 (A=0.5005, вход=тон) xv=0.631 -> red 13.38 (ВЫШЕ при меньшем заxode-sharing) -## al_6 (A=0.3543) xv=0.481 -> red 12.21 -## => при практически одном xv dual режет МЕНЬШЕ (10.2) чем al_4/5 (12.2-13.4). -## Больше энергии (dual=2 тона, A=0.44) -> МЕНЬШЕ reduction = ПРОТИВОРЕЧИТ al_-монотонности. -## Per-bin и per-band интерпретации обе проваливаются => механизм уровня (0x540678 per-bin -## IIR, точная схема отбора) НЕВОССТАНОВИМ статически из wav-пар. НУЖЕН runtime-захват: -## под отладчиком снять маску (0x5407c8/0x540658-окно) и per-bin уровень (0x540678) для -## dual_b1q_1.0 и al_6 при одинаковом цене -> дифферить механизм. -## Именно поэтому dB-потолок B.12-суррогата ~0.20-0.27 dB — линия DATA-уровня, не тюнинг. -## СЛЕДУЮЩИЙ ШАГ (рекомендация): runtime-захват уровня/маски под gdb (soothe2_x64.vst3), файл -## двойного стимула; после локализации механизма — закрыть LUT и 0.540658 эмпирически в C++. - -## ============ 2026-08-18i (MULTI-BAND COMBINATION - MEASURED, MODEL FAILS) ============ -## Эксперимент (вход tone1778 A=0.707, band2 fc=1778.7 = центр; depth=0.86397 mix=100): -## red@1778: b2only_12/b2_b1s0/b2_b1s0on = 16.55 (ВСЕ ОДИНАКОВО) -## b2_b3s12=16.62 b2_b4s12=16.51 -## b1on12_b2on12 = b1s12_b2s12 = b1on12_b2s12 = 17.08 (идентичные) -## b1only_12 (band1 fc=500, band2 off) red@1778 = 11.47 (soothe режет ВНЕ полосы сильно!) -## ВЫВОДЫ: -## 1) depth НЕ делится по числу активных полос (b2only 1-полоса == b2_b1s0on 2-полосы 16.55). -## 2) Полоса sens=0 даёт НУЛЕВОЙ вклад (b2_b1s0on == b2_b1s0 == b2only). -## 3) Комбинация band1+band2: 16.55->17.08 (+0.53 над макс), НЕ product (дал бы ~27), -## НЕ additive-C (насыщает в +180). => общая маска строится из СУММЫ displacement по полосам -## (аккумулятор 0x5407c8 += ...) через НАСЫЩЕНИЕ. exp-насыщение(0.9) даёт 19.5 > 17.08 -## => точная насыщающая кривая/уровень требуют runtime-захвата (0x5407c8/0x540658). -## 4) B.12 single-band band1@1778 предсказал 10.04 vs измеренных 11.47 (уровневая семантика -## опять +1.4 dB). -## => мультибанд: сумма displacements -> sat-кривая. Каждая полоса отдельно калибруется single- -## band рендерами; комбинация закрывается runtime или sat-подгонкой на b1on12_b2on12. - - -## ============ 2026-08-18h2: RUNTIME CAPTURE PARTIAL (base 0x180000000, 0x540658 = FIELD NOT RVA) ============ - -### Runtime capture infra (rtcapture2.py, WORKS): -- Launch: `reaper -nosplash -renderproject X.rpp`; host pid found by scanning /proc/*/maps for 'soothe2' - (lowest map addr with soothe2 in path = PE base). **vst3 loads AT ImageBase 0x180000000** (no reloc). -- Reading /proc/pid/mem (+0x180000000) works DURING render; process dies right after render finishes. -- Section table (Authoritative): .text rva 0x1000 raw 0x600 vsize 0x1a52000; IPPCODE 0x1a53000/0x1a51e00; - .rdata 0x1baa000/0x1ba8400 vsize 0xa60000; .data 0x260a000/0x2608200 vsize 0x72000; .pdata 0x267c000. -- FILE->RVA mapping: raw off F maps to rva = F - raw_sec + va_sec (e.g. .text: F=0x53F658<->rva=0x541058). - -### CRITICAL: 0x540658 (and 0x540678/0x5407c8/0x5408b0/etc) are FIELD OFFSETS, NOT RVAs! -- Disasm at rva 0x52b771: `mov rax,[rdi+0x540658]` -> window read is [ctx+0x540658] (ctx = DSP object). -- Static soothe_mem.bin is RVA-linear; reading at 0x540658 gives .text CODE bytes (garbage floats). -- So the "window" (FIR*=WINDOW in FFT-conv step 5) lives in the HEAP DSP object at offset 0x540658, - NOT in module image. Runtime must locate `ctx` = base of DSP object. -- Beacon idea: phase_table_1024 (twiddle doubles 0x182615608 in .data) is the FFT-plan table; - scan host heap for u64==0x182615608, then the pointer owner is plan (+0x18/+0x20/+0x68 fields) - -> ctx = byte_addr_of_(pointer_field) - field_offset; window at ctx+0x540658. - (rtdeep2.py implements this; currently racy - host may exit before numpy import + first scan.) - -### Notes for next session: -- Render lifetime is short (~2-19s); to win the race: import numpy BEFORE Popen, scan /proc instantly, - and re-run if host missed. Could also pre-load module into gdb for direct ctx inspection. -- Alternative: find ctx via 'consumers_out' alloc chain (note :119-135) if heap layout known. -- Verified render outputs: out_dual300.wav etc; run_sweep works; pkill -9 -x reaper hangs shell - - use `pkill -9 -f "reap[e]r"` style to avoid killing own bash. - - -## ============ 2026-08-18h3: LIVE GUI CAPTURE SUCCESS (pid 652462, SR=48000) ============ - -### Infra achieved: -- User ran REAPER GUI + soothe2 (yabridge-host.exe.so pid 652462), tone playing, band1 fc=500, Q=1, stereo balance. -- rtsnap.py: page-wise snapshot of all readable regions -> /tmp/rtA|B|C|D.{raw,idx} (~821MB each), skips EIO pages. -- Live process loads vst3 at ImageBase 0x180000000; `.data` shifted +0x1e00 vs static RVA. -- Diff A/B (Q turned) and C/D (idle 6s) both dominated by audio-buffer noise; direct ctx discovery by - (len,ptr)-registry + static curves instead. - -### Registry of DSP buffers (found at 0x28b06c0, vector<{u64 count, u64 ptr}>): -- [0x00] count=8193 ptr=0x2c1a680 (all 1.0f) - identity/gain table -- [0x10] count=8193 ptr=0x1930100 **STATIC** 0.5->0.8 (freq-path window, monotonic) -- [0x20] count=8193 ptr=0x1938180 **STATIC** 0.0->3.899 -- [0x30] count=8193 ptr=0x19401c0 **STATIC** 0.596->0.126 -- [0x40] count=8193 ptr=0x29000c0 0.404->0.874 (dynamic) -- [0x50] count=8193 ptr=0x2908100 (dynamic) -- [0x70] count=8193 ptr=0x2c72800 (dynamic) -- [0xd0] count=2049 ptr=0x2e79040 freq-axis 0..23988.3 Hz (linear, spacing 11.71 = 48000/4096) -- [0xe0] count=8193 ptr=0x2e810c0 (dynamic) -- [0x110] count=8193 ptr=0x2c62740, [0x120] 0x2c12600, [0x130] 0x2c6a780, [0x140] 0x2eb1180 (zeros/ones) -- STEREO PAIR: 0x1930100 == 0x1a04240 (identical) => two copies (per-channel). -- "static" = identical bytes between snapshot C and D (idle) => candidate WINDOW tables. - -### THE WINDOW (0x540658 area) - EXTRACTED: -- rwin_A0.npy (0x1930100): 2048 floats, 0.50000006 .. 0.79990, strictly monotonic, reaches 1.0 at idx 2049+ - (saturates: plateau 1.0 after bin 2048). Shape = 0.5 + 0.3*g where g = K*x/(K+x), x=f/24000, K≈1.9 - (fit rmse 0.0024; K sweep 1.9 best). freq-axis r_freqaxis.npy (2049 floats, 48000/4096 spacing). -- Interpretation: freq-path window = 0.5+0.3*warp(f); warp = K·x/(K+x), x = f/(SR/2). - NOTE: GUI SR=48000; offline renders SR=44100 (out_dual*.wav all 44100) - must renormalize x by actual Nyquist. - -### Integration attempt (framed_render refit, G/W/p free, cases dual_b1q q=0.1/1/10 @500+2000): -- baseline warp^p: G=1.278 W=0.130 p=6.077 meanerr=0.323 dB (warp=(f/2000)^p) -- real window as warp term: G=1.199 W=1.328 meanerr=0.359 dB (NOT better) -- window*input-amp + warp^p: meanerr=0.470 dB (worse) -=> The real 0x540658 window does NOT beat empirical (f/2000)^p when used as the mask warp term. - BOTTLENECK remains the LUT-leg (level semantics), not the window. See al_* conflict in framed_render.py. - -### Remaining hints for next session: -- The 8193-count tables vs 2049 freq-axis => N=4096 FFT at 48k (rfft bins 2049). Window arrays sized 8193 - = complex bins? or 2*N? Actually 8193 = 4096*2+1 => likely full complex spectrum storage per channel. -- Re-running parity: use tone_cmp() (ndarray) not render_parity.tone_amp (file path) when testing live windows. -- For Q/depth level-path: the live diff method (snap A, change, snap B) is viable; noise is huge, use - (len,ptr) registry+static checks as anchors instead of raw byte diffs. - -## ============ 2026-08-19: LUT-LEG CALIBRATED VIA al_* DATASET (JOINT FIT, DONE) ============ - -### What was done: -1. Decoded full al_* RPPs (binary b64 header): trim to len%4==0, find ` reduction INCREASES with input level. -3. Pipeline-computed xv = log10(am_i / res_i) at bin=1000 (am=smoothed 2|X|/wsum, tatt=11ms/trel=80ms): - lv3:.5488 lv6:.3988 lv9:.2488 lv12:.0988 lv18:-.2012 lv24:-.5012. - NOTE: earlier al_* xv (0.631 etc.) were computed with different am normalization - ALWAYS use pipeline's. -4. Pure-LUT nodes at each xv: lut = ((1-m) - W*warp^A) / G (mask C = G*LUT + W*warp^A). -5. JOINT FIT (dual + al_*, objective=mean|err| over 6 dual tones + 6 al levels): - best: G=1.0850 W=0.2819 A=1.1377 -> al_* err <=0.19 dB ALL; dual err <=0.62 dB ALL. - The LUT leg is a SLANTED curve (~0.36 at xv=-0.5 rising to ~0.64 at xv=+0.55), NOT the flat B.12 (~0.5). -6. framed_render.py updated: LX/LY = merged anchors + al_* nodes; G/W/A = 1.0850/0.2819/1.1377. - CRITICAL BUGFIX: lut clip must be [LY.min(), LY.max()] (0.366..1.0) not LY[0] (0.4402) - node at - xv=-0.5012 (0.366) was being floored, breaking q10@2000 by 1.4 dB. - -### Final validation (framed_render.py dual): - q0.1 @500 -0.55, @2000 +0.14; q1 @500 -0.62, @2000 -0.57; q10 @500 +0.01, @2000 -0.00 - envRmse@steady: 0.64/0.04/0.71/0.78/0.11/0.11 dB. al_* lv3..24 err: +0.11~+0.19 / +0.01 / -0.02. - -### Conflict (t1k fc-scan vs al_* clif at xv=0.93) - RESOLVED: - al_* "-104 dB @ lvl 0dBFS" comes from overdriven input (am >> any res), saturating mask to C->1. - t1k 15.6 dB @ xv=0.931 was a *different* level point (soothe's own fc-scan dataset). Both acceptable - once we fit AT THE MEASURED level points (which the joint fit does); no premise is wrong, input differs. - -### Next: (1) hammer the residual -0.6 dB on dual 500Hz cases (structural); (2) multi-band combos (band>=2); (3) C++ port. - -## ============ UPDATE 2026-08-19f: LEVEL-TRACKER UPDATE-LOOP LOCATED (structural) ============ -The UPDATE loop is NOT a separate function — it is the inline bidirectional IIR smoothing -inside FUN_180529fe0 per-band loop (consumers_out.txt). Three first-order IIR stages: -- (a) FUN_18052d650(state 0x440518, out 0x5406f8, in 0x540678[band]) — outer smoother -- (b) inline: coeff-A array 0x4c0528, scalar-acc state 0x540528, mask src/dst 0x5406f8 (count iVar19) -- (c) inline: coeff-A array 0x3c0510 / 0x2c04f8, acc 0x440510 / 0x3404f8+0x2404e8 (two more) -Scalar form (proven, consumers 745-1015): - acc = 0; for i in 0..N-1: y = x[i]*A[i] + acc; acc = y; x[i] = (float)y -i.e. cumulative leaky-integrator over A[i] (running-acc coefficient). Vector variant does 4/iter. -=> The sample-path smoothing the tracker needs is STATICALLY known in structure; only the - coefficient arrays A (0x4c0528/0x3c0510/0x2c04f8) + the 0x530d30 per-bin level-weight caps - remain runtime values (0x530d30 shown numerically flat w~=0, NOT the tilt source). -Bit-exact port of the UPDATE requires A[] live capture (or FUN_18052d650 body) — next P1.5-ish. -Boundary: structural scaffolding now complete (levelpath + fftconv + mask-canon + tracker form); -all remaining unknowns are runtime scalar/array values, not structure. - -## ============ 2026-08-19 continuation: LEVEL-PATH DECOMPILED — FUN_180563440 + FUN_180563ce0 ============ - -### FUN_180563440 (dsp/levelpath.cpp dump, f_563440.dis 222 lines) — LUT curve + twin-mask + combine -Three-phase per-frame structure: -1. **1024-bin LUT loop**: bin k → x = clamp(k/1024, 0, 1). Band config at context+0x188: - {A(+0x00), B(+0x04), threshold(+0x0c), flag(+0x10), callback(+0x50)}. Three paths: - - vtable callback (dynamic, when 0x50 non-null) - - **power-law**: centered=2x-1 → `sign()·10^(log10(|x|)/C)` — compression by sharpness - - **linear**: `(B-A)·x + A` — plain interpolation - Output stored DOUBLE at +0x198. -2. **Twin-mask factory** FUN_18056e3e0: 6 bands × 1024 bins, stride 0x2000. -3. **Combine**: max 2 channels (stereo), 6 bands, `1 - Σ weights`. - -### FUN_180563ce0 (f_563ce0.dis 163 lines) — IIR level-tracker INIT (NOT update) -- 341 bins (0x155), **order-3 IIR** (3 coeffs/bin: 0x40400000 = 3.0 markers). -- Level coefficient: **0.1** (0x3dcccccd IEEE 754) — this is the attack/release α. -- State layout: rcx+0x28/+0x40/+0x58 (3 buffers). Init [1,0,0,0] / [-1,0,0,0]. -- **UPDATE loop NOT found yet** — the actual sample-path smoothing is elsewhere. - -### Key constants (all verified from soothing_mem.bin): -| Address | Value | Meaning | -|---|---|---| -| 0x24c3c54 | 0.0009775 | 1/1024 bin scale | -| 0x24c3ea4 | 1.0 | clamp max | -| 0x24c41e0 | 2.0 | power-law centering | -| 0x24c4680 | -1.0 | sign flip | -| 0x24c3d8c | 0.5 | threshold | -| 0x24c4334 | 4.0 | depth range (oversample os=4) | -| 0x24c43e0 | 8.6859 | 20/ln(10) dB conversion | -| 0x24c4704 | -6.9078 | ln(0.001) floor | - -### CRITICAL: The "warp" is NOT a table — computed at runtime -- `rwin_A0.npy` (live 0.5→0.8) is the **frequency WINDOW** (per-bin mask shaping), NOT the warp. -- Empirically fitting `W·warp^A` was approximating the runtime LUT curve evaluation. -- The LUT is **parametric** (linear or power-law by band flag), not a fixed lookup. -- To reach bit-exact, replace PCHIP-fitted LUT with the parametric curve from FUN_180563440. - -### Structural finding (per-bin gain from reference, avg over steady frames): -- Reference mask is FLAT ~-10.2 dB across 100-540 Hz REGARDLESS of Q (dual_b1q q0.1/1/10). -- Model produces res-shaped notch → source of the -0.6 dB residual at 500 Hz. -- **Hypothesis to test**: mask uses a SCALAR per-frame level (broadband), not per-bin am/res - (this matches the "twin-mask factory" combining band contributions). Test in /tmp/scalartest.py. - -## ============ 2026-08-19h3: RES_POWER OPENING — 500Hz RESIDUAL SOLVED ============ - -### The fix: gain_k = (1-C) × res_k^rp (rp ≈ 0.05) -The reference applies the mask NOT as `(1-C)` directly to the per-bin gain, but as -`(1-C) * res^rp` — a small res-dependent correction. This flattens the per-bin gain -across the band center (where res=1 → correction=1, matching baseline) while dampening -off-center bins (res<1 → correction<1, reducing the notch). - -### Validation (test_joint_rp2.py, multi-start Nelder-Mead, 5000 iter): -``` -BEST: G=0.9696 W=0.3503 A=1.0887 rp=0.0516 mean=0.208 (dual only, 6 pts) - q0.1@500: err=+0.00 (was -0.55) - q0.1@2000: err=+0.00 (was +0.15) - q1@500: err=-0.05 (was -0.62) - q1@2000: err=-0.41 (was -0.57) - q10@500: err=+0.57 (was +0.01) - q10@2000: err=+0.22 (was -0.00) -``` -The rp parameter SOLVES the 500Hz residual for q0.1 and q1 (the main bottleneck), -but slightly degrades q10 (the narrow-Q case). - -### Physical interpretation: -In soothe2, the mask is computed in the RESONANCE-DOMAIN (xv = am/res), but the -applied gain has an additional res-dependency. This matches FUN_180563440's structure -where the LUT curve is evaluated per-bin (1024 bins) and the twin-mask factory -(FUN_18056e3e0) combines band contributions with a res-weighted path. - -### Next steps: -1. Refine rp jointly with al_* (need faster al_* rendering — batch the 6 lv cases) -2. Update framed_render.py with rp parameter -3. C++ port of res_power term (trivial: multiply gain by pow(res, rp)) - -## ============ 2026-08-19 (Q-DEP RP + HONEST METRIC) ============ - -### CRITICAL: earlier "mean=0.160/0.144" were ARTIFACTS (untracked output length) -- Fit scripts (qdep_rp.py) rendered OUT of len nfr*HOP+N (untracked tail) and measured tone_cmp on last 0.75s of that extended buffer. -- framed_render.synthe trims to len(x). The two disagree by ~+0.2dB systematically. -- Honest (trimmed) numbers for scalar rp=0.0169 (h3): mean=0.280 max=0.618 (q10@500). -- Earlier claim "dual mean=0.160" does NOT reproduce on trimmed metric. - -### Q-dependent rp: rp(Q) = rp0 * Q^drp (physical warp kept) -Fitted on TRIMMED metric (render_trim same as synthe): - G/W/A/rp0/drp = 0.9963/0.3335/0.9807/0.0275/0.2159 - mean=0.175 max=0.707: - q0.1: 500 +0.000, 2000 +0.001 - q1: 500 -0.256, 2000 -0.707 - q10: 500 +0.086, 2000 +0.001 - q0.1 and q10 both essentially perfect. Bottleneck is q1@2000 (over-reduction -0.71). -Weighted refits move error around but never reduce max < 0.7: - - w(q1@2000)=3, w(q10@500)=3: q0.1@2000 -> +0.885 (rows shift, no global win). - - free warp (K/alpha/p): mean=0.139 but q1@2000 -0.534 and warp non-physical (alpha=3.19). - -### Committed to framed_render.py (Q-dep rp, trimmed honest baseline): - G/W/A = 0.9963/0.3335/0.9807, RES_POWER = lambda q: 0.0275*q**0.2159 - -### Interpretation -rp(Q) ~ Q^0.216 means the res-power correction grows with Q: high-Q resonance pit -dips harder off-center. Consistent with decomp (res-weighted gain path in twin-mask -factory). The q1@2000 residual is structural warp/model mismatch, not rp. - -## ============ 2026-08-19: STATIC DECOMP LOCKED (all DSP bodies recovered) ============ -New full decomp run (DumpFuns2.java, no haveDec gate, seeds=dsp+vtables+reverse-callers, -skip<8): decomp_funs2.txt (7746 bodies), fun_map2.txt (7928 funcs), consts2.txt (41622). -Previously-cold roots now present: 180529fe0 (2051in), 180563440, 180563ce0 (IIR INIT), -18056e3e0, 18052e9b0 (3167in), 18052e260, 18052e190/52e9b0 allocators. - -### MAIN RENDER LOOP (found): FUN_18052e260 -- Signature (param_1=DSP obj, param_2=nSamples). iVar5=NFFT/2+1; loop per band - (count=0x2404d0): FUN_180536300(param_1+0x3d8, band_in_ptr, 0x540758+bufoff, band, n). - Uses full-STFT frame budget 0x2404d8; tail marks bands needing rebuild (0x240467 flags). -- => 52e260 = per-frame dual-band SIDE-CHAIN driver; twin = per-band. - -### FULL decomp FUN_18052e260 (decomp_funs2.txt:348915) — the frame driver: -``` -iVar5 = NFFT/2+1; iVar2 = min(iVar5, [0x2404d8]); [0x2404d8] = iVar2; param_2=min(iVar2,param_2) -for band in 0..[0x2404d0]: FUN_180536300(ctx+0x3d8, band_in + (iVar5-iVar2)*4, - [0x540758] + (iVar5-iVar2)*4, band, param_2) -[0x2404d8] -= param_2 -if [0x2404d8]<1 && [0x30]>0: fill(0x540788, 1.0, iVar5) // frame done -> reset dry/wet -``` -So 52e260 only DRIVES the per-band twin side-chain (536300); the LUT (563440) + -mask-apply (529fe0) + FFT-conv (52b550) run elsewhere (triggered when [0x2404d8] -drains, i.e. per STFT frame). Full pipeline assembly (P4) = wire these in order: -twin(536300) -> level -> LUT(563440/56e3e0/563a60) -> mask(529fe0) -> FFT-conv(52b550). - -### FUN_180536300 (twin caller, size=475) — audio path per band: -- lVar1 normalize = FUN_18052da00(scratch, in, scale_bin, n): pointwise in[i]*scale. -- band active (0x814): twin kernel FUN_180535880/180536f90(lVar2, base, band_i, lVar1, n), - else fill FUN_18052db50 (1.0 / 0.0). -- combine lVar4/lVar3 (52d990, 0x814>1), output copy 52dbc0(base, lVar4, n). -- TWIN KERNELS (535880/536f90): Ghidra emits ONLY the tail-call frame — - "WARNING: Removing unreachable block ..." => state shown = {pdVar1,*pdVar2} double pairs - read from param_3/param_4, then noreturn tail into thunk_FUN_181ba94b0. **The recursive - per-bin IIR level-smoothing (tatt/trel) lives INSIDE these two tail-calls, which static - decomp cannot recover** — same family as 0x540658 window (step-5 window). Confirmed - statically-invisible frontier = {532a715 twin kernels, 0x540658}. - -### Level-param setters (vtable stubs, auto-named .?AV?$Soothe2Module@M$01@@::vtbl_...): -- 18052bba0: 0x540870 = expf((p*DAT_1824c4348 + DAT_1824c44a4)*DAT_1824c3cd4) [gain/sens] -- 18052bb80/bb60: 0x540878 / 0x54087c = raw int [freq?/bandwidth?] -- 18052bb40/bb20: 0x540880 / 0x540884 = raw int [per-band level / dur] -- 18052bb00: 0x540874 = p (locked); 18052bad0/baa0: 0x540888 / 0x54088c = - expf(p*DAT_1824c3cd4) [attack / release coeff db] -- DAT_1824c3cd4 = 0.115129255 = ln(10)/20 => expf(p*0.11513) = 10^(p/20): **all these - setters convert dB-speed params to linear coeffs**. 0x540888/88c feed FUN_180529fe0 - step-5 dry/wet (mask peak) — the real attack/release coeffs. -- FUN_180530d30 (0x5406b8/6c8/6d8/6e8 weights): fVar9 = 2000.0/NFFT? base; w8=pow(base,0.25); - v = level*0.25*w8*factor(4.0); q=1/(1+v/(level*4)); dVar1=(sr/0x1a0)*0x1ac*0.001; - w = 0.1^(1/(max(q*v)*dVar1)); pairs (w,1-w). -- Numeric check (this session, real values): bin500 w~10^-6, bin2000 similar => 0x530d30 - does NOT tilt 2000>500. Tilt stays = warp (0x5406a8, K=7.942) + twin-mask curving. - Static decomp now COMPLETE up to the statically-invisible frontier confirmed above. - -## ============ 2026-08-19 (runtime capture SETTLED): registry heartbeat + live tables ============ -## **Window 0x540658 + freq-axis CAPTURED live. Internal DSP sample rate = 48000 (not 44100).** -- rtsnap_fast.py: spawn reaper render_long offline -> find host (soothe2 in /proc maps, not reaper - cmd) -> sleep 6s -> snapshot ALL readable maps chunked 8MB -> /tmp/snap_all.bin (318MB, 546 regs, - entries {lo,sz,bytes}+pad, idx in /tmp/snap_all.idx). Chunked pread REQUIRED: whole-region pread EIO. -- **registry heartbeat**: heap run of {u64 count, u64 ptr} pairs (stride 0x10) at **0x29b06c0** - (arena anon 0x2922000, 0x77e000), 36 entries — analog of GUI note 0x28b06c0. Every DSP buffer - base is a registry entry: [00] identity, **[01] = WIN_freq window 0.5->1.0 (saturates)** (this is - live 0x540658 8193 f32: 0.5, idx512=0.345?; idx1024=0.68, idx2048=0.8, tail 1.0), - [02] 0->~0.01, [03]=rwin_C0 0.596->0.126, [04]=0.404->0.874 complement, [05]=0.0435->~0, - [06]=0.9565->~1.0, [07] zeros+neg, [0d]=**freq-axis 0..23988.3 spacing 11.713 = 48000/4096**, - [0e]=2.017->0, [0f] doubled, [10]=1.2914->0, FFT work bufs 131072/32768/65536/16384. -- **CRITICAL**: freq-axis spacing 11.713 Hz => internal SR = 48000 regardless of project 44100. - Registry[01] window & weights are therefore the 48k tables; rwin_C0[03] byte-matches GUI 44100 file - (same table reused). Saved live tables: - - handoff/rtfreqaxis_48000_internal.npy (2048 f32, 0..23988.287, spacing 11.713) - - handoff/rtwin_freq_44100.npy (8193 f32 WIN window 0.5->1.0) <-- THE 0x540658 live capture - - handoff/rtwa_596.npy, rtwb_404.npy, rtwc_043.npy, rtwd_956.npy (registry [03][04][05][06]) -- vptr/44100-marker object-scan (rtobj/rtdump2/rtall) remains DEAD: no vtable 0x1824abb90 bank hit in - heap, fresh-render host has no 44100.0 constants (internal is 48k). The registry run is the beacon. -- twin IIR per-bin attack/release: NOT in registry (those are {att,rel} pair floats at 0x540888/88c, - expf(p*0.11513), per-note-band smoothing inside tail-call thunks). Live capture needs the twin band - object; not yet located. Registry gives per-bin WEIGHTS (done) — attack/release remain static-only. -- Registry dump + all raw tables saved: /tmp/rtcapt/registry.txt, /tmp/rtcapt/*.f32. -- Scripts captured into repo: rtsnap_fast.py (snapshot), findctx.py (locate), rtchunk.py (chunked IO). - -## ============ 2026-08-19 (STRUCTURAL LUT CURVE FUN_180563440 — EXACT FORMULAS) ============ -- Verified against f_563440.dis + PE constants 0x1824c3c54=0.0009775171(=1/1023? 1/0x3ff), - 0x1824c3ea4=1.0, 0x1824c41e0=2.0, 0x1824c4680=-1.0, 0x1824c3d8c=0.5, 0x1824c4f10=0x7fff.. (double |x| mask). -- Loop: 0x400 iterations (0..0x3ff), x = i*(1/1023)? const 0.0009775171 = 1/1023 → last bin x≈1.0. - (prior note said 1/1024 scale — corrected: constant value is 1/1023.) -- Curve config at ctx+0x188: word A(+0x00)=min, B(+0x04)=max, gamma(+0x0c), flag(+0x10), - callback(+0x50). Output written as double to ctx+0x198[i*8] (0x400 doubles). -- **Linear path (flag 0x10==0, 0x563595)**: t = x; if gamma!=1.0 and x>0: t = exp(log(x)/gamma); - val = A + (B-A)*t. (A=min of output, B=max). -- **Power-law path (flag!=0, 0x5635cd)**: t = 2x-1 (centered -1..1); if gamma!=1.0: - t = sign(t) * exp(log(|t|)/gamma) (abs-mask 0x24c4f10, log DIVSS gamma, exp, sign via xmm11/xmm8); - val = A + (B-A)*0.5*(1+t). When gamma=1.0 → val = A + (B-A)*x (identity linear). -- This is the exact runtime curve to replace Pchip/LUT empirical (roadmap Q1). Need live (A,B,gamma) - per band config from ctx+0x188 — requires DSP ctx base (registry doesn't own the curve struct). -- Level-tracker IIR (FUN_180563ce0) INIT confirmed: 341 bins x2?, state rows at +0x28/+0x40/+0x58 - init [1,0,0,0]/[-1,0,0,0], level coeff 0.1 (3dcccccd) = attack/release α; UPDATE loop remains - unmapped (field for future live capture; per registry tables stable between snapshots). - -## ============ UPDATE 2026-08-20i: REAL MASK CHAIN IN C++ (P4) ============ -Replaced empirical PCHIP detector with the live-calibrated mask chain: - -### Structure (mirrors FUN_180529fe0 mono path): - level_k = am_k * res_b[k] * level_scale - track += w_k * (level - track) per-bin attack/release (rt_weights.hpp) - acc_k = (level - track) + level accumulator (live peak ~10.08 at tone) - mask_k = (1/(1+K*acc))^n K=9.8026, n=0.25966 (fit to live mask) - final = min over bands - -### Live calibration zipped: -- live acc 0x5407c8 @1000Hz = 10.08 ; mask @1000 = 0.2976 (level_scale 1630 makes - am*res*1630*... hit acc ~78 at tone -> mask 0.178, matches ref -14.98dB). -- 678[0]=mask (0..1) is what we model as gain; warp/band-mult still missing. - -### Results (framed_test, N=2048 hop=512, sqrt-Hann OLA): -- t1kq single band (678.76,q~1,sens12,scale1630): ref -14.98 dB, out -14.47 dB, err +0.51 dB -- comb 4-band (678.76/q1/s12,1778.7/q3/2.24,195.1/q3/2.24,13408/q3/s12): per-tone err - 500:+5.8, 1000:+1.3, 1500:+1.8, 2000:-3.4, 3000:+5.6 dB. RMS err -16.8 dB (ref rms +4.3). -- Old PCHIP cut comb by ~6dB; new mask chain preserves comb (mostly ~1dB off). - -### Remaining: -- warp tilt (0x5406a8) + per-band 0x540768 mult + FFT-conv smoothing (step 6-8 decomp) -- exact BandConfig (A/B/gamma) for band LUT 0x563a60 -- rt_weights tables only cover bins 0..1024 (2049 len, rest zero); extend if higher bins matter - -## ============ UPDATE 2026-08-20j: FUN_180529fe0 MONO-PATH FULLY DECODED + TABLES EXTRACTED ============ -Read /tmp/consumers_out.txt:471-1277 (full decomp) + f529fe0.dis + live snap_rt.bin (ctx 0x2370040). - -### Exact per-band mono-path (0x5408b8==0), FUN_180529fe0: - 1. scale: 0x540678[band] *= (fVar30/0x1a0)·0x540870·0x54088c (0x9be0) [fVar30=PRNG, 0x540870=440.95] - 2. IIR1: FUN_18052d650(state 0x440518, out 0x5406f8, in 0x540678) = leaky: - y[i] = A[i]·acc + B[i]·x[i]; acc=y; x[i]=(float)y - A@0x4c0528 B@0x440528 (2049 doubles), B=1−A, A ramps 0→0.692 (fast attack at low bins) - 3. copy: 0x5406f8 <- 0x540678[band] (0x5160) — actually the IIR1 in/out feed; then - 4. IIR2: inline on 0x540678[band]: A@0x2c04f8 B@0x2404f8 (A ramps 0→0.086, SLOW release) - 5. mirror halves (0x11940) - 6. blend: 0x5406f8 = 0x540698·(1−mix) + mix·0.8; mask[band] = bigkernel(mask,0x5406f8) - bigkernel 0x26b820 = VECTORIZED exp2 (tables log2e=1.4424@0x1f31740, floor −708.9, - mantissa 2^(k/256)@0x1f38c80) — computes mask[i]=exp2-based op on (mask,0x5406f8) - 7. combine: 0x5407c8[band] = 0x540678 − 0x5406f8 (0x8d60 sub); mirror; - += 0x5406c8·upper (0x3c40 stride4); += 0x5406e8·lower (0x3c40); += 0x540678 (0x5a20) - 8. WARP: 0x540678 *= 0x540768[band] (0x8700); *= 0x5406a8 (0x8700) [TWO warp mults] - 9. IIR3: inline on 0x540678[band] TWICE: A@0x3c0510 B@0x340510 (=A2/B2 shape) -10. dry/wet: 0x540678 = mask·(fVar30·0x540888) + (1−fVar30); fVar30=0x540874−rnd -11. FFT-conv (0x535a70) -> FIR -> conv(audio param_2) - -### Runtime tables EXTRACTED -> dsp/rt_mask_tables.{hpp,cpp} (2049 doubles each, live): - - kIIR_A1 (0x4c0528): 0→0.692 rising; kIIR_B1 (0x440528) = 1−A1 - - kIIR_A2/B2 (0x2c04f8/0x2404f8): A 0→0.086 (slow); kIIR_A3/B3 (0x3c0510/0x340510) = same - - kWarp (0x5406a8): 0→3.899 (freqpath tilt, 8193 f32) - - kBand768 (0x540768[band0]): 2.017→0.271 (per-band warp mult, 2049 f32) - - kPRNGLut (0x5408b0 -> 0x152a3d0): 512 f32 (0.35..6.66, 189 nz) - - PRNG state 0x2404e0 = 112; DAT_18262b5c8=0.4552, b704=0.6089, b700=0.6070 (int32->float) - - scalars: 0x540870=440.95, 0x540874=1.0, 0x54087c=1.0, 0x540888=1.0, 0x54088c=1.0 - - 0x1a0=2048, 0x540868=16384, 0x54086c=8193, band count 0x2404d0=7, 0x30=2 (stereo) - -### NOTE (SR mismatch): internal DSP SR=48000, N=4096 (bin spacing 11.713 Hz). Host refs 44100. - IIR tables indexed 0..2048 (N/2+1). For bit-exact the detector must run N=4096@48k internally. - -## ============ UPDATE 2026-08-20k: bigkernel 0x26b820 = vectorized exp2 (semantics LOCKED) ============ -Read the SIMD loop (objdump 0x18026b820-0x18026bcaf). Key line 0x18026bba0: - vmulpd ymm12 (exp2 result) by ymm11 (loaded from rbp = blend buffer 0x5406f8). -=> mask[i] = exp2(x) * blend[i], where x is the mask value (0x540678[band]) and - blend = freqaxis*(1-mix)+mix*0.8 (=0.8 at mix=1.0). sign: to get attenuation - (mask<1) x must be negative, so mask = exp2(-level) * blend. Locked. -exp2 is standard log2e/poly/mantissa-table (0x1f34a80..., log2e~1.4424, floor -708.9). - -### Calibration (framed_test, structural chain now in framed_model.cpp): -- t1kq single band (678.76,q~1,sens12): level_scale=600 -> err -0.06 dB (ref -14.98). -- comb 4-band at scale 600: per-tone err 300:-5.1, 500:+7.8, 1000:+4.4, 1500:+3.9, - 2000:-1.3, 3000:+4.8 dB (max 7.8). Mask shape (warp tilt + FFT-conv smoothing) - still approximated. - -## ============ UPDATE 2026-08-20m: PRNG prologue transcribed (structure) ============ -FUN_180529fe0 prologue (decomp :515-583) = PRNG coefficient generation feeding -scale fVar30 and dry/wet, via LCG + PRNG LUT (0x5408b0 -> 0x152a3d0, 512 f32, live). - -LCG: state += (round offsets) & 0x8000007f with sign fixup. Constants: - DAT_18262b5c8=0.4552, DAT_18262b704=0.6089, DAT_18262b700=0.6070, DAT_1824c3c58=0.001 -Round products fVar27*LUT[..]*fVar28*LUT[..]+0.001 are ~0.28 at PRNG state 112 -(live), so (int) truncates to 0 => fVar30 can be 0..~ range frame-to-frame. -This is genuine per-frame RANDOMIZATION in the scale step (x *= (fVar30/0x1a0)*...). -structural model averages it via level_scale calibration; bit-exact requires the -exact LCG sequence per frame. scale already calibrated (t1kq -0.43 dB). - -### Remaining bit-exact gaps (documented, next sessions): -1. exact LCG/LUT per-frame fVar30 (scale + dry/wet randomization) -2. FFT-conv (0x535a70) mask smoothing before FIR -3. bit-exact vectorized exp2 (0x26b820) vs std::exp2 -4. combine/accumulator (0x5407c8) exact feedback (1500Hz comb err +6.4 dB) -5. internal SR=48000/N=4096 vs host 44100/N=2048 and twin per-bin IIR (statically - invisible, FUN_180535880 tail-calls) - -## ============ UPDATE 2026-08-20n: PRNG fVar30 LOCKED + CONSTANT FIX ============ -### CRITICAL CORRECTION: DAT_18262b5c8/b704/b700 == 1 (NOT 0.4552/0.6089/0.6070) -soothe_mem.bin is a VA-linear dump: file offset = RVA = VA - 0x180000000. -Earlier 0.4552/0.6089/0.6070 came from adding a spurious (+0x1e00-ish) section -adjustment that is NOT applicable. Verified at raw=RVA=0x262b5c8/b704/b700: -bytes 01 00 00 00 => int 1. Code does cvtdq2ps => (float)1 = 1.0. All three = 1. -Verified constants at raw=RVA: 0x24c3c58=0.001, 0x24c3e28=0.8, 0x24c4674=-0.7, -0x24c4670=-0.5 (match NOTES). - -### PRNG prologue (FUN_180529fe0 :515-583) TRANSCRIBED to C++ (prng_fvar30): - LCG state 0x2404e0 (live=112), round offsets: +0x3cdca,+0x140236,+0x10d56,+0xdf6b6 - fVar30(line554) = (int)(f32(LUT[s+1])*f32(LUT[s]) + 0.001f) [b5c8=b704=1] - => at live state 112, fVar30 == 1.0 deterministically over 300 frames. - => scale step: level *= (fVar30/0x1a0)*0x540870*0x54088c = /2048*440.95*1.0. - scale is NOT effectively randomized for this LUT/state. - dry/wet fVar30 (line1163) = 0x540874 - rnd, rnd from IAT stub 0x181a14cac - (jmp *0x181bab330, CRT import) - random dither, not statically lockable. - -### framed_model.cpp: scale coeff now computed via prng_fvar30() (prng_state_=112), - not a hardcoded constant. t1kq still -0.43 dB at level_scale=600 (unchanged). - -## ============ UPDATE 2026-08-20o: CRITICAL — level = am / res (not am * res) ============ -Found + fixed a sign/direction bug in framed_model.cpp level computation. - -res = |2B/A| (twin) is MINIMAL at band centre (0.117), NOT maximal. The model uses -xv = log10(A_k / res_k), i.e. LEVEL = am / res. My code had level = am * res which -inverted the fc-resonance: it cut MORE off-center and LESS at fc, producing a flat -inverted fc-response. - -Fix: level = am_ / res_[b][k] (with 1e-12 floor). Now the fc-scan (tone1kq, band fc -swept 800..1200, refs t1kq_only1_ = true single-band) tracks the reference shape -(deepest at fc==tone): - scale=42: fc900 +0.3, fc950 +1.3, fc1000 +0.9, fc1100 +0.2; mean|err| 2.46 dB, - max 5.3 dB (worst at edges fc800 +3.9, fc1200 +5.3 - ref cuts broader than model, - likely needs FFT-conv mask smoothing / wider effective notch). -Remaining under-cut off-center: real soothe reduces ~16-22 dB broadly across -800-1200 while model narrows; candidate = FFT-conv smoothing + warp + q shape. -DEFAULT level_scale for am/res path ~= 35-45 (was 600 for the wrong am*res path). - -## ============ UPDATE 2026-08-20p: Phase B — framed_model RE-TRANSCRIBED ============ -Re-transcribed dsp/framed_model.cpp per confirmed chain (NOTES:199-226, source -/tmp/consumers_out.txt:638-1111). Structural divergences from the old approximation: -- IIR1 (FUN_18052d650, state 0x440518) writes into the SHARED 0x5406f8 buffer, - then bridges into the band's 0x540678 (:731 0x5160); IIR2 (0x3404f8/0x2c04f8 on - the band mask) kept separate. -- mirror 0x11940: upper half = reversed lower (Hermitian), full-nfft scratch. -- blend step: f6f8 = 0x540698·(1−mix) + mix·0.8; mask = exp2(−mask)·f6f8. C_AXIS=1.3 - placeholder (offline 0x540698 = per-band scalar, NOT the exp-formula; NOTES:354). -- combine (0x8d60/0x3c40/0x5a20): track = (mask−blend) + kRTAtt·blend + - kRTRel·blend + mask, using live weight tables kRTAtt/kRTRel (0x5406c8/6e8). -- warp (0x540768·0x5406a8), IIR3 (A3/B3)x2, dry/wet identity. - -VALIDATION (scale sweep): -- DUAL (band fc=500, tones 500+2000 at −7.23 dBFS): ref @500 −53.7/@2000 −29.6. - model @500 matches over scale (s30 −51.5, s42 −58.1); @2000 stuck ~−4..−13 (way - short). The DAZING far-field reduction (reduce 2000 by −29.6 when band is at 500) - does NOT come from the exp2(−linear·level) chain — exp2(am/res) collapses to ~0 - once res@2000 (1.344) ≫ res@500 (0.1175). -- T1KQ only1 fc1000: −21.1 vs ref −22.1 (OK). fc-scan shape intact. -=> Phase B confirms structurally: the missing stage for dual-far-field is the - dB-domain LUT FIX_180563a60 (level_dB = 20·log10(mask) → t=(dB−A)/(B−A) clamp → - t^γ), which COMPRESSES the exp2 collapse into a smooth log curve. NEXT = decode - BandConfig ctx+0x188 writers (A/B/gamma) + 0x540698 -> then wire into the chain. - -## ============ UPDATE 2026-08-20q: METRIC FIX + LOG-DOMAIN LUT PORTED (honest baseline) ============ -### CRITICAL METRIC BUG FOUND: 24-bit refs mis-decoded as 16-bit -Earlier dual - -## ============ UPDATE 2026-08-20r: FULL-CORPUS HONEST ASSESSMENT (committed baseline) ============ -Honest 24-bit metric across ALL datasets (input=3.5s window, correct sw decoding): - dual (band fc=500, q-sweep 11): mean|err| 0.41-0.61, max 0.72 ✓ - al_* (fc=1000==tone, level sweep): lv3-12 err 0.05-0.43; lv18 -1.12, lv24 -1.80 (LOW-level over-cut) - res_only1 (resonant.wav, fc 300..700): -1.71..+0.81 (under-cut off-center, shape) - t1k_b1f (tone1k 0dBFS loud, fc sweep): +1.6..+2.12 (LOUD under-cut: LUT cap 0.667 too low) - t1kq_only1 (tone1kq, fc 800..1200): -0.59..+0.01 ✓ - OVERALL mean|err|=0.77, max 2.12. -Systematics: Pchip LUT frozen (floor 0.366/cap 0.667 from al_* mid-level) under-predicts -BOTH the loud tail (t1k_b1f xv>0.8 needs LUT~0.70) and over-predicts low tail (al lv24 -xv<-0.4 needs LUT~0.35). Real LUT extracted from refs: 0.351@-0.5, 0.60@0.07, 0.70@0.85. -Parametric FUN_180563a60 fit to extracted points: A=-13.78dB B=68.29dB gamma=0.344 -(mean|err| 0.017 over extraction pts) but full-render VALIDATION regressed dual (real -multiband interplay absent) -> keep Pchip baseline committed; A/B/gamma = runtime -BandConfig ctx+0x188, NOT live-captured (SET TO: static path uses preset curve). -Structural next: decode BandConfig ctx+0x188 writers (FUN_1804835d0/487200 at -decomp_funs2.txt:21937/21962), wire combine 0x5407c8 + FFT-conv 0x535a70. - -## ============ UPDATE 2026-08-20s: BROADBAND HYPOTHESIS TESTED (NEGATIVE) + LUT re-cut attempt ============ -Tested the mask - -## ============ UPDATE 2026-08-20s: BROADBAND HYPOTHESIS TESTED (NEGATIVE) + LUT re-cut attempt ============ -Tested the "mask uses scalar broadband level" hypothesis (NOTES:637) directly: windowed -FFT of dual_b1q_1.0 ref vs dual.wav input (2.5-3.5s steady): - gain@500 = 0.309 (-10.2dB), gain@2000 = 0.292 (-10.7dB), gain@1000 (NO tone) = 0.995 (-0.05dB), - gain@1500/3000/5000 = 0.85/1.00/1.00. -=> REDUCTION IS PER-BIN: only bins *with a tone* are cut, empty bins pass through (~1.0). - So the level path is am_k/res_k (per-bin), NOT a scalar broadband level. Model structure correct. -LUT re-cut attempt (floor 0.35/cap 0.70 from t1k_b1f+al_ extraction, monotone 16 knots): - REGRESSED whole corpus (mean|err| 0.77 -> 1.09, dual/t1kq/local worse). Reverted to the - joint-fit 12-knot Pchip (dual<=0.72, fc-scan<=0.59, corpus mean 0.77, max 2.12) = KEEP. -Root cause of t1k_b1f residual (+2.1 loud) and al low-level (-1.8): NOT LUT cap shape alone; - al/t1k use DIFFERENT input (0dBFS vs -18dBFS) and the am normalization (pipeline am vs - tone_amp metric) differs; the joint-fit nodes were fit AT the al_* pipeline xv, so they - don't extrapolate cleanly to t1k_b1f's xv~0.85. Structural A/B/gamma (BandConfig ctx+0x188) - would give the *curve*, but writers are JUCE param plumbing (FUN_1804835d0/487200 = UI curve - setters, not DSP scalar setters) - static decode exhausted for now. -NEXT: wire combine/accumulator 0x5407c8 + FFT-conv 0x535a70 into the chain (structural, may - close the t1k_b1f/al tail by smoothing the mask) - todo #3. - -## ============ UPDATE 2026-08-20t: FIT EXPLORATION (todo #3 diagnostics) ============ -Tracked what DOESN'T move the needle (all reverted, committed 12-knot Pchip + G/W/A/rp -= 0.9963/0.3335/0.9807/0.0275/0.2159 remains optimal, corpus mean|err| 0.774 max 2.12): -1. sqrt-Hann STFT analysis window (matching framed_render canon) REGRESSED to 0.788 max - 2.91 -> kept Hann-Hann (the C++ full-render metric is authoritative, not the python - mask-level canon). -2. Runtime (env) full-corpus fit of G/W/A/rp via framed_test renderer - mask-level - optimizer said G=0.877/W=0.402/A=0.673/rp0=0.0041/drp=0.28 (mean 0.66 mask-level) but - FULL RENDER regressed to 0.812 max 2.37 -> reverted. Mask-level objectives do NOT - transfer to the OLA render (mask applied per-bin interacts with window vs tone_amp - metric); the render metric is the ONLY honest one. -3. Residuals are level-dependent (t1k_b1f loud +0.8..2.9, al low -1.1..-1.8, everything - else <=0.9): the empirical LUT is optimal AT the fit levels; structural A/B/gamma - (FUN_180563a60 band curve) is the missing level-dependence. -TODO todo#3 remains: structural combine/acc 0x5407c8 + FFT-conv 0x535a70 wiring (NOTES: - FFT-conv window ~flat, likely marginal; combine adds band-weight interplay). - -### TWIN RES vs PY CANON (todo#3 check) -C++ twin |2B/A| (fs=44100,N=2048, sens_stored=24.65, built via build_twin_coeff/twin_apply) -vs Python bandres (GAIN=4.132=10^(24.65/40), render_parity): match within ~1.5% at fc -300..2000 (fc300: 1.1798 vs 1.1887; fc1000: 0.11746 vs 0.11714; fc2000: 0.7057 vs 0.6963). -=> res shape is NOT the source of the res_only1 fc=300 residual (-1.71 over-cut); that - residual is level-LUT related (input resonant.wav level differs from fit levels). - -## ============ UPDATE 2026-08-20u: F0 GATE — DETERMINISM VERIFIED (bit-exact REACHABLE) ============ -Re-rendered test RPPs twice each (reaper -nosplash -renderproject on /tmp copies with -RENDER_FILE -> /tmp), then byte-compared data chunks: - -1. res_only1_500.rpp (4s): - - a vs b (two fresh renders): data chunk IDENTICAL (max|dx|=0 over 352800 samples). - - a vs committed res_only1_500.wav: data chunk IDENTICAL. -2. t1kq_only1_1000.rpp (6s, 1587600 data bytes): - - a vs b: IDENTICAL. a vs committed t1kq_only1_1000.wav: IDENTICAL. - -Whole-file cmp differs ONLY in the bext chunk timestamp (offset ~381: "02-08-2020") -= Reaper render-metadata, NOT audio. Dither/PRNG does not corrupt the rendered signal -(dry/wet rnd in FUN_180529fe0:1163 evidently locked/stabilized in offline render). - -CONCLUSION: soothe2 output is byte-deterministic and our Reaper/yabridge CLI re-renders -reproduce the committed references EXACTLY. => Bit-exact (bytes) is REACHABLE; the gate -that could have made it impossible (per-run randomization) is closed. The remaining work -to bytes-parity is purely: exact structural chain + exact kernels + internal 48000/4096 -geometry + live A/B/gamma, NOT fighting host nondeterminism. - -## ============ UPDATE 2026-08-20v: EXISTING PARAMETRIC LUT SETS BENCHED (F1 closure) ============ -All previously-derived parametric-LUT sets (from repo scripts + NOTES) tested on the -FULL honest 24-bit corpus (59 cases) inside ONE fixed bridge pipeline -(C = G*LUT(xv)+W*warp^A, gain=(1-C)*res^rp, committed G/W/A/rp). Result: - - LUT form mean|err| max - ------------------ -------- ----- - pchip (committed) 0.773 3.426 <-- BEST (unchanged) - linear (0.483/0.717) 1.017 3.116 - realpts (lutpts.npy) 1.134 3.192 - u563a60 (A=-13.78/B=68.29/g=0.344) 1.669 6.004 - lut5 (0.4356/0.5914/1.999/0.9364) 1.785 3.951 - lut4 (fit_lut) 1.785 3.951 - powerlaw(C=8) 6.809 11.880 - -=> The empirical Pchip curve remains the best form-factor; NO existing parametric set - beats it at fixed bridge params. Realistic conclusion: the recovered runtime curve - (NOTES:967) was fit to extracted (xv,C) points, but those points themselves came from - the SAME bridge-shaped model (LUT=xv->C), so parameteric fits overfit their source - and lose on the honest render metric. Pchip baseline (0.773) stays canonical. - F1 (params of the LUT curve) is hereby CLOSED as "already produced, none accepted"; - the structural A/B/gamma from ctx+0x188 remains the only path to bytes (blocked: - level-path object not live-captured; see F2/F5.) - -## ============ UPDATE 2026-08-20w: F2 STRUCTURAL WIRING TESTS — BOTH REGRESS (documented) ============ -Tested wiring the two remaining structural chain pieces onto the committed bridge -(quick python precompute+OLA on a representative subset; honest 24-bit tone metric): - base : mean|err| 0.75-0.80, max 2.1 - FFT-conv step5 (mask *= win8193 0.5->1.0) : mean 5.22 (worse) — masking the gain by - the freq window is WRONG direction; the 0x540658 window enters the FIR step, not a - simple gain multiply (confirms NOTES "window ~flat, adds almost nothing"). - combine 0x5407c8 accumulator (acc += att*df+rel*df+g, df=g-acc, per-bin kRTAtt/kRTRel): - mean 4.68 (worse) — naive acc toward 2.36*g overd red by ~6x; the decomp combine - operates in the REDUCTION/exp2 domain (blend 0.8 pedestal + bigkernel exp2), NOT on - the fitted bridge final-gain. Mismatched domain => not a valid wiring test. -CONCLUSION: F2 "wire combine+FFT-conv into the bridge" is rejected in naive form. The -structural pieces belong to the exp2(mask) chain which regressed (Phase B, da63adc -replaced it with the log-domain LUT bridge). The bridge (Pchip, 0.773) remains canonical. -Path to bytes stays: exact A/B/gamma (blocked live), SR geom 48000/4096, bit-exact -exp2/FFT. No code change made this session; NOTE-only. - -## ============ UPDATE 2026-08-20x: F2/F4 structural geometry test — NOT the gap ============ -Tested internal-geometry hypothesis (internal DSP SR=48000/N=4096 vs host 44100/2048): -render the bridge at 48000/4096 with scipy resample_poly in/out to 44100: - host44100/2048 : mean|err| 0.683 (subset) - internal48000/4096: mean|err| 1.004 (worse) -=> geometry/SR-mismatch is NOT the source of the level-dependent residuals. -ALL structural adaptations tried and rejected (doc'd): parametric LUT sets (6), FFT-conv -window (step5), combine 0x5407c8 accumulator, internal 48000/4096 geometry. The bridge -(Pchip, full-corpus mean 0.774) remains canonical; empirical ceiling. -Remaining real paths to bytes: (F1) live capture of level-path object (A/B/gamma) OR -(F5) bit-exact exp2/FFT kernels — neither changes the mean but both are required for -byte parity. No code change; NOTE-only. - -## ============ UPDATE 2026-08-20y: F5b — exp2 0x26b820 (assets + numeric fallback) ============ -- 0x26b820 = SCALAR double exp2 (vmulsd/vaddsd family; vmovups wrapper 0x26c220 = - 32-at-a-time bigkernel with MXCSR/fnstcw control). Table-driven fdlibm-style, - Cody-Waite hi/lo: 8x16 irr tables + 0x181f31940.. series (-708.449.. step, companion - -1.684e-9) + kExp2_f2f4e0.. (log2e~1.4428 head) + vfmadd213sd poly chain. -- EXTRACTED bit-exact P3 assets: dsp/exp2_tables.{hpp,cpp} (8x16 doubles straight from - soothe_mem.bin file=RVA; lead-in series kExp2_big). NOT yet wired 1:1 (algorithm body - has many special branches: subnormal/overflow/precision split). -- exp2_dsp() (dsp/exp2.cpp): numerically correct double exp2 (=std::exp2) for wiring; - RELIABLY NOT bit-exact to plugin (documented). exp2_check: 2e6 dense grid + edges, - PASS vs std::exp2 (0 cells >1e-13) -> ensures fallback correctness only. -Bit-exact exp2 transcription stays P3 (same bucket as DSP-FFT butterfly wiring). - -## ============ UPDATE 2026-08-20z: F5c — DSP-FFT 0x140a70 status (P3 confirmed) ============ -Full dsp/ check run (twin/tables/fftconv/vlog/leveltrack/levelpath/exp2): ALL PASS. -FFT status: dsp/fft.cpp = numerical radix-2 (std::cos twiddle) used by the STFT -framework — produces correct transform, but NOT bit-exact to the plugin DSP-FFT -0x140a70. P3 bit-exact DSP-FFT plan: twiddle source (DAT_182616800 sin-table, sin(k*2pi/ -1024), loader FUN_180039b00 stride 2^(10-m)) + split-radix 2/4/8 in-place butterflies -(FUN_18000bfc0/18000c5e0 with 0x8440 elementwise mul + FUN_1800437c0 acc) + plan-gen -giant (FUN_18002f980 recursion + per-log2 factor tables) remain unwired. This is the -multi-week gap (roadmap P3), required only for byte-parity of the FFT-conv path; the -bridge renderer does not depend on it. - -## ============ UPDATE 2026-08-21: FIX — restore Pchip LUT (revert 12094f8) + comb diagnosis ============ -### FIX (commit e5e9700): revert parametric LUT regression. -12094f8 replaced the empirical Pchip LUT (framed_model) with the parametric linear -form (CAP_A_LEVEL=-24/B=28/gamma=1). With gamma=1 that reduces to IDENTITY on -xv=log10(am/res), breaking the whole mask chain: t1kq fc-scan err jumped +7.9 dB. -Re-instated lut_pchip: t1kq mean|err| 0.226 (was 7.9). Confirms NOTES:1060 F1 closure -(no parametric set beats Pchip at fixed bridge params). lut_parametric helper + CAP_ -consts remain in framed_model.hpp as live-capture reference (currently unused). - -### 24-bit metric loader CHECK -render_parity.py:43 already uses correct 24->64-bit sign extension (x>=0x800000 => -x-0x1000000), NOT the buggy OR-0xFF000000. Repo metric harness is correct; an earlier -symptom of ~50 dB phantom ref levels was an artifact of a scratch loader, not the repo. - -### COMB DIAGNOSIS (biggest open gap, mean 10 dB / max 14.7) -comb.wav tones are at ~-25.6 dBFS (500/1000/1500/2000/3000). comb_ref reduce = +0.02 dB -= soothe2 lets ALL tones pass ~unity on this input. Our model over-cuts -7..-15 dB. -=> NOT a multiband-combine problem; it is LEVEL-dependent: at input ~-25.6 dBFS with -band sens 2.24 for two of four bands, real reducer output is ~neutral, ours still cuts. -Likely the bridge LUT/level_scale is calibrated on the al_* fit levels, not on this -low/wide band config. Same class as the known low-level over-cut (al lv18/24, NOTES:964). -Next: calibrate level_scale per input level / verify sens enters the level path correctly. - -## ============ UPDATE 2026-08-21: COMB PARAMS VERIFIED — two SEPARATE phenomena ============ -Decoded true comb wav params from comb_all.rpp (real 4-band config, NOT what tests assumed): - band1 fc=1000 q=1.0 sens=+12 mode 1 - band2 fc=1778.7 q=4.5 sens=-12 mode 1 <-- NEGATIVE sens - band3 fc=195.1 q=0.494 sens=+2.24 mode 0 - band4 fc=13408 q=1.0 sens=+12 mode 3 - depth=0.864, mode=1, selectivity=10, sharpness=10, resolution=1 -Earlier comb tests fed wrong params (band1=678.76, band2/3 sens=+2.24, band4 q3) — those -were NOT the reference config. Even with corrected fc/q/sens the model still over-cuts -7..-15 dB. - -### Two SEPARATE problems (verified): -1. band1-ONLY (fc=1000 q1 s12, input comb.wav -25.6 dBFS): real soothe2 cuts tones ~5.9 dB - @1000 (comb_b1_1000 real ref, NOT neutral); our model cuts -8..-15 (out-real = -2.8..-7.7). - => BASELINE depth is too big at low level (level-scale/LUT low-tail over-cut). -2. comb_ALL (=comb_ref≈comb_byp, reduce ~0 dB on all tones): real soothe2 returns to NEUTRAL - despite band1 active. => MULTIBAND interplay (combine/acc 0x5407c8) must cancel band1's cut, - consistent with M11 additive red=base+ΣW·H and band2 sens=-12 acting as a positive adder. - This is the combine gap (NOTES:1071 F2), and it needs negative-sens + per-band mode handling. - -### Model gaps surfaced: -- negative sens (band2=-12) not modeled -> band2 treated as +suppressor instead of detractor. -- multiband min() (not additive combine) -> cannot reproduce the all-band cancellation to ~0. -- per-band mode 0/3 (peak vs shelf/tilt band types, M10) not dispatched. -No code change; investigation only. These require the structural combine/acc + per-band type -before 4-band comb can close. - -## ============ UPDATE 2026-08-21: multi-band parser fix + combine/sens experiments ============ -### infra FIX: framed_test comma form only parsed argv[3] -> multiband comb tests actually -ran a SINGLE band (first comma-arg ignored the rest). Now parses every comma-arg from -argv[3..]. With correct 4-band params and faithful min(), honest comb = mean 10.15 (over-cut --7..-15 dB). Single-band corpus unchanged (t1kq 0.226, dual 0.726) => parser fix is safe. - -### combine/sens experiments (reverted, doc only) -Tried closing comb via additive dB-sum (red=sum_i red_i) and signed sens-weight -S=sign(sens)*min(1,|sens|/12) applied to C. Neither reproduces comb_ref neutrality (~0 dB): - min() comb mean 10.15 (baseline) - additive comb mean 15.68 (worse: all bands cut, no compensation) - additive+S comb mean 15.68 (neg-sens band2 at fc=1778 doesn't counter band1 @1000) -Real soothe2 comb is neutral DESPITE band1 (fc=1000 sens+12) active. band2 sens=-12 at -fc=1778 has res~1.9 on the 500..3000 tones, so its naive compensation is tiny vs band1's -cut. => Naive additive/sens formulas CANNOT model the negative-sens band interaction; it -lives in the structural combine/acc 0x5407c8 (per-bin weights kRTAtt/kRTRel + negative -sens entering the level path with opposite sign), which requires the real exp2-domain -chain, not the bridge. comb stays OPEN (structural combine/acc, P4/P5). - -## ============ UPDATE 2026-08-21: LUT moved to LEVEL domain (structural chain) ============ -### Discovery: mask-domain LUT clamps on loud input -- Mask-domain LUT (committed 9b7e9d3) computes t=(mask_dB-A)/(B-A); for loud input - (tone1k 0dBFS) post-exp2 mask_dB < A=-13.78 => t clamps to 0 => val=0 => FULL cut. - Measured t1k err -24.04 dB. Root cause: exp2(-level) output dB range is unbounded - below, LUT domain [A,B] assumes bounded input. -- FIX: apply FUN_180563a60 power-law to LEVEL (am/res*scale) BEFORE IIR/exp2: - lvl = pow(t, gamma)*MULT, t=(20*log10(lvl)-A)/(B-A) clamp [0,1]. - Level dB stays inside [A,B] for both quiet (t1kq: +6.2dB) and loud (t1k: +24dB). - Result: t1k err -24.04 -> +2.45 dB. - -### TOOLING HAZARD: stale-binary sweeps -- cmake --build skips recompile when source mtime unchanged within same second; - param sweeps silently re-run the SAME binary (9-point sweep -> 1 unique result). - Earlier "gamma doesn't matter" observation was this artifact, NOT DSP behavior. -- Protocol now: os.utime(src) before build + assert binary mtime fresh (sweep_fresh.py). - -### Fresh 2D sweep (g x mult, verified builds), 3-case smoke (render48k 48k/4096): - g=0.344 m=3.8 optimum: t1kq=-0.788 t1k=+2.453 al12=+0.037 mean|err|=1.093 - (committed version was mean 8.2). gamma>=0.9 ALL worse (+2..+5): spatial IIR - after LUT redistributes peaks; naive ratio math (need g=1.17 from raw t ratio - 1.744->2.0) does NOT survive the IIR chain. Empirics rule. -- MULT=3.8 is a PLACEHOLDER (maps t^gamma back to level domain; no decomp basis yet). - A/B/gamma are decomp-extracted (BandConfig ctx+0x188 refs, NOTES:967). - -### Scope clarification -- scripts/corpus.py exercises the BRIDGE path only (44.1k non-internal grid): - full corpus 1.594 mean / comb 10.15 — UNCHANGED by structural work (verified - stash-compare). comb regression is pre-existing bridge state, separate issue. -- Structural chain validated via render48k smoke (3 cases) + module checks - (fn529fe0/exp2/twin/tables/leveltrack/levelpath/fftconv all PASS). -- render48k tail artifact: last BLK=65536 block zero-pads => am decays in final - frames (~last 0.06s of y48). Cosmetic; metric window barely affected. - -### Commit: 535b150 (LUT level-domain restructure). -NEXT: step 3 wire combine_acc track into mask output (currently computed, -discarded); step 4 FFT-conv 0x535a70 mask smoothing; then decode BandConfig -ctx+0x188 writers to replace MULT placeholder with decomp value. - -## ============ UPDATE 2026-08-21b: IIR3 bidirectional + combine semantics from raw decomp ============ -### IIR3 is BIDIRECTIONAL (corrects NOTES:830 "inline TWICE") -- consumers_out.txt:955-1075: TWO [reset state 0x440510=0, forward pass, backward - pass] pairs. State PERSISTS from forward into backward within a pair; reset - between pairs. Operand order verified: y[i] = x[i]*B3[i] + state*A3[i] - (A3@0x3c0510 multiplies STATE, B3@0x340510 multiplies x). Backward iterates - nbin-2 .. 1 with descending coefficient index. -- Wired into structural chain (commit 1a616e1); LUT_MULT retuned 4.4->4.2; - smoke mean|err| 1.021 (vs 0.993 fwd-only — parity within noise, kept faithful). - -### Combine block EXACT semantics (thunk-level, corrects both 20h and 20j notes) -- 8d60 sub: args (acc=0x5407c8, mask=0x540678, dst=f6f8=0x5406f8), dst=3rd arg: - f6f8[i] = mask[i] - acc[i] (acc NOT overwritten by sub; 20j was wrong) -- mirror f6f8 (11940 x2) -- 3c40 fma: f6f8_upper += kRTAtt*acc_upper ; f6f8_lower += kRTRel*acc_lower - (dst = F6F8 halves, b = acc — 20h had this right) -- 5a20: acc += mask (persistent per-band accumulator) - -### CRITICAL SCOPE FINDING for Step 2 (combine wiring) -- In the ONLINE single-band path (0x5408b8==0) the updated f6f8/acc have NO - consumer inside FUN_180529fe0 before warp/IIR3/dry-wet: warp applies to - band buffer directly, blend overwrites f6f8 next band/frame. Verified by - exhaustive grep of consumers_out.txt (last 0x5406f8 use = offline branch). -- => combine CANNOT close t1k/al single-band tails by itself; its effect must - enter via (a) multiband combiner 0x5316e0 reading acc/f6f8 across bands, or - (b) FFT-conv stage. Step 2 re-scoped: decode 0x5316e0 acc consumption FIRST, - only then wire. Do NOT invent a track->mask feedback (would be empirical). -- Online blend confirmed: f6f8 = freqaxis*(1-mix) + mix*fVar25(0.8) via - 6e40+15060 thunks, then bigkernel exp2 in-place on band (line 805-832). - -### Commits: 9ffe60e (IIR3 fwd-only + retune), 1a616e1 (bidirectional + decode). - -## ============ UPDATE 2026-08-21c: FULL STRUCTURAL CORPUS (first honest numbers) ============ -### Harness: scripts/corpus_structural.py (render48k x all 62 cases, bridge-compare mode) -Structural chain (48k/4096, LUT level-domain g=0.344 m=4.2, IIR3 bidi) vs bridge: - group n bridge struct delta - t1kq 7 0.226 0.768 +0.54 worse - t1k 11 1.801 2.114 +0.31 worse - al 6 0.638 0.986 +0.35 worse - res 11 0.628 0.437 -0.19 BETTER - dual 22 0.726 3.264 +2.54 MUCH worse - comb 5 10.149 6.116 -4.03 MUCH BETTER - TOTAL 62 1.594 2.286 +0.69 worse - -### KEY FINDINGS: -1. **comb: structural ALREADY beats bridge by 4 dB** (6.1 vs 10.1) WITHOUT acc - wiring — per-band structural min() + warp closes part of the multiband gap. - Plan's "combine closes comb" partially pre-empted by the chain itself. -2. **dual_*_500: constant -6.7 dB over-cut for ALL q** (q-independent!). - res[center] is q-independent (0.117) => systematic LEVEL offset at band - center on two-tone input, not shape. dual_*_2000 (off-center bin) BETTER - than bridge (-0.1..-0.4 vs +0.1..+2.3). At same xv, bridge-LUT cut ~-9.3 dB, - our exp2 path cuts ~-20 dB: real reduction SATURATES (~C_max~0.70 per - extracted LUT pts), exp2(-lvl) does not. -3. al level-sweep error is MONOTONE in level (+0.88@lv3 -> -1.82@lv24): our - level->cut curve too steep mid-range; but t1k (loudest) flips back to - under-cut (+1.8) => NON-monotonic overall, no monotone LUT fixes all groups. -4. gamma/MULT sweep (g in {0.15,0.22,0.30} x m in {4.2,5.0,5.8}, fresh builds, - 12-case subset incl. dual/res/comb): ALL points worse than current - g=0.344/m=4.2; gradient points BACK to current optimum. Blind param space - EXHAUSTED around this parametrization. - -### CONCLUSION (Step 1 status): criterion "structural >= bridge on single-band" -NOT met globally (regress on t1kq/t1k/al/dual; better on res). The missing -piece is the REAL reduction curve saturation — needs either: - (a) Step 7 BandConfig ctx+0x188 live capture (A/B/gamma per config), or - (b) re-examination of bigkernel INPUT: maybe level into exp2 carries an - additional transform (log-domain offset / per-band depth scalar - 0x540888 / offline 0x5e80+FUN_180323f20 ops) that saturates the cut. -Do NOT tune MULT further — it is calibrated; the CURVE SHAPE is wrong. - -### FFT-conv priority DOWNGRADED: NOTES:18c shows window 0x540658 tail is -near-flat (0.8->1.0 plateau at N/2>=2048), FIR-construction effect on mask -shape minimal for N=4096. Step 4 stays P3. - -## ============ UPDATE 2026-08-21d: REDUCTION-LAW DISCOVERY + Phase A closure ============ - -### Discovery: effective reduction is AFFINE in dB(level) -RT_DUMP_BIN tracts (frame 100) across al sweep fit: - x_exp2 = -0.6646 - 0.05877 * dB(lvl_raw) (mask ~ 0.8 * lvl^(-0.354)) -gamma_eff 0.354 ~= decomp BandConfig gamma 0.344 (NOTES:967) -> power law, -NOT the double-exp t^gamma->exp2 form (21c). Tone-levels verified +-0.13 dB. - -### Pre-IIR application calibrated through chain model -Python simulator (tables from rt_mask_tables.cpp; IIR1 fwd, IIR2 fwd, exp2*0.8, -W, IIR3 bidi x2): pre-IIR affine peak dilutes ~2.3x by spatial IIRs -> -X0=1.8, SLOPE=0.11 (max|x-resid| 0.09 ~= +-0.5 dB on tones). -FULL CORPUS fresh-build A/B (stale-binary protocol, both reproduce exactly): - group old(HEAD LUT) new(affine pre-IIR) - t1kq 0.768 0.460 v - t1k 2.114 1.196 v - al 0.986 0.132 v (all 6 levels |e|<=0.25) - res 0.437 2.046 x - dual 3.264 4.317 x - comb 6.116 5.718 v - TOTAL 2.286 2.633 - -### Phase A experiments (ALL on fresh builds / validated models) -1. res regression is REAL (not stale binary; reproduced both directions). -2. High-bin/IIR3-backward propagation hypothesis REFUTED: at frame-100 the - two laws give IDENTICAL masks on res_500 (<1e-4 everywhere); divergence - lives in other frames' level zone (-13..+15 dB) where old cuts more. -3. Direct-affine (bypass IIR1/2, raw constants) REFUTED: TOTAL 2.777. - Spatial IIRs are integral to the effective curve. -4. Offline trajectory model (RT_DUMP_ALL per-frame lvl_raw; chain sim; - metric-window frames [55,90)/[243,278) @48k/1024-hop; zero-pad frames - excluded — they are beyond the trimmed output!) VALIDATED: known-law - predictions within 0.09..0.19 dB on res_500/al_24/t1k_1000 (al_12 0.53). -5. Grid search over affine family (X0, SLOPE, floor F>=0, ceiling CMAX), - objective = sum pred_err^2 over anchored cases: - OPTIMUM X0=2.00 S=0.0825 F=0 CMAX=inf -> rms 0.75 dB with - res_500 +0.84 / al12 +0.29 / al24 -1.21 / t1k -0.02. - Ceiling never active in-grid; positive floors do NOT help (top all FL=0). - -### KEY NEGATIVE RESULT -NO law of the scalar affine family fits tone AND noise content -simultaneously (res tension +0.4..+1.1 unavoidable). Old Pchip "wins" on -res/dual/comb via error cancellation, not shape correctness; affine wins -tones decisively (al +-0.13 dB linearity over 42 dB!). The missing piece is -CONTENT-DEPENDENT behavior: detector dynamics (att/rel interplay with -fluctuating spectra), or combine/acc consumer (multiband feedback), or -saturation mechanism. => Gate "no group regression" FAILS for every family -member -> committed canon stays HEAD (LUT g=0.344/m=4.2). - -### Tooling kept (opt-in env, zero cost when unset) -- RT_DUMP_BIN= [RT_DUMP_FRAME=n]: single-frame tract dump (7 cols). -- RT_DUMP_ALL=: append per-frame binary lvl_raw spectra - [i32 frame][i32 nbin][f32 x nbin]. Use ONE env per run (BIN early-return - eats ALL frames if combined!). rm target first (append mode). - Captures live under /tmp/opencode/traj_*.bin (law-independent detector). -- Offline engine: /tmp/opencode/phaseA_grid_fast.py (combo-vectorized chain - sim + grid search); estimator zoo: phaseA_estimator_zoo.py. - -### NEXT (Phase B candidates, priority order) -1. Detector dynamics: att/rel on fluctuating spectra vs ref renders of - noise-like material (res group) — per-frame gain extraction protocol - ready (STFT ratio out/in at metric bin, see phaseA session notes). -2. Step 7 live capture ctx+0x188 (constants + possible content-dependent - branch), NOTES_CAPTURE.md method. -3. combine consumer hunt (multiband cascade) for dual/comb. - -## ============ UPDATE 2026-08-22a: PHASE B RECOVERY — offline detector hypotheses REFUTED ============ - -Контекст: Phase B сессия 2026-08-21 (вечер) + утро 08-22 осталась незакоммиченной — -скрипты в /tmp/opencode, результаты не сохранялись. Все 4 эксперимента перепрогнаны, -stdout зафиксирован (`phaseB_*.out`), скрипты перенесены в `scripts/phaseB_*.py`. -Движок: phaseA_grid_fast.py (валидированный офлайн-тректор, погрешность 0.09..0.19 dB -на анкорах). Анкоры res_500/al_12/al_24/t1k_1000, критерий pred_err ~ 0 на ВСЕХ. - -| # | Гипотеза | Скрипт | Результат | Статус | -|---|----------|--------|-----------|--------| -| 1b | slide_max pooling lvl (w=3..33) | phaseB_pool2.py | w=3: tones −2.7/−2.3/−4.9, rms 3.02 (base 1.04) | **REFUTED** | -| 1c | neighborhood mean/RMS pooling | phaseB_pool3.py | best mean w=3 rms 1.35; res +1.9..+5.1 vs t1k −1.9..−4.4 tradeoff, ни одна точка не закрывает все 4 | **REFUTED** | -| 2 | temporal dynamics (hold b/dbdecay r/ema a на полной траектории) | phaseB_temporal.py | ВСЕ варианты = baseline (rms 1.03–1.05): метрика в steady-state, состояние успевает устояться до окна | **REFUTED (no-op)** | -| 3 | ρ(IIR1)+Δ joint scan (ρ∈[0.30,0.95], Δ∈[−1.5,+1.5]) | phaseB_rho.py | best ρ=0.830 D=+1.40 → rms 0.654; НО al_24 стабильно −1.19..−1.25, ρ без источника в декомпе (канон 0.692) | **REJECTED** (нарушает golden rule #1, тот же scalar-family тупик) | - -Выводы: -1. Пространственный pooling ЛЮБОГО вида (max/mean/rms) не объясняет content-gap. -2. Temporal-класс гипотез НЕПРОВЕРЯЕМ на steady-state анкорах — нужен переходный - контент (burst-рефы уже есть в soothe-bt) или другой анкорный набор. -3. Лучший (ρ,D) = репараметризация affine-семейства Phase A → упирается в тот же - KEY NEGATIVE RESULT (scalar-семейство не закрывает тон+шум одновременно). - Канон НЕ сменён: HEAD (LUT γ=0.344/MULT=4.2, ρ=0.692). - -NEXT (приоритеты без изменений): -1. Step 7 live capture ctx+0x188 (NOTES_CAPTURE.md метод) — константы A/B/γ + - возможная контент-зависимая ветка. Требует REAPER+плагин. -2. combine consumer hunt (межполосный каскад) для dual/comb. -3. Опционально: transient-анкоры для проверяемости temporal-класса. - -## ============ UPDATE 2026-08-22b: STEP 7 EXECUTED → PREMISE REFUTED (BandConfig = GUI-only) ============ - -Инфраструктура захвата готова и работает: -- `scripts/step7_capture.py ` — spawn reaper (+play.lua realtime) → chunked snapshot - хоста yabridge → fingerprint level_gain-пар (level[j]==j/1024 exact) → u64-ref голосование - базы level-path объекта (+0xe0+band*0x18) → decode всех BandConfig вокруг базы. -- `play.lua` ИСПРАВЛЕН: reaper.Sleep НЕ существует в API (падал скрипт, транспорт - продолжал играть без keep-alive) → цикл reaper.defer. -- Грабли: /proc/ vs /proc//mem (pread каталога = тихий EIO на всё); layout - объекта ПЛАВАЕТ между сессиями → оффсеты BandConfig не хардкодить, только fingerprint. - -Живые константы (7 захватов: render_long, t1kq_base, t1k_b1f_1000, al_12, al_24, -dual_b1q_0.5, comb_base — ВСЕ идентичны): -- cfg@obj+0x168: **A=−24.0 B=+28.0 γ=1.0 flag=0 cb=SET** («кривая редукции») -- cfg@obj+0x170: A=16.0 B=20000.0 γ=1.0 cb=SET; shaper f32 @+0x20..: 0.55, 7.130898, - 2.772589(=ln16), 2.718282(=e) -⇒ противоречие gap #3 разрешено: −24/28/1 подтверждено живьём; −13.78/68.29/0.344 - был ФИТОМ к рендер-референсам, не свойством плагина. - -FUN_180563a60 расшифрован ДО КОНЦА (f_563a60.dis, 165 строк): -- вход: mask-doubles obj+0x4198+band*0x2000 (пишет FUN_18056e3e0 из band-list - +0x178→[+0x78/+0x84]); x-axis пар = j*(1/1024) (конста 0x24c3c50); - dB = logf(mask)*8.685889 (=20/ln10, 0x24c43e0); -- выход: gain[j] = clamp01(cb(A,B,dB)) где cb = std::function @cfg+0x90 — В ЖИВЫХ - ЗАХВАТАХ cb ВСЕГДА SET ⇒ статическая ветка A/B/γ НЕ ВЫПОЛНЯЕТСЯ НИКОГДА; -- статическая ветка (cb==NULL, для протокола): t=clamp01((dB−A)/(B−A)); - γ==1 → t; flag==0 → t^γ; flag!=0 → 0.5·(1+sign(2t−1)·|2t−1|^γ). - -Callgraph: весь кластер 5631c0/563260 → 563440 → {56e3e0, 563a60} достижим ТОЛЬКО по -DATA-xrefs (vtables 1826a03cc..420, 1824b14f0..510) — GUI-timer heartbeat. АУДИО-метод -FUN_180529fe0 (Soothe2Module::vtbl) НЕ ссылается на {+0x168,+0x170,+0x178, -+0xe0,+0x198,+0x4198}; его callees — только memcpy-туннели (181ba94b0 ×13, 52dbc0, 52d920). -Файл /tmp/consumers_out.txt восстановлен из decomp_funs.txt (215 строк, вне git!). - -### ВЫВОДЫ (меняют приоритеты) -1. **Шаг 7 BITEXACT_PLAN в исходной постановке ОПРОВЕРГНУТ**: BandConfig A/B/γ питает - только GUI-отрисовку кривой, аудио-путь его не читает. Live-захват A/B/γ не может - закрыть насыщение кривой редукции аудио-цепи. -2. LUT-блок структурной цепи (t^γ·MULT с −13.78/68.29/0.344/MULT=4.2) = EMPIRICAL фит - БЕЗ декомп-источника в аудио-пути (формула списана с GUI-функции!). Помечен EMPIRICAL; - канон не меняем (корпус держит), но источник правды теперь внутри аудио-модуля. -3. Новый приоритет №1: полный разбор аудио-тела FUN_180529fe0 (215 строк декомпа, - /tmp/consumers_out.txt) + combine-consumer hunt — насыщение C_max≈0.70 сидит там. - -## ============ UPDATE 2026-08-22c: PARAM BRIDGE + RENDER-BENCH CALIBRATION ============ - -Инфраструктура управления плагином через ОФИЦИАЛЬНЫЙ параметр-мост REAPER: -- `dump_params.lua` — дамп всех FX-параметров (имя, raw 0..1, formatted UI) в /tmp/opencode/fxparams.txt. -- `setparam.lua` — установка по имени через TrackFX_SetParam + SaveProjectEx (env S2_SET="name=val;...", S2_OUT). -- `scripts/rpp_setparam.py` — редактор XML-стейта в RPP с корректным апдейтом length-полей - (A=buflen−16, B=len(xml), хвост JUCEPrivateData сохраняется). - -### КЛЮЧЕВОЕ ОТКРЫТИЕ: XML `` секция НЕ источник VST3-стейта -Живой дамп: depth raw=0.524 ↔ fmt=0.9; в XML при этом "0.8639736175537109" (=UI-единицы!). -Плагин восстанавливает параметры из БИНАРНОЙ части чанка; XML-список — декоративная -копия для GUI-восстановления. Правки только XML → рассинхрон → плагин откатывается в -дефолт (объясняет «магию» идентичных рендеров −31.76 у любых depth-правок; контроль -same-value rewrite давал байт-в-байт реф ✓). ВНИМАНИЕ к rpp_setparam.py: менять можно, -но аудио это НЕ меняет. - -### Семантика параметров (через мост) -- `depth`: raw 0..1 → UI **±18 линейно** (ui=36·raw−18). Рендеры подтверждают монотонность: - raw=0 → юнити; raw=1.0 (+18) → −37.6 dB @tone. -- `input trim`: raw → **±24 dB**. -- band sens: fmt в dB; stereo mode mid|side; balance 100%|57%. - -### WAV-грабли (живое подтверждение класса багов 71644ff) -REAPER пишет bext(602B)+junk(28B) ПЕРЕД data; наивный парсер (readframes+reshape без -каноничного loader) даёт фантомный широкополосный шум и фальшивый клиппинг -(corr(L,R)≈0 артефакты). Каноничный render_parity.load корректен; санити bypass: -corr(L,R)=1.0000, rms=out=in — РЕНДЕР-ПАЙПЛАЙН ЧИСТЫЙ. - -### Измеренные кривые редукции (t1kq-пресет, тон@1000, Goertzel 0.75s) -- Depth-свип: монотонный, raw .5→−5.5 dB; .75→−21 dB; 1.0→−37.6 dB @tone. -- Input-trim свип (холодный тон −18 dBFS): R_eff растёт 1.4→15.7 dB на всём ±24 — - ПОТОЛКА НЕТ в этом диапазоне (против гипотезы C_max≈0.70 как свойства плагина). -- Горячий тон (+6 dBFS): out@1000 ЗАМИРАЕТ на −26.74 dB при trim ≥+12 (R_eff cap ≈20.7), - плато воспроизведено дважды. -- НО: при одинаковом относительном драйве холодный/горячий дают РАЗНЫЙ gain - (−15.7 vs −20.7 @L≈+6) ⇒ отклик детектора не чистая f(level) — контент/нормализация - (согласуется с KEY NEGATIVE RESULT Phase B). - -### Следствия для приоритета №1 -1. «Насыщение C_max≈0.70» старой модели — артефакт placeholder MULT=4.2/Pchip cap 0.667, - а не свойство плагина в рабочем диапазоне. -2. Реальный потолок проявляется как FLOOR выходного уровня (−26.74 dB @hot) — искать - механизм floor/clamp в level-path декомпе с конкретной целью. -3. Инструмент готов для систематического картирования R(L, sens, depth): скрипты + - мост позволяют прогонять десятки конфигов без ручного GUI. - -## ============ UPDATE 2026-08-22d: SATURATION KNEE MAPPED — HARD FLOOR −20.72 dB ============ - -Hot-тон (+6 dBFS, band1 fc679 q1 sens+12 depth+0.9), gain=out−dry по Goertzel@1000: -| trim | L=in+trim | gain | -|------|-----------|------| -| 0 | −6.02 | −3.39 | -| +6 | −0.02 | **−20.72** | -| +9..+24 | +3..+18 | **−20.72** (pinned, ≥24 dB драйва) | - -1. ЖЁСТКИЙ ПОТОЛОК: gain замирает на −20.72 dB (ratio 0.0921) — не плавное плато, - а колено между L=−6..0 с последующим pin. Форма ⇒ t-clamp модели (t→1 при - dB_lvl≥B=28 → lvl=MULT → фикс. маска) КАЧЕСТВЕННО подтверждена живьём. -2. Колено: между trim 0 и +6. При γ=1/A=−24/B=28 это задаёт офсет - физический уровень → внутренний lvl-dB ≈ 31±3 dB. -3. ЗАГАДКА для следующей сессии: холодный тон при том же относительном драйве - (L=+5.94 через trim+24) даёт gain только −15.74 (НЕ насыщен), горячий при L=+5.98 — - −20.72 (насыщен). Trim ≠ простой pre-gain детектора ИЛИ отклик зависит от - абсолютного контента. Тест-план: (a) спектральный профиль нотча сатурированного - рендера vs форма маски модели; (b) файловый gain вместо trim; (c) алгебра пола: - 0.0921 = blend·exp2(−MULT)·W·kBand768·kWarp·IIR3·convwin @bin85 — все live-таблицы - есть в dsp/, решается относительно MULT офлайн. -4. RT_DUMP_ALL НЕ работает в оффлайн-рендере плагина (это инструментация только - нашего C++; у настоящего бинар её нет). Live-capture траекторий = только память. -5. +0x4198 маски уровня объекта = GUI-кривые (band1 растёт 1.0→3.98 к ВЧ) — ещё раз - не аудио-маски; для floor-hunt использовать алгебру (3c), не этот объект. - -## ============ UPDATE 2026-08-22e: FLOOR ALGEBRA SOLVED — blend·ln10/20 EXACT ============ - -Модельный grid (render48k, live A=−24/B=28/γ=1 через RT_LUT_* env, файловый gain): -| MULT | B | base(−6dB) | p3(−3.1dB) | -|------|---|-----------|-----------| -| 4.2 | 28 | −8.78 | −9.70 | -| 9 | 28 | −16.35 | −18.18 | -| 12 | 28 | −20.49 | −22.77 | -ВЫВОД: t-clamp в транскрибированной цепи НЕ даёт pin (base/p3 расходятся на 2+ dB -при любом MULT/B) ⇒ механизм пола живёт НЕ в LUT-блоке. - -### РЕШЕНИЕ АЛГЕБРАИЧЕСКИ -Измеренный pin −20.72 dB совпадает с точностью 0.0055 dB: - - gain_floor = 20·log10(blend · ln10/20) = 20·log10(0.8 · 0.115129) = −20.7145 - -т.е. маска пола = blend × (ln10/20). Константа ln10/20 = 0.1151292546 @0x1824c3cd4 — -ТА ЖЕ семья что и коэфф. attack/release expf(p·0.11513) (NOTES_CAPTURE 19b). -Audio-референсы константы: **FUN_180529c60**, 18052baa0, 18052bad0, 18052bba0. - -### Ведущий след (f_529c60.dis:94-101) - xmm0 = *(this+0x54087c) - xmm0 *= 30.0 (@0x1824c4478) - xmm0 -= 90.0 (@0x1824c44d0) - xmm0 *= 0.115129 (@0x1824c3cd4) - call expf (181a14cac) - xmm0 *= результат FUN_1804d56b0(band buffer 0x540678[band], r13d=n) -⇒ параметрический dB→linear фактор (30p−90 ∈ [−90,−60] dB) умножается на вычисленное - над полосой значение и идёт в band-буферы. Точная роль (floor/dither/scale) TBD. - -### NEXT -1. Декодировать FUN_1804d56b0 + контекст использования фактора в 180529c60. -2. Проверка предсказания: floor_gain(mix) должен сдвигаться как 20log10(blend′/0.8) - (рендер mix=50% через мост). -3. Порт найденного клампа в структурную цепь → корпус с гейтом vs bridge. - -## ============ UPDATE 2026-08-22f: FLOOR DISCRIMINATION — 3 гипотезы, детекторный кап ============ - -Дискриминационные рендеры (hot-тон, trim+12, через параметр-мост): -| конфиг | gain изм. | mask=10^(g/20) | интерпретация | -|--------|-----------|----------------|----------------| -| mix100 sens12 | −20.72 | **0.8·ln10/20** | базовый пол | -| mix50 sens12 | −5.20 | eff 0.552=out/dry | **dry/wet кроссфейд**: 0.5+0.5·0.0921=0.546→−5.26 ✓ | -| mix100 sens6 | −18.78 | **1.0·ln10/20** | пол сдвинулся ровно на ×ln10/20 | - -ОПРОВЕРГНУТЫ: (а) явный кламп mask≥const (mix50 должен был остаться −20.72); -(б) blend-floor 20log10(blend′·ln10/20) для mix50 (предсказывал −26.74); -(в) naive detector-sat lvl∝sens (предсказывал −11.33 для sens6). - -ЧТО СОШЛОСЬ: -1. Mix = классический кроссфейд out=(1−m)·dry+m·processed; пол в PROCESSED пути. -2. Полы = ТОЧНЫЕ кратные ln10/20: {0.8× @sens12; 1.0× @sens6}. -3. Движение пина с sens: Δx_exp2 = log2(mask/0.8): −3.1188→−2.7996, Δ=−0.3192 - на 6 dB sens ⇒ наклон −0.0532 dB⁻¹ ≈ аффинный закон Phase A (−0.05877!) - ⇒ похоже, что НАСЫЩАЕТСЯ ДЕТЕКТОР (кап его dB_lvl ≈ 36..42 в завис. от sens), - а редукция следует аффинному закону до этого капа. Точные значения ln10/20 - при этом — след пересечения закона с сеткой экспонент, не отдельный кламп. -4. Наша модель на том же материале достигает lvl_max=3.012 ≈ log2(ln10/20)=3.1188 - (Δ3.5%) — почти касание первого пола естественно (RT_DUMP_ALL render48k). - -### NEXT (Шаг 9 продолжение) -1. Локализовать кап детектора: где dB_lvl перестаёт расти (vlog/leveltrack путь, - FUN_18052d650 IIR1 / leveltrack таблицы) — искать ограничитель амплитуды FFT/am. -2. Точная формула капа от sens: снять полы на сетке sens {3,6,9,12} × trim{18,24}. -3. Порт: вместо t-clamp LUT — детекторный кап + аффинный закон Phase A; корпус с гейтом. - -## ============ UPDATE 2026-08-22g: NO-LUT TEST + CLIPPING THEORY REFINED ============ - -### NO-LUT эксперимент (RT_LUT_OFF=1, render48k) -Тракт@bm85 (hot-тон, RT_DUMP_BIN): lvl_raw=3.0117 → post-IIR1/2 band_level=1.0646 -(IIR1 A[85]≈0.65 сильно сглаживает изолированный пик) → prewarp mask=0.3825·W=0.408 -→ итоговый gain модели −7.67 dB (против real −20.72). Убрали LUT — модель всё равно -далека: разрыв теперь ЛОКАЛИЗОВАН в детекторном фронте (am/res/scale и/или IIR-сглаживание), -а не в форме кривой. Модельный lvl_max=3.012 почти касался бы пола 3.1188=log2(20/ln10), -если бы не разводился IIR1 по частоте. - -### Клиппинг-теория: частично опровергнута, но указывает на clamp sidechain -- REAPER рендерит в float: trim-овердрайв кормит плагин честными float >1.0, обрезки на - itemе - -## ============ UPDATE 2026-08-22g: NO-LUT TEST + CLIPPING THEORY REFINED ============ - -### NO-LUT эксперимент (RT_LUT_OFF=1, render48k) -Тракт@bm85 (hot-тон, RT_DUMP_BIN): lvl_raw=3.0117 → post-IIR1/2 band_level=1.0646 -(IIR1 A[85]≈0.65 сильно сглаживает изолированный пик) → prewarp mask=0.3825·W=0.408 -→ итоговый gain модели −7.67 dB (против real −20.72). Убрали LUT — модель всё равно -далека: разрыв теперь ЛОКАЛИЗОВАН в детекторном фронте (am/res/scale и/или IIR-сглаживание), -а не в форме кривой. Модельный lvl_max=3.012 почти касался бы пола 3.1188=log2(20/ln10), -если бы не разводился IIR1 по частоте. - -### Клиппинг-теория: частично опровергнута, но указывает на clamp sidechain -- REAPER рендерит в float: trim-овердрайв кормит плагин честными float больше 1.0, - обрезки на item'е НЕТ. -- Pre-clipped файл (x12dB с клиппингом на диске) @trim0 -> -12.31 dB != -26.74: сравнение - НЕКОРРЕКТНО (искажён DRY-путь файла), но показало, что «клиппинг на входе» сам по себе - не воспроизводит пин. - -### ОБЪЕДИНЁННАЯ ГИПОТЕЗА (лид): clamp копии sidechain на +-1.0 внутри плагина -Факты: -1. Колено hot-тона ровно при переходе эффективного уровня через 0 dBFS (trim 0->+6). -2. h_06 == h_12 == h_24 по метрике — после достижения 0 dBFS драйв не растёт. -3. Полы = точные кратные ln10/20 ({0.8x, 1.0x} для sens 12/6). -4. mix = сухой кроссфейд (-5.26 предсказано / -5.20 измерено). -Открытое: cold trim+24 (+6 eff) дал -15.74 вместо -20.72 => кламп либо мягкий (tanh-like, -разная глубина насыщения), либо детектор реагирует на RMS/форму, а не только пик. - -### NEXT -1. Прямая верификация: live-capture sidechain-буфера плагина во время сатурированного - рендера (step7_capture-style скан памяти, искать плоскую вершину +-1.0). -2. Снять форму клампа: сетка hot-файлов {-9,-6,-3,0 dBFS} x trim {6..24} -> кривая - насыщения mask(eff_level). -3. Порт: sidechain-clamp в render48k (min(max(in,-1),1) на копии для детектора) + - аффинный закон Phase A без LUT -> корпус с гейтом. - -## ============ UPDATE 2026-08-22h: FLOOR LAW MAPPED — sens и depth ============ - -Пол редукции как функция параметров (hot-тон −6 dBFS, trim+12, mix100): -| sens | floor gain | mask | lvl_cap=log2(0.8/mask) | -|------|-----------|--------|---------| -| 3 | −17.74 | 0.12973 | 2.6245 | -| 6 | −18.78 | 0.11502 | **=ln10/20 точно** | -| 9 | −19.78 | 0.10256 | 2.9635 | -| 12 | −20.72 | 0.09208 | 3.1191 | - -### Эмпирический закон - floor_dB(sens) ≈ 20·log10(ln10/20) − (sens − 6)/3 (якорь sens6; ±0.06 dB) -Depth тоже сдвигает пол: depth UI 0 → **−19.10 dB** против base (+0.9) → −20.72 -(Δ≈1.62 dB на 0.9 ед. depth ⇒ ~1.8 дБ/ед., НЕ как sens/3). -lvl_cap(sens) квазилинеен: наклон ≈0.055/oct-per-dB ≈ аффинный наклон Phase A (0.0588). - -### Статус механизма -Пол — гладкая поверхность F(sens, depth); сайт в декомпе не найден (юзеры ln10/20 — -сеттеры att/rel и dither-фактор). Гипотеза детекторного капа остаётся лидом: -lvl_raw детектора перестаёт расти при эффективном уровне ≈0 dBFS (колено), а кривая -редукции следует закону до этого капа. Точная форма F(...) — на картирование. - -### NEXT сессии -1. Декод FUN_18052d650 (IIR1) + leveltrack-путь на предмет ограничителя am. -2. Картирование F(sens,depth) сеткой 4×4 через мост (~30 мин рендеров) → фит. -3. Порт эмпирического пола в структурную цепь (env-параметризованный), корпус с гейтом. - -## ============ UPDATE 2026-08-22i: RT_LVL_CAP ИНЕРТЕН — разрыв в детекторном фронте ============ - -Порт капа пост-IIR уровня (RT_LVL_CAP env в process_band_structural, после IIR2): -корпус с cap=3.119 и 2.798 ДАЛ ТОЧНО БАЗОВЫЕ ЧИСЛА (TOTAL 2.286) — кап не связывается, -т.к. пост-IIR уровни модели максимум ~1.06 (lvl_raw 3.01 разводится IIR1 A[85]≈0.65). -⇒ Модели не ХВАТАЕТ уровня, а не лишний: реальный плагин доводит пост-IIR lvl до 3.12 -(пол), наш детекторный фронт (am/res/scale нормировка + IIR-сглаживание изолированных -пиков) теряет ~x2.8. ПРИОРИТЕТ СДВИНУТ: чинить детекторный фронт (амплитудная -нормировка am, форма res_k, сила IIR1/2 вдоль частоты), а не форму кривой. -RT_LVL_CAP оставлен в коде как инструмент (opt-in, инертен при текущих уровнях). - -## ============ UPDATE 2026-08-22j: WHITE-NOISE PROBE — архитектура расшифрована ============ - -Белошумный пробник через сатурированный конфиг (trim0 на шуме −6 dBFS RMS): - -REAL (плагин): передача ПЛОСКАЯ −0.01…−1.65 dB на ВСЕХ частотах; - лёгкая вмятина −1.04 @678 (центр полосы), HF rolloff −1.5 @8k. -МОДЕЛЬ (canon): фейковый низкочастотный «пол» −24 дБ @50Гц от W=kBand768·kWarp·res^rp. -МОДЕЛЬ (NOWARP): форма совпала — плоско ~−2.1…−2.7, вмятина −4.45 @678. - -### ВЫВОДЫ АРХИТЕКТУРЫ -1. W=warp НЕ является бланкетным множителем выхода: реальный плагин пропускает - широкополосный контент почти на unity ⇒ RT_NOWARP правилен по духу. -2. Реальный плагин режет СТАЦИОНАРНЫЙ ТОН на −20.72, но НЕ режет шум ⇒ редукция - управляется КОНТРАСТОМ бина к соседям/шумовому полу (смысл IIR1/2 вдоль частоты = - бегущий референс), а не абсолютным уровнем. -3. Без blend в выходе уровни ШУМА сходятся: real lvl≈+0.123 vs наш +0.098 (Δ0.02 окт); - уровни ТОНА: real 3.444 vs наш 3.012 (Δ0.43 окт = ×1.35 амплитуда) ⇒ расхождение - растёт с уровнем ⇒ подозрение на res_k (twin АДАПТИРУЕТСЯ к сигналу во времени, - res_k уменьшается при длительном тоне) либо am-нормировку при высоких уровнях. -4. blend(0.8) в нашей модели даёт фантомный −1.94 dB бланкет — в реальном выходе его нет; - exp2(−lvl) без blend точнее описывает шумовой случай. - -### Рабочая гипотеза цепочки (v22j) - lvl_raw[k,t] = am[k,t]/res_k(t)·scale (res_k(t) — адаптивный twin!) - lvl_ref[k,t] = IIR1/IIR2 вдоль ЧАСТОТЫ от lvl_raw (бегущий референс соседей) - редукция = f(lvl_raw − lvl_ref или lvl_raw/lvl_ref) ← контраст - mask = exp2(−редукция) (без blend-бланкета) - sidechain-in clamp ±1.0 (колено/пин при 0 dBFS) - -### NEXT -1. Декод FUN_18052d650/IIR1 семантики заново: аргументы (что есть x, что есть acc), - направление и КАК результат используется (референс vs сигнал). -2. Тест адаптивного twin: рендер тона разной ДЛИТЕЛЬНОСТИ (0.5/2/8 c) через мост — - если пол зависит от длины ⇒ res_k(t) адаптируется, взять динамику из NOTES_TWIN. -3. Формулы контраста перебрать офлайн на траекториях RT_DUMP_ALL (движок phaseA готов). - -## ============ UPDATE 2026-08-22k: BARE-CHAIN АРХИТЕКТУРА ПОДТВЕРЖДЕНА ============ - -Цепочка «голая» exp2-архитектура (RT_LUT_OFF=1 RT_IIR12=0 RT_NOWARP=1 RT_NOBLEND=1 -RT_NOIIR3=1) на белошумном пробнике совпала с реальным плагином в пределах ~0.5 дБ -почти везде: - -| freq | real | model | -|------|------|-------| -| 50 | −0.01 | −0.12 | -| 300 | −0.08 | −0.26 | -| 678 | −1.04 | −2.59 | -| 850 | −0.76 | −0.94 | -| 1000 | −0.74 | −0.62 | -| 2000 | −0.22 | −0.20 | - -⇒ АРХИТЕКТУРА ПОДТВЕРЖДЕНА: - mask[k] = exp2(−lvl_raw[k]) БЕЗ blend-бланкета, БЕЗ warp-аттенюации - lvl_raw[k] = per-bin TIME envelope от am/res·scale - output = conv(audio, FIR(mask)) -Частотно-направленные каскады IIR1/2/3 в аудио-пути ОТСУТСТВУЮТ (они и создавали -фантомное смазывание нотча и бланкет-пол). kIIR_A1==A_ATTACK, kIIR_A2==A_RELEASE — -это время-энвелоп коэффициенты (feed-forward атака, retention релиз), НЕ частотные. - -### Остаточный разрыв ТОНА: −15.13 vs −20.72 (нужен lvl 3.4436, есть 3.0117) -Фактор ×1.1437 в амплитуде. Кандидаты: -(a) am-нормировка (wsum/окно) отличается; -(b) res_k twin АДАПТИРУЕТСЯ во времени (tatt=11ms/trel=80ms из NOTES_TWIN!) — - при длинном тоне res_k уменьшается → lvl растёт; -(c) scale_factor формула (наш 15·440.95/2048=3.231). -Мелочи: модель глубже у центра полосы на шуме (−2.59 vs −1.04 @678); у real -HF-rolloff −1.5@8k (вероятно WIN_freq окно FFT-conv — таблица [01] registry!). - -### NEXT -1. Декомп амплитудной цепочки: wsum/am нормировка (spectral.cpp), масштаб окна. -2. Twin-адаптация: проверить динамику res_k по длине тона (рендеры 0.5/2/8 c). -3. WIN_freq rolloff: добавить окно FFT-conv в модель (registry [01] 0.5→1.0). - -## ============ UPDATE 2026-08-22l: POOL+SCALE+FLOOR — тон точен до 0.02 дБ, корпус 5.9 ============ - -Новая архитектура (все за env-флагами, канон не тронут — санити TOTAL 2.286 ✓): - RT_LUT_OFF=1 RT_IIR12=0 RT_NOWARP=1 RT_NOBLEND=1 RT_NOIIR3=1 - RT_POOL=5 RT_SCALE_M=1.144 RT_FLOOR=1 - lvl_in = pool_max(lvl_raw, ±5 бин) · 1.144; cap = (16.78+sens/3)/6.0174 - mask = exp2(−min(lvl_in,cap)) - -Результаты: -- TONE floor: **−20.74 dB vs real −20.72** (Δ0.02!) при pool∈{4,5}, scale 1.144. -- White noise: mean|err| 0.63 dB (форма плоская ✓), но перерез центра полосы - (−3.54 vs −1.04 @678). -- Корпус 62: TOTAL **5.905** (t1k 60→7.2 благодаря полу; t1kq 5.57 — тихие случаи - теперь перерезаются scale/pool). Пока ХУЖЕ канона 2.286/bridge 1.594. - -### Интерпретация -Архитектурно верно (пол/пин/форма тона), но константы (pool width, scale 1.144, -cap-закон) сняты с ОДНОГО случая (hot tone trim+12) и не обобщаются на тихие. -Масштаб 1.144 требует источника в декомпе (золотое правило) — кандидаты: wsum -(sqrt-Hann vs др.), формула scale_factor, sens внутри twin. - -### NEXT -1. Систематический фит (pool_w, scale, cap-параметры a/b в (a+b·sens)) по МИНИМИЗАЦИИ - corpus TOTAL — офлайн через render48k прогонки (быстро, ~2 мин/конфиг). -2. Источник ×1.144: декоп wsum/am нормировки детектора (spectral.cpp vs decomp). -3. Разобраться с перерезом центра полосы на шуме (pool слишком широкий для шума? - может, pooling по МАКСИМУМУ неверно — попробовать contrast-vs-pool комбинацию). - -## ============ UPDATE 2026-08-22m: ГИПОТЕЗЫ ПОЛЬЗОВАТЕЛЯ ПРОАНАЛИЗИРОВАНЫ (E1/E3/E4/E5) ============ - -Гипотезы пользователя: (1) BandConfig влияет на детектор; (2) a/b = слоты состояний -плагина; (3) gamma = дельта-режим. - -### E1 — delta on/off: γ НЕ дельта -Захват с delta=1.0 vs база: конфиги идентичны (−24/28/γ=1 flag=0 cb=set). γ не кодирует -дельта-режим (тот «пересекает линию» только в отрисовке). - -### E3 — depth ±18: BandConfig стабильна -depth UI −18 и +18 (мост): те же −24/28/1 и 16/20000/1. Вместе с E1 и 7 пресетами — -структуры НЕ реагируют ни на один параметр обработки ⇒ консистентно с ролью -конфигурации отображения кривой (диапазон графика −24..+28 dB / 16..20000 Hz!). - -### E4 — callgraph расширен -FUN_180529610 (SpectralProcessor-метод, 1154B) дизассемблирован: НОЛЬ ссылок на объект -level-path и на ctx-буферы 540678/5406f8/5407c8/540870; callee — мелкие функции -(52e190×5, 52e130, 52df60, 52dc30). Grep всех .dis по оффсетам объекта: совпадения -только в GUI-кластере (563440/563a60) + 0x540678 (ctx-буфер аудио, другой объект). -Оговорка: полный pointer-flow анализ требует Ghidra-сессии; grep по готовым .dis -покрывает ~140 функций. - -### E5 — heap-diff межсессионный: неинформативен -Heap layout плывёт между запусками (базы сдвигаются, соседние аллокации чужие) — -451/512 блоков «различаются» мусором. Для честного diff нужен same-session capture -с переключением входа между снапшотами. - -### Статус гипотез -1. BandConfig→детектор: подтверждений нет (E1/E3/E4), но полная верификация указателей - не проведена (Ghidra data-flow). Приоритет низкий. -2. Слоты A/B: существуют (manual, AorBStateOption), наши структуры им не соответствуют - (идентичны во всех захватах в слоте «a»). Тест со слотом «b» отложен (нужен ручной - клик или ReaScript-команда переключения). -3. γ=delta: ОПРОВЕРГНУТА (E1). - -## ============ UPDATE 2026-08-22n: LIVE CTX НАЙДЕН + МЕДЛЕННАЯ АДАПТАЦИЯ ДЕТЕКТОРА ============ - -### E2-lite (без пользователя): sidechain/L-R/lowcut/eco — конфиги опять идентичны -Итого 9 захватов на широкой поверхности параметров: структуры +0x168/+0x170 статичны -всегда ⇒ окончательно display-config. - -### LIVE CTX НАЙДЕН В ТЕКУЩИХ СНАПШОТАХ (ctx≈0x2370048!) -Анкор: паттерн kIIR_A2[1..3] в куче → ctx = hit − 0x2c04f8. Совпало со старой сессией -(0x2370040±8). Живые скаляры (t1kq-пресет): - int@1a0 = 4096 (=NFFT, не 2048!) - f32@870 = 1.0000 (НЕ 440.95 — то было значение другого пресета render_long!) - f32@878 = 25.0 f32@87c = 10.0 (=UI sharpness/selectivity!) - f32@888 = 0.0 f32@88c = 1200.0 -⇒ Скаляры КОНФИГОЗАВИСИМЫ. Наш хардкод scale_factor=15·440.95/2048 калиброван под -чужой пресет и неверен для t1kq-корпуса! Формула масштаба требует перечтения -mangled-блока декомпа (Ghidra headless re-run) с живыми значениями. - -### МЕДЛЕННАЯ АДАПТАЦИЯ ДЕТЕКТОРА (главное!) -Короткий тон 0.75с (тот же сатурированный конфиг): gain во времени: - [0.02-0.35s]: −10.32 dB [0.35-0.72s]: −10.98 dB -Длинный тон 6с: −20.72 dB (пин). -⇒ Редукция УГЛУБЛЯЕТСЯ СЕКУНДАМИ. Постоянная времени ~секунды соответствует -kRTRel≈0.96..1.0 retention на кадр (hop 1024@48k → τ≈2с)! -Следствия: -1. «Статический» ×1.144 калибровался на несошедшемся состоянии длинного рендера — - делить нужно на динамику, не на константу. -2. Короткие рендеры корпуса меряют НЕнашедшееся состояние → часть ошибок корпуса - может быть фазой адаптации, а не формой кривой! -3. Модель обязана интегрировать медленный релиз через кадры (RT_IIR12=3 каркас есть, - но нужен настоящий покадровый цикл с персистентным env и правильной FRAMES-rate). - -### NEXT -1. Реализовать покадровую персистентность env в render48k (env_time уже static — - проверить, что реально тянется между вызовами process_band_structural). -2. Откалибровать att/rel семантику по переходным кривым (short-tone транзиент - −10.3→−20.7 за ~секунды — идеальный тестовый сигнал для фита kRTAtt/kRTRel). -3. Перечитать mangled scale-блок декомпа с живыми 1a0=4096/870=1.0. - -## ============ UPDATE 2026-08-22o: RT_ENV=live + корпусные прогоны новой архитектуры ============ - -RT_ENV=live (детекторный энвелоп из kRTAtt/kRTRel: атака feed-forward, релиз retention -~tau2s) добавлен в FramedDetector::processFrame за флагом. -- Белый шум: mean|err| улучшился 0.632 → **0.461** ✓ -- Корпус: НЕЙТРАЛЕН (метрика меряет сошедшиеся окна — энвелоп сходится и там). - -Корпусные варианты новой архитектуры (все с RT_LUT_OFF/IIR12=0/NOWARP/NOBLEND/NOIIR3/FLOOR): -| конфиг | TOTAL | примечание | -|--------|-------|------------| -| pool=5 scale=1.144 | 5.905 | тон точен, шум-центр перерезан | -| pool=0 scale=1.144 | **5.386** | t1kq 5.574→3.621, res хуже 1.713 | -| канон (без env) | 2.286 | HEAD, не тронут | - -Вывод: архитектурные находки верны на пробниках (тон-пол точен, шум плоский), но -константы (scale 1.144 снят с одного случая) не обобщаются на корпус ⇒ нужен -систематический joint-фит (pool_w × scale × cap-law) минимизацией TOTAL, плюс -поиск источника масштаба в декомпе (золотое правило). Канон остаётся HEAD до -превышения гейта. - -### NEXT (приоритеты) -1. Joint-фит сеткой: pool_w {0,3,5} x scale {1.05,1.10,1.144,1.20} x cap{on,off} - (~12 прогонов корпуса по ~2 мин). -2. Декомп амплитудной нормировки: wsum/am (spectral.cpp vs детекторный путь), - источник scale (int@1a0=4096, f32@870=1.0 живые!). -3. Транзиентный калибратор: короткий тон некорректен из-за латенси/WOLA коротких - рендеров — заменить на длинный файл со СТУПЕНЬЮ уровня внутри. - -## ============ UPDATE 2026-08-22p: JOINT-FIT ГРИД — оптимум scale 0.90, пол обязателен ============ - -Сетка (scripts/gridfit22p.py): pool×scale×floor по корпусу (62 случая). - -### Результаты -| scale | floor | TOTAL | t1kq | t1k | al | res | dual | comb | -|-------|-------|-------|------|-----|----|----|------|------| -| 0.90 | 1 | **5.215** | 1.961 | 5.801 | 5.328 | 2.663 | 7.774 | **2.707** | -| 0.95 | 1 | 5.242 | 2.255 | 5.980 | 5.541 | 2.432 | 7.699 | 2.811 | -| 1.00 | 1 | 5.261 | 2.547 | 6.155 | 5.704 | 2.201 | 7.621 | 2.915 | -| 1.05 | 1 | 5.298 | 2.892 | 6.279 | 5.842 | 2.008 | 7.588 | 3.019 | -| 1.144 | 1 | 5.386 | 3.621 | 6.456 | 6.123 | 1.713 | 7.542 | 3.213 | -| 0.95 | 0 | 11.021 | — | **22.5** | 10.4 | — | 14.4 | — | -| 1.00 | 0 | 11.396 | — | **23.4** | 11.1 | — | 14.8 | — | - -### Выводы -1. **FLOOR ОБЯЗАТЕЛЕН**: без него громкие случаи уходят в тишину (t1k 60→22 дБ ошибок, - dual ×2). Кап детекторного уровня — реальный механизм плагина, подтверждён корпусом. -2. Оптимум scale≈0.90 (тренд вышел на плато 5.2-5.3); pool=0 лучше (пул перерезает - центр полосы на широкополосном материале). -3. **comb впервые лучше канона** (2.707 vs 6.116) — мультиполосный случай выигрывает от - новой архитектуры. -4. Канон 2.286 всё ещё лучше по TOTAL: однополосные тихие случаи (t1kq/al) в новой - архитектуре перерезаются (scale-закон глобальный, а нужен уровнезависимый). - dual ~7.7 не двигается ни одним параметром — его причина не в уровне/форме кривой. - -### Статус -Канон остаётся HEAD (гейт «не хуже bridge» не пройден: 5.2 > 1.594). Новая архитектура -живёт за env-флагами: RT_LUT_OFF=1 RT_IIR12=0 RT_NOWARP=1 RT_NOBLEND=1 RT_NOIIR3=1 -RT_POOL=0 RT_SCALE_M=0.90 RT_FLOOR=1 RT_ENV=live → TOTAL 5.215. - -### NEXT сессии -1. dual@500: константная ошибка при всех q/уровнях — исследовать отдельно (возможно - межполосный combine-консюмер или второй тон 2000Hz взаимодействует). -2. Уровнезависимость закона: scale-множитель должен зависеть от уровня сигнала - (компрессия в детекторе?) — ступенчатый калибратор внутри файла. -3. comb-выигрыш закрепить: понять, почему мультиполосность выигрывает (перенос в канон?). - -## ============ UPDATE 2026-08-22q: DUAL ДИАГНОСТИКА — насыщающая кривая, не контраст ============ - -Кейс dual q=0.5 (тона 500+2000, оба −7.2 dBFS), trакт@frame150: -| тон | bin | am | res | наш lvl | нужен (real) | -|-----|-----|----|----|---------|--------------| -| 500 (центр) | 43 | 0.321 | **0.117** (res_min) | 8.85 | 1.70 | -| 2000 (вне) | 171 | 0.321 | 0.839 | 1.24 | 1.95 | - -GAIN'ы: real @500=−10.24 @2000=−11.75 (почти РАВНЫ!); newarch @500 упёрся в кап -(−20.4), @2000 недорез (+5.5); canon: @500 перерез −6.9, @2000 точен (−12.4≈−11.75). - -### ВЫВОДЫ -1. Деление am/res(twin) создаёт контраст ×5 между центром и краем — реальный плагин - такого контраста НЕ имеет: режет оба тона поровну при q=0.5. Twin-делитель в роли - полного делителя lvl ОПРОВЕРГНУТ для аудио-выхода. -2. canon согласуется с real на ВНЕполосном тоне (res^rp мягкий вес ✓) — форма - «кривая от уровня + мягкий вес res» ближе к истине, чем «жёсткое деление». -3. Мануал: «до 60 dB нотчей» при выкрученном depth + наш пин −20.7 при перегрузе - и номинальные −10 дБ здесь ⇒ ГИПОТЕЗА: cut_dB = насыщающая функция уровня - (например A·(1−exp(−lvl/T)) или soft-knee), а не линейный exp2(−lvl) с капом. -4. Уровни: real нужен lvl 1.70 там, где наш 8.85 — часть разрыва всё ещё - амплитудная нормировка (int@1a0=4096 и др.). - -### NEXT -1. Собрать пары (наш lvl_raw, real cut_dB) по ВСЕМ корпусным случаям (62×бины тонов) - → фит насыщающей кривой cut=f(lvl) — единый закон вместо подгонки параметров. -2. Проверить форму: линейно-лог / экспоненциальное насыщение / soft-knee. -3. Роль res: сравнить cut@center vs cut@skirt при ОДИНАКОВОМ lvl — отделить вес res - от кривой уровня. - -## ============ UPDATE 2026-08-22r: АФФИННЫЙ ЗАКОН ПОРТИРОВАН — НОВАЯ АРХИТЕКТУРА БЬЁТ КАНОН ============ - -RT_LAWAFFINE="A_db,S_db": mask = exp2(−(A_db + S_db·log2(lvl))/6.0174) поверх голой цепи -(NOTES 22k). Константы с al-семейства (грубо): A=6.14/S=2.17 → **TOTAL 2.152 < канона 2.286!** - -### Грид-фит -| A | S | TOTAL | t1kq | t1k | al | res | dual | comb | -|---|---|-------|------|-----|----|----|------|------| -| 6.14 | 2.17 | 2.152 | 0.426 | 0.889 | 0.090 | 1.795 | 3.953 | 2.678 | -| 6.60 | 2.00 | 2.047 | 0.409 | 0.930 | 0.294 | 1.262 | 3.702 | 3.349 | -| **7.40** | **1.85** | **1.931** | 0.852 | 0.577 | 0.804 | **0.395** | 3.480 | 4.335 | - -Трейд-оффы: A↑ → res ↓↓ но comb ↑; S↓ → t1k ↓ но al ↑. -Против bridge (1.594): новая арх. лучше на t1k (0.58<1.80), al (0.80<0.64~паритет), -comb (4.3<<10.1); хуже на t1kq/res/dual. Против канона (2.286) — лучше по TOTAL и -на 4 группах из 6. - -### Смысл -Аффинный закон Phase-A (21d) пересажен на ПРАВИЛЬНУЮ архитектуру (голый exp2 без -blend/warp/частотных каскадов) — и сразу даёт лучший корпусный результат среди всех -конфигураций проекта. Двух констант достаточно; их источник — следующий вопрос -(золотое правило: сейчас это фит, помечено). - -### NEXT -1. Фит A/S раздельно по группам → понять зависимость от sens/q (сейчас глобальные). -2. dual ~3.5 стабилен — межполосный механизм (приоритет из 22p). -3. WIN_freq HF rolloff не добавлен — после добавления comb может улучшиться ещё. -4. При достижении ≤bridge: гейт пройден, переносить в канон. - -## ============ UPDATE 2026-08-23a (22s): ОФЛАЙН-ФИТ A/S — СКАЛЯРНЫЙ ЗАКОН НАСЫТИЛСЯ ============ - -Инструментарий: `scripts/lawfit22r.py` — полный numpy-реплей render48k/spectral -(ресемпл 44.1<->48 poly, Hann STFT 4096/1024@48k, поточечная маска, WOLA, -FULL-BLK чанкинг = 61 хоп-фрейм на блок 65536 с zero-pad хвоста — именно так -вызывается processBlock в render48k.cpp:137, поэтому кадров 61·nblk, НЕ (L−N)/hop+1). -Детектор независим от закона ⇒ один захват RT_DUMP_ALL траекторий lvl[frame][band][bin] -(`/tmp/opencode/lawfit_traj.npz`, 47 уникальных рендеров) обслуживает ЛЮБОЙ (A,S) офлайн. -Скорость: весь корпус ~2 c/точку (против ~2 мин на реальный рендер). - -### Валидация реплея (A=7.4,S=1.85 против реального corpus 1.931) -sim TOTAL 1.895; группы: al +0.045 / dual +0.013 / res −0.085 — отлично; -t1kq −0.30 / t1k +0.32 / comb −0.64 — СТАТИЧЕСКИЕ смещения (риппл ресемплера -scipy≠libsamplerate вокруг крутых спектральных форм; у al полоса на тоне — плоско, -дельта нулевая). Смещение не зависит от закона ⇒ рейтинг кандидатов корректен. - -### ПОЧЕМУ точечная модель запрещена (важно для будущих сессий) -1. Тон 1000 Гц = бин 85.33 (не целый!) + главный лепесток Hann ±2-3 бина: при крутой - юбке реализованный гейн тона ≠ mask[tone_bin] (res_500: mask[85]=+8.4 дБ БУСТ, - рендер режет −7.2 дБ). -2. Детектор НЕстационарен в метрическом окне: resonant.wav lvl[85@bin] падает - 0.86→0.0002 за файл (медленная адаптация резонатора, 22n) при ПОСТОЯННОМ rms входа. - Медиана по хвосту смешивает 2 декады ⇒ мусор. - -### Метод линий (бисекция A* при S∈{0.5,1.5,2.5} на реплее) -У однотонового рендера одно уравнение cut=A+S·x ⇒ вырожденная ЛИНИЯ в (A,S); -x_i=−dA*/dS — точная эффективная координата кейса (включая транзиенты и лики). -Результаты (`lawfit_lines.json`): -- x_eff ОТОБРАЖАЕТ уровень входа ТОЧНО: al-свитч Δx=−3.48 на Δ21 дБ = −1/6.02 окт/дБ - ⇒ детектор линеен по амплитуде (подтверждение масштабной цепи). -- НО линии семейств НЕ пересекаются: локальные наклоны cut(x): t1kq 0.85 / - t1k 1.54 / res 1.65 / al 2.19. Единого аффинного закона НЕТ даже на чистых тонах - ⇒ кривизна/контент-геометрия, скалярная пара (A,S) принципиально недостаточна. -- Глобальный LSQ по 35 линиям: A=7.396 S=1.973, остаток mean 0.600 дБ, - corr(resid,rms)=+0.71, corr(resid,|log2 fc/1k|)=+0.69 — два недостающих фактора. -- dual: пер-рендерный оптимум даёт floor 0.52-0.62 при q≥1.5 даже своим (A,S) ⇒ - межполосный разрыв НЕ закрывается никакими константами закона (приоритет 22p стоит). -- comb (4 полосы): оптимум mean|e|=1.28, константы нестабильны (угол сетки). - -### Реальные рендеры (валидация кандидатов, гейт --vs-bridge не гонялся — канон не меняем) -| конфиг | t1kq | t1k | al | res | dual | comb | TOTAL | -|--------|------|-----|----|-----|------|------|-------| -| HEAD 7.4,1.85 | 0.852 | 0.577 | 0.804 | 0.395 | 3.480 | 4.335 | **1.931** | -| LSQ-линии 7.4,1.97 | 0.926 | 0.430 | 0.986 | 0.455 | 3.660 | 4.120 | 1.988 | -| спуск 7.6,1.6 | 0.894 | 1.123 | 0.750 | **0.069** | **3.105** | 5.053 | 1.894 | - -Вывод: скалярный закон насыщен в коридоре 1.87-1.93; (7.6,1.6) даёт res 0.069 (!), -но регрессирует t1k/comb — Канон ОСТАЁТСЯ HEAD (7.4,1.85). Теоретический потолок -пер-групповых оптимумов ~0.78 TOTAL недостижим одной парой констант. - -### NEXT -1. Двухфакторный закон cut=A+S·x+T·g(fc-дистанция/контент): корреляции указывают - на геометрию полосы относительно тона; данные линий уже собраны (lawfit_lines.json). -2. Механизм межполосного консюмера для dual (Шаг 2/9, приоритет 22p) — константами - не решается, подтверждено покейсовой бисекцией. -3. Реплей-инфраструктура готова для быстрого перебора ЛЮБЫХ параметрических законов - (2 c корпус) — использовать вместо реальных рендеров до финальной валидации. - -## ============ UPDATE 2026-08-23b (22t): ДВУХФАКТОРНЫЕ ЗАКОНЫ ОТВЕРГНУТЫ — БЮДЖЕТ ОШИБКИ В DUAL ============ - -Инструменты расширены: -- `RT_DUMPRESPATH=` в FramedDetector::setParams — статические спектры twin-response - res[band][bin] ({i32 band,i32 nbin,f32[]}, append); lawfit collect кладёт их в npz рядом с lvl. -- `RT_RESPRP=1` в process_band_structural — применять res^rp при NOWARP (для реальных - рендеров двухфакторного закона). -- `lawfit22r.py fit`: координатный спуск по семействам законов на реплее (2 c/корпус). - -### Результат фит-семейств (офлайн, старт 7.6/1.6) -| семейство | A | S | Q | rp | TOTAL(sim) | -|-----------|---|---|---|----|-----------| -| scalar | 7.600 | 1.694 | 0 | 0 | **1.860** | -| quad (Q·x²) | →scalar | | **→0** | | 1.860 | -| resrp (mask·res^rp) | →scalar | | | **→0** | 1.860 | -| quad+resrp | 7.452 | 1.540 | +0.069 | 0 | 1.865 | - -ОБА вторых фактора отброшены спуском: кривизна не нужна, res-вес не нужен. -Объяснение resrp: lvl = am/res УЖЕ содержит геометрию твин-юбки; домножение на -res^rp сверху — двойной учёт (в каноне res^rp компенсировал другое — отсутствие -res в xv-домене LUT). - -### Реальный рендер скалярного оптимума (7.6, 1.694) -t1kq 0.954 / t1k 0.842 / al 0.799 / res **0.117** / dual **3.246** / comb 4.855 -⇒ TOTAL **1.898** (sim 1.860 + стат. смещения реплея ~0.04). Против HEAD 1.931: -выигрыш мизерный, t1kq/t1k/comb РЕГРЕССИРУЮТ ⇒ гейт не пройден, канон НЕ тронут. - -### Бюджет ошибки — где лежит остаток (решающий факт) -abs-сумма по корпусу при (7.6,1.694): 115.4 дБ·кейсов, из них: -- **dual: 22 кейса × 3.246 = 71.4 (62%!)** — межполосный механизм (Шаг 2/9, консюмер acc/f6f8); -- comb: 24.3 (21%) — мультиполосное взаимодействие; -- всё прочее: ~20 (17%). -Если бы dual был на уровне bridge (0.726), TOTAL упал бы до ~0.96. Никакая -донастройка закона это не даст — ПРИОРИТЕТ №1 подтверждён количественно: -искать точку потребления acc/f6f8 (межполосный каскад), а не крутить маску. - -### NEXT -1. dual/межполосный консюмер: декомп-охота вокруг FUN_180529fe0 callers - (52e260 render-loop, multiband combine), трассировка acc/f6f8 в живом плагине. -2. comb: после dual — пере-тест 4-полосного взаимодействия. -3. Реплей-инфра готова; для любых новых гипотез сначала офлайн (2 c), потом рендер. - -## ============ UPDATE 2026-08-23c (22u): РЕАЛЬНАЯ СТРУКТУРА 529fe0 ПО RAW ASM — ПЕРЕСМОТР ЦЕПИ ============ - -Охота за консюмером acc/f6f8 по всем дизасмам/декомпам. ГЛАВНОЕ: наша транскрипция -цепи расходится с тем, что метод реально делает. - -### Новые декоды (ground truth из handoff/nls_dasm/f529fe0.dis, 505 строк) -1. **FUN_18052d650 — БИДИР-ИРРАЦИОННЫЙ ФИЛЬТР В DOUBLE** (полный декод): - сигнатура (state=rcx, out=rdx/f6f8, in=r8/band, n); acc=[rcx+0x100010]=0 RESET; - forward: acc = upCoef[i]·acc + downCoef[i]·band[i], запись в f6f8[i] - (upCoef double @state+0x80010+i·8, downCoef @state+0x10+i·8 — ПЕР-БИНОВЫЕ массивы); - backward: продолжение с ТЕМ ЖЕ state по nbin−2..1. Это наш «IIR3», но вход band, - выход f6f8, и коэффициенты пер-биновые double, не табличные float. -2. **ВТОРОЙ инлайн бидир-проход в теле 529fe0** (52a65d..52a85b): вход f6f8, выход - ОБРАТНО в band[i]. Итого пара [reset,fwd,bwd]×2 — как в заметках, но точные - адреса/направления теперь залочены. -3. **Межполосное ПРЕ-КОМБИНИРОВАНИЕ ВНУТРИ 529fe0** (52a39a..52a4f6, ДО основного - цикла): f6f8 ← copy bands[0]; цикл по bands[1..n−1] (массив @0x540688 = соседний - элемент того же массива векторов!): поэлементные thunks (float 0x20f0/double - 0x1850 пары = шаблонный transform); нормировка делением на int [rsp+0x148]; - затем второй проход: band[i] *= (xmm11−xmm7), xmm7=pow([ctx+0x2c],xmm13) - (mix-вес!), и f6f8 ⊕= ... Семантика thunks ещё не залочена. -4. Основной пер-полосный цикл: scale (=PRNG·[0x540870]·[0x54088c], флаг 0x5408b8 - добавляет exp-random) → in-place кернел (thunks 0x140980/0x1409b0, вероятно - bigkernel exp2) → 52d650 (bidi#1) → инлайн bidi#2. -5. Пер-полосный массив состояния **0x540768[i]** читается в основном цикле - ([rsp+0x40]) — роль не ясна (кандидат: трек/acc). -6. **FUN_180529c60** (vtbl, параметр = индекс полосы): band←5355d0(param_2); - осцилл-цикл по [ctx+0x1b0] c фильтрацией через f6f8; скалярная нормировка - band *= exp((0.5·K1−K2)·ln10/20) · **FUN_1804d56b0(band)** (агрегат от всей - кривой полосы!); релиз-коэф pow(...) от sr/0x1a0·0x1ac; трек-обновление через - массив **0x5407a8[i]** (52dae0 axpy + 52dbc0 add). Кандидат на «медленную - адаптацию» (22n) и/или постобработку маски. -7. **0x5407c8 НЕ читается НИГДЕ в рантайме** (только ctor/dtor в f_535a70.dis) — - адрес «combine acc» из NOTES 21b был неверен; реальные пер-полосные состояния: - 0x540768[i] (в 529fe0) и 0x5407a8[i] (в 529c60). - -### Офлайн-проверка гипотезы «спектральное сглаживание маски» (lawfit iir_c) -Бидир-однополюсник по бинам (нормированный, ×2 прохода) при законе 7.6/1.694: -c=0.2 → TOTAL 3.23; c=0.6 → 9.83 (монотонная деградация всех групп). ОТВЕРГНУТО: -сглаживание размывает нотч в центре, но физически не дотягивается до тона-конкурента -за ~130 бинов. Двойной cut в dual объясняется НЕ межбиновым сглаживанием маски. - -### Следствие для dual -Реальный плагин режет 500 и 2000 почти поровну при q=0.5. Раз контраст lvl -(res-деление) есть у нас и его нет в результате — значит либо (а) детекторный -уровень считается во ВРЕМЕННОЙ области через тот же twin-фильтр (536300 строит -res-кривые на сетке 0.25 Гц — а детекция может идти банком time-domain фильтров), -либо (б) нормировка агрегатом FUN_1804d56b0 выравнивает кривую, либо (в) закон -не логарифмический по lvl. Приоритет — полный перенос 529fe0 из raw asm (505 строк, -подъёмно) вместо текущей реконструкции из кусков. - -### Инструментарий -lawfit22r.py: +iir_c параметр (бидир-IIR по бинам в реплее), +res в npz; -sim_errors кэширует по (Q,rp,iir_c). - -## ============ UPDATE 2026-08-23d (22w): FAITHFUL-ЦЕПЬ v1 — ИНФРАСТРУКТУРА ГОТОВА, ДЕТЕКТОР ГЛАВЕН ============ - -Инфраструктура (dsp/fnfaith.{hpp,cpp}, env RT_FAITHFUL=1 в render48k): -транскрипция маско-формирующей цепи по BLOCKMAP_529fe0: band=lvl·s → exp2(-band) -→ бидир-IIR ×2 (setA tau19/mult360) → бидир (setB tau10.7/mult2400) → ×2 (setC). -Коэффициенты — точная транскрипция FUN_180533340: частотный варп -fc_bin=(1000/(sr/2))·nbin (=85 @48k/4096!), g=fc/i ниже кросса, степенной выше, -up=exp(-2π·κ·c·g·tau), down=1-up. Константы из дампа: DAT_24c46b8=-2π, -24c3d8c=0.5 (sr→Найквист), mult 1200/800/360, тау-интерполяция 15↔9 и 9↔180. -Все параметры — env (RT_FAITH_*). Combine acc в v1 опущен (нет консюмера, 22u). - -### Смоук-свипы (t1kq1000 / dual500 / dual2000 / al12; err dB) -- κ=0.05,s=8: t1kq −25 / dual500 −58 / **dual2000 −2.84** — тон-конкурент ВПЕРВЫЕ - дотягивается до реала, но центральный тон улетает в перерез. -- κ=0.2..0.5: насыщение (up→0 на низких бинах), баланса пары нет нигде; - Δerr(500−2000) ≈ 15-20 дБ во всех точках (в реале ~1.5). -- Корпус (κ=0.02, s=2): TOTAL 5.315 (t1kq 1.35 / t1k 1.55 / al 0.89 / - res 2.87 / dual 11.52 / comb **2.57** — лучший comb в истории проекта, - базлайн 4.34!). Ресурс мультиполосной механики виден. - -### ВЫВОД (решающий) -Форма маски НЕ может сбалансировать dual: контраст lvl между тонами создаётся -САМИМ ДЕТЕКТОРОМ (am/res из STFT), а не пост-обработкой. Реальный детектор -даёт почти равные уровни драйва на 500 и 2000 Гц при q=0.5 ⇒ гипотеза (а) -из 22u — ПРИОРИТЕТ №1: уровни считаются во ВРЕМЕННОЙ области (банк twin- -фильтров 536300 строит res-кривые на сетке 0.25 Гц — вероятно, и уровни тоже -через них), либо тяжёлый кросс-бин пулинг ДО кривой полосы. -Смена канона НЕТ; faithful-путь остаётся за флагом для следующих экспериментов. - -### NEXT -1. Детектор: декод FUN_180536300 до конца (scratch-лейаут известен, NOTES_TWIN) - → понять, откуда берутся bands[] кривые на входе 529fe0. -2. comb 2.57 при живом faithful — разобраться после детектора. -3. RT_DUMP_BIN-трасса живого плагина на dual-конфиге (покадрово mask/lvl) — - прямой ответ на вопрос «какой lvl реально у тона 2000». - -## ============ UPDATE 2026-08-23e (22x): ТЕОРЕМА О ОТСУТСТВИИ α + ТАБЛИЦА DUAL ПО q ============ - -Инструмент: scripts/resalpha.py — RT_DUMP_BIN-тракты всех уникальных рендеров -(57 пар case/freq: lvl_a1, res, am на бине тона + реальные cut из рефов), -CSV /tmp/opencode/resalpha_pairs.csv. - -### Теорема об отсутствии α -Модель lvl = am/res^α с ЕДИНЫМ законом cut=L(log2 lvl): имплицитные наклоны -семей (концевые точки, дБ/октава lvl) при НИКАКОМ α ∈ [−0.6..1] не совпадают: -- dual требует α≈−0.5 (наклон +0.2..+0.4); -- при α≤0 t1k/t1kq/res получают БЕСКОНЕЧНЫЙ/отрицательный наклон (весь fc-скан - схлопывается в точку по x — res перестаёт различать fc); -- при α>0 dual-пара даёт отрицательный наклон (запрещено для монотонного закона). -⇒ ВЕСЬ класс точечных моделей lvl=f(am, res^α) с общим законом ОТВЕРГНУТ. -Глобальный LSQ по α плоский (1.59-1.63 дБ) — семейные смещения доминируют. - -### Таблица dual по q (реальные cut, одна полоса @500) -| q | res500 | res2000 | cut@500 | cut@2000 | Δ | -|---|--------|---------|---------|----------|---| -| 0.1 | 0.117 | 0.216 | 10.32 | 15.33 | +5.01 | -| 0.5 | 0.117 | 0.839 | 10.32 | 11.82 | +1.50 | -| 1.0 | 0.117 | 1.353 | 10.32 | 10.78 | +0.46 | -| 3.0 | 0.119 | 1.880 | 10.29 | 10.32 | +0.03 | -| 10 | 0.139 | 1.988 | 9.94 | 10.25 | −0.07 | - -Структура: cut@500 КОНСТАНТА по q (драйвер центра не зависит от ширины полосы); -Δcut(res2000) ≈ экспонента 5.4·exp(−res/0.49), схлопывание при res→2. -cut@2000 ПАДАЕТ с ростом res2000 — противоположно любому «lvl растёт с res» -и противоположно делению. Геометрическая интерпретация: широкий нотч (малое q) -НАКРЫВАЕТ тон-конкурент сам по себе; вклад центра всегда полный. Это свойство -ФОРМЫ twin-шаблона на мелкой сетке (0.25 Гц, FUN_180536300), а не пер-биновой -арифметики на сетке 4096. - -### al-группа (якорь уровня, fc=tone=1000) -lvl_a1: 10.08/7.14/5.05/3.58/1.79/0.90 → cut: 13.64/12.44/11.28/10.13/7.97/6.01. -Ломаная: наклон ≈1.16 дБ/окт для lvl≥1.8 и ≈2.06 ниже — кривизна закона -подтверждена независимо (ср. 22s линии). - -### Следствие -Детекторный уровень НЕ восстанавливается из спектра бина функционально. -Кандидаты: (а) скалярная огибающая сигнала через time-domain twin-банк -(536300 строит res-кривые на сетке 0.25 Гц — возможно, тот же банк даёт и -уровни), (б) форма кривой = res-шаблон × скалярный драйв, что естественно -даёт постоянство центра и геометрическое покрытие скайрта. -Решающий эксперимент — живая покадровая трасса детектора на dual-конфиге -(registry-heartbeat инфраструктура готова, NOTES_CAPTURE.md). - -## ============ UPDATE 2026-08-23f (22z): FFT-conv статика + СПРЕДИНГ ОПРОВЕРГНУТ ============ - -### Статика mask→FIR (52b550–52b8bb, per-band цикл построения FIR) -Полная последовательность на полосу: scale-ops на кривой → **call 0x180535a70** -(стаб → ILT → 0x180140a10 float / a70 double → импорт-прыжки → тела 0x1802a24c0 -float / 0x1802fa420 double — векторизованный AVX2, функционально FFT-дизайн) → -копия в r14=[ctx+0x540668] → complex-op с буферами 0x540548/550/598 → -FIR[n/2]=0 → bigkernel in-place → ещё complex-op → **WIN_freq умножает сегмент: -rdx = win_ptr + (n/2)*4** (вторая половина 8193-таблицы!) → масштаб второй половины -xmm9 → **FIR[0]=1.0, FIR[1]=0** → init пар, dry/wet-scale 0x540888. -Размерности: n=[ctx+0x540534], половина rbp=n/2, счётчики n/2+1. -Живой каталог (22y): 0x540758=freqaxis, 0x540658=WIN_freq 0.5→0.8. - -### Спрединг-гипотеза (IDFT(R)→окно L→DFT) — ЧИСЛЕННО ОТВЕРГНУТА -На живой кривой dual R(7f8) (v43=3.98, v171=1.41): окна Hann L∈{32..2048} дают -cut@171 ≤ 2.60 дБ (нужно 11.82); cut@43 падает с 12.0 до 4.4 при малых L. -Сглаживание НЕ может поднять долину до уровня соседнего пика — механика скайрта -dual НЕ в пост-обработке кривой R. - -### Что остаётся (структура данных dual по q, 22x) -Δcut(скайрт−центр) ≈ линейно по −ln res_скайрта с наклоном b≈2.25 дБ/e-fold, -НО центр из этого закона выпадает (его cut константен при res≈const). Это -согласуется с моделью «скалярный драйв через time-domain twin + шаблонная форма», -но требует знать, как именно нормируется шаблон. Прямое декодирование тел -0x1802a24c0 (3КБ AVX2) и complex-op шагов с буферами 0x540548/550/598 — следующий -статический шаг; альтернатива — трассировка промежуточных буферов живьём -(снапшоты в момент аудио-колбека, не между кадрами). - -## ============ UPDATE 2026-08-23g (23b): ДИЗАЙН=LOG(band), Hann-окно, PRNG-дизеринг ============ - -### Что доказано декодом (детали в BLOCKMAP 23b) -1. Полная цепь построения FIR на полосу залочена по raw asm (52b550–52b8bb): - scalar-shape ×2 → **DESIGN: scratch=log(bands[i])** (векторный log2, полином - идентифицирован по константам, тело 0x1802a24c0) → copy→FIR@0x540668 → - 4 комплекс-op (A..D) на буферах 0x540548(SIMD/twiddle-workspace)/0x540598 - (YMM-маски 8×1/8×0×64) вокруг нормировки &FIR[1]÷K и bigkernel → - **FIR[2048..4095] ×= Hann_freq[2048..4095]** (падающая половина периодического - Hann(4096), пик ровно на стыке!) → FIR[0]=1,FIR[1]=0 → dry/wet над 2n флоатами. - Структура log→ops→exp-класс + окно = классический frequency-sampling / - минимально-фазовый дизайн кернела. Гипотеза механизма скайрта: геометрия - задаётся ОКНОМ и комплекс-ops на log-спектре, а не постобработкой R. -2. WIN_freq опознан численно (w[1024]=0.5,w[2048]=1.0) — прежняя трактовка - «0.5→0.8» относилась к другому слоту (0x540698). -3. freqaxis: шаг 11.71303 vs точные 11.71875 — накопительная f32-ошибка построения; - сетка внутренняя 48k/4096 подтверждена независимо. -4. Выход плагина недетерминирован от рендера к рендеру (md5 разные, rms равные) — - LCG-прологи реально дизерят; честная метрика только спектральная. - -### Динамика: почему живые кривые «статичны» -Realtime-playback без аудио-девайса НЕ вызывает processing (все треды спят); -кривые 22y — продукт инициализации проекта. `-renderproject` прожимает весь -цикл за ~0.8 c (init+render+exit); перехват требует SEIZE+TRACECLONE ДО первого -CONT главного треда. FUN_180529fe0/52d650/536300/52e260 не ловятся в рендер-окне -⇒ маск-цепь выполняется при загрузке/param-change, рендер использует кэш. -Следующий динамический шаг (если понадобится): bp на входе process-callback vtbl -или трасса окна init (поймать DESIGN в момент param-change). - -### Инструментарий этого раунда (закommичено) -scripts/ilt_resolve.py (резолв стабов офлайн), scripts/firtrace.py (полный каталог -буферов + GUI-флаг 0x2404bc), scripts/firstop.py (SIGSTOP-семплирование), -fntrace*.py/fnhw.py/fnall.py (ptrace-лаборатория: INT3/DR, уроки — см. BLOCKMAP), -hotips.py. .dis тела: conv_float_a24c0, conv_double_fa420, imp_*, th_* (19 impl). - -### Контрольные числа dual (цель раунда, неизменны) -cut@500=10.32 дБ const ∀q; @2000=11.82 @q=0.5; Δcut≈2.25·(−ln res). -Модель сейчас: −0.18/@500-ок + провал скайрта. - -### NEXT (пересмотр после декода) -1. numpy-реплика цепи log→opsA-D→window с КАЛИБРОВКОЙ ops по измеренным H(f) - (корпус референсов уже есть); критерий — воспроизведение Δcut(res) геометрии. -2. Если ops A-D ≈ exp-раундтрип со сглаживанием: транскрипция за RT_FIRCONV, - гейт corpus --vs-bridge + контрольные числа dual. -3. ptrace-эскалация ТОЛЬКО если численная репликация не сойдётся: цель — - брейкпоинт в момент param-change (init-окно хоста), а не рендер. - -### 23c (короткое): феасибилити log-свёртки — ОТРИЦАТЕЛЬНЫЙ результат -На живой кривой R@0x5407f8 (dual q=0.5): подбор ОПТИМАЛЬНОЙ симметричной -гаусс/бокс-свёртки в log-домене (ширина до 1500 бинов, свободный аффинный сдвиг, -точное совпадение @bin43=10.32 дБ) даёт минимум ошибки на bin171 ≈ 0.94–1.10 дБ -при ширине ~314–324 бина (~3.7 кГц) — такая ширина уничтожает избирательность -нотча на всём остальном корпусе. Вывод: механизмы класса «свёртка log-маски с -симметричным ядром» геометрию dual НЕ воспроизводят (расширяет теорему 22x с -поточечных законов на симметрично-свёрточные). Остаются: (а) вход bands[i] у -DESIGN ≠ захваченные кривые (768/788/7f8 — возможно, GUI/init-версии); -(б) асимметричные/структурные ops (cepstral-liftering класс); (в) темпоральный -кросс-фрейм механизм. Решающий недостающий факт — ИСТИННЫЙ массив bands[0] в -момент вызова DESIGN; рецепт добычи: seize главного треда yabridge-host в момент -ПОЯВЛЕНИЯ pid (ppid=reaper, comm ещё 'wine'), INT3 на 0x1802a24c0/0x1802fa420, -CONT, дамп rcx/rdx на хите (скрипт fnall.py готов, нужен только ранний захват — -нынешний finder по maps опаздывает на init-бурст <5 мс). - -## ============ UPDATE 2026-08-23h (23d): ПРОРЫВ — ЖИВОЙ КЕРНЕЛ СНЯТ, bands[] = СПЕКТР СИГНАЛА ============ - -Маршрут A удался БЕЗ ptrace и БЕЗ аудио-девайса: **SIGSTOP-семплирование -офлайн-рендера** (`-renderproject`; обработка back-to-back ⇒ случайные остановки -попадают внутрь конвейера). scripts/rendersnap.py: find-loop ctx (vtable-маркер -0x1824AC210 / float 48000+ sens; ctx=0x2370040 стабилен и в рендере), -затем STOP→читаем все слоты→CONT c детекцией изменений. 24 фазы за прогон. - -### Решающие факты из phase-снапов (dual q=0.5) -1. **bands[0] @ctx+0x540678 = АМПЛИТУДНЫЙ СПЕКТР ТЕКУЩЕГО ФРЕЙМА**: - ненулевые группы ТОЛЬКО вокруг тонов (бины 41–45 и 168–173, Hann-лепестки; - band[43]=7.65, band[171]=9.91), между ними нули. Это вход DESIGN, НЕ шаблон - маски и НЕ пост-детекторная кривая! -2. scratch@540628 ≈ log(bands)·k (после ops значения −0.66@43, −0.76@171). -3. Живой FIR@668 (комплексные пары): mag(43)=0.524 (−5.61 дБ), mag(171)=0.473 - (**−6.51 дБ**) — скайрт режется ГЛУБЖЕ центра, КАК В РЕАЛЕ (+1.5 дБ), - хотя кривая R@7f8 утверждает обратное (R171=1.410), а wine-треды дают ENOSPC ⇒ - **wine резервирует аппаратные BP-слоты** под эмуляцию Windows-DR. Это же - объясняет глобальную тишину ptrace-DR: ядро хранит значения, но в кремний - их программировать нечем. Маршрут аппаратных ловушек внутри wine ЗАКРЫТ - окончательно (23h–24a). Скрипт scripts/perfbp.py готов для нативных целей. -2. Опознан объект conv-движка: **ctx+0x540530**, инлайн-конфиг {2,4096}, - {16384,8192}, {2,257}; члены-векторы +0xb8..+0x130; методы FUN_18052dc30 - (config), FUN_18052fe00 (resize), FUN_18052dd30 (dtor). Его process-метод — - следующий кандидат на «потребителя кернела» (ищется рядом по декомпу/вызовам - с arg=ctx+0x540530 из аудио-клея). -3. INT3-маршрут остаётся единственным динамическим (слоты не нужны): требует - дисциплины fnwatch4 (INTERRUPT→wait→arm→CONT всем тредам) + контроля - render_fresh в каждом прогоне (флак ~50%). -### NEXT -1. Найти process-метод движка 0x540530: перечислить функции, получающие - ctx+0x540530 (grep decomp), восстановить vtbl объекта из дампа памяти - ([ctx+0x540530]-объект начинается с указателя? проверить офлайн из s1.bin), - декодировать метод → источник ×1.805. -2. INT3-ловушка на найденный метод + на design-тела в param-окно (рендер- - свежесть обязательна в логе). -3. RT_FIRCONV=1 после декода масштаба; контрольные числа dual; гейт корпуса. - -### 24b (короткое): vtable модуля полная; процесс-метод ещё не опознан -vtbl @0x1824ac210: +0=535ed0,+1=52cfb0,+2=529550,+4=52b940(→rebuild), -+5=529c60(осцилл),+6=529fe0(маск-цепь),+8=52bbf0(getter blocksize), -+12=52bba0,+13=52bb80; сеттеры параметров 52b980-baa0 (52ba20 пишет флаг -0x5408b8 и зовёт полный rebuild 52e9b0 ✓; 52baa0 пишет 0x54088c=expf(x·K)). -Объект conv-движка (ctx+0x540530) невиртуальный: методы прямые -{dc30 cfg, fe00 resize, dd30 dtor}; его process-метод = прямой вызов из -аудио-клея — искать среди функций, вызывающих dc30/fe00 (их CALLER'Ы) и в -диапазоне 52d650..532xxx. Следующий раунд: декод этого клеевого метода даст -потребителя кернела и ×1.805. - -## ============ UPDATE 2026-08-24a (24c): ЖИВАЯ ОБРАБОТКА ПОДТВЕРЖДЕНА; «BUILD-ONCE» ОТЗЫВАЕТСЯ ============ -Чистый эксперимент без трассировки: файл seq_in.wav = [A: 500+2000Гц | B: только -2000Гц] по 2 с, полоса @500 q=0.5, рендер до естественного завершения. -Результат (выравнивание по LOOP=1, выход 6 c = A+B+A): - half A: cut@2000 = 21.91 дБ half B: cut@2000 = 25.74 дБ - (поведение @500 меняется кардинально: контент-зависимость живьём) -⇒ ПЛАГИН ОБРАБАТЫВАЕТ АУДИО В РЕНДЕРЕ ПО-РАЗНОМУ ДЛЯ РАЗНОГО КОНТЕНТА ⇒ -маск-цепь выполняется по-кадрово. Вывод 23k «build-once» ОТЗЫВАЕТСЯ — он -опирался на exec-bp, которые не доставляются под wine (см. 24a: wine держит -слоты HW BP; ptrace-DR в кремний не программируются). -ПОПУТНО: (а) рендер-выход длиннее входа из-за LOOP=1 в проекте (6 c = A+B+A); -(б) убивать reaper во время рендера нельзя — выходной файл остаётся частичным -с предварительной шапкой (источник «флака» свежести); (в) выход 16-bit stereo. -### Методологический итог для следующих раундов -Динамические ловушки внутри wine сейчас НЕ работают ни в каком виде (HW занят -wine, INT3-прогоны не контролировались на свежесть рендера). Единственный -работающий канал наблюдения — rendersnap (STOP-семплирование), но он снимает -между колбеками офлайн-рендера... что при back-to-back обработке ЕСТЬ момент -обработки — противоречие с молчанием ловушек требует пересмотра: возможно, -в wine-хосте ДВА экземпляра модуля (GUI/DSP), а vtable-кандидат у нас один; -multictx сканировал только rw-регионы с маркером vtable — второй экземпляр мог -иметь другой адрес vtbl-указателя (heap-копия класса?). Проверка: полный скан -всех экземпляров по сигнатуре sens>100 БЕЗ привязки к vtable (float 48000 + -sens + 0x540870-паттерн уже покрывал; расширить на поиск по содержимому -R-кривой в куче). - -### 24d (стоп раунда): двухэкземплярная гипотеза не подтверждена/не опровергнута -Сканер всех кандидатов (scan2.py в /tmp/opencode, не закоммичен из-за бага: -после починки finder'а — исключён reaper по cmdline — 25 STOP-сканов подряд -на правильном wine-хосте не находят ДАЖЕ известный ctx 0x2370040 с vtable- -маркером, хотя идентичный код rendersnap.py находит стабильно). Расхождение -не отлажено (контекст исчерпан). Состояние задачи «потребитель кернела»: -- живая обработка доказана (24c); -- HW-ловушки в wine невозможны (wine держит слоты, 24a); -- INT3 не проверялся при ГАРАНТИРОВАННО свежем рендере; -- статически: все disp32-ссылки учтены, conv-движок = объект ctx+0x540530 - (методы dc30/fe00/dd30 прямые), его process-метод среди caller'ов в - 52d650..532xxx — НЕ НАЙДЕН декомпом (grep 0x540530 даёт только init/dtor). -### Рекомендация следующему раунду -1. Отладить scan2.py (почему пусто: сравнить с rendersnap пошагово на одном - запуске — вероятно, порядок STOP→scan ломается на первом STOP до маппинга - DLL, а CONT после каждого скана пропускает окно создания экземпляра). -2. Либо полный декод аудио-клея: найти функцию, вызывающую vtbl слот +6 - (529fe0) у СОСЕДНЕГО экземпляра, или decode 52e9b0-вызовителя (14191 в - decomp_funs2 — это 52ba20 setter); glue где-то в 52exx–52fxx. -3. После опознания потребителя — ×1.805 и RT_FIRCONV. - -## ============ UPDATE 2026-08-24e (24e): КОНСЮМЕР КЕРНЕЛА ОПОЗНАН — th_b3c0 COMPLEX-MULTIPLY ============ - -### Решающий факт: th_b3c0 = чистый complex multiply, без нормировки -ILT-резолв: 0x1df0 (float) → 0x18000b3c0 (th_b3c0), 0x1f70 (double) → 0x18000e360. -Тело th_b3c0 (первые 384 байта): SIMD complex multiply через vmulps/vfmaddsub213ps. -Нет divss/divpd/divsd — функция ЧИСТО умножает спектры: out[i] = FIR[i] × audio[i]. - -### Конвейер кернела (полный, из asm 52b550–52b8bb + BLOCKMAP 23b) -1. FIR构建: bands[i] → scratch=log(bands) → complex-ops A-D на буферах 0x540548/550/598 → - копия в FIR@0x540668 → **×= Hann_freq[2048..4095]** (падающая половина) → FIR[0]=1,FIR[1]=0 -2. **×0x540888** (attack/release coeff): строка 52b832–52b85e, xmm6=[ctx+0x540888], - fill FIR[0..2n-1] ×= xmm6 через 0x2030/0x1d30 -3. **Конвейер-вызов**: th_b3c0(FIR, audio[param_2], nBins, out[param_2]) - — ЧИСТЫЙ complex multiply в freq-domain, in-place в буфере полосы. - → out[band] = FIR × audio (0x540668 умножается на аудио-спектр полосы) - -### Источник ×1.805 -Конвейер НЕ содержит нормировки (th_b3c0 = чистый mul). ×1.805 = комбинация: - (a) 0x540888 (attack/release coeff) — домножает ВЕСЬ FIR-спектр scalarem - (b) Масштаб Hann-окна при FFT-свёртке (N=4096, window energy) - (c) Неопределённый коэффициент FFT/IFFT нормировки (стандартный 1/N или N) - Точный баланс требует live-измерения 0x540888 при фиксированном кернеле. - -### Ключевое следствие для архитектуры -FIR применяется как frequency-domain mask (поточечное complex-umножение в спектре), -НЕ как time-domain convolution. Это ПОДТВЕРЖДЕНИЕ NOTES 23e "применение = свёртка × -масштаб 1.805": хост берёт аудио-спектр полосы и умножает на FIR-маску bin-by-bin. -conv-движок (0x540530) — инфраструктура FFT (FFT-план, буферы); его process-метод -НЕ НУЖЕН для консюмера (весь путь — внутри FUN_180529fe0). - -### scan3.py (scripts/scan3.py) -Фикс scan2.py: ports rendersnap.py find-loop + multi-instance detection + full state -чтение. Ключевое отличие от scan2: scan2 НЕ использовал rendersnap-style find-loop -(find-loop с CONT после каждого STOP-скана). - -### NEXT -1. Измерить 0x540888 live при dual_b1q_0.5 через rendersnap → точный баланс ×1.805 -2. RT_FIRCONV=1: транскрипция th_b3c0 как frequency-domain mask multiply -3. Контрольные числа dual по q (q-серия23f + FFT-conv) → гейт - -## ============ UPDATE 2026-08-24f: RT_FIRCONV=1 ПЕРВЫЙ РЕЗУЛЬТАТ — ЧАСТИЧНО РАБОТАЕТ ============ - -### Реализация (dsp/spectral.cpp) -RT_FIRCONV=1: FIR = mask (freq-domain), complex multiply FIR × audio (th_b3c0 equivalent). -DC passthrough (FIR[0]=1.0).Upper half zeroed. - -### Результаты на dual_b1q_0.5 (single band, fc=500, q=0.5, sens=12): -| конфиг | cut@500 | cut@2000 | -|--------|---------|----------| -| Default (scalar mul) | -14.35 | -6.58 | -| RT_FIRCONV=1 (complex mul) | -20.37 | -12.60 | -| Reference (plugin) | -10.32 | -11.82 | - -### Анализ -1. RT_FIRCONV перерезает на 500 Гц (-20.37 vs -10.32) но БЛИЖЕ на 2000 Гц (-12.60 vs -11.82) -2. Default недорезает на 2000 Гц (-6.58 vs -11.82) но перерезает на 500 (-14.35 vs -10.32) -3. Ни один не совпадает с референсом → маска-цепь в detector неполна -4. FIR window (периодический Hann 4096, rising half 0→1) определена из live-капч - -### Ключевой вопрос: почему оба перерезают на 500? -Маска на бине 43 (500 Гц) при structural chain должна быть ~0.31 (= -10.32 dB), -но реальный structural chain даёт -14.35 dB → маска ~0.19. Это указывает на -неправильный scale_factor или LUT-кривую в detector. ×1.805 НЕ применяется -(он =.effects窗外 + conv-normalization, НЕ scalar в freq-domain). - -### Дальнейшие шаги -1. Калибровка scale_factor/level_path чтобы маска на 500 Гц ≈ 0.31 -2. Проверка RT_FIRCONV на t1kq/al/res (не только dual) -3. Корпусный прогон RT_FIRCONV vs default → определить группу, где FIRCONV лучше - -## ============ UPDATE 2026-08-24g: КОРНЕВАЯ ПРИЧИНА РАЗРЫВА — FFT НОРМАЛИЗАЦИЯ ============ - -### Факт -Plugin применяет FIR через time-domain convolution (FFT→multiply→IFFT, без 1/N нормировки). -Наш FFT делит на N при IFFT. Это создаёт ×N разницу в gain. - -### Живые измерения (dual_b1q_0.5, bin 43 = 500 Hz): -- raw_level@43 = 8.86 (am/res × scale_factor) -- mask@43 = 0.510 (per-band spectrum, 0x540678) -- FIR@43 = 0.524 (FIR magnitude, after construction) -- Final cut = 10.32 dB → final gain = 0.305 - -### Наша цепь: -- raw^0.984 = 8.58 (GAIN > 1, boost, not cut!) -- exp2(-LUT(lvl)) = 0.12 (cut 18.4 dB, too much) - -### Анализ -Plugin's FIR construction: FIR = raw^0.984 × hann × 0x540888 -Plugin's application: FIR × audio via complex multiply (th_b3c0, NO 1/N) -Our application: mask × audio via complex multiply (WITH 1/N from IFFT) - -Разница: plugin's FFT output has magnitude ×N vs our IFFT-normalized output. -Это объясняет为什么 raw^0.984 > 1 (boost) в нашей цепи, но cut в плагине. - -### Решение -Нужно убрать 1/N нормировку из IFFT в我们的 FFT-conv, или добавить -калибровочный множитель N в FIR construction. Это ТОЧКА ОСТАНОВКИ -для текущей сессии — требует изменения dsp/fft.cpp IFFT implementation. - -## ============ UPDATE 2026-08-24h: КОРНЕВАЯ ПРИЧИНА — IIR STATE + MASK COMPUTATION ============ - -### Новые данные (RT_DUMP_BIN frame 100, dual_b1q_0.5): -| bin | am | res | lvl_raw | band_level | mask | -|-----|-----|-----|---------|------------|------| -| 43 | 0.099 | 0.117 | 2.725 | 1.934 | 0.209 | -| 171 | 0.321 | 0.839 | 1.240 | ? | ? | - -### Live plugin (scan3 captures): -- mask@43 = 0.510 (0x540678 buffer, per-band spectrum) -- FIR@43 = 0.524 (FIR magnitude) -- Final cut = 10.32 dB → final gain = 0.305 - -### Анализ разрыва -1. **Наш mask = 0.209, плагин mask = 0.510** → ×2.44 разница -2. Плагин's mask = IIR-smoothed per-band spectrum (0x540678) -3. Наш mask = exp2(-LUT(lvl)) — ДРУГОЙ домен (reduction, не spectrum) -4. LUT отвечает за нелинейное преобразование level→gain -5. Плагин's LUT (A=-24, B=28, γ=1) даёт ДРУГОЙ результат чем наш LUT (A=-13.78, B=68.29, γ=0.344) - -### Ключевой факт -Наш LUT (A=-13.78, B=68.29, γ=0.344) — эмпирический фит из NOTES:967. -Плагин's LUT (A=-24, B=28, γ=1) — live-captured в 22b, но помечен как GUI-only. - -### Вопрос -Плагин ЧИТАЕТ ли свой LUT в аудио-пути? Если да — нужно заменить наш LUT на плагин's. -Если нет — LUT не нужен, mask = raw_level (am/res × scale). - -### Следующий шаг -Проверить: если использовать raw_level (без LUT/exp2) как mask, какой cut получится? - -## ============ UPDATE 2026-08-24i: ×1.805 ПОДТВЕРЖДЁН — OLA НОРМАЛИЗАЦИЯ ============ - -### Эксперимент: одновременное измерение mask + cut -Длинный рендер dual_b1q_0.5, scan3 захват 60 фаз, cut измерен по тому же выходу. - -### Результаты (steady-state, 60 фаз — mask КОНСТАНТЕН): -- mask@43 = 0.5099 (постоянен с 1-й фазы — IIR УЖЕ сошёлся) -- fir@43 = 0.5241 (постоянен) -- actual cut = -10.32 dB → final gain = 0.305 - -### Подтверждение ×1.805: -| Измерение | Значение | Cut (dB) | -|-----------|----------|----------| -| mask@43 | 0.5099 | -5.85 | -| fir@43 | 0.5241 | -5.61 | -| actual | 0.305 | -10.32 | -| ratio actual/fir | 1.839 | **×1.805** | - -### Вывод -×1.805 = OLA normalization factor. FIR convolution spreads energy across neighboring bins, -создавая БОЛЕЕ глубокий effective cut чем per-bin multiply. Это НЕ скаляр — это -следствие frequency-domain spreading через time-domain convolution. - -### Два независимых источника ошибки (ИСПРАВЛЯТЬ ОБА): -1. **Mask computation**: наша цепь 0.209 vs плагин 0.510 (×2.44, LUT issue) -2. **Application**: per-bin multiply vs FIR convolution (×1.805, OLA) - -### Следующие шаги -1. Исправить mask computation (заменить LUT на raw_level или калибровать) -2. Реализовать FIR convolution (OLA) вместо per-bin multiply -3. Оба исправления требуются для bit-exact - -## ============ UPDATE 2026-08-24j (24j): ПРИМЕНЁННЫЙ ОТКЛИК = ЖИВОЙ FIR ПОТЕНЦИАЛЬНО; ×1.805 = ФАНТОМ STALE-КЕРНЕЛА ============ - -### Метод (новый канал, скрипты rendersnap2.py + генератор зондов) -1. МЯГКИЙ снаппер `scripts/rendersnap2.py`: НЕ убивает reaper (частичный-wav - hazard 24c обойдён — рендер доестественного выхода), дополнительно дампит - банк скаляров ctx+0x540860..0x5408c0 каждый фазой. -2. Мультитон-проект: клоны dual_b1q_0.5.rpp с входом «2 главных тона 500+2000 - @0.4248 (= амплитуды dual.wav) + 20 зондов −30 дБ на сетке 100..7200 Гц». - Детектор в том же адаптивном состоянии, H измерима Goertzel'ем на всех - зондах. Генерация inline (/tmp/opencode/multi_in.wav + multi.rpp). -3. Свип НЕ годится для Y/X-отношения (энергия на гребне) — только мультитон. - -### Результат multi_b1q_0.5 (живой FIR ph069: m@43=0.320, m@171=0.267) -| f, Гц | cut дБ | gain | m(FIR) | gain/m | -|-------|--------|------|--------|--------| -| 500 | 10.32 | 0.3048 | 0.3234 | 0.943 | -| 2000 | 11.84 | 0.2558 | 0.2698 | 0.948 | -| 700..7200 (18 зондов) | 1.9–4.6 | — | — | **0.98–1.00** | -(450/550 аномальны — утечка боковых лепестков окна Гёрцеля от главного -тона 500; не в счёт.) Контрольные числа воспроизведены: 10.32/11.84 ✓✓. - -### ВЫВОДЫ (три, все жёсткие) -1. **Применённый отклик = mag(живого FIR@540668) побиновно** (±2%; главные - тона −0.5 дБ — недозахват глубины между фазами). НИКАКОЙ дополнительной - свёртки/OLA/×1.805 поверх per-bin multiply НЕ СУЩЕСТВУЕТ. -2. **«Свёртка ×1.805» (23e) и «OLA-нормализация» (24i) ОТЗЫВАЮТСЯ**: источник — - сравнение cut живого рендера с init-time кернелом. Старые phase*.npz из - rendersnap-сессий держат m@43=0.588 СЕКУНДАМИ — это ядро после param-rebuild - при ЗАГРУЗКЕ (52e9b0 строит FIR из init-state bands[]); живой рендер - перестраивает FIR КАЖДЫЙ кадр и мгновенно потребляет → STOP-семпл ловит - identity/reset (все 110 фаз sweep-рендера ≈ 1.0). 0.305/0.32≈0.95, - 0.524(init)/0.32(live)=1.64~«1.805» — вот вся «константа». -3. **R-слоты 0x5407f8/0x540788 — СТАТИКА**: r43=3.9807/r43=4.0285 ИДЕНТИЧНЫ во - всех трёх проектах (dual/sweep/multi, разный контент). Это twin-шаблон - полосы (геометрия), НЕ динамическая маска. Интерпретация 22y «R=1/mask - живая кривая редукции» — снята (кривая была от инициализации). -4. **Скаляр [ctx+0x540888]=1.000000** весь рендер (и s874/s87c/s88c=1.0; - s870=440.955=sens-scale). Wet-скаляр BLOCKMAP 23b НЕ вносит масштаба при - дефолтных параметрах. ×N-нормировка FFT (24g) тоже не подтверждается: - применённый gain совпадает с mag(FIR) без поправок. - -### Что это меняет в архитектуре транскрипции -- RT_FIRCONV как «особое применение» НЕ нужен: достаточно per-bin multiply - спектра кадра на маску-кривую (как default-путь spectral.cpp). -- ВСЕ усилия — в ФОРМУ кривой: bands[i] → (log→ops A-D→exp0.984→hannfall→ops) - → FIR. Наш structural chain должен выдать кривую, чья mag совпадёт с живым - FIR фазы. Эталонные кривые теперь ЕСТЬ: rendersnap2/ph*.npz (fir_re/fir_im). - -### NEXT (24k+) -1. Дискретизация формулы: снять ph-серии с ОДИНАКОВЫм статичным контентом при - вариации одного параметра (q-серия уже есть рефами; прогнать rendersnap2 по - dual_b1q_0.1..10.0) → восстановить зависимость m(fir) от параметров. -2. Сравнить живой FIR с нашим structural mask: где именно расходится форма - (хвост? ширина? пол?) — починка detector-цепи по живым эталонам. -3. Проверить hannfall-мультипликацию: fit exp(0.984·log(bands))·w против - захваченного fir_im≠0 (фаза!) — complex ops A-D дают мнимость. -4. Починить wav-path баг rendersnap2 (проверяет rpp.wav вместо RENDER_FILE). - -## ============ UPDATE 2026-08-24k (24k): ЗАКОН ПРИМЕНЕНИЯ НАЙДЕН ТОЧНО — A = 1.019·V^1.8345; «×1.805» = ПРОИЗВЕДЕНИЕ ЭКСПОНЕНТ 0.984×1.8345 ============ - -### Инструменты раунда (все рабочие, закоммичены) -- `scripts/rendersnap2.py` v3: мягкий снаппер (не убивает reaper), t_snap в npz, - слоты {540668 FIR, 540678 bands, 540688, 540788 R, 5407f8 R} + скалярный банк - 0x540860..c0. OUT = argv[3] (иначе каталог перезаписывается!). -- `scripts/hunt2.py`: полный чанковый перебор ВСЕХ ctx-кандидатов в рендере. - РЕЗУЛЬТАТ: инстанс ОДИН (0x2370040) — GUI/DSP-пара (24c) НЕ ПОДТВЕРЖДЕНА, - гипотеза закрыта. -- Зондовый мультитон: 2 главных тона (= амплитуды dual.wav 0.4248) + зонды - −30 дБ на фикс. сетке; Гёрцель по рефу даёт применённый отклик на всех - зондовых частотах при том же адаптивном состоянии детектора. - -### Ключевые отрицательные результаты (не возвращаться) -- [ctx+0x540888] и весь банк 0x540874..88c = 1.000000 весь рендер; 0x540870 = - 440.955. Скаляров-множителей в применении НЕТ. -- R-слоты 0x540788/0x5407f8 ИДЕНТИЧНЫ во всех проектах (r43=4.0285/3.9807) — - статические twin-шаблоны; с формой V НЕ коррелируют (rms 0.47). -- Слот 0x540678 = спектр кадра (HF→0), не кривая маски; 0x540688 = большие - значения (~183) — сырые аккумуляторы. -- Свип для Y/X-отношения непригоден (энергия только на гребне) — только - мультитон/мультиуровень. -- Динамическое выравнивание V(t)↔g(t) на рампе НЕ сходится (инерция детектора - + STOP-семплирование) — пары брать только из стационарных прогонов. -- Каталоги снапов перезаписываются друг другом — всегда отдельный argv[3]. - -### ГЛАВНЫЙ ЗАКОН (8 уровней драйва −0..−30 дБ, q=0.5, fc=500) -Стационарная пара (V = устоявшийся видимый FIR@540668 «most-common complete -state», A = применённый отклик по Гёрцелю рефа): -``` -cutA[дБ] = 1.8345 · cutV[дБ] + 0.1615 дБ (@500 Гц, центр нотча) -rms = 0.0025 дБ (!), max 0.005 дБ — 8 точек, cutV от 1.0 до 5.55 дБ -``` -На скирте (@2000): та же форма, γ≈1.812, offset≈0.18, rms 0.087 дБ. -В линейном домене: A ≈ 1.019 · V^1.8345. -Погрешность выбора V (медиана most-common вместо истинного кадра) ~0.05-0.1 дБ -на скирте объяснима; закон устойчив по всем уровням. - -### РАЗГАДКА «×1.805» (23e/24i окончательно закрыты) -``` -0.984 × 1.8345 = 1.80515 -``` -23e эмпирическая константа «свёртка ×1.805» — это ПРОИЗВЕДЕНИЕ экспонент: -видимый кернел строится как exp(0.984·log(bands)) (23d ✓), а ПРИМЕНЯЕМЫЙ -kerнел = bands^1.805 (в эквиваленте). Никакой свёртки/OLA/скаляра нет: -применение = побиновный комплекс-multiply (th_b3c0 ✓ 24e) на кернел -A = 1.019·V^1.8345. Старые phase*.npz (m43=0.588) — init-кернелы, всё что -строилось на них — артефакты. - -### Двойные аттракторы видимого буфера -V_steady@43 = 0.524±0.002 ∀q (0.1..10); редкие флэши D@43=0.315 ∀q; -ln(D)/ln(V)=1.85 ≈ γ ⇒ флэш = применяемый кернел A, пойманный в буфере -(перестройка каждый кадр: rebuild→A→consume→запись V-стадии). - -### Что осталось до транскрипции применения -1. Формула V(content,params): ширина провала vs q, глубина vs drive/sens, - позиция vs fc. Зондовая методика + rn_q*/rn_lv* каталоги = данные уже есть. -2. Источник 1.8345 в коде: искать вторую экспоненту в ops A-D / bigkernel - 140b30 (exp2-кернел?) — кандидат: двойное применение стадии exp или - exp(0.984·log)²·коррекция окна (0.984·2=1.968≠1.805 — нет; искать точно). -3. RT_FIRCONV=3: маска = 1.019·M^1.8345, где M = выход нашей detector-цепи, - после того как M приведён к форме V (единичный passband, провалы у контента). -4. Корпусный гейт --vs-bridge после каждого шага. - -### Контрольные числа уровня −0 дБ (dual-эквивалент) -V43=0.5279 → cutV=5.550 дБ; cutA=10.34 дБ (реф 10.32 ✓); cutA@2000=11.85 (реф -11.82 ✓). Уровневая серия: см. /tmp/opencode/{lv*,rn_lv*}, pairs в -level_pairs.npz. - -### ДОПОЛНЕНИЕ 24k-2: чистая q-таблица V (most-common стеади, vfamily_clean.npz) -| q | m43 | m171 | примечание | -|-----|-------|-------|------------| -| 0.1 | 0.5240 | 0.3781 | | -| 0.2 | 0.5240 | 0.4099 | | -| 0.3 | 0.5240 | 0.4373 | | -| 0.5 | 0.5241 | 0.4728 | контроль 10.32/11.82 ✓ | -| 0.7 | 0.5241 | 0.4915 | | -| 1.5 | 0.5245 | 0.5141 | | -| 2.0 | 0.5250 | 0.5176 | | -| 3.0 | 0.5262 | 0.5203 | | -| 5.0 | 0.5298 | 0.5217 | | -| 10 | 0.5431 | 0.5223 | | - -1. **Глубина в центре константна ∀q** (0.524±0.002, лёгкий подъём к q=10). -2. **Форма узкого провала ИНВАРИАНТНА по q**: ширина на полуглубине 6–7 бинов, - на 3/4 глубины 8–9 бинов ∀q; хвост +128 бинов = 1.0000 ∀q. -3. Вся q-зависимость — ШИРОКАЯ ФОНОВАЯ КОМПОНЕНТА: V(f;q) = D(f)·Wq(f), - где D — фиксированный узкий провал у контент-бина (форма от q не зависит), - Wq — широкая юбка с глубиной, растущей при q↓ (Wq@171: 0.72@q0.1 … 1.00@q10). - Проверка соответствия Wq статическому twin-шаблону R^−p — ПЕРВЫЙ кандидат - следующего раунда (данные: vfamily_clean.npz + слоты R788/R7f8). -4. Через степенной закон применения это даёт большие dB на скирте: - A@2000(q=0.1) = 1.019·0.378^1.8345 → 15.5 дБ vs реф 15.33 ✓. -5. Каталоги данных раунда: /tmp/opencode/rn_q* (q-серия), rn_lv* (уровни −0..−30), - rn_ramp (рампа, для динамических опытов непригоден), multi2 (зонды+слоты), - level_pairs.npz, vfamily_clean.npz. - -## ============ ДОПОЛНЕНИЕ 24k-3: структура V окончательна; законы ветвей; разрыв единого закона ============ - -### V = сумма независимых узких провалов на контентных бинах -Сырые кривые (vfamily_clean + сырой вывод): passband РОВНО 1.0000, провалы -~7 бинов ТОЛЬКО на бинах тонов (43 и 171), между ними 1.0000. Никакой широкой -юбки в V нет (dU вне контентных бинов = 0.000 во всех q). Корреляция dU со -статическими R-шаблонами = 0 ⇒ Wq-гипотеза 24k-2 снята; q меняет ГЛУБИНУ -провала на дальнем от fc бине, форма локального провала инвариантна. - -### Железный закон применения (не менялся): A = 1.019·V^1.8345 -Проверен побиновно на скирте: q=0.1: A@2000=15.31 дБ vs реф 15.33 ✓; -q=10: 10.13 vs 10.25 ✓. - -### Закон ветви ЦЕНТРА (уровневая серия, q=0.5, sens=12) -``` -cutV@fc[дБ] = 1.7436 · ln(1 + lvl_raw(fc)/0.3824) rms 0.045 дБ, 8 точек -``` -lvl_raw — НАШ тракт (am/res·scale_factor, render48k RT_DUMP_BIN): наш детектор- -фронтенд совместим с плагином по шкале (без подгоночного k!). - -### Закон ветви СКИРТА (bin 171, q-серия + уровневая) -Чисто лог-аффинный: cutV = 1.2775·ln(lvl_raw)+6.383, rms 0.087 дБ (q-серия); -в ln(1+x/b)-форме требует множитель аргумента λ≈12±6, НЕ константу ⇒ единый -однoаргументный закон от lvl_raw ОТСУТСТВУЕТ. Двухаргументная регрессия -ln(am)+c·ln(res) по всем 36 точкам: rms 0.64 дБ, систематика на скирте низких q -(+2.6 дБ) — скирт структурно круче. - -### Следствия для транскрипции -1. Применение = готовая формула (выше), можно внедрять хоть сейчас: маска из - ЛЮБОЙ кривой M даёт корректную глубину через ^1.8345. -2. Осталась ОДНА неизвестная — отображение (frame spectrum, band params) → - V-провалы. Датасеты для идентификации полные: 36 стационарных точек - (уровни × q × {центр, скирт}) с нашими трактами. -3. Кандидаты механизма на проверку: (а) пер-бин уровень ПОСЛЕ частотных - бидир-IIR (сглаживание тянет центральный уровень в соседей — но ширина - провала 7 бинов против тау-IIR не сходится тривиально); (б) clamp/LUT - FUN_180563a60 (A,B,γ) на уровне полосы; (в) отдельная обработка - «центр vs вне» из BLOCKMAP (шаги 9-13: разные ветки флага 0x5408b8). -4. sens-зависимость ещё не снималась (все данные @sens=12). - -### Статус канона -Канон НЕ тронут; все результаты — новые факты поверх. Коммиты раунда: -6629e8b (24j) → cc8ba34 (24k) → 6dc2967 (24k-2) → этот. - -## ============ ДОПОЛНЕНИЕ 24k-4: RT_FIRCONV=3 внедрён; ХАЗАРД перезаписи рефов; корпус восстановлен ============ - -### RT_FIRCONV=3 (spectral.cpp): применение по закону 24k -`buf *= 1.019·mask^1.8345` побиновно. Первый прогон dual_b1q_0.5: -| конфиг | cut@500 | cut@2000 | -|--------|---------|----------| -| default | 16.73 | 12.01 | -| FIRCONV=3 | 30.31 | 21.71 | -| реф | 10.32 | 11.82 | -Ожидаемо: наш детектор M ≠ V (перерез в центре), экспонента углубляет. -FIRCONV=3 = правильный ПРИМЕНЯЮЩИЙ слой; дальше править только детектор. -Канон (default-путь) не тронут, corpus подтверждён (ниже). - -### ХАЗАРД (был, исправлен): rendersnap2 удалял rpp.replace('.rpp','.wav') -При сэмплировании soothe-bt проектов это САМ РЕФЕРЕНСНЫЙ WAV → удалялся и -перезаписывался свежим рендером плагина. Пострадал dual_b1q_1.0.wav (обрыв, -EOFError в corpus). Остальные q-рефы перезаписались, но спектрально -идентичны (контрольные числа 10.32/11.82 воспроизвелись ✓). ФИКС: путь берётся -из RENDER_FILE проекта; удаление только по нему же. dep0b_rt.wav / out_rt.wav / -out-nofx.wav / out_dual300.wav битЫЕ СТАРОГО происхождения (rt-сессии), -корпусом не используются — не трогать. -Корпус после восстановления: t1kq 0.768 / t1k 2.114 / al 0.986 / res 0.437 / -dual 3.264 / comb 6.116 / TOTAL **2.286** (dual улучшился против 3.578 — -свежие рефы). - -## ============ ДОПОЛНЕНИЕ 24l-2: ЖИВАЯ ПАРА (scratch, FIR); FIR = exp(log(bands)); поддержка log ОГРАНИЧЕНА ============ - -### Решающий снапшот -rendersnap2 v4 (слоты +540548/550/598/628) на multi.rpp → multi3/ph040.npz: -в ОДНОМ STOP пойманы scratch@540628 НЕнуловой (231 бин, max |s|=0.75) и FIR. -Проверка поточечно: -``` -bin43: scratch=-0.6472 exp(s)=0.5235 FIR=0.5292 (+1.1%) -bin171: scratch=-0.7498 exp(s)=0.4724 FIR=0.4763 (+0.8%) -``` -⇒ **FIR ≈ exp(scratch) = bands[i] напрямую** (расхождение ~1% — окна/ops). -«Строение кернела» не преобразует кривую — оно ВОСПРОИЗВОДИТ её через -log→exp-тракт (инверсия знака/твидлы/окна из 24l почти взаимно компенсируются; -их остаточная роль ~1% — выяснить отдельно). - -### Поддержка лога ОГРАНИЧЕНА -scratch непулевой ТОЛЬКО в бинах 6..623 (~70..7300 Гц) при нотчах на 43 и 171. -Вне поддержки scratch=0 ⇒ exp=1 ⇒ ЕДИНичный passband кернела — вот механизм -ровной единички пасбэнда! Границы поддержки = детекторная активность/порог -(уточнить законом). - -### Сшивка всех фактов раунда -``` -applied_A = 1.019 · B^1.8345 (24k, rms 0.0025 дБ) -B = exp(scratch) ≈ bands[i] (24l-2, ±1%) -⇒ остаётся ОДНА неизвестная: кривая bands[i] детектора (наша M ≠ B). -Контроль: A(B@43=0.5235)=0.310 → −10.17 дБ vs реф −10.32 ✓; -глубокий флэш D@43=0.315..0.32 = ровно значение, дающее реф-глубину при -подстановке в закон (D и есть B другой мгновенной фазы адаптива). -``` - -### Буферы -buf548: стабилен внутри сессии, span 4..8193, НЕ классические твидлы -(pair512=(−0.707,−0.724)) — построение при init, реплицировать численно. -mask598: паттерн lane-select подтверждён. - -### NEXT (24m) -1. Формула B(b): центр-закон 1.7436·ln(1+lvl/0.3824) уже есть; скирт-ветка + - границы поддержки (бины 6..623!) — следующие цели. Датасеты: multi3/ - rn_lv*/rn_q* + тракты. -2. Численная репликация log→exp тракта (ops A-D, buf548) до ±1% для bit-exact. -3. sens/fc-серии для масштабирования закона. -4. RT_FIRCONV=3 уже даёт правильную композицию — детектор доводить до B. - -## ============ UPDATE 2026-08-24m: ДВУХСТАДИЙНЫЙ ДЕТЕКТОР — ГЛУБОКАЯ/МЕЛКАЯ СВЯЗАНЫ КОНСТАНТОЙ γ₀≈1.79 В дБ; ЕДИНАЯ АРХИТЕКТУРА ЗАКОНА ============ - -### Данные -rendersnap2 v4 (со слотом scratch@540628) прогнан по всем q (sc_q*) и уровням -(sc_lv*): 18 полных кривых поддержки (deepest-scratch отбор). Стеади-scratch -внутри рендера КОНСТАНТЕН (s43=−0.6477±0.0005 через 50 фаз!), дискретные -состояния {0, релиз, стеади, глубина}. - -### ГЛАВНЫЙ ФАКТ: cut_deep = γ₀ · cut_shallow, γ₀ = 1.79±0.02 -| точка | shallow дБ | deep дБ | отношение | -|-------|-----------|---------|-----------| -| центр @43, drive 0 | 5.550 | 9.90-10.04 | 1.789 | -| @171 q=0.1 | 8.447 | 14.89 | 1.763 | -| @171 q=0.5 | 6.442 | 11.57 | 1.796 | -| @171 q=10 | 5.642 | 10.07 | 1.785 | -Отношение константанно по всем бинам/q/уровням ⇒ «таинственные» 1.805/1.8345 — -ЭТО ОНО: применяемый кернел = стадия-2 детектора, буфер обычно держит стадию-1, -отношение их дБ-масштабов = γ₀. (Точная природа двух стадий — кандидат: -двойной exp-проход тракта 24l или итеративный детекторный контур.) -⇒ закон применения A = 1.019·V^1.8345 и «audio = deep напрямую» — ЭКВИВАЛЕНТНЫЕ -записи одного факта. - -### Формы ветвей стадии-S (shallow; стадия-D = ×γ₀) -``` -центр (bin fc): cutS = 1.7436·ln(1 + lvl_raw/0.3824) rms 0.045 дБ (8 уровней) -скирт (@171 все q): cutS = 7.57 + 2.70·ln(1 + lvl_raw/0.341) rms 0.22 дБ (holdout q=1.5/3/10) -``` -Скирт-офсет +7.57 дБ НЕ объяснён (кандидат: вторая компонента детектора / -кросс-тон вклад). Ширина провалов 5-6 бинов константна ∀q; между контентными -пиками B≡1.00000 ровно. Глубина центра сатурируется ~9.9-10.0 дБ (S-стадия) -∀q при drive 0; @171 монотонно растёт 10.07→14.89 при q↓. - -### Уровневая серия (стадия-D, sc_lv*, центр): -drive −0..−30 дБ → s43: −1.143, −1.021, −0.903, −0.791, −0.685, −0.494, −0.339, -−0.222 (= cut_D 9.93…1.93 дБ). - -### Следствия для транскрипции -Детектор = вычислить cutS(b) побиновно, затем маска = 10^(−γ₀·cutS/20). -Для bit-exact остались: (а) источник γ₀ и двух стадий в декомпе; -(б) объяснение офсета +7.57 скирта; (в) правило границ поддержки лога. -Датасет: /tmp/opencode/scratch_family.pkl (18 кривых) + tract_*. -Инструмент-обновление: rendersnap2 v4 пишет полный набор слотов включая -scratch — канал прямого наблюдения кривой детектора БЕЗ инверсии. - -## ============ ДОПОЛНЕНИЕ 24m-2: RT_VLAW=1 внедрён; формула в коде верна; end-to-end разрыв = слой применения ============ - -### Реализация (framed_model.cpp, env RT_VLAW=1) -Ветка заменяет LUT/exp2/blend: `band_level = 10^(−γ₀·cutS/20)`, -`cutS = 1.7436·ln1p(lvl_raw/0.3824)` (центр-форма глобально), γ₀=1.79. -Проверено stderr-принтом (RT_VDBG): k=43 lvl=8.815→g=0.3189 ✓ формула в коде -даёт точные значения стадии-D. Маска идёт напрямую (exp2/warp/IIR3 отключаемы -RT_NOWARP/RT_NOIIR3). - -### Статус end-to-end (dual_b1q_0.5): -``` -REF: cut@500=10.32 cut@2000=11.82 -VLAW: cut@500= 7.56 cut@2000= 6.41 (+NOWARP+NOIIR3: 5.36 / 1.73) -``` -Формула кривой верна (маска@43=0.319 ≈ плагинная B@43=0.320!), но сквозной -результат недобирает ⇒ расхождение живёт В СЛОЕ ПРИМЕНЕНИЯ (STFT/WOLA): -эффективный gain на тоне = усреднение маски по главному лепестку окна; -узкий дип 5-6 бинов размывается. Исторически канон-константы были ПОДГОНЯНЫ -сквозь этот слой (смещение впитано), поэтому старые группы корпуса сходились. -Глубокая симуляция WOLA-нормировок не завершена (следующий раунд): кандидат — -плагин нормирует окно/хоп иначе (cfg {16384,8192} движка?), либо анализ без -окна/с другим окном. Ключевой контрольный эксперимент: прогнать ОДИНАКОВУЮ -кривую через наш слой и измерить эффективную глубину на тоне против плагина. - -### Гигиена -TOOLING HAZARD снова сработал (same-second build → stale бинарник дал ложные -5.36/1.73). Протокол touch+mtime-проверка ОБЯЗАТЕЛЕН перед каждым замером. -RT_VDBG stderr-принт оставлен (env-gated, нулевая цена). - -## ============ UPDATE 2026-08-24n: ЕДИНЫЙ ЗАКОН СТАДИИ-S; RT_IIR12=0 КРИТИЧЕН; ЦЕНТР 9.40/10.32; РАМПА ПОДТВЕРЖДАЛА γ₀ ============ - -### Единая форма стадии-S (shallow) — обе ветви одной формулой: -``` -cutS(b) = α · ln(1 + lvl_raw(b)/0.3824) + Δ(b) -центр (бин fc): α=1.729 Δ=0.00 rms 0.044 дБ (уровневая серия) -второй контент-пик: α=1.656 Δ=+4.177 rms 0.051 дБ (q-серия @171) -``` -Одинаковый масштаб β=0.3824 (!), близкие α. Δ эквивалентен сдвигу уровня -на +2.23 в lvl-домене (детекторный порог/вторая компонента) ТОЛЬКО у -внецентрового пика. У зондов вне пиков B≡1 (Δ не действует). -Стадия-D = ×γ₀=1.79 (см. 24m). - -### РАМПА подтверждает закон на независимых данных -Траектория u(t) через 30 дБ драйва (ramp_ref, скользящий Гёрцель): -измеренное vs 1.79·cutS: остатки +0.12..+0.23 дБ системно (лаг атаки при -падающей рампе — детектор видит исторически больший уровень). Тройное -подтверждение γ₀ (уровневая серия rms 0.0025 + q-таблица + рампа). - -### RT_VLAW статуса-2 -КРИТИЧНО: частотные бидир-IIR1/2 (RT_IIR12) работают ПОВЕРХ vlaw-маски и -размывают плечи дипа ⇒ рабочий набор флагов: -``` -RT_VLAW=1 RT_NOWARP=1 RT_NOIIR3=1 RT_IIR12=0 -``` -Результат dual_b1q_0.5: **cut@500=9.40** (реф 10.32, зазор −0.92 дБ), -cut@2000=4.14 (реф 11.82 — Δ-ветка ещё не реализована в коде). -Зазор центра-0.9 дБ: кандидат — окно/нормировка нашего STFT-слоя либо тонкая -калибровка α/γ₀. Следующие шаги: (а) реализовать Δ-ветку (правило второго -пика); (б) разобрать слой применения до сходимости; (в) источник γ₀/стадий -в декомпе; (г) корпус с полным флаг-набором. - -## ============ ДОПОЛНЕНИЕ 24n-2: Δ-ветка в коде; dual 0.708 (было 3.264); константы НЕ универсальны ============ - -### Реализовано (framed_model.cpp, vlaw-ветка v2) -Δ-правило: локальные максимумы lvl_raw>0.25 вне ±8 бинов от fc помечают -окрестность ±3 бина → +4.177 дБ. Двухпроходно (без O(n²)). -dual_b1q_0.5 end-to-end: **9.32 / 11.58** vs реф 10.32 / 11.82 -(скирт −0.24 дБ!, центр −1.0). - -### Корпус с флагами (62 случая): -| group | canon | VLAW | -|-------|-------|------| -| t1kq | 0.768 | 3.633 | -| t1k | 2.114 | 3.296 | -| al | 0.986 | 2.620 | -| res | 0.437 | 3.896 | -| **dual** | 3.264 | **0.708** | -| comb | 6.116 | 4.014 | -| TOTAL | 2.286 | 2.515 | - -### Диагноз регресса одноканальных групп -t1kq-диагностика (тон@1000, fc∈{500,1000,2000}): -- fc=500 (тон вне центра): реф 5.45, VLAW 10.37 ⇒ Δ НЕ должен применяться к - этому случаю (реф = shallow-уровень!); при q≈1 и отступе 500 Гц офсета нет, - а в dual (q=0.5, отступ 1500 Гц) был +4.18 ⇒ правило Δ зависит от q/дистанции. -- fc=1000 (тон в центре): реф 8.70 vs dual-центр 10.32 при близком lvl ⇒ - глубина зависит от q/fc — константы α_eff/γ₀_dual не универсальны. -Вывод: закон верен В СЕМЕЙСТВЕ калибровки (dual_b1q_0.5: q=0.5, sens=12). -Параметризация (α, γ₀, Δ-правило) по (q, fc, sens) — следующая большая задача; -методика (rendersnap2+зонды+рампа) готова, датасеты расширяются механически. -Канон не тронут: default-корпус воспроизведён (TOTAL 2.286 ✓). - -## ============ ДОПОЛНЕНИЕ 24o: точки параметризации — sens/q серии; закон НЕ монотонен по lvl между конфигами ============ - -### Новые захваты (rendersnap2 v4, deepest scratch, ПРАВИЛЬНЫЙ центр-бин!) -ВНИМАНИЕ баг анализа: для fc≠500 центр-бин K=round(fc/48000·4096) -(fc=1000 → K=85!). Данные: -``` -проект lvl_ours@K cut_D@K реф-кат -dual q0.5 s12 8.55 10.03 10.32 -qmap q2 s12 20.11 15.44 15.94 -qmap q3 s12 20.06 15.43 15.93 -qmap q6 s12 19.78 15.39 15.90 -sens_6 q1 4.88 10.68 10.90 -sens_9 q1 9.91 11.20 11.44 -sens_18 q1 83.1 11.74 12.01 -sens_24 q1 341 11.74 12.01 -``` -ref_cut ≈ cut_D везде (−0.3..−0.5 дБ) — аудио слышит глубокую стадию, -подтверждение универсальное. - -### Наблюдения -1. **γ₀ проверяется и тут**: shallow=q-таблицы дают те же ×1.79 отношения. -2. **Насыщение по sens**: lvl растёт 70× (4.9→341), кат меняется лишь - 10.68→11.74 и выходит на плато ≈11.74 (q=1). -3. **Не монотонно по lvl МЕЖДУ конфигами**: (q2,lvl20)→15.44 но - (q1,lvl83)→11.74. Закон зависит от q ОТДЕЛЬНО от lvl (форма/масштаб). - Наивная инверсия даёт β(q2..6)≈0.138 против β(q0.5)≈0.35 — но это - подгонка одной точки; нужна фит-форма с q-аргументом. -4. Кат всегда = плато конкретной конфигурации: cap(q,sens). - Гипотеза: cap ∝ глубина полосы (зависит от q через геометрию шаблона - и/или sens-гейн детектора). - -### Датасеты раунда -/tmp/opencode/sc_qmap_1000_q{2,3,6}, sc_sens_{6,9,18,24} (+sc_q*, sc_lv*), -param_points.pkl, tract_qmap*/tract_sens*. -Рефы qmap_*/sens_* в soothe-bt целы (перезаписаны свежими рендерами -rendersnap2 v4 с RENDER_FILE-фиксом — контроль чисел сошёлся). - -### NEXT (24p) -1. Полный кривой фит: cut(b)=F(lvl(b),res(b);q,sens) по ВСЕМ бинам всех - проектов (сотни точек с формой дипа) вместо одиночных бинов. -2. sens-плато: найти механизм насыщения (кандидат: LUT FUN_180563a60 верхний - упор B=28 дБ? 11.74·2стадии? нет — искать точно). -3. Δ-правило для t1kq-семейства (fc-дистанция в октавах? q-нормировка?). - -## ============ ДОПОЛНЕНИЕ 24o-2: глобальный фит формы НЕ сошёлся — путь к механизму через декомп ============ - -### Попытка: cut = α·[ln1p(k·L/β) − ln1p(k·L/C)] (двусторонняя сатурация, -α,β,C общие + пер-проектный масштаб k) по 13 кривым × все значимые бины. -Результат: rms 1.40 дБ, шкалы k хаотичны (k(dual)=19.9 при том что -константы откалиброваны на dual!), qmap-кривые не вошли (тракт-файлы -перепроверить). ВЫВОД: форма «мягкий предел с общими константами» не -описывает семейство; пер-конфигурационные капы (q0.5:10.03 / q1s24:11.74 / -q2:15.44) не выводятся из одной (β,C). - -### Интерпретация -Кап растёт с q (шаблон острее ⇒ глубже детекция?) и слегка с sens. -Кандидаты механизма насыщения и q-зависимости: -(а) частотно-варпированные коэф. генератора FUN_180533340 (p-экспонента!) - — сила IIR зависит от fc/sr и p; -(б) LUT FUN_180563a60 (A,B,γ в dB-домене); -(в) mix^p / blend-цепочка BLOCKMAP pre-combine; -(г) двухстадийность = двойной проход exp-тракта (24l) — тогда γ₀ должно - быть точной функцией отношений окон/нормировок, вычислимо из декомпа. -Путь по золотому правилу: СТАТИЧЕСКИЙ ДЕКОД этих тел (все адреса известны), -затем параметризация станет следствием, а не подгонкой. - -### Датасеты для будущих фитов сохранены -/tmp/opencode/mega_curves.pkl (13 полных кривых cut+lvl), param_points.pkl, -scratch_family.pkl, плюс сырые ph*.npz каталоги sc_*. -### Коммиты раунда 24j..24o-2 -6629e8b, cc8ba34, 6dc2967, 1605e0b, 65fb983, fe01ef5(?), b00a8a6(?), fc224b0, -31cc554, 5f822eb + этот. Все запушены в origin/main. - -## ============ ДОПОЛНЕНИЕ 24p: СКАЛЯРНОСТЬ СТАДИЙ ПОБИННО; ГИПОТЕЗА BLEND; mix-тест некорректен ============ - -### Побиновое отношение стадий (multi3, чистые данные) -deep/steady по 82 значимым бинам: **среднее 1.760, std 0.0115** (min 1.698, -max 1.822 на краях с малыми катами) ⇒ стадии = ОДНА ФУНКЦИЯ × константа в -дБ-домене, не разные формы. Механизм — скалярный множитель dB-стадии. - -### Гипотеза blend: γ₀ = 1 + blend (=1+0.8=1.8) -xmm10=0.8 (blend!) стоит в парно-скалярной ветке FIR-цикла (24l). γ₀≈1.76-1.83 -во всех измерениях — 1.80 в центре. ПРОВЕРЯЕМОЕ ПРЕДСКАЗАНИЕ: γ₀(mix) = -1 + f(mix); при mix→0 γ₀→1. - -### mix-тест (s_corr_mix_*) НЕ КОРРЕКТЕН -Проекты используют другой вход (resonant.wav, кат 0.6 дБ против наших 5.5) и -разные уровни; глубокие состояния не отделяются. Нужен клон multi.rpp с -вариацией ТОЛЬКО mix внутри VST-чанка (требует decode/encode чанка или -REAPER-автоматизации setparam.lua) — отдельная задача. -Захваты sc_s_corr_mix_* сохранены (вход resonant). - -### Статус раунда 24j..24p -- Применение: per-bin complex-multiply, A=1.019·V^1.8345 (≡ deep=γ₀×shallow) -- Стадия-S: α·ln1p(lvl/β=0.3824)+Δ, центр откалиброван (rms 0.044) -- RT_VLAW=1 в коде: dual 9.32/11.58 vs реф 10.32/11.82; корпус dual 0.708 -- Открыто: q/sens-параметризация капов; Δ-правило для других семейств; - источник γ₀/двух стадий в декомпе; mix-тест с контролируемой вариацией. -- Канон не тронут; все эксперименты env-gated. - -## ============ ДОПОЛНЕНИЕ 24q: КОНТРОЛИРУЕМЫЙ depth/mix СВИП (patchparam.py!) — blend-гипотеза опровергнута; +0.4 дБ константа ============ - -### Инструмент -patchparam.py правит внутри base64-VST-чанка RPP. Полная -карта параметров multi.rpp получена (band1: 500/q0.5/sens12/on ✓; -**depth=0.864**, mix=100, selectivity=10, sharpness=10, resolution=1, -offline oversample=3). Созданы контролируемые клоны: depth∈{0.5,0.7,0.9}, -mix∈{25,50,75}, вход/прочее идентичны. - -### Результаты свипов -DEPTH (mix=100): γ₀ = 1.7597 / 1.7600 / 1.7608 для depth 0.5/0.7/0.9 — -γ₀ ОТ ГЛУБИНЫ НЕ ЗАВИСИТ; cutS слегка растёт (5.30→5.66). -MIX: cut_S = 1.10 / 2.36 / 3.84 / 5.55 (25/50/75/100) — стадия-S масштабируется -миксом (см. BLOCKMAP mix^p); глубокие состояния в захватах m50/m75 отсутствуют. - -### Аудио (чистые рендеры рефов) против стадии-S: -``` -вариант cut@500 cut@2000 S@43 S@171 audio/S@500 -m25 1.79 1.93 1.098 — 1.63 -m50 3.79 4.14 2.363 — 1.61 -m75 6.44 7.15 3.841 — 1.68 -d0.5(m100)9.70 11.21 5.296 — 1.83 -m100 orig 10.32 11.84 5.550 6.442 1.859/1.839 -``` -⇒ **blend-гипотеза «γ₀=1+mix» ОПРОВЕРГНУТА**: отношение audio/steady НИКОГДА -не падает до 1 (даже при mix=25 оно 1.63); слабо растёт с mix (1.6→1.86). -Двухстадийность существует ВО ВСЕХ mix, микс лишь модулирует величину. -Константа раунда: audio − самый-глубокий-захваченный-кернел = **+0.40..+0.45 дБ** -(d0.5: 9.70−9.32=+0.38; m100: 10.32−9.93=+0.39) — стабильный зазор захвата. - -### ХАЗАРД (новый): rendersnap2-клоны с одним RENDER_FILE -Все клоны multi.rpp наследуют RENDER_FILE=/tmp/opencode/multi_ref.wav ⇒ каждый -захват перезаписывал общий реф последней конфигурацией. При работе с клонами — -ПЕРВЫМ ДЕЛОМ переписывать RENDER_FILE (как сделано для *_ref вариантов). - -### NEXT (24r) -1. Зазор +0.4 дБ: либо мгновенная фаза адаптива, либо пост-df0 обработка — - поймать пару (pre-df0, post-df0) в одном STOP (слот track@768 уже пишется). -2. Источник γ₀ и двух стадий в декомпе: df0(FIR,track_i) семантика! -3. Δ/q/sens параметризация по накопленным датасетам. -4. Корпус dual-семейства через RT_VLAW после фиксации зазора. - -## ============ ДОПОЛНЕНИЕ 24r: track статичен; DTFT-тест кернелов; упаковка потребления — последний разрыв ============ - -### Слот track@768 добавлен в rendersnap2 v5; multi4 снят -track ИДЕНТИЧЕН во всех фазах (deep==steady бит-в-бит) — статическая таблица, -не динамическая вторая стадия. - -### DTFT-тест (ирfft кернелов → точный отклик на частотах тонов): -``` - @500 @2000 -shallow 0.524 (5.61 дБ) 0.474 (6.49) -deep 0.294 (10.63)✓ 0.594 (4.52)✗✗ -REF 0.305 (10.32) 0.257 (11.82) -``` -Deep-кернел совпадает с рефом НА ЦЕНТРЕ (+0.3 дБ) но проваливается на скирте -⇒ irfft(captured_spectrum) НЕ есть тот фильтр, что слышен на скирте. -Причина: спектр кернела в буфере НЕ эрмитов (fir_im≠0, антисимметрика), а -потребление идёт через комплексные стадии ops A–D (твидлы buf548) — их -упаковка/распаковка определяет действительный фильтр. Это ПОСЛЕДНИЙ -нерасшифрованный кусок применения. - -### Инструменты состояния -rendersnap2 v5 (SLOTS+768), patchparam.py — рабочий правщик VST-чанка -(параметры: depth/mix/bands/selectivity/sharpness/...), полная карта PARAM id -получена из multi.rpp. Клоны проектов: multi_d{0.5,0.7,0.9}, multi_m{25,50,75} -(+_ref рендеры). - -### NEXT (24s) -1. Декод ops A–D (воркер 4ca80→диспетчер типов; твидл-стадии над упакованным - вещественным спектром) — замкнёт путь преобразования буфер⇒фильтр. -2. После этого: bit-exact репликация применения станет механической. -3. Параллельно: Δ/q/sens по датасетам (mega_curves.pkl). - -## ============ UPDATE 2026-08-24v: СЛОЙ STFT НАЙДЕН — БЕЗ СИНТЕЗ-ОКНА; DUAL КОРПУС 0.193 ============ - -### Эксперимент RT_SYN/RT_WIN (spectral.cpp, env-gated) -Варианты слоя на dual_b1q_0.5 (маска VLAW v3 фиксирована): -``` -база sym-hann + syn-окно : 9.47 / 11.16 -периодический ханн : 9.48 / 11.16 -прямоугольный анализ : 10.18 / 11.27 -БЕЗ СИНТЕЗ-ОКНА : 10.27 / 11.59 ← победитель -``` -⇒ плагин НЕ домножает выход обратного преобразования на окно -(архитектура partitioned-conv движка; нормировка Σw_ana·1/hop). -Зазор к рефу: −0.05 (@500) / −0.23 (@2000). - -### Корпус с RT_VLAW+RT_SYN=1 (+NOWARP/NOIIR3/IIR12=0): -| group | canon | VLAW+SYN | -|-------|-------|----------| -| t1kq | 0.768 | 3.380 | -| t1k | 2.114 | 3.271 | -| al | 0.986 | 1.872 | -| res | 0.437 | 3.861 | -| **dual** | 3.264 | **0.193 (max 0.438!)** | -| comb | 6.116 | 4.410 | -| TOTAL | 2.286 | 2.252 | - -### Статус -dual-семейство РЕШЕНО (все 22 случая <0.44 дБ). Одноканальные группы ждут -параметризации α/β/c/Δ по (q, fc, sens) — константы откалиброваны на -dual(q=0.5). Механика применения и слой полностью декодированы. - -### NEXT (24w) -1. Параметризация закона по q/sens/fc (датасеты sc_* готовы; методика - deepest-scratch + Гёрцель рефов). -2. Δ-правило вторых пиков из декомпа pre-combine. -3. Решение о каноне: после параметризации сравнить TOTAL vs 2.286. - -## ============ UPDATE 2026-08-24s: ПРИМЕНЁННЫЙ ЗАКОН ПРЯМО (rms 0.016 дБ); АУДИО = exp(глубокого scratch); RT_VLAW v3 ============ - -### Развязка объектов (главная путаница раундов устранена) -Для deep-кадра multi4-ph063 одновременно: -``` -exp(scratch)[43]=0.320 [171]=0.267 ↔ аудио 0.305 / 0.257 ✓✓ -FIR-буфер [43]=0.528 [171]=0.475 ← промежуточное представление -``` -⇒ **АУДИО = exp(глубокого scratch) НАПРЯМУЮ** (оба тона!). Буферные состояния -и все γ₀-соотношения — внутренняя кухня тракта построения (мин.-фазовая -упаковка exp(s−iH(s)) подтверждена на re/im ±1%), для транскрипции НЕ нужны. -Применение = побиновное умножение кадра на ВЕЩЕСТВЕННУЮ маску exp(s_deep). - -### ПРИМЕНЁННЫЙ ЗАКОН (центр, 8 уровней драйва): -``` -cut_dB(b) = 3.2193 · ln(1 + lvl_raw(b)/0.4927) + 0.5423 rms 0.016 дБ -скирт-офсет (второй пик): +7.46 − 0.54 ≈ +6.92 дБ сверху (глуб-домен, -согласован с ×γ₀ старого 4.18) -``` -Контроль lvl=8.85 → 10.02 vs реф 10.32 (зазор 0.30 — фаза адаптива). - -### RT_VLAW v3 (константы применённой стадии напрямую): -dual_b1q_0.5: **9.47 / 11.16** vs реф 10.32 / 11.82 (было 9.32/11.58). -Остаток −0.85/−0.66 дБ = СЛОЙ STFT (усреднение по лепестку, диагноз 24m-2) -— следующий кандидат на разбор: параметры окна/hop нашего render48k против -движка {16384,8192}. - -### Формулировки для транскрипции (итог раунда) -``` -lvl_raw(b) = am(b)/res(b)·scale (наш детектор-фронтенд ✓) -cut_D(b) = 3.2193·ln1p(lvl/0.4927)+0.54 [+6.92 у вторых пиков] -mask = 10^(−cut_D/20) (вещественная!) -применение = per-bin multiply кадра -``` -Открыто: параметризация (α=3.22, β=0.49, Δ) по q/sens; слой STFT; -мин.-фазовая упаковка буфера как объект декодера ops A–D. - -## ============ ДОПОЛНЕНИЕ 24w-2: параметризация — скалярные комбинации lvl/res отвергнуты ============ - -### Данные центров всех конфигов (lvl_ours, res_c, cut_D): -dual q0.5 (8.55,.117,10.03); qmap q2/3/6 (~20,.117,15.4); -sens6 (4.88,.484,10.68); sens9 (9.91,.238,11.20); sens18 (83,.028,11.74); -sens24 (341,.0069,11.74). -Замечание: наш twin res@центра почти НЕ зависит от q (0.117 при q0.5 и q2!), -зависит от sens (s18→0.028, s24→0.0069) — sens реализован усилением шаблона. -lvl между конфигами масштабируется по sens корректно (×2.03/+3дБ ✓). - -### Отвергнуты (немонотонность против фактов): -- F(lvl) одна на все конфиги: (q2,L20)→15.44 vs (q1s18,L83)→11.74 ✗ -- X=L·R^b, L/R, L·R² и др.: порядок точек противоречит катам ✗ -⇒ q входит в закон через МЕХАНИЗМ (частотные IIR с коэф. FUN_180533340, -зависящими от selectivity→p; либо LUT-индексация), не скаляром. -Следующий бит-exact шаг: декод p/tau/mult-параметров 533340 (сеттеры -selectivity/sharpness) и их роль в формировании bands[]. - -### Итог раундов 24j..24w-2 (все запушены) -Применение декодировано до формул; слой STFT найден (без синтез-окна); -dual-семейство решено корпусно (0.193 mean). Параметризация остальных -семейств = следующий мейлстоун (методика готова). - -## ============ ДОПОЛНЕНИЕ 24x: структурный диф кривых; таблица k-маппинга эффективных уровней ============ - -### Структурный диф (dual q0.5, plug exp(scratch) vs наш канон-prewarp) -``` - plug_B our причина расхождения -passband 1.0000 0.8000 blend-флор (у VLAW=1 ✓) -меж дипами 1.0000 0.60-0.80 warp/res^rp-одеяло (VLAW отключает ✓) -дип@43 0.315 0.159 глубина-закон (VLAW ✓) -плечи узкие/крутые широкие IIR3/freq-IIR (VLAW без них ✓) -``` -⇒ RT_VLAW-обход структурно ВЕРЕН; канон-цепь не воспроизводит ни одну зону. - -### Таблица k-маппинга (lvl_plugin_implied / lvl_ours, инверсия dual-закона) -``` -конфиг lvl_ours lvl_impl k -dual q0.5 s12 8.550 8.607 0.9934 ← калибровка -qmap q2 s12 20.107 49.896 0.4030 -qmap q3 s12 20.055 49.740 0.4032 ← константа ∀q≥2! -qmap q6 s12 19.780 49.119 0.4027 -sens6 q1 4.875 10.994 0.4434 -sens9 q1 9.909 13.008 0.7618 -sens18 q1 83.085 15.473 5.3696 -sens24 q1 341.03 15.473 22.0402 -``` -Свойства: k(q≥2)=const 0.403; k(sens) растёт экспоненциально на верхнем крае; -ни аффинная, ни степенная формы не покрывают ⇒ фронтенд детектора плагина -(am/res/scale) отличается от нашего конфиг-зависимо. Bit-exact замыкание = -точный декод twin_coeff + амплитудного тракта детектора (модуль twin/fn529fe0). - -### Практическое следствие -Пока k-маппинг не декодирован, VLAW-константы действительны только для -калибровочного семейства (dual q0.5 s12) — что уже даёт корпус 0.193. - -## ============ ДОПОЛНЕНИЕ 24y: закон подтверждён на fc500-q2 (−0.06 дБ); САТУРАЦИЯ уровня плагина обнаружена ============ - -### Расширение валидации закона -``` -конфиг lvl_ours cut_D предсказание (dual-константы) ошибка -fc500 q2 s12 8.782 10.02 9.96 −0.06 ✓ -fc1000 q2 s12 20.107 15.44 12.32 −3.12 ✗ -``` -Закон работает ЗА пределами калибровочной точки по q (та же fc!) — -семейство шире, чем думали. - -### Обнаружена САТУРАЦИЯ эффективного уровня плагина -На tone1k-проектах (am@центра константна 0.7307): -``` -конфиг наш lvl impl-lvl (инверсия закона) -q2 s12 20.1 50.2 ← impl РАСТЁТ -s18 q1 83.1 15.47 ← упёрлось -s24 q1 341 15.47 ← жёсткое плато -``` -Их детекторный уровень НАСЫЩАЕТСЯ (плато impl≈15.47 при q1), тогда как наш -lvl растёт линейно с sens. Плато q≥2 на 49.9 — аналогичный эффект от q. -Формы-кандидаты: лог-компрессия res-гейна, деление на (res+floor), -или LUT FUN_180563a60 (A,B домен!). Это и есть источник «капов» 24o. - -### Следствие для транскрипции -Между нашим lvl и законом нужна СТАДИЯ КОМПРЕССИИ уровня (насыщаемая), -параметризованная по конфигу. Кандидат №1 — именно LUT 563a60 (дБ-домен, -A/B упоры = насыщение!). Её декод = следующий мейлстоун. - -## ============ ДОПОЛНЕНИЕ 24z-2: попытка живого съёма A/B/γ — объект банды не найден пока ============ - -rendersnap2 v6 пытается читать цепочку [ctx+0x540678]→band→[+0x180]→sub→A/B/γ. -Результат: поля отсутствуют ⇒ [ctx+0x540678] указывает на ДАННЫЕ кривой -(массив float'ов), а не на объект банды. Адрес массива ОБЪЕКТОВ банд -(тех, что с [+0x180]-LUT и [+0x4198]-входом из 563a60) нужно найти отдельно: -кандидат — скан heap'а на структуры с валидным [sub+0x90]/[sub+0xc] паттерном, -либо трассировка инициализации (563a60 получает rcx=band obj — ловить её -аргументы при param-rebuild через rendersnap2-стиль STOP в момент загрузки). - -## ============ ДОПОЛНЕНИЕ 24aa: 563a60 — GUI-ветка; компрессия аудио в семантиках ops шагов 1–19 ============ - -Скан всей rw-памяти при рендере (scan_pairs.py): пары (A<0 **Навигатор:** `NOTES_LEVEL.md` — живая голова (24mm5+, 2026-08-28, 389 строк). Архив 2026-08-18—2026-08-23 → [`handoff/archive/NOTES_LEVEL_2026-08-18_2026-08-23.md`](archive/NOTES_LEVEL_2026-08-18_2026-08-23.md) (4024 строки). Статус TOTAL → [`README.md:13`](../README.md). + +## Живая голова (`handoff/NOTES_LEVEL.md`) + +| Дата | Заголовок | Суть | +|------|-----------|------| +| 24mm5–24mm7 | Полная буферная карта, DESIGN 1802a24c0 | Два круга log→exp, bidir×2 в лог-домене | +| 24mm8-бис | Точка входа следующего раунда | opB 18004ca80, поиск γ | +| 24mm9 | FIR-цепь MIN-PHASE, 0.0065 dB | Цепь 52b60c+, RFFT близнецы, df0 complex-mul, валидация | +| 24mm10 | EXP ядро полностью, парадокс q | Формула 1803831c0, нормировки s_f/s_i=1 | +| 24mm10-бис | Итог близнецов radix-4 | INV/FWD 2048, твидлы 548, без масштабов | +| 24mm11 | LIVE ptrace, цепь до df0 бит-точна | wine_ptrace_trace, Y_model=1.0, df0 complex-mul | +| 24mm12 | Детекторный каскад vt+0x28=180529c60 | Оркестратор 5300f0, vtable карта, 0x281 байт | +| 24mm13 (+доп) | Хелперы каскада 529c60 | 5355d0→16140, 530080, 20f0/1850/1a00, рекуррентия Haar | +| 24mm14 | Каскад декодирован — 3 фазы | `|z|` → Haar×2 → peak/sin/w/blend 5407a8 | + +## Архив (`handoff/archive/NOTES_LEVEL_2026-08-18_2026-08-23.md`) — по периодам + +| Период | Ключевые темы | Где | +|--------|---------------|-----| +| 2026-08-18 | Две twin-цепочки, FUN_180563440/563ce0, 530d30 веса, FUN_180529fe0 consumer, 0x530d30 NEGATIVE, bridge B.12 | строки 1–300 | +| 2026-08-18h2–h3 | Runtime capture, поле 0x540658, registry 0x28b06c0/0x29b06c0, window 0.5→0.8, freq-axis 48000 | 475–580 | +| 2026-08-19 | LUT-leg joint fit, level-tracker, decomp lock, registry heartbeat, P4 mask-chain, SR mismatch | 580–900 | +| 2026-08-20i–x | P2 kernels 8d60/3c40, twin-mask, F0 gate determinism, parametric LUT benches, wrappers | 900–1100 | +| 2026-08-21b–d | IIR3 bidir, combine scope, structural corpus 2.286, reduction-law affine, Phase A | 1100–1360 | +| 2026-08-22a–f | Phase B refuted, Step7 GUI-only, param bridge, floor −20.72 blend·ln10/20 | 1360–1600 | +| 2026-08-22g–p | NO-LUT, white-noise probe, bare chain, pool/scale/floor, joint-fit, dual diag, affine port | 1600–1970 | +| 2026-08-23a–z | Скалярный закон насыщен, 529fe0 по raw asm, faithful-цепь, теорема α, спрединг отвергнут | 1970–2300 | +| 24j–24ab | Применение декодировано: A=1.019·V^1.8345, FIR=exp(scratch), двухстадийность γ₀=1.79 | 2750–3550 | +| 24bb–24kk3 | k-маппинг, q-независимость, далёкий тон локально, дистанционная серия, симулятор не замкнулся | 3550–4000 | +| 24ll–24mm4 | multi6, g_k пер-пиковый, archive закрытие, γ=1.760561, точ. законы отвергнуты | 4000–4024 | + +## Как читать + +1. **Старт:** `README.md:13` (статус) → `AGENTS.md` (runbook) → `BITEXACT_PLAN.md:1` (3 шага) → этот индекс → `handoff/BLOCKMAP_529fe0.md`. +2. **Детали по теме:** `rg -n "24mm9\|Q-НЕЗАВИСИМОСТЬ\|Haar"` в соответствующем файле. +3. **Полный поиск:** `rg -n "cut_D\|lvl_raw\|5407c8" handoff/archive/NOTES_LEVEL_2026-08-18_2026-08-23.md handoff/NOTES_LEVEL.md`. + +## Дубли устранены + +- `UPDATE 2026-08-22g` дубль (строки 1577/1591 в архиве) — логически один. +- `UPDATE 2026-08-20s` дубль (974/977) — один. +- Заголовки унифицированы: `## ============ UPDATE` → `##` в живой голове; архив сохранён как есть для истории. diff --git a/handoff/NOTES_TWIN.md b/handoff/NOTES_TWIN.md index e1e6440..2d503b4 100644 --- a/handoff/NOTES_TWIN.md +++ b/handoff/NOTES_TWIN.md @@ -1,76 +1,29 @@ -# Twin FUN_180535880 transcription (Phase 2) +# Twin FUN_180535880 — краткая справка (детали → `dsp/twin.cpp`) -## PHASE-3 CALLER + GRID (2026-08-18) -- Caller FUN_180536300 (decomp 6218 / sp_180536300.txt) decoded at buffer level: - - params: param_1=top DSP, param_2=out, param_3=?, param_4=band obj, param_5=cell count, - param_7=scratch (>=52*N bytes complex). - - scratch: +0..8N A-accum, +8N..16N B-accum, +16N..24N z-powers, +24N..32N rotor-in - (fill 1.0), +32N..36N phase ramp lVar1, +36N..44N rotor-out z1 (r14), +44N..52N - lVar3(1.0)/lVar4(0.0) for interleave+negate+exp build. - - 0x18052da00(lVar1, scale=2pi/(iVar7*sr), N) = ramp fill (float, via 0x180001a00); - - twin always = FUN_180535880 (type-info compare of same addr -> equal); - sibling FUN_180536f90 is dead. - - 0x18052dbc0(param_2, lVar4, N) = cplx interleave -> out=(cos,sin)=e^{+i theta}. -- GRID LAW (verified from trace.txt case8 + constructor): - - os = *(int*)(param_1+0x240080) = 4 (written 0x18052ce3e `mov ...,0x4`) - - sr = *(float*)(param_1+0x24) = 44100.0 - - fs_total (generator) = os*sr = 176400 => resonance shape f/fc invariant to fs. - - per-cell phase step = 2*pi/(os*sr); cell k <-> freq = k/os Hz (0.25 Hz steps). - - Phase-1 model FS=44100 was a valid normalization (res(f) depends only on f/fc). -- case8 generator call (trace 33361..33490, switchD_1805318ab): - - +0x814 numBands = 1, +0x103 mode flag = 1 - - param_5 (float, xmm4) = 10^(sens_dB/20) (pow10 10.0^([band+0x101]/20.0)) - - gain = sqrtf(param_5) = 10^(sens_dB/40); model fit gain=4.132 => sens_dB@0x101 ~= 24.65 - - freq = [band+0x804], Q = [band+0x80c], fs_total = os*sr (w0 = max(fc,2)*2pi/fs_total) +> **Статус:** транскрибирован, gate PASS `dsp/build/twin_check` max rel err 1.27e-5 (float-parity). Полный журнал 2026-08-18 → [`handoff/archive/NOTES_LEVEL_2026-08-18_2026-08-23.md`](archive/NOTES_LEVEL_2026-08-18_2026-08-23.md) + [`handoff/NOTES_LEVEL_INDEX.md`](NOTES_LEVEL_INDEX.md). -## PHASE-2 GATE: PASSED (2026-08-18) -- `dsp/twin.hpp` + `dsp/twin.cpp` written: build_twin_coeff (FUN_180533ec0 double pipeline - -> cvtpd2ps), cplx_div_exact (0x181a77520: mulps |A|^2, rcpps + Newton, NaN guard), - cplx_mul_exact (0x18000ad60 scalar body @0x18000ae00: vfmaddsub213ps form), - twin_apply (seed A0/B0 + 2 vfmadd213ss Horner stages + 2*div). -- twin_check gate: fc-scan grid (7 fc, Q=0.9999978, gain=4.132, tone 1000 Hz) - max |rel err| = 1.27e-5 vs double reference res_at (float rcpps parity). - DUAL sweep (Q=0.1..10 x tones 500/2000, fc=500) max = 1.23e-5. Both PASS. -- Constants locked: A1 = B1 = cos(w0)*-2.0, z1 = conj(exp(+i*theta)), - z2 = cplx_mul(z1,z1), out = 2*B/A. +## PHASE-3 Caller + Grid -## Confirmed scalar math (from generator + cplx-div kernels) -- Generator FUN_180533ec0 (per band, case8): - `fVar1 = sqrtf(param_5)` (param_5 = pow(10, sens/20) via cd6 IAT stub) - `w0 = max(2.0, freq)*2*pi / fs_total` (fs_total = 2*FS frame?) - `p = sin(w0)*0.5 / Q` - `dVar5 = p*fVar1` (A tap), `param_4 = p/fVar1` (B tap) - A = [1+dVar5, dVar2, 1-dVar5], B = [1+param_4, dVar2, 1-param_4] - where dVar2 = cos(w0) * DAT_1824c46b0 (=-2.0? VERIFY) -- Twin FUN_180535880: for each FFT bin z (rotor): - `res(z) = 2 * B(z) / A(z)`, B(z)=B0+B1*z+B2*z^2, A(z)=A0+A1*z+A2*z^2 - implemented as complex FMA accumulation over 3 taps: - r15 (A-accum) and r12 (B-accum) via 0x180001fd0 vfmadd213ss (coeff [rsp+k*4+0x28]=A_k, [+0x38]=B_k) - rbp buffer = z powers via rotor ops (0x1800019a0 fill / 0x1800021e0 / 0x1800018b0 / 0x180001af0 / 0x180001dc0) - final 0x180001e20 -> 0x181a775a0 cplx-div: out = 2*B/A (xmm7=0x40000000=2.0, xmm6=NaN guard) -- cplx-div kernel 0x181a77520: |A|^2 via shufps 0x88/0xdd, rcpps + 1 Newton step (xmm7=2.0), NaN guard via cmpeqps. - Matches rotor_kernel.hpp cplx-div exactly. +- Caller `FUN_180536300` (6218): scratch 52·N, `0x18052da00` ramp `2π/(os·sr)`, `0x18052dbc0` interleave `e^{+iθ}`. Twin всегда `180535880`, sibling `180536f90` dead. +- Grid: `os=4` (`+0x240080`), `sr=44100` (`+0x24`), `fs_total=176400`, `phase=2π/176400`, cell `k ↔ k/4 Hz` (0.25 Hz). +- case8: `+0x814=1`, `+0x103=1`, `param_5=10^(sens/20)`, `gain=√param_5=10^(sens/40)` → fit 4.132 ⇒ `sens≈24.65 dB`. -## Dispatch (all stubs share global 0x1826159a0 = 4 = AVX2) - 0x180001d00 -> 0x180009860 -> 0x180040d40 (cplx fill const, NT stores) - 0x1800019a0 -> 0x180004200 (float fill -> 0x181a63fe0) - 0x1800021e0 -> 0x1800130e0 (large AVX, 0x450 stack, rotor/exp?) - 0x1800018b0 -> 0x180003040 (-> 0x181a557c0) - 0x180001af0 -> 0x180005a20 (loop -> 0x18001a5a0) - 0x180001dc0 -> 0x18000ad60 (cplx op r14*rbp) - 0x180001e20 -> 0x18000b820 (-> 0x181a775a0 cplx-div 2B/A) - 0x180001fd0 -> 0x18000f620 (vfmadd213ss: out = src*coeff + dst) +## PHASE-2 Gate -## Coefficient layout in twin (param_3 = {double A[3], double B[3]}) - stack: +0x28=A0 +0x2c=A1 +0x30=A2 | +0x38=B0 +0x3c=B1 +0x40=B2 - loop k=0..2 uses A_k via [rsp+k*4+0x28], B_k via [rsp+k*4+0x38] - r15 = out A-accum, r12 = out B-accum, rbp = z^1..z^2 buffer, r14 = rotor(z) input +- `dsp/twin.cpp`: `build_twin_coeff` (FUN_180533ec0), `cplx_div_exact` (rcpps+Newton), `cplx_mul_exact` (vfmaddsub), `twin_apply` `2·B/A`. +- Gate: fc-scan 7fc + dual Q0.1..10 PASS. -## PHASE-1 LOCK (empirical, 36 points, LUT chain B.11) - Effective sqrtf(param_5) needed ~= 4.13 (NOT 1.995). - => param_5 ~= 17.07 = 10^(1.2325) => internal sens_stored ~= 24.65 dB (not XML 12.0) - OR equivalent. RULED OUT: sqrt(10^(12/20))=1.995 (rmse 1.38/5.68); - 10^(12/20)=3.981 direct (t1kq 0.056 but t1k 0.934); |2B/A|^2 (1.5/6.6). - Sens setters (0x18053795d, 0x18056f002) both store RAW value; +0x808 scaling is HOST-side - (plugin controller, outside dump) => must be resolved via bit-exact render or host trace. - NOTE: single-gain compromise 4.132; t1kq wants 4.055, t1k wants 4.137 (tilt residual). +## Scalar math + +- Generator: `fVar1=√param_5`, `w0=max(2,freq)·2π/fs_total`, `p=sin(w0)·0.5/Q`, `A=[1+p·fVar1, −2cos w0, 1−p·fVar1]`, `B=[1+p/fVar1, −2cos w0, 1−p/fVar1]`. +- Twin: `res(z)=2·B(z)/A(z)`, Horner `0x180001fd0`, `cplx-div 0x181a77520`, `z2=z1·z1`. + +## Dispatch + +Стабы → `0x1826159a0=4` (AVX2). Layout `param_3={A[3],B[3]}` `+0x28/+0x38`, coeff `[rsp+k*4]`. + +## PHASE-1 lock + +`√param_5≈4.13` (не 1.995), `sens≈24.65 dB` (host ×2 над XML 12.0). Setter `+0x808` host-side. + +Детали декомпа — `handoff/nls_dasm/twin*.dis`, `dsp/twin.cpp:27`. diff --git a/handoff/SESSION_HANDOFF.md b/handoff/SESSION_HANDOFF.md index b181f66..7e32e43 100644 --- a/handoff/SESSION_HANDOFF.md +++ b/handoff/SESSION_HANDOFF.md @@ -1,256 +1,8 @@ -# SESSION HANDOFF — soothe2 detector reverse → bit-exact render +# SESSION_HANDOFF — перенесён в архив -Prepared: 2026-08-18 (checkpoint end-of-session: commits c8f97e4 + 60bf3a2 pushed). Start: READ THIS FIRST. +> **Исторический чекпоинт 2026-08-18 (коммиты c8f97e4+60bf3a2).** +> Актуально: декомп DSP-ядра ~95% закрыт, `FUN_180529fe0` mono-path, `FUN_180563440/563ce0/563a60` расшифрованы, дизассемблы в `handoff/nls_dasm/` (134 файла). +> Полный текст сохранён: [`handoff/archive/SESSION_HANDOFF_2026-08-18.md`](archive/SESSION_HANDOFF_2026-08-18.md) (256 строк, инвентарь декомпа §0, ключевые адреса §2, Phase-5 план §6). +> Навигация → [`handoff/NOTES_LEVEL_INDEX.md`](NOTES_LEVEL_INDEX.md) + [`handoff/BLOCKMAP_529fe0.md`](BLOCKMAP_529fe0.md) + [`README.md:13`](../README.md). -> **2026-08-20 UPDATE**: см. актуальный канон — `AGENTS.md` и `handoff/NOTES_LEVEL.md`. -> Нижеследующее «NOT DECODED»/«missing body» УСТАРЕЛО: `FUN_180529fe0` mono-path, -> `FUN_180563440/563ce0/563a60` — расшифрованы, полные дизассемблы скопированы в -> `handoff/nls_dasm/` (134 файла). Этот файл — исторический чекпоинт. - -## 0. DECOMPILATION INVENTORY (2026-08-19 — what's decoded, where, and what's missing) -Goal: bit-exact parity is gated by EXACT tables/window/constants. The chunk-level model hits -err ≤0.62 dB (dual) / ≤0.19 dB (al_*) — to go sample-exact we need precise values from the binary. - -### Static decomp assets IN REPO (use these, don't re-decompile): -- `ghidra-proj/soothe2.rep` — full Ghidra project (vst3 at ImageBase 0x180000000). -- `decomp_funs.txt` (312K lines, ~1640 functions), `fun_map.txt` (2285 addr→FUN), `consts.txt` (11602), - `decomp_dsp.txt` / `decomp_vtables.txt` / `decomp_candidates.txt` / `focus_decomp.txt`. Generators: - `Dump*.java`, `ImportRtti*.java`, `ListFuns.java`, `SearchRefs.java` (+ headless logs). -- RTTI: `rtti_dsp.json` / `rtti_full.json` — class hierarchy - `SpectralProcessor`, `Soothe2ModuleBase`, `FilterGraph`, - `DigitalFilter`, `IIRFilterExtended`, `AudioProcessingModule`. -- `handoff/nls_dasm/` — 120 hand-picked `.dis` (twin, iface_18052da00/dbc0, fft, generator, ctor). -- `dsp/` — working C++ transcription (twin, detect, freqpath, spectral, fft_stage) + harness; - `build/twin_check` passes float-parity gate (§2). - -### KEY DSP ADDRESSES — decoded / not decoded: -DECODED (formula-level, notes at NOTES_TWIN.md / NOTES_LEVEL.md): -- twin kernel `FUN_180535880`; generator `FUN_180533ec0`; caller `FUN_180536300`; dead sibling `180536f90`. -- level-weight formula `FUN_180530d30` (0x540880/884, warp 0x5406a8=0.87·x/(1+x/7.942), w=0.1^(...)). -- mask-apply entry `FUN_180529fe0` (accumulator 0x5407c8 += w·res; mask *= warp; FIR *= 0x540658). -- FFT-conv loop 0x52b550-0x52b8b5 (plan 0x540530, windows 0x540548/550/598, freq-axis 0x540698=offline const). -- level-path map: `0x563440` (LUT curve +0x188, 6 band-slots, combine→+0x2198), `0x56e3e0` (twin-mask factory), - `0x563ce0` (IIR level-tracker INIT only). - -NOT DECODED / MISSING FROM decomp_funs.txt (critical): ⚠️ → РЕШЕНО (2026-08-19/20), см. выше -- `FUN_180529fe0` body — РЕШЕНО (decomp в `/tmp/consumers_out.txt`, mask-цепь в `dsp/framed_model.cpp`). -- `FUN_180563440` / `FUN_180563a60` (LUT curve + gamma + combine), `FUN_180563ce0` — РЕШЕНО, - дизассемблы скопированы в `handoff/nls_dasm/` (`f_563440.dis`, `f_563a60.dis`, `f_563ce0.dis`, `f529fe0.dis`). -- Window `0x540658` — РЕШЕНО (live-захват, см. `handoff/NOTES_CAPTURE.md`; `rtwin_freq_44100.npy`). -- sens source: XML 12.0 → runtime sens_dB≈24.65 (host ×2) not found in dump; `IAT\*0x181bab370` outside dump. - -### Bridge model STATUS (2026-08-19): -- `framed_render.py` full STFT frame-render (N=2048, hop=512, sqrt-Hann, twin env tatt=11ms/trel=80ms): - `C(f)=G·LUT(xv)+W·warp(f)^A`, G=1.0850, W=0.2819, A=1.1377 (joint dual+al_* refit). -- Validation: dual q0.1/1/10 @500+2000 err ≤0.62 dB, envRmse@steady ≤0.78 dB; al_* lv3..24 err ≤0.19 dB. -- LUT = slanted al_*-leg (0.366@xv=-0.50 → 0.636@xv=+0.55), replaces flat B.12 (~0.5). Bugfix: clip range - must be [LY.min(), LY.max()] not [LY[0], LY[-1]]. -- Remaining structural residual: −0.6 dB systematic on dual 500Hz (q0.1/q1). Live evidence (avg per-bin - gain H=|Y|/|X|): reference mask is FLAT ~-10.2dB across 100-540Hz regardless of Q — model produces - res-shaped notch. Hypo tested: freq-smoothing of C(f) fails (kills 2000Hz). NEXT: scalar per-frame xv - (broadband level, not per-bin am/res) — see /tmp/smoothtest.py (+ edit scalar=True). - -## 1. Objective (unchanged since session 1) -Transcribe the decoded soothe2 detector ("twins" 0x180535880/0x180536f90, 2nd-order resonator) into -C++ and reach **bit-exact render parity** with the Reaper reference wavs in `/home/m/soothe-bt/*.wav` -(checking dB/magnitude parity first, then byte-diff). - -Path A (bit-exact) chosen. Phase 0,1,2 done. Phase 3,4 advanced. Phase 5 (render diff) is next. - -## 2. DECOMPILATION FINDINGS (2026-08-19 — FUN_180563440, FUN_180563ce0 decoded) - -### FUN_180563440 — LUT curve evaluation + band combine (222 lines disasm) -Structure: **3 phases per frame**: -1. **1024-bin LUT loop** (0x400 iterations): for each bin k: - - x = clamp(k * 0.0009775, 0, 1.0) = k/1024 - - Band config at +0x188: {A(+0x00), B(+0x04), threshold(+0x0c), flag(+0x10), callback(+0x50)} - - **Path 1** (callback exists): vtable call → dynamic LUT - - **Path 2** (flag=1, threshold≠1.0): **power-law** → centered = 2*x - 1, then `sign(x) * 10^(log10(|x|) / threshold)` — this is a **compression curve** controlled by sharpness/threshold - - **Path 3** (default): **linear interpolation** → `(B - A) * x + A` - - Output: double-precision at +0x198, stride 8 -2. **Twin-mask factory** (FUN_18056e3e0): 6 bands × 1024 bins, stride 0x2000 -3. **Combine loop**: stereo (max 2 channels), 6 bands, `1 - sum(band_masks)` - -### FUN_180563ce0 — IIR level-tracker INIT (163 lines disasm) -- **341 bins** (0x155 iterations), **order-3 IIR** (3 coefficients per bin) -- Coefficient: **0.1** (`0x3dcccccd` = IEEE 754 float 0.1) -- Initial state: [1.0, 0, 0, 0] and [-1.0, 0, 0, 0] (identity + zero) -- Buffer layout: 3 × (16 bytes coeff) per bin, stored at rcx+0x28/+0x40/+0x58 -- **Not the update loop** — init only; UPDATE is elsewhere - -### FUN_180529fe0 — Coefficient setup (2051 instructions, in decomp_funs.txt) -- **vtable method** on Soothe2Module -- **Lock** at +0x2404dc (atomic test-and-set) -- **PRNG state** at +0x2404e0, LCG with offset 0x3cdca -- **6-iteration coefficient generation** from 0x5408b0 buffer (LCG-indexed) -- **Depth scaling**: `powf(normalized, depth)` at +0x2c -- **Mask assembly**: normalize by 0x1a0 (NFFT), × 0x540870 (level weight), × 0x54088c (sharpness), invert -- **Copy output** via SIMD memcpy (thunk 0x181ba94b0) - -### FUN_18052e9b0 — SpectralProcessor main (3167 instructions, in decomp_funs.txt) -- **Same PRNG + coefficient setup** as FUN_180529fe0 -- **Band chain**: FUN_18052f500 (interleave) → FUN_18052ee70 (per-bin gain) → FUN_18052d650 (setup) → FUN_18052d920 (window) -- **Buffer alloc**: FUN_18052e190 for 0x540668, 0x540698, 0x5406a8, 0x5406b8-e8 (6 bands) -- **Depth scaling**: `powf(normalized, depth)` with depth at +0x2c -- **Final mask**: `1 - C` (inversion) -- **Window application**: `FIR *= 0x540658` (the live-captured window table) - -### Constants verified from binary: -| Constant | Address | Value | Meaning | -|---|---|---|---| -| SCALE | 0x24c3c54 | 0.000977517 | 1/1024 (bin→x) | -| ONE | 0x24c3ea4 | 1.0 | clamping max | -| TWO | 0x24c41e0 | 2.0 | centering (2*x-1) | -| NEG1 | 0x24c4680 | -1.0 | sign flip | -| HALF | 0x24c3d8c | 0.5 | threshold | -| DEPTH_SCALE | 0x24c4334 | 4.0 | depth range | -| DB_CONV | 0x24c43e0 | 8.6859 | 20/ln(10) | -| FLOOR | 0x24c4704 | -6.9078 | ln(0.001) | -| IIR_COEFF | embedded | 0.1 | attack/release per bin | -| IIR_ORDER | embedded | 3.0 | IIR filter order | -| LCG_OFFSET | embedded | 0x3cdca | PRNG state advance | - -### Live-captured tables (re-verified): -- `rwin_A0.npy` (0x1930100): 0.5→0.8, **frequency window** (NOT warp formula) -- `rwin_B0.npy` (0x1938180): 0→3.899, **power-law depth curve** (exponent ~0.66) -- `rwin_C0.npy` (0x19401c0): 0.596→0.126, **level-dependent weight** -- `r_freqaxis.npy`: 0→23988.3 Hz, 11.71 Hz spacing (48000/4096) - -### Key insight: The warp formula is NOT a table — it's computed at runtime -The empirical `0.87*7.942*x/(7.942+x)` is an approximation of a runtime computation -in FUN_180563440 that evaluates the LUT curve parametrically. The actual LUT has TWO modes: -- **Linear** (default): simple interpolation between A and B -- **Power-law** (flag=1): `sign(x) * 10^(log10(|x|) / C)` — compression curve - -## 3. PROOF OF STATE — run this first (5 min) -Everything below must reproduce. If `twin_check` fails, the transcription moved stale. - -```bash -cd /home/m/re-tools/dsp && cmake --build build && ./build/twin_check -# expect: -# max rel err = 1.268e-05, PASS = yes (float-parity) -# max sweep rel err = 1.233e-05, SWEEP PASS = yes, exit 0 -``` - -`build/soothe2_dsp.so` and `build/twin_check` are current. CMakeLists already has `twin.cpp` + `twin_check` executable. - -## 3. TRANSCRIPTION STATE (summary — details in NOTES_TWIN.md) -- `dsp/twin.hpp` + `dsp/twin.cpp` = twin kernel FUN_180535880: - - build_twin_coeff (FUN_180533ec0 double pipeline → cvtpd2ps floats) - - generator formula (confirmed): `fVar1=sqrtf(param_5)`; `w0=max(2.0,freq)·2π/fs_total`; - `p=(sin(w0)·0.5)/Q`; `A=[1+dVar5, dVar2, 1−dVar5]`, `B=[1+param_4, dVar2, 1−param_4]`, - `dVar2=cos(w0)·−2.0` (DAT_1824c46b0=−2.0). - - cplx_div_exact (rcpps+Newton: `ref=r0·(2−den·r0)`, NaN-guard cmpeqps+movmskps, numerator B·conj(A)) - - cplx_mul_exact (vfmaddsub213ps form `re=fma(br,ar,−bi·ai)`, `im=fma(br,ai,bi·ar)`) - - twin_apply: conj(z1) mirroring 0x1800018b0, z2=z1·z1, out=2·B/A -- **OPEN phase question (byte-exactness)**: conjugate shaping — (-α+π/2) vs (-α) axis. Magnitudes match - either way; final proof needed via phase of a single-bin run before claiming byte-exactness. - -## 4. GRID + CALLER (Phase 3 — details in NOTES_TWIN.md §Phase 3) -- Caller FUN_180536300 (decomp 6218, `caller_536300.dis`), scratch ≥52·N complex bytes: - +0..8N A-accum, +8N..16N B-accum, +16N..24N z-powers, +24N..32N rotor-in (filled 1.0), - +32N..36N phase ramp lVar1, +36N..44N rotor-out z1, +44N..52N lVar3(1.0)/lVar4(0.0) -- `0x18052da00(lVar1, scale=2π/(os·sr), N)` = ramp fill; `0x18052dbc0(param_2, lVar4, N)` cplx-interleave - → out slots get (cos,sin) = e^{+iθ}. -- **Grid law**: `os=*(int*)(cfg+0x240080)=4` (mov DWORD PTR [rbx+0x240080],0x4 @0x18052ce3e), - `sr=*(float*)(cfg+0x24)=44100.0` → generator `fs_total=os·sr=176400`; per-cell phase `2π/176400`; - cell k ↔ freq k/os Hz (0.25 Hz steps). Phase-1 FS=44100 was a valid normalization (res depends on f/fc). -- twin always = FUN_180535880 (type-info compare equal); sibling FUN_180536f90 dead. -- case8: numBands `+0x814`=1, `+0x103`=1; `param_5=10^(sens_dB/20)`, `gain=sqrtf(param_5)=10^(sens_dB/40)` - → fit gain=4.132 means stored `sens_dB@+0x101 ≈ 24.65`; freq=[band+0x804]=1000.0, Q=[band+0x80c]. - -## 5. LEVEL-WEIGHT FORMULA (Phase 4 — DECODED, NEGATIVE RESULT — NOTES_LEVEL.md §UPDATE 2026-08-18) -Exact formula from decomp 22330 (FUN_180530d30): -- `base=(2000/(sr·0.5))·iVar5/(bin+1)`, iVar5=NFFT/2+1 -- `w8=powf(base,0.25)` -- `v=[0x540880]·0.25·w8·fVar12` (fVar12=4.0 if offline flag [0x5408b8], else 1.0) -- `q=1/(1+v/([0x540880]·4))` (2nd pair 0x540884: q2=1/(1+v2/[0x540884])) -- `dVar1=(sr/[0x1a0])·[0x1ac]·0.001` -- `w=0.1^(1/(max(q·v,floor)·dVar1))`; bufs: 0x5406b8=w, 0x5406c8=1−w (and 0x5406d8/6e8 w/ 0x540884) -- consts: c3d3c=0.25, c4334=4.0, c3d8c=0.5, c45b4=2000.0, c3f70=0.1 f32, c3e30=0.001 f64, c3f70=0.1 -- **Numerically**: w(500)=w(1000)=w(2000)≈0 (1e-20..1e-0.07), complements≈1 → but the weights are STILL - APPLIED (as 1−w ≈ 1) by FUN_180529fe0 (`0x5407c8[band] += 0x5406c8·res_upper + 0x5406e8·res_lower`, - thunk 003c40, kernel 0x1800752e0 = vfmadd213pd) — the form is weight·resonance, not dead code. -- **TILT analysis (2026-08-18, checkpoint NEGATIVE result)**: warp `0x5406a8 = 0.87·x/(1+x/K)`, - `K=exp(2.0723267)≈7.942`, `x=f/2000`, applied by FUN_180529fe0 as `mask *= 0x5406a8` (float, thunk 008700) - BEFORE the FFT-conv. warp: 500→0.211, 1000→0.409, 2000→0.773 (lin ratio 2000/500=3.67). - **BUT warp ≠ empirical tilt (ratio 1.27)**: `C(dB2000)=depth·warp(2000)·LUT_max = 0.864·0.772·0.667=0.445`, - yet red2000=15.2 dB needs `C=0.826` ⇒ warp alone CANNOT produce deep-2000 (dual_b1q). Empirical tilt is the - envelope of the BLEND term (0x5407c8 += weights·res + freq-axis 0x540698) + FFT-conv shaping, NOT warp — - simple tilt→warp substitution in model_lut.py is INVALID. Nothing else decoded yet closes dual_b1q. -- **FUN_180563ce0 decoded (2026-08-18)**: INIT of per-bin IIR level-trackers (0x156=342 bins, order=3.0f - 0x40400000, coeff A/B from 0x24c4780/4790, strided fill; third pass +0xffd0 fills 0x40e00000=7.0f). - Not the update loop. Full disasm: `/tmp/opencode/f_563ce0.dis`. -- **Level-path map (2026-08-18, NOTES_LEVEL.md)**: 0x563440 (LUT curve +0x188, gamma, 6 band-slots, combine - →+0x2198, LUT pairs +0x98 at 0x3ff pts) + 0x56e3e0 twin-mask factory (N=1024, DOUBLE, LUT-ramp input) + - 0x563ce0 IIR init. 0x563440 NOT in decomp_funs.txt (search via /tmp/opencode/f_563440.dis + level_notes.md). -- **DIAGNOSTIC BRIDGE DONE (2026-08-18) — model_fir.py**: реальная цепочка из FUN_180529fe0 - воспроизводит 36 точек до rmse=0.236 dB БЕЗ эмпирического tilt: - `C(f) = g·LUT(log10(L0/res(f))) + w·warp(f)^a`, g=1.221, w=0.358, a=3.143. - - dual500-константа = res_band(500;fc=500)=0.117 Q-НЕЗАВИСИМ, не tilt; - - dual2000-глубина = АДДИТИВНЫЙ терм `w·warp^3.14` (на 1000 Гц вклад ≤0.021 — иначе t1k - рушится; на 2000 = 0.149) — это аккумулятор `0x5407c8 += weights·res + mask` (шаг 5), - НЕ мультипликация warp·LUT (та проваливается >10 dB); - - warp^~3.14 ≈ π — подозрение на кратный каскад (0x540698 freq-axis, ∏0x540688, двойной FFT); - - остаток 0.7 dB при Q=0.1 = форма LUT-колена 0.574, не закрывается per-bin → нужен FFT-уровень. -- **FFT-CONV FULLY MAPPED (2026-08-18, NOTES_LEVEL.md §2026-08-18c, /tmp/opencode/f_52b570.dis)**: - 0x535a70 = dispatcher to base-CRT FFT (0x140a10/0x140a70); 0x540548/0x540550/0x540598 = FFT-plan - tables (plan 0x540530, built by 0x52dc30), NOT windows. Per-band loop 0x52b550-0x52b8b5: - mask→fwdFFT→invFFT→fill low/high (xmm13/xmm9 consts)→inv→cplx-op→fwd→`FIR *= 0x540658`→fill→inv→ - `FIR[0]=1,FIR[1]=0`→`*=0x540888`→copy out. - **freq-axis 0x540698**: OFFLINE = constant scalar (NOT exp-formula — exp/rand = online dither). - **Window 0x540658**: only ONE direct ref in dump (read at 0x52b771), written only indirectly — - STATICALLY INVISIBLE → the only plausible source of warp^3.14/LUT-knee. Need runtime capture or - full-pipeline empirical closure. - -## 6. PHASE 5 PLAN (updated 2026-08-18 — bridge done, tilt replaced, FFT mined out) -Decision (user): bridge FIRST. **DONE — bridge result above in §5.** -Status: step 2 DONE, step 3 PARTIAL (static analysis exhausted). -1. (15 min) Proof-of-state above; confirm twin + mdl files complete; repo at `60bf3a2`. -2. **Replace empirical tilt** in model_lut.py with `g·LUT(xv) + w·warp(f)^a` — **DONE**, rmse 0.236 dB - reproduced, committed `c8f97e4`. Optional refinement fit (2 LUT-knots) reaches 0.167 but is overfit. -3. **Close the 0.7-dB gap**: FFT-conv loop mapped + freq-axis cleared (offline const) — **DONE/PARTIAL**, - committed `60bf3a2`. Remaining unknown = 0x540658 window content (static-invisible). Options: - (a) runtime capture of 0x540658, (b) skip and close empirically in full-pipeline step 5. -4. Runtime values: per-bin IIR level trackers (0x563ce0), 0x540880/884, `[0x1a0]/[0x1ac]`, cell count N. -5. Full pipeline + render diff vs `/home/m/soothe-bt/*.wav`: dB parity → byte parity. - -NEXT SESSION START (recommendation): skip deeper FFT mining; go straight to step 5 full pipeline -(mask-chain already in model_lut.py B.12 at 0.236; freq-axis const; FFT-conv reduces to a per-bin -smoothing + 0x540658 shaping that can be fit/absorbed). Runtime capture of 0x540658 only if step 5 -stalls on the Q=0.1 knee. - -### STEP 5 STARTED (2026-08-18) — dB-parity harness DONE (commit f96bb42) -- `/home/m/re-tools/render_parity.py`: measures steady-state per-tone reduction DIRECTLY on the - reference wavs (Goertzel tone-amp over late steady window; handles 16/24-bit, mono/stereo). - NOTE: Goertzel must use the SEQUENTIAL update form (s2=s1;s1=s0;s0=v+cw*s1-s2) — the tuple-unpack - form is numerically unstable (overflows ~1500 steps). -- Validation: measured dual Q-scan == stored dataset (dual.wav is 4s mono 16-bit; renders 6s stereo 24-bit). -- **dB-PARITY RESULT (canonical B.12, 36 pts, real wavs): TOTAL rmse=0.268 dB** - (dual500 0.130, dual2000 0.387, t1kq 0.320, t1k 0.076). - residual structure: dual2000 -0.80@Q0.1..+0.36@Q10 (LUT-knee), t1kq +0.36..+0.26 (level region), - t1k ±0.1 (good). Refit (g,w,a)=(1.224,0.379,3.405)→0.264; +2 LUT-knots→0.203 (plateau). -- CONCLUSION: B.12 form plateau ~0.20-0.27 dB vs real renders; residual is structural (0x540658 - FFT shaping + level-curve region), matches plan expectation. Re-measured-on-wav numbers differ - slightly from the stored model_lut.py dataset (t1kq ~+0.3 dB) — different steady-window method. -- Open: full PIPELINE frame-render (STFT + per-frame mask + time constants) is the remaining big - lift for byte-parity; dB-parity milestone achieved at 0.268 dB. - -Candidates that may consume Phase 5 time: source of sens≈24.65 (host ×2 over XML 12.0 — not found in dump), -IAT\*0x181bab370 outside dump (non-blocking), final z¹ phase proof, the 0x563440 LUT curve (+0x188) values. - -## 7. FILES LAYOUT -- `/home/m/re-tools/dsp/` — twin.{hpp,cpp}, twin_check.cpp, CMakeLists.txt, build/ -- `/home/m/re-tools/model_lut.py` — model **B.12** (Q=xmlq, gain=10^(sens/20), depth 0.8639736175537109, - `C=g·LUT(xv)+w·warp^a`, g=1.221/w=0.358/a=3.143) — 0.236 dB bridge model; PCHIP LUT nodes frozen -- `/home/m/re-tools/model_fir.py` — bridge canonical source (rmse 0.236, committed b1066f3) -- `/home/m/re-tools/framed_render.py` — full frame-render pilot (2026-08-19 params; modes dual|al) -- `/home/m/re-tools/rwin_A0/A1/B0/C0.npy`, `r_freqaxis.npy`, `rwin_warp.npy` — live-window tables (48k) -- `/home/m/re-tools/rtsnap.py` — live-process page snapshotter; snapshots /tmp/rt{A,B,C,D}.{raw,idx} -- `/home/m/re-tools/handoff/decode_rpp_full.py`, `handoff/rpp_allparams.py` — RPP b64-XML full decoder - (trim to len%4==0, ` copy lVar2->base(0x1800096c0); + base*=product (0x180008700); if band_active: save->lVar4, product=1.0; out=copy lVar4->param_2. + => out = PROD_active_bands mask_band(норм. аудио). На |x|<1 z=ротор(1e-5)=i -> маска ~пост. +(b) FUN_18056e3e0 (double, N=0x400=1024): вызван FUN_180563440 (6 band-слотов). Аналогично. + Вход param_3 в контексте FUN_180563440 = LUT-рампа [0,1] (param_1+0x198, шаг 1/1023), + shape через кривую param_1+0x188 => это УРОВНЕВАЯ LUT-маска, не аудио. + +## FUN_180563440 (per-block маска-таблица, 6 полос) +- param_1+0x198[i] = i*(1/1023), затем маппинг кривой param_1+0x188: + pfVar10[0]=min, [1]=max, [3]=gamma-кривая, [4]=флаг linear, [+0x14]=nodes. + linear: val = min+(max-min)*exp(log(x)/c) = min+(max-min)*x^(1/c) (gamma). +- 6x FUN_18056e3e0 -> 6 полосных масок (param_1+0x4198+0x2000k); комбайн -> param_1+0x2198; + затем LUT (param_1+0x98): пары (x=i*fVar3, y=mask) на 0x3ff точек (уровень->маска). +- вызывается FUN_1805631c0 (vtable 0x1824b14f0) + FUN_180563ce0/563fa0 (следующие шаги). + +## FUN_180563ce0 — per-bin IIR level-трекеры (342 бина, 2nd-order state pairs) +- Константы 0x1824c4780/4788/4790/4798 (double A/B), 0x40400000=3.0f (порядок). +- Буферы: param_1+0x28 (float order), +0x40/+0x58 (double state A/B), stride 0x60, 0x156 итераций. + => на каждый бин ставится 2nd-order IIR (атака/релиз) для сглаживания уровня. + +## FUN_180530b60 — параметры сглаживания (log-interp + exp) +- cac=exp, cd0=log. freq-диапазон: online 800..1200 Гц (DAT_1824c4594=800/45a8=1200), + offline 15..180 Гц (4418=15, 4518=180), gain 0.01..1.0 (3c70=0.01, 3ea4=1.0). +- FUN_180533340(+0x2404e8/0x340500/0x440518, ...) — инициализация smoothing-фильтров + (3 экземпляра: 3 канала/oversample?). + +## FUN_180530d30 — per-bin СПЕКТРАЛЬНЫЕ ВЕСА (0x5406b8/0x5406c8/0x5406d8/0x5406e8) +- fVar9 = cdc(2000/(sr*0.5)*513/(i+1)) = cdc(2000/f_bin) — pow? (IAT-стаб 0x181b3b378). +- fVar11 = [param_1+0x540880]*0.25*fVar9*fVar13 (0x540880/884 = per-band уровень/сенс). +- buf[i] = cd6(0.1, 1.0/(exp(1/(1+x))*dVar1)); buf_c[i]=1-buf[i]. cd6 = log10? (0x1824c3f70=0.1). +- dVar1 = (sr/[param_1+0x1a0])*[param_1+0x1ac]*0.001. +- => частотно-зависимый вес: 2000 Гц (f_bin больший) -> fVar9 другой, чем 500 Гц. + Кандидат объяснения red2000>red500 (dual_b1q): вес нормирует уровень бина выше на + высоких частотах -> избыток уровня больше -> глубже крас. ПОТРЕБУЕТ ВЕРИФИКАЦИЮ. + +## IAT-хелперы (0x181a14xxx — jmp [GOT 0x181b3b3xx], цели 0x6fffff, динамич. загрузка) +- Последовательность стабов каждые ~6-12 байт => стандартные CRT math (в порядке импорта). +- Семантика по алгебре: cac=exp, cd0=log (log-interp в 530b60), cdc=pow (2-арг, pow(x,0.7/2.0)), + cd6=log10? (cd6(0.1, y)=log10(y)/log10(0.1) в 530d30), cd6 может быть log2. +- cf4/ce2/c9a/cc4/cfa — для case1/case8 генераторов (cfa в FUN_1805343e0: w=1/cfa(fc*pi/fs) + => cfa=sin, cf4-цепочка для q-клипинга). + +## КЛЮЧЕВОЙ ВЫВОД +- Частотная СЕЛЕКТИВНОСТЬ маски = freq-path нотч |2B/A| (m2c, центр=fc) на оси бинов + (t1kq fc-скан rmse=0.007). +- ГЛУБИНА/уровневая зависимость = level-path LUT (уровень->маска, кривая +0x188) + + per-bin IIR-трекеры уровня (0x563ce0) + спектр. веса (0x530d30, 2000/f_bin). +- dual_b1q (red2000>red500 при q<1) требует совместной модели level*веса*нотч — + единственный способ получить больше cut на 2000 чем на 500 при band=500. + +## ============ UPDATE: CONSUMER OF PER-BIN WEIGHTS FOUND (FUN_180529fe0) ============ + +## FUN_180529fe0 — per-block СПЕКТРАЛЬНЫЙ ПРИМЕНИТЕЛЬ (5 каналов, ключевой DSP-цикл) +Сигнатура (param_1=DSP-объект, param_2, param_3, param_4=nBands). Для каждой полосы: +1. 0x540678[band] *= (fVar30/sr) * 0x540870 (нормализация уровня × sens 0x540870) +2. IIR-сглаживание спектра (5 каналов, состояния 0x540528/2c04f8/3404f8/4c0528/440510, + через DBL-петли: y += x*a + y*b, backward/forward проходы) => level-трекер per-bin +3. 0x5406f8 = резонансная реакция полосы (копия 0x540678[band], каскад 0x540688-коэфф, + scale fVar30/nBands) [здесь twin-результат 0x5406f8] +4. 0x5406f8 *= 0x540698 (частотная ось, log-интерп) и *= 0x5406a8 (axis) +5. *** ПРИМЕНЕНИЕ per-bin ВЕСОВ: *** + thunk_180003c40(lVar21=0x5406f8+N, 0x5406c8, 0x5407c8[band], iVar19, 4) -> 0x5407c8 += 0x5406c8*contrib + thunk_180003c40(lVar17=0x5406f8, 0x5406e8, 0x5407c8[band], iVar19) -> 0x5407c8 += 0x5406e8*contrib + (0x5406c8=1-0x5406b8, 0x5406e8=1-0x5406d8 — КОМПЛЕМЕНТАРНЫЕ веса) +6. накопление: thunk_180005a20(0x5407c8[band], 0x540678[band], iVar19) +7. dry/wet: 0x1c[band]*scale (0x5408b8==0: +0x540678*0x1c[band]*...) +8. FFT-свёртка в time-domain: FUN_180535a70(0x540628, 0x540678[band], nBins+1) -> frame; + FFT-таблицы 0x540548/550/598; 0x540668 -> out-буфер (0x3f800000=1.0 в DC-бин). + +## FUN_180530d30 — точная формула per-bin весов (0x5406b8/6c8/6d8/6e8) + iVar6 = NFFT/2+1; fVar12 = 2000/(0x24*0.5) [p24=40000 -> fVar12=0.1] + fVar13 = 4.0 (или 1.0 если 0x5408b8); dVar1 = (0x24/sr)*0x1ac*0.001 [секунды] + for i in bins: + fVar9 = cdc( (fVar12*iVar6)/(i+1) ) = exp?/pow? (0.1*513=51.3 / (i+1)) + fVar11 = 0x540880 * 0.25 * fVar9 * fVar13 + dVar10 = 1/(1 + fVar11/(0x540880*4)) = 1/(1+fVar11/(4*level)) + dVar10 = exp(dVar10 * fVar11) + dVar10 = cd6(0.1, 1.0/(dVar10*dVar1)) = log10(1/(dVar10*dVar1))/log10(0.1) + buf0x5406b8[i] = dVar10; buf0x5406c8[i] = 1-dVar10 + (аналогично 0x540884 -> 0x5406d8/6e8) + Численно (p24=40000, sr=48000, 0x540880=1, 0x5408b8=0): + bin500: fVar9=exp(4.4)=81 -> w=2.49 ; комплемент 1-w=-1.49 + bin2000: fVar9=exp(1.17)=3.2 -> w=1.60 ; комплемент = -0.60 + => частотно-зависимые (сильно убывающие с частотой) веса. НО q входит через + 0x540880/884 (устанавливается из полосы) и через 0x5406f8-резонанс. + +## BUFFERS (map окончательный) + 0x540698 freq-ось (log-интерп, множится в band-ответ) + 0x5406a8 axis-буфер (множится в band-ответ) + 0x5406b8/6c8, 0x5406d8/6e8 per-bin level-веса (пары w, 1-w) — потребляются в FUN_180529fe0 + 0x540678[band] per-band спектр/вход (0x10-стрide, 2 слова = {ptr,count}? нет, 0x540678+0x10k) + 0x5407c8[band] per-band маска-аккумулятор + 0x540748 warp-ось = 8.3 - 7/(1+exp((x*20000-120)*(-0.01))) ~= 1.3 (DC-буст 6.68), НЕ f/(f+K)! + 0x540628 FFT-work, 0x540548/550/598 FFT-таблицы, 0x540668 out, 0x5406f8 band-фильтр/рабочий + +## FUN_180535ae0 = конструктор DSP-объекта: 0x540874=1.0, 0x54087c=0.5, 0x540884=1.0, + 0x54088c=1.0, 0x540894=1.0, 0x24=0x472c4400=40000.0f (!), 0x5408ac=0x01000000. + +## FUN_180535f10 = деструктор (free всех 0x5406xx-буферов). + +## ИТОГ: dual_b1q объясняется комбинацией + mask(bin) = |резонанс_полосы|(bin) × freq_axis(bin) × level_weight(bin, 2000/f_bin) + где level_weight через exp(51.3/(bin+1)) даёт РАЗНЫЕ веса на 500 и 2000 Гц, и на 2000 Гц + маска глубже при низком Q (резонанс шире/мелкие). Верифицировать в standalone-харнессе. + +## ============ UPDATE 2026-08-18: 0x530d30 weights DECODED + NUMERICALLY NEGATIVE ============ +- Exact formula (decomp 22330, FUN_180530d30): per bin + base = (2000.0/(sr*0.5))*iVar5/(bin+1) [iVar5=NFFT/2+1] + w8 = powf(base, 0.25) + v = [0x540880]*0.25*w8*fVar12 [fVar12=4.0 if offline flag, else 1.0] + q = 1/(1 + v/([0x540880]*4)) (second pair 0x540884: q2=1/(1+v2/[0x540884])) + dVar1= (sr/[0x1a0])*[0x1ac]*0.001 + w = 0.1^(1/(max(q*v,floor)*dVar1)) [0x1824c3f70=0.1, 0x1824c3e30=0.001] + buf0x5406b8 = w ; buf0x5406c8 = 1-w (and 0x5406d8/6e8 with 0x540884) +- Constants locked: c3d3c=0.25, c4334=4.0, c3d8c=0.5, c45b4=2000.0, c41e0=2.0. +- NUMERIC RESULT: at any plausible runtime (sr=44100, NFFT=1024, L=0.05..1.0, hop 1024..4096, + n1ac 1..12) -> w(500)=w(1000)=w(2000) ~= 0 (=10^-20..10^-0.07); complements ~= 1. + => the 0x530d30 level-weights DO NOT create the 2000-vs-500 tilt (ratio2000/500 ~1.0). +- CONCLUSION: dual_b1q red2000>red500 does NOT come from 0x530d30. Suspect = freq-axis warp + (0x540698 '8.3-7/(1+exp((x*20000-120)*(-0.01)))' ~1.3 DC boost) x resonance. The model's + empirical tilt {500:1.414,1000:1.454,2000:1.795} stays as the placeholder; replacing it + requires FUN_180529fe0 consumer step-5 algebra + runtime level values (Phase-4/5 dive). + +## ============ UPDATE 2026-08-18 (CHECKPOINT): tilt != warp, level-path decoded ============ +- FUN_180563ce0 = INIT of per-bin IIR level trackers (not update): 0x156=342 bins, order=3.0f (0x40400000), + coeff A/B mvaps 0x24c4780/4790, strided fill; 3rd pass fills 0x40e00000=7.0f (post-gain). FULL: /tmp/opencode/f_563ce0.dis. +- Level-path map: 0x563440 (LUT curve +0x188: [min,max,gamma,linear flag,nodes]; 6 bands via 0x56e3e0 + double-twin N=1024 with LUT-ramp input; combine -> +0x2198; LUT pairs +0x98 at 0x3ff pts level->mask). + NOT in decomp_funs.txt; use /tmp/opencode/f_563440.dis + level_notes.md (LOCAL: /home/m/re-tools/handoff/). +- **DECISIVE NEGATIVE**: warp(2000)=0.773 cannot reach dual red2000=15.2dB — C=depth·warp·LUTmax=0.864·0.772·0.667 + =0.445 < needed 0.826 (C=1-10^(-15.2/20)). warp is NOT the empirical tilt. The gap is closed by the blend + step (0x5407c8 += weights·res_upper/lower, freq-axis 0x540698) + FFT-conv 0x535a70 shaping. +- CHECKPOINT decision (user): STOP; next session = diagnostic bridge (two-term mask: warp·LUT + α·freq-axis_term, + fit vs measured curves) BEFORE transcribing FFT. do NOT curve-fit without the blend term. + +## ============ UPDATE 2026-08-18 v2: BRIDGE DONE + FUN_180529fe0 full algebra ============ +- **Полный декомп FUN_180529fe0: /tmp/consumers_out.txt:471-1277** (f529fe0.dis в /tmp обрезан на 52a813). + Поправки к прежним заметкам: + - 0x5408b0 = таблица ГРАНИЦ бинов (прелюдия: cvttss2si+imul+idiv, iVar20=band-start, iVar19=len), НЕ LUT. + - Уровневая маска резолвится ИНЛАЙН: fVar30=0x5408b0[idx]; fVar25=(float)FUN_181a14cdc()=pow (IAT→GOT + 0x181bab378, CRT-цель вне дампа); пары `×(fVar30−fVar25)`/`+fVar25·` (dry/wet уровня). + - Цепочка per-band (по декомпу): 0x540678=res; res*=∏0x540688[k]; res*=(fVar30/nBands); *=fVar29·0x54088c + (fVar29=(fVar30/0x1a0)·0x540870); IIR 0x52d650 state 0x440518; двойное IIR-сглаживание (double, + states 0x3404f8/2c04f8 и 0x440510/3c0510); 0x5407c8+=0x5406c8·res_up + 0x5406e8·res_low + 0x540678 + (003c40, kernel vfmadd213pd) — **АДДИТИВНЫЙ аккумулятор**; 0x540678 *= res(008700) и *= warp 0x5406a8; + offline: *=0x1c[band]·fVar27 (глубина, D=-0.5@0x24c4670); dry/wet: mask·fVar30·0x540888 + (fVar11−fVar30) + (fVar11=1.0 c3ea4) — **педастал**; FFT-conv 0x535a70 (fwd, window 0x540550/598, kill mirror, + inv, ×0x540658, fwd, inv, 0x540668[0]=1.0[1]=0, apply FIR к param_2[band]). + - Константы offline: c3ea4=1.0, c3e28=0.8, c4670=−0.5, c4674=−0.7, c4680=−1.0, c3c58=0.001. +- **BRIDGE RESULT (model_fir.py, rmse=0.236 dB)**: C(f)=1.221·LUT(log10(L0/res)) + 0.358·warp(f)^3.143. + res(500;fc500)=0.117 Q-независим -> C500-константа без tilt; xv(2000)=0.308..−0.656 (Q) -> C2000-и-гг-форма; + аддитивный терм warp^3.14 даёт +0.15 на 2000 и ≤0.02 на 1000 (иначе t1k рушится) — единственная точка, + совместимая с 36 замерами. Мультипликативный warp·LUT проваливается (>10 dB). warp^3.14≈π: кратный каскад? + Остаток 0.7 dB (Q=0.1) = LUT-колено 0.574 -> нужен FFT-уровень (0x535a70 + окно 0x540658 + freq-axis + 0x540698), см. SESSION_HANDOFF §5-6. + +## ============ UPDATE 2026-08-20h: P2 mask-accumulator combine kernels DECODED ============ +Raw bytes from rt snap (objdump of image 0x180008d60/5a20/3c40). Implemented in dsp/levelpath.cpp. + +### Exact CRT thunk semantics (per-bin double ops in FUN_180529fe0): +- **0x8d60 combine3(dst,a,b,n) = sub**: out[i] = a[i] − b[i] (vsubpd; dst is the 3rd pointer). + Per-band call: 0x5406f8[i] = 0x540678[i] − 0x5407c8[i]. (corrects NOTES:348 "combine" TBD) +- **0x5a20 acc_add(dst,src,n)**: dst[i] += src[i] (double; kernel 0x18001a5a0). +- **0x3c40 acc_fma(dst,a,b,n)**: dst[i] += a[i]·b[i] (double; vfmadd213pd). +- 0x11940 = copy/mirror (complex, 5th arg stride 0/4 → kernel 0x1a88300), 0x6e40 = dst=src·scalar, + 0x15060 = buf+=scalar, 0x8700 = dst*=src (float, warp), 0x9be0 = buf=scalar·buf. + +### Full mask-accumulator per band (0x5408b8==0): + 1. 0x5406f8 = 0x540678 − 0x5407c8 (8d60 sub) + 2. mirror 0x5406f8 halves (11940) + 3. 0x5407c8 += 0x5406c8 · 0x5406f8_upper (3c40, stride4) + 4. 0x5407c8 += 0x5406e8 · 0x5406f8_lower (3c40) + 5. 0x5407c8 += 0x540678 (5a20) + +## ============ UPDATE 2026-08-20g: P2.5 twin-mask factory + band LUT apply DECODED ============ +Source: decomp_funs2.txt (FUN_18056e3e0:7988, FUN_180563a60:8975, FUN_180563440:7697) + +f_56e3e0.dis + f_563a60.dis. Implemented in dsp/levelpath.cpp (levelpath_check ALL OK). + +### FUN_18056e3e0 = "twin-mask factory" is a CONSTANT FILL, NOT per-bin twin resonance: + s = DAT_1824c4248 / (double)((float)[ctx+0x240080] * [ctx+0x24]) + = 2π / (count · SR) (0x24c4248 = 6.283185307 = 2π) + fills 0x400 bins of the mask (stride 0x2000/band) with s. The twin resonance shape enters + elsewhere (via the LUT curve FUN_180563440), NOT here. (corrects NOTES:547 "twin resonance".) + +### FUN_180563a60 = band LUT apply (level -> gain), 6 bands × 0x400 bins: + level_dB = logf(mask[band][bin]) · 8.6859 (20·log10) + axis[bin] = bin · (1/1024) (0x24c3c50 = 0.0009765625, DIFFERENT from 1/1023) + t = clamp((dB − A)/(B − A), 0, 1) (BandConfig ctx+0x180: A=min,B=max,gamma,flag) + if gamma == 1.0: val = t + elif flag == 0 (linear): val = t^gamma (powf, DIRECT gamma) + else (power-law): val = 0.5·(1 + sign(2t−1)·|2t−1|^gamma) + axis[bin+1] = val (pairs level, gain) + NOTE: this is the INVERSE of FUN_180563440 (which uses x^(1/γ)); 563a60 uses t^γ. + +### Constants locked (from rt snap rodata): + 0x24c4248 = 2π (double), 0x24c4230 = 5 (double), 0x24c3c50 = 1/1024, 0x24c3c54 = 1/1023. + +## ============ UPDATE 2026-08-19e: FUN_180529fe0 FULL DECOMP CONFIRMS MASK-CANON ============ +Source: /tmp/consumers_out.txt:471-1277 (full Ghidra decomp). Validates + completes the +"CONFIRMED per-band pipeline" section below (lines 237-253). Notes: + +### Mask chain (0x5408b8==0 mono path; 0x5408b8!=0 = dynamic/stereo-extra path) +Per band (band in [iVar20, lVar18)): + 1. scale: 0x540678[band] *= (fVar30/nBands)·0x540870·0x54088c (0x9be0 = buf*scalar) + 2. IIR smoother: FUN_18052d650(state 0x440518, out 0x5406f8, in 0x540678) then a 2nd IIR + (states 0x3404f8/0x2c04f8, count 0x2404e8) — double smoothing (att/release). + 3. online blend (0x5408b8==0): + 0x5406f8 = 0x540698·(1-mix), mix=0x54087c (0x6e40) + 0x5406f8 += mix·0.8 (fVar25=0x24c3e28=0.8) (0x15060) + 0x540678[band] = bigkernel(0x540678,0x5406f8) (0x26b820) + 4. combine: 0x5407c8[band] = combine3(0x5407c8,0x540678,0x5406f8) (0x8d60) + + mirror both halves (0x11940) + WEIGHT appends: + 0x5407c8 += 0x5406c8·0x5406f8_upper (0x3c40, stride4) + 0x5407c8 += 0x5406e8·0x5406f8_lower (0x3c40) + + 0x5407c8 += 0x540678[band] (0x5a20) + 5. (0x5408b8!=0 branch: freq-axis blend 0x6e40/0x15060 + 0x26b820 + 0x4f0400 weight) + 6. WARP (tilt): 0x540678 *= 0x5406a8 (freqpath warp) (0x8700) + 7. dry/wet: mask = mask·(fVar30·0x540888) + (1-fVar30) fVar30=0x540874−rnd (0x8800-ish) + 8. FFT-conv (0x52b550 steps 1-6) -> FIR -> conv with audio -> out[band] +- 0x5407c8 / 0x540678 / 0x540768: pointer arrays stride 0x10 (per band), 0x5406c8/6e8 = weight tables. +- Constants: fVar11=1.0 (c3ea4), fVar25=0.8 (c3e28), fVar28=0x24c4674(-0.7), fVar27=0x24c4670(-0.5). + +### Confirmed scalar A/R (0x540888/88c) = expf(p·0.11513), captured window tail degenerate flat. +=> Mask-canonic is now FULLY mapped structurally. Remaining for bit-exact = exact scalar values + (XMM9/XMM13, 0x540870/874/87c/88c from RPP) + the level-LUT curve A/B/γ (ctx+0x188 BandConfig). + Structural C++ port of steps 3-8 exists: dsp/fftconv.cpp (P1.3) + dsp/levelpath.cpp (LUT curve). + +## ============ UPDATE 2026-08-19d: f_52d990 / f_52d920 / f_52db50 DECODED (CORRECTION) ============ +Disasm: handoff/nls_dasm/f_52d990.dis (52 B), f_52d920.dis, f_52db50.dis; caller f_52b570.dis. + +### Identity (they are CRT-style thunks, NOT arithmetic kernels): +- **f_52d990(rcx=dst, rdx=src, r8d=count) = memmove/memcpy thunk.** + Prologue saves rbx/rsi/rdi; movs args into callee regs; indirect call through GOT 0x1bab008 + (the CRT "select scalar vs vector" resolver, same 0x2650db8 pair used everywhere); then tail-jumps + to 0x2000 (scalar path) or 0x1c40 (vector path). Returns void. This is a COPY, not a multiply. +- **f_52d920(rcx=dst, xmm1=val, r8d=count) = scalar fill (low-half filler).** tail 0x2030(float)/0x1d30(double). +- **f_52db50(rcx=dst, xmm1=val, r8d=count) = scalar fill variant.** tail 0x19a0/0x22d0. +=> The earlier NOTES "0x52d990 = FIR *= WINDOW" was WRONG. Corrected step map below. + +### Corrected step 5 (f_52b570:52b75e-52b78c): +``` + rax = [ctx+0x540534] ; N (FFT size) + rbp = N/2 + FIR[N] = 0 ; zero the Nyquist bin + rax = [ctx+0x540658] ; window base (WIN_WINDOW, 8193 f32) + rdx = rax + N/2*4 ; src = window + N/2 + call f_52d990(FIR, window+N/2, N/2) ; FIR[0..N/2-1] = window[N/2..N-1] (COPY) + call f_52db50(FIR+N/2, xmm9, N/2) ; FIR[N/2..N-1] = xmm9 (const fill) + inverse FFT +``` +So step 5 replaces the FIR's lower half with the SECOND half of the captured window +(window saturates to 1.0 at idx 2049; so window[N/2:] is all 1.0 WHATEVER N/2>=2049 — the tail +copy degenerates to a flat 1.0 unless N/2 < 2049, i.e. FFT size would have to be < 4098 for the +window shape to matter here). Upper half filled with a constant xmm9. + +### Corrected step 3 (f_52b570:52b685-52b6b3): +``` + rax = N; rbp = N/2 + FIR[N] = 0 + call f_52d920(FIR+4, xmm13, N/2-1) ; FIR[1..N/2-1] = xmm13 (LOW fill) + call f_52db50(FIR+4+N/2, xmm9, N/2-1) ; FIR[N/2+1..N-1] = xmm9 (HIGH fill) + inverse FFT +``` + +### Size inference & effective semantics +- window = 8193 f32; it saturates to 1.0 at idx 2049 (w[2048]=0.8, plateau 1.0 beyond). +- freq-axis = 2048 f32, spacing 48000/4096 = 11.718 (matches 4096 FFT half). => N≈4096, N/2≈2048. +- Then step 5 copies window[2048:4096] = {0.8 -> 1.0 (mostly flat)} into FIR[0:2048], + upper FIR[2048:4096]=xmm9(=0?), FIR[4096]=0. Effective = gentle low-pass ramp, near-flat. + This matches NOTES line 416: captured window adds almost nothing over empirical (it's essentially 1.0). + +### Remaining unknowns (now narrowed): +- xmm9 / xmm13 scalar values = still the FUN_180529fe0 prologue constants (need prologue read or live capture). +- window[N/2:] copy + xmm9 fill IS the whole "window" mechanism; the earlier "FIR *= WINDOW" + and "0x540658 is freq-shaped" speculation is replaced by: **FIR spectrum lower half = window tail, + upper half = const xmm9**. Window content already captured (rtwin_freq_44100.npy → WIN_WINDOW). + +## ============ UPDATE 2026-08-18c: FFT-CONV LOOP FULLY MAPPED (Phase 5 step 3 partial) ============ +Per-band FFT-conv loop disasm: /tmp/opencode/f_52b570.dis (0x52b550-0x52b8b5 == consumers :1195-1271). + +### What 0x535a70 / helpers ARE (they're dispatchers, real kernels in base-CRT): +- 0x535a70 = FFT fwd dispatcher -> 0x140a10 (scalar) / 0x140a70 (vector). NOT hand-transcribable, unnecessary. +- 0x540548/0x540550/0x540598 = FFT-PLAN table ptrs (plan 0x540530; +0x18/+0x20/+0x68), NOT windows. + Plan built by FUN_18052dc30(param_1+0x540530, [0x64]=FFT size, 0x540868). 0x540534 = plan+0x4 = N/2 usage. +- Per-band ops (dispatch variants collapsed): iVar20=N/2; r14=FIR(0x540668); + 1) 0x535a70(fftctx 0x540628, mask, N/2+1) forward FFT of mask + 2) 0x2210(0x540628, 0, r14, N/2+1) inv FFT into FIR + 3) fwd FFT (0x2180/0x1bb0) ; zero @[N] ; 0x52d920(r14+4, xmm13, N/2-1)=fill LOW half; + 0x52db50(r14+4+N/2, xmm9, N/2-1)=fill HIGH half; inv FFT (0x1a90/0x19d0) + 4) 0x140b30/0x140aa0(r14,r14,N/2+1) in-place complex op + 5) fwd FFT ; zero @[N] ; 0x52d990(r14, 0x540658+N/2, N/2) = FIR *= WINDOW 0x540658; + 0x52db50(r14+N/2, xmm9, N/2) = fill HIGH half; inv FFT + 6) FIR[0]=1.0f, FIR[1]=0; if 0x540890!=0: complex ops (0x1880/0x1ca0) + FIR *= 0x540888 over 2*N; + copy FIR->out (0x1df0/0x1f70). + +### Key unknowns (static analysis exhausted): +- **0x540658 window content**: ONLY 1 direct ref in whole dump (the read at 0x52b771). Not allocated in + consumers_out (:119-135 alloc 668/698/6a8/6b8/6c8/6d8/6e8/6f8/708/718/798), NOT set by 52dc30 (plan tables + only at +0x18/0x20/0x48/0x58/0x68). Written only indirectly -> needs runtime capture OR plan-internal + region. If 0x540658 is freq-shaped, it is the source of warp^3.14 (~π) & LUT-knee 0.574. +- xmm13/xmm9 fills = config constants from FUN_180529fe0 prologue (offline), unknown values. +- FFT size N = 2*0x540534; 0x1a0 = pow(2,floor(log2(SR/44100)))*412 (SR 44.1k -> 412); 0x540868=0x1a0*iVar9*8. + +### Conclusions: +- freq-axis NOT the boost source (offline const). FFT tables NOT windows. The whole freq-shaping funnels + through 0x540658 window in step 5 + the offline fill constants. To resolve: runtime capture of 0x540658 + (needs the plugin under a debugger) OR empirically back out its response from the model residual. +- Recommend: since bridge already at 0.236 and FFT window content is static-invisible, next high-value move = + full pipeline render (step 5) rather than more static FFT mining. + +## ============ UPDATE 2026-08-18b: FREQ-AXIS 0x540698 BUILDER (decomp :21735-21757) ============ +- Allocation+fill of freq-axis 0x540698 lives in the SAME function as the 0x540668 FIR window alloc + (decomp_funs.txt :21735-21757, the builder calls FUN_18052e190(param_1+0x540698, count=0x54086c)). +- OFFLINE branch (param_2=='\0'): the whole 0x540698 array is filled with ONE scalar + fVar29 = (DAT_18262b5c8 * LUT_BANDBOUNDS[uVar3+1] * DAT_18262b704 * LUT_BANDBOUNDS[uVar3] + fVar22) + * DAT_18262b700 + (DAT_18262b5c8/DAT_18262b704/DAT_18262b700 = Soothe2ModuleBase statics, uVar3 = 0x5408b0 boundary index). + => OFFLINE freq-axis is CONSTANT (per-band-band-width scalar), NOT the '8.3-7/(1+exp...)' formula. + The exp-formula suspicion was wrong for offline path. Any per-bin frequency dependence must enter via + WARP 0x5406a8 (freqpath.cpp, already transcribed) and the 0x540668 FIR (FFT-conv impulse). +- 0x540668 (FIR window, count 0x54086c*2) allocated right before; filled with 1.0f (0x3f800000) in offline + init at :21798-21804 (<=> flat impulse). So in offline the FFT-conv kernel starts as a pure del + a; the real kernel arrives from 0x535a70 processing (to transcribe next). +- 0x5406a0 = element count used by the fill loop (large, ~0x54086c). Buffer 0x5406b8..0x5406e8 + (level-weights caps) allocated at :21753-21757 with the same count. + +## ============ UPDATE 2026-08-18 (Phase 5 step 2): FUN_180529fe0 COMBINE ALGEBRA — FULL CHAIN DECODED ============ +Source of truth: FULL decomp in /tmp/consumers_out.txt:471-1277 (the decomp_funs.txt copy at :11425 is +TRUNCATED — Ghidra dropped the middle because of the noreturn/dispatch thunks `(*DAT_181bab008)(&..,&..)` +guarding small-vs-vectorized variants; the consumers_out.txt copy came from a previous session's +getFunctionContaining(0x52ac64) and has the complete per-band loop + FFT-conv). + +### Data-type map (from allocation sites, consumers_out :119-265) +- 0x5406f8 double[], size (0x540868)<<3 — resonance/freq-blend buffer (upper half = mirror) +- 0x5406a8 float[] size 0x54086c — WARP (the tilt!) +- 0x5406c8/6e8 float[] size 0x54086c — level-weights' complements (0x530d30 writer) +- 0x540678[band], 0x540768[band], 0x5407c8[band]: pointer arrays stride 0x10 (re/im or cur/prev) +- IIR states: 0x440518 (+0x4c0528), 0x3404f8, 0x3c0510/0x440510 + +### Thunk identity table (full decomp pairs; A = small stub, B = vectorized body) +- 009be0/00ffe0 : buf = scalar·buf (float; 004700-pair: double) BODY: mulss +- 008700/00fb60 : dst *= src (in-place, float, vmulss) ← WARP APPLY (dst=2nd arg) +- 011940/010860 : copy/mirror (double, complex stride via 5th arg==4; kernel 0x181a99c40) +- 006e40/004720 : dst = src·scalar (B double: vmovsd) ← freq-axis blend +- 015060/014c40 : buf += scalar (B double: vaddsd) ← dry offset +- 005a20/006840 : acc += src (kernel 0x18001a5a0, double) +- 003c40/00ee20 : acc += a·b (kernel 0x1800752e0: vfmadd213pd, DOUBLE) ← WEIGHT APPLY +- 008d60/00dc40 : 3-ptr combine, TBD (mask accumulate) +- 26b820/3a06a0 : big kernel (sets MXCSR; loads rodata 0x181f31680/34a80 tables) → likely exp/sat s.t. begin? TBD +- 2dc0e0/367980 : fills dst buffer with constant pattern 1.0/0.0 (vmovdqu rodata) — TBD (dry/wet?) + +### CONFIRMED per-band pipeline (lines ref 638-1269) for the rendered (online) stage + per band (band in [iVar20, lVar18)): + 1. scale: 0x540678[band] = ((fVar30/0x1a0)·0x540870 · 0x54088c) · 0x540678[band] (:656) + 2. IIR: FUN_18052d650(state=0x440518, out=0x5406f8, in=0x540678[band]) → smoother (:668) + 3. online: blend & weight into mask: + 0x5406f8 = 0x540698·(1−mix) 006e40 mix=0x54087c (:814) + 0x5406f8 += mix·0.8 015060 c3e28=0.8 (:823) + 0x540678[band] = bigkernel(0x540678[band],0x5406f8) 26b820 (:832) + 0x5407c8[band] = combine(0x5407c8[band],0x540678[band],0x5406f8) 00dc40 (:843) + mirror 0x5406f8 halves (011940, n and n+stride4) (:852 861) + WEIGHTS: 0x5407c8[band] += 0x5406c8·0x5406f8_upper 003c40(stride4) (:869) + 0x5407c8[band] += 0x5406e8·0x5406f8_lower 003c40 (:877) + 0x5407c8[band] += 0x540678[band] 005a20 (:885) + 4. WARP (TILT): 0x540678[band] *= 0x5406a8 008700 (:938,:947) + 5. dry/wet: 0x540678[band] = mask·(fVar30·0x540888) + (1−fVar30) fVar30=0x540874−rnd (:1184,:1193) + 6. FFT-conv: FUN_180535a70(0x540628, mask, n+1) fwd-FT → window×0x540658 (52d990) → inv-FT + → impulse 0x540668, DC=(1.0,0.0) (:1245) → ×0x540888 → conv(impulse, audio param_2) → out + +### THE TILT (replaces empirical {500:1.414,1000:1.454,2000:1.795}) + warp 0x5406a8 = 0.87·x/(1+x/K), K=exp(2.0723267)≈7.942, x = f_bin/2000 [c3e50=0.87, c4208, c45b4=2000] + warp(500)=0.211 warp(1000)=0.409 warp(2000)=0.773 (linear depth ratio 2000/500 = 3.67) + applied directly to the FLOAT mask before FFT-conv ⇒ enters the dB via the impulse response. + NOTE: 3.67 linear is ≫ the fitted tilt ratio (1.27) ⇒ the rest comes from the LUT-level flattening + (step 5 dry/wet + 0x540888) and the level sidechain 0x563xxx — that is the Phase-5 step-4 integration. + +## ============ 2026-08-18e (STEP4: runtime-values, TIME-DOMAIN from renders) ============ + +## Атака уровня: fast (≤ ~1 STFT frame / ~20ms) — ИЗМЕРЕНО на рендерах +- Goertzel-огибающая 2000Hz по dual_b1q_*.wav (100ms/20ms окна): env ПЛОСКАЯ 0.18 (q0.1), + 0.30 (q1), 0.32 (q10) уже на 0.2s; пик 0.208/0.344/0.364 (атака, ~первые 50ms меньше подавления). +- red(dB) по времени (вход dual 500+2000, канал 2000): t=0: 13.2 dB -> t=0.02s: 15.2 dB (steady). + => reduction выходит на стационар к ~20ms. 0.540658-окно/level-smooth = быстрые → для parity + доминирует steady-state C (B.12, 0.268 dB); атака нужна лишь как короткий onset (~1 frame). +- Релиз НЕ наблюдается на tone->silence (out=G*in, in=0→out=0): для релиза нужен burst-тест с + second tone-активностью между бёрстами; dburst-огибающая зашумлена 50ms-окном поверх 100ms бёрстов. + +## Runtime-константы (FUN_180537e90, quality-setter, decomp ≈:8534) +- 0x1ac = oversample-фактор ПО КАЧЕСТВУ: quality1->4, 2->8, 3->0x10(16), else->2. +- 0x1a4 = fVar6 / (float)0x1ac (см. строку 482: есть деление на 0x1ac => 0x1a4 = analysis-res". +- 0x1a0 = pow(2,floor(log2(SR/44100)))*412 (@44.1k = 412) — база cell-count геометрии. + +## Коэф-таблицы IIR 0x24c4780/0x24c4790 (soothe_mem.bin offset 0x24c4780) +- floats: {1,0,0,0,-1,0,0,0,0,1,0,0,1,1,0,0} (A) / {-1,0,...} (B) — шаблон блочный, НЕ чистая + attack/decay константа; doubles: 0.0078125=1/128 на 4-й/6-й слотах. => отложить, не декодить. +- Более информативна ctor-инициализация 0x540880=0x540884=1.0f (movl 0x3f800000, disasm 0x535d19). + +## ============ 2026-08-18f (STEP5b: FULL FRAME-RENDER PILOT - WORKS) ============ +## framed_render.py: STFT(2048/512, sqrt-Hann) -> per-bin amp 2|X|/wsum (att 11ms/rel 80ms) +## -> B.12 mask C=g*LUT(xv)+w*warp^a, gain=1-C -> OLA. VALID: dynamics match, steady ~B.12. +## al_* DATASET (центр band fc=1000 sens12, tone1000, level-свип 0..-24dBFS): +## lvl 0,-3,-6,-9,-12,-18,-24 dBFS -> red -104,-6.1,-7.9,-9.7,-11.6,-15.4,-19.5 dB. +## xv=-0.269..0.931 (res_center=0.1171) => REAL LUT-нога КРУЧЕ frozen-узлов (cap 0.667): +## на xv=0.931 C->1 (клиф), у B.12 только -9.5. КОНФЛИКТ с t1k fc-scan (15.6@0dBFS там же) +## => премьюза fc-scan рендеров неверна (вход/настройки) - ПЕРЕСОГЛАСОВАТЬ. +## al_* = калибровочный датасет для замены LUT-узлов B.11 (structural 0.540658-piece). + +## ============ 2026-08-18g (STEP5c: DATASET INCONSISTENCIES DECODED) ============ +## КЛЮЧ: RPP несёт base64 XML "" — декодер: +## b64 = join всех строк-бейс64 (>40 chars) из .rpp; decode utf8; regex id/value. +## 1) t1k/t1kq _b1f_* = МУЛЬТИБАНД: band1 on sens=12 + band2 on sens=-12 (fc=1778.7, q=4.5) +## + band3/4 on (defaults) => НЕ single-band. B.12 фит впитал multi-band depth-split +## => LUT_obs у b1f сидит на ДРУГОЙ кривой (0.07-0.17 vs 0.38-0.63 у al_/dual). +## 2) t1kq_only1_* = НАСТОЯЩИЙ single-band fc-скан (band2 on=0). НО в RPP НЕТ 'depth' => +## default depth (не 0.8639736) => нельзя мешать с al_/dual без нормализации depth. +## 3) tone_amp АБСОЛЮТНАЯ калибровка НЕСОСТОЯТЕЛЬНА (t1k: 1.058 vs true 0.215; t1kq: 0.284 vs 0.058). +## Для xv использовать FFT-амплитуду: seg=1..4s, Hann, A=4|X|/N (проверено: lv12 0.17759==peak). +## 4) ЧИСТЫЕ single-band датасеты (depth=0.8639736, band1 один): +## dual_b1q (22) + al_* (6) — СОВМЕСТИМЫ ~0.05 LUT_obs: +## dual@2000 xv -0.66..0.31 -> LUTobs 0.434..0.547 (гладкая S-кривая) +## dual@500 xv=0.574 -> 0.564 +## al_* xv -0.42..0.63 -> 0.381..0.626 (круче внизу, ~0.05-0.08 выше dual@2000) +## Остаток dual@2000 vs al_ на низких xv => семантика уровня: per-band bandpass-энергия +## (включает ликование др. тонов) vs per-bin амплитуда — НЕ РЕШЕНО. Кандидат-тест Q=10 +## (рес-широкая, ликование 500-тона в band1 при измерении 2000). +## 5) b2_* семейство (band2 fc=1778.7, sens=12): b2only_12, b2q_*, b2_b1s0/on, b1on12_b2on12 +## (band1 500 + band2 1778, оба sens=12) — датасет multi-band depth-split для проверки. +## 6) ПЛАН (следующая сессия): (а) refit single-band LUT по dual_b1q+al_ (не b1f!); +## (б) уточнить depth-контекст t1kq_only1 перед микшированием; (в) решить семантику уровня +## через dual Q=10; (г) влить в framed_render.py. + +## ============ 2026-08-18h (STEP5c: REFIT FAILS - LEVEL SEMANTICS UNRESOLVED) ============ +## Совместный LUT-фит dual_b1q+al_ (9-узловой PCHIP + g,w,a): +## g=1.1895 w=0.3749 a=2.0 (на границе!) -> TOTAL 0.474 dB, dual 0.26, al 0.894. +## АЛ-точки не ложатся на dual-кривую: xv>0.3 al хочет +1.3..-1.0 dB сверху. +## КЛЮЧЕВОЙ ФАКТ (оба = тон в ЦЕНТРЕ полосы, res=0.117, depth/sens одинаковы): +## dual@500 (A=0.4397, вход=ДВА тона) xv=0.574 -> red 10.22 +## al_3 (A=0.5005, вход=тон) xv=0.631 -> red 13.38 (ВЫШЕ при меньшем заxode-sharing) +## al_6 (A=0.3543) xv=0.481 -> red 12.21 +## => при практически одном xv dual режет МЕНЬШЕ (10.2) чем al_4/5 (12.2-13.4). +## Больше энергии (dual=2 тона, A=0.44) -> МЕНЬШЕ reduction = ПРОТИВОРЕЧИТ al_-монотонности. +## Per-bin и per-band интерпретации обе проваливаются => механизм уровня (0x540678 per-bin +## IIR, точная схема отбора) НЕВОССТАНОВИМ статически из wav-пар. НУЖЕН runtime-захват: +## под отладчиком снять маску (0x5407c8/0x540658-окно) и per-bin уровень (0x540678) для +## dual_b1q_1.0 и al_6 при одинаковом цене -> дифферить механизм. +## Именно поэтому dB-потолок B.12-суррогата ~0.20-0.27 dB — линия DATA-уровня, не тюнинг. +## СЛЕДУЮЩИЙ ШАГ (рекомендация): runtime-захват уровня/маски под gdb (soothe2_x64.vst3), файл +## двойного стимула; после локализации механизма — закрыть LUT и 0.540658 эмпирически в C++. + +## ============ 2026-08-18i (MULTI-BAND COMBINATION - MEASURED, MODEL FAILS) ============ +## Эксперимент (вход tone1778 A=0.707, band2 fc=1778.7 = центр; depth=0.86397 mix=100): +## red@1778: b2only_12/b2_b1s0/b2_b1s0on = 16.55 (ВСЕ ОДИНАКОВО) +## b2_b3s12=16.62 b2_b4s12=16.51 +## b1on12_b2on12 = b1s12_b2s12 = b1on12_b2s12 = 17.08 (идентичные) +## b1only_12 (band1 fc=500, band2 off) red@1778 = 11.47 (soothe режет ВНЕ полосы сильно!) +## ВЫВОДЫ: +## 1) depth НЕ делится по числу активных полос (b2only 1-полоса == b2_b1s0on 2-полосы 16.55). +## 2) Полоса sens=0 даёт НУЛЕВОЙ вклад (b2_b1s0on == b2_b1s0 == b2only). +## 3) Комбинация band1+band2: 16.55->17.08 (+0.53 над макс), НЕ product (дал бы ~27), +## НЕ additive-C (насыщает в +180). => общая маска строится из СУММЫ displacement по полосам +## (аккумулятор 0x5407c8 += ...) через НАСЫЩЕНИЕ. exp-насыщение(0.9) даёт 19.5 > 17.08 +## => точная насыщающая кривая/уровень требуют runtime-захвата (0x5407c8/0x540658). +## 4) B.12 single-band band1@1778 предсказал 10.04 vs измеренных 11.47 (уровневая семантика +## опять +1.4 dB). +## => мультибанд: сумма displacements -> sat-кривая. Каждая полоса отдельно калибруется single- +## band рендерами; комбинация закрывается runtime или sat-подгонкой на b1on12_b2on12. + + +## ============ 2026-08-18h2: RUNTIME CAPTURE PARTIAL (base 0x180000000, 0x540658 = FIELD NOT RVA) ============ + +### Runtime capture infra (rtcapture2.py, WORKS): +- Launch: `reaper -nosplash -renderproject X.rpp`; host pid found by scanning /proc/*/maps for 'soothe2' + (lowest map addr with soothe2 in path = PE base). **vst3 loads AT ImageBase 0x180000000** (no reloc). +- Reading /proc/pid/mem (+0x180000000) works DURING render; process dies right after render finishes. +- Section table (Authoritative): .text rva 0x1000 raw 0x600 vsize 0x1a52000; IPPCODE 0x1a53000/0x1a51e00; + .rdata 0x1baa000/0x1ba8400 vsize 0xa60000; .data 0x260a000/0x2608200 vsize 0x72000; .pdata 0x267c000. +- FILE->RVA mapping: raw off F maps to rva = F - raw_sec + va_sec (e.g. .text: F=0x53F658<->rva=0x541058). + +### CRITICAL: 0x540658 (and 0x540678/0x5407c8/0x5408b0/etc) are FIELD OFFSETS, NOT RVAs! +- Disasm at rva 0x52b771: `mov rax,[rdi+0x540658]` -> window read is [ctx+0x540658] (ctx = DSP object). +- Static soothe_mem.bin is RVA-linear; reading at 0x540658 gives .text CODE bytes (garbage floats). +- So the "window" (FIR*=WINDOW in FFT-conv step 5) lives in the HEAP DSP object at offset 0x540658, + NOT in module image. Runtime must locate `ctx` = base of DSP object. +- Beacon idea: phase_table_1024 (twiddle doubles 0x182615608 in .data) is the FFT-plan table; + scan host heap for u64==0x182615608, then the pointer owner is plan (+0x18/+0x20/+0x68 fields) + -> ctx = byte_addr_of_(pointer_field) - field_offset; window at ctx+0x540658. + (rtdeep2.py implements this; currently racy - host may exit before numpy import + first scan.) + +### Notes for next session: +- Render lifetime is short (~2-19s); to win the race: import numpy BEFORE Popen, scan /proc instantly, + and re-run if host missed. Could also pre-load module into gdb for direct ctx inspection. +- Alternative: find ctx via 'consumers_out' alloc chain (note :119-135) if heap layout known. +- Verified render outputs: out_dual300.wav etc; run_sweep works; pkill -9 -x reaper hangs shell - + use `pkill -9 -f "reap[e]r"` style to avoid killing own bash. + + +## ============ 2026-08-18h3: LIVE GUI CAPTURE SUCCESS (pid 652462, SR=48000) ============ + +### Infra achieved: +- User ran REAPER GUI + soothe2 (yabridge-host.exe.so pid 652462), tone playing, band1 fc=500, Q=1, stereo balance. +- rtsnap.py: page-wise snapshot of all readable regions -> /tmp/rtA|B|C|D.{raw,idx} (~821MB each), skips EIO pages. +- Live process loads vst3 at ImageBase 0x180000000; `.data` shifted +0x1e00 vs static RVA. +- Diff A/B (Q turned) and C/D (idle 6s) both dominated by audio-buffer noise; direct ctx discovery by + (len,ptr)-registry + static curves instead. + +### Registry of DSP buffers (found at 0x28b06c0, vector<{u64 count, u64 ptr}>): +- [0x00] count=8193 ptr=0x2c1a680 (all 1.0f) - identity/gain table +- [0x10] count=8193 ptr=0x1930100 **STATIC** 0.5->0.8 (freq-path window, monotonic) +- [0x20] count=8193 ptr=0x1938180 **STATIC** 0.0->3.899 +- [0x30] count=8193 ptr=0x19401c0 **STATIC** 0.596->0.126 +- [0x40] count=8193 ptr=0x29000c0 0.404->0.874 (dynamic) +- [0x50] count=8193 ptr=0x2908100 (dynamic) +- [0x70] count=8193 ptr=0x2c72800 (dynamic) +- [0xd0] count=2049 ptr=0x2e79040 freq-axis 0..23988.3 Hz (linear, spacing 11.71 = 48000/4096) +- [0xe0] count=8193 ptr=0x2e810c0 (dynamic) +- [0x110] count=8193 ptr=0x2c62740, [0x120] 0x2c12600, [0x130] 0x2c6a780, [0x140] 0x2eb1180 (zeros/ones) +- STEREO PAIR: 0x1930100 == 0x1a04240 (identical) => two copies (per-channel). +- "static" = identical bytes between snapshot C and D (idle) => candidate WINDOW tables. + +### THE WINDOW (0x540658 area) - EXTRACTED: +- rwin_A0.npy (0x1930100): 2048 floats, 0.50000006 .. 0.79990, strictly monotonic, reaches 1.0 at idx 2049+ + (saturates: plateau 1.0 after bin 2048). Shape = 0.5 + 0.3*g where g = K*x/(K+x), x=f/24000, K≈1.9 + (fit rmse 0.0024; K sweep 1.9 best). freq-axis r_freqaxis.npy (2049 floats, 48000/4096 spacing). +- Interpretation: freq-path window = 0.5+0.3*warp(f); warp = K·x/(K+x), x = f/(SR/2). + NOTE: GUI SR=48000; offline renders SR=44100 (out_dual*.wav all 44100) - must renormalize x by actual Nyquist. + +### Integration attempt (framed_render refit, G/W/p free, cases dual_b1q q=0.1/1/10 @500+2000): +- baseline warp^p: G=1.278 W=0.130 p=6.077 meanerr=0.323 dB (warp=(f/2000)^p) +- real window as warp term: G=1.199 W=1.328 meanerr=0.359 dB (NOT better) +- window*input-amp + warp^p: meanerr=0.470 dB (worse) +=> The real 0x540658 window does NOT beat empirical (f/2000)^p when used as the mask warp term. + BOTTLENECK remains the LUT-leg (level semantics), not the window. See al_* conflict in framed_render.py. + +### Remaining hints for next session: +- The 8193-count tables vs 2049 freq-axis => N=4096 FFT at 48k (rfft bins 2049). Window arrays sized 8193 + = complex bins? or 2*N? Actually 8193 = 4096*2+1 => likely full complex spectrum storage per channel. +- Re-running parity: use tone_cmp() (ndarray) not render_parity.tone_amp (file path) when testing live windows. +- For Q/depth level-path: the live diff method (snap A, change, snap B) is viable; noise is huge, use + (len,ptr) registry+static checks as anchors instead of raw byte diffs. + +## ============ 2026-08-19: LUT-LEG CALIBRATED VIA al_* DATASET (JOINT FIT, DONE) ============ + +### What was done: +1. Decoded full al_* RPPs (binary b64 header): trim to len%4==0, find ` reduction INCREASES with input level. +3. Pipeline-computed xv = log10(am_i / res_i) at bin=1000 (am=smoothed 2|X|/wsum, tatt=11ms/trel=80ms): + lv3:.5488 lv6:.3988 lv9:.2488 lv12:.0988 lv18:-.2012 lv24:-.5012. + NOTE: earlier al_* xv (0.631 etc.) were computed with different am normalization - ALWAYS use pipeline's. +4. Pure-LUT nodes at each xv: lut = ((1-m) - W*warp^A) / G (mask C = G*LUT + W*warp^A). +5. JOINT FIT (dual + al_*, objective=mean|err| over 6 dual tones + 6 al levels): + best: G=1.0850 W=0.2819 A=1.1377 -> al_* err <=0.19 dB ALL; dual err <=0.62 dB ALL. + The LUT leg is a SLANTED curve (~0.36 at xv=-0.5 rising to ~0.64 at xv=+0.55), NOT the flat B.12 (~0.5). +6. framed_render.py updated: LX/LY = merged anchors + al_* nodes; G/W/A = 1.0850/0.2819/1.1377. + CRITICAL BUGFIX: lut clip must be [LY.min(), LY.max()] (0.366..1.0) not LY[0] (0.4402) - node at + xv=-0.5012 (0.366) was being floored, breaking q10@2000 by 1.4 dB. + +### Final validation (framed_render.py dual): + q0.1 @500 -0.55, @2000 +0.14; q1 @500 -0.62, @2000 -0.57; q10 @500 +0.01, @2000 -0.00 + envRmse@steady: 0.64/0.04/0.71/0.78/0.11/0.11 dB. al_* lv3..24 err: +0.11~+0.19 / +0.01 / -0.02. + +### Conflict (t1k fc-scan vs al_* clif at xv=0.93) - RESOLVED: + al_* "-104 dB @ lvl 0dBFS" comes from overdriven input (am >> any res), saturating mask to C->1. + t1k 15.6 dB @ xv=0.931 was a *different* level point (soothe's own fc-scan dataset). Both acceptable + once we fit AT THE MEASURED level points (which the joint fit does); no premise is wrong, input differs. + +### Next: (1) hammer the residual -0.6 dB on dual 500Hz cases (structural); (2) multi-band combos (band>=2); (3) C++ port. + +## ============ UPDATE 2026-08-19f: LEVEL-TRACKER UPDATE-LOOP LOCATED (structural) ============ +The UPDATE loop is NOT a separate function — it is the inline bidirectional IIR smoothing +inside FUN_180529fe0 per-band loop (consumers_out.txt). Three first-order IIR stages: +- (a) FUN_18052d650(state 0x440518, out 0x5406f8, in 0x540678[band]) — outer smoother +- (b) inline: coeff-A array 0x4c0528, scalar-acc state 0x540528, mask src/dst 0x5406f8 (count iVar19) +- (c) inline: coeff-A array 0x3c0510 / 0x2c04f8, acc 0x440510 / 0x3404f8+0x2404e8 (two more) +Scalar form (proven, consumers 745-1015): + acc = 0; for i in 0..N-1: y = x[i]*A[i] + acc; acc = y; x[i] = (float)y +i.e. cumulative leaky-integrator over A[i] (running-acc coefficient). Vector variant does 4/iter. +=> The sample-path smoothing the tracker needs is STATICALLY known in structure; only the + coefficient arrays A (0x4c0528/0x3c0510/0x2c04f8) + the 0x530d30 per-bin level-weight caps + remain runtime values (0x530d30 shown numerically flat w~=0, NOT the tilt source). +Bit-exact port of the UPDATE requires A[] live capture (or FUN_18052d650 body) — next P1.5-ish. +Boundary: structural scaffolding now complete (levelpath + fftconv + mask-canon + tracker form); +all remaining unknowns are runtime scalar/array values, not structure. + +## ============ 2026-08-19 continuation: LEVEL-PATH DECOMPILED — FUN_180563440 + FUN_180563ce0 ============ + +### FUN_180563440 (dsp/levelpath.cpp dump, f_563440.dis 222 lines) — LUT curve + twin-mask + combine +Three-phase per-frame structure: +1. **1024-bin LUT loop**: bin k → x = clamp(k/1024, 0, 1). Band config at context+0x188: + {A(+0x00), B(+0x04), threshold(+0x0c), flag(+0x10), callback(+0x50)}. Three paths: + - vtable callback (dynamic, when 0x50 non-null) + - **power-law**: centered=2x-1 → `sign()·10^(log10(|x|)/C)` — compression by sharpness + - **linear**: `(B-A)·x + A` — plain interpolation + Output stored DOUBLE at +0x198. +2. **Twin-mask factory** FUN_18056e3e0: 6 bands × 1024 bins, stride 0x2000. +3. **Combine**: max 2 channels (stereo), 6 bands, `1 - Σ weights`. + +### FUN_180563ce0 (f_563ce0.dis 163 lines) — IIR level-tracker INIT (NOT update) +- 341 bins (0x155), **order-3 IIR** (3 coeffs/bin: 0x40400000 = 3.0 markers). +- Level coefficient: **0.1** (0x3dcccccd IEEE 754) — this is the attack/release α. +- State layout: rcx+0x28/+0x40/+0x58 (3 buffers). Init [1,0,0,0] / [-1,0,0,0]. +- **UPDATE loop NOT found yet** — the actual sample-path smoothing is elsewhere. + +### Key constants (all verified from soothing_mem.bin): +| Address | Value | Meaning | +|---|---|---| +| 0x24c3c54 | 0.0009775 | 1/1024 bin scale | +| 0x24c3ea4 | 1.0 | clamp max | +| 0x24c41e0 | 2.0 | power-law centering | +| 0x24c4680 | -1.0 | sign flip | +| 0x24c3d8c | 0.5 | threshold | +| 0x24c4334 | 4.0 | depth range (oversample os=4) | +| 0x24c43e0 | 8.6859 | 20/ln(10) dB conversion | +| 0x24c4704 | -6.9078 | ln(0.001) floor | + +### CRITICAL: The "warp" is NOT a table — computed at runtime +- `rwin_A0.npy` (live 0.5→0.8) is the **frequency WINDOW** (per-bin mask shaping), NOT the warp. +- Empirically fitting `W·warp^A` was approximating the runtime LUT curve evaluation. +- The LUT is **parametric** (linear or power-law by band flag), not a fixed lookup. +- To reach bit-exact, replace PCHIP-fitted LUT with the parametric curve from FUN_180563440. + +### Structural finding (per-bin gain from reference, avg over steady frames): +- Reference mask is FLAT ~-10.2 dB across 100-540 Hz REGARDLESS of Q (dual_b1q q0.1/1/10). +- Model produces res-shaped notch → source of the -0.6 dB residual at 500 Hz. +- **Hypothesis to test**: mask uses a SCALAR per-frame level (broadband), not per-bin am/res + (this matches the "twin-mask factory" combining band contributions). Test in /tmp/scalartest.py. + +## ============ 2026-08-19h3: RES_POWER OPENING — 500Hz RESIDUAL SOLVED ============ + +### The fix: gain_k = (1-C) × res_k^rp (rp ≈ 0.05) +The reference applies the mask NOT as `(1-C)` directly to the per-bin gain, but as +`(1-C) * res^rp` — a small res-dependent correction. This flattens the per-bin gain +across the band center (where res=1 → correction=1, matching baseline) while dampening +off-center bins (res<1 → correction<1, reducing the notch). + +### Validation (test_joint_rp2.py, multi-start Nelder-Mead, 5000 iter): +``` +BEST: G=0.9696 W=0.3503 A=1.0887 rp=0.0516 mean=0.208 (dual only, 6 pts) + q0.1@500: err=+0.00 (was -0.55) + q0.1@2000: err=+0.00 (was +0.15) + q1@500: err=-0.05 (was -0.62) + q1@2000: err=-0.41 (was -0.57) + q10@500: err=+0.57 (was +0.01) + q10@2000: err=+0.22 (was -0.00) +``` +The rp parameter SOLVES the 500Hz residual for q0.1 and q1 (the main bottleneck), +but slightly degrades q10 (the narrow-Q case). + +### Physical interpretation: +In soothe2, the mask is computed in the RESONANCE-DOMAIN (xv = am/res), but the +applied gain has an additional res-dependency. This matches FUN_180563440's structure +where the LUT curve is evaluated per-bin (1024 bins) and the twin-mask factory +(FUN_18056e3e0) combines band contributions with a res-weighted path. + +### Next steps: +1. Refine rp jointly with al_* (need faster al_* rendering — batch the 6 lv cases) +2. Update framed_render.py with rp parameter +3. C++ port of res_power term (trivial: multiply gain by pow(res, rp)) + +## ============ 2026-08-19 (Q-DEP RP + HONEST METRIC) ============ + +### CRITICAL: earlier "mean=0.160/0.144" were ARTIFACTS (untracked output length) +- Fit scripts (qdep_rp.py) rendered OUT of len nfr*HOP+N (untracked tail) and measured tone_cmp on last 0.75s of that extended buffer. +- framed_render.synthe trims to len(x). The two disagree by ~+0.2dB systematically. +- Honest (trimmed) numbers for scalar rp=0.0169 (h3): mean=0.280 max=0.618 (q10@500). +- Earlier claim "dual mean=0.160" does NOT reproduce on trimmed metric. + +### Q-dependent rp: rp(Q) = rp0 * Q^drp (physical warp kept) +Fitted on TRIMMED metric (render_trim same as synthe): + G/W/A/rp0/drp = 0.9963/0.3335/0.9807/0.0275/0.2159 + mean=0.175 max=0.707: + q0.1: 500 +0.000, 2000 +0.001 + q1: 500 -0.256, 2000 -0.707 + q10: 500 +0.086, 2000 +0.001 + q0.1 and q10 both essentially perfect. Bottleneck is q1@2000 (over-reduction -0.71). +Weighted refits move error around but never reduce max < 0.7: + - w(q1@2000)=3, w(q10@500)=3: q0.1@2000 -> +0.885 (rows shift, no global win). + - free warp (K/alpha/p): mean=0.139 but q1@2000 -0.534 and warp non-physical (alpha=3.19). + +### Committed to framed_render.py (Q-dep rp, trimmed honest baseline): + G/W/A = 0.9963/0.3335/0.9807, RES_POWER = lambda q: 0.0275*q**0.2159 + +### Interpretation +rp(Q) ~ Q^0.216 means the res-power correction grows with Q: high-Q resonance pit +dips harder off-center. Consistent with decomp (res-weighted gain path in twin-mask +factory). The q1@2000 residual is structural warp/model mismatch, not rp. + +## ============ 2026-08-19: STATIC DECOMP LOCKED (all DSP bodies recovered) ============ +New full decomp run (DumpFuns2.java, no haveDec gate, seeds=dsp+vtables+reverse-callers, +skip<8): decomp_funs2.txt (7746 bodies), fun_map2.txt (7928 funcs), consts2.txt (41622). +Previously-cold roots now present: 180529fe0 (2051in), 180563440, 180563ce0 (IIR INIT), +18056e3e0, 18052e9b0 (3167in), 18052e260, 18052e190/52e9b0 allocators. + +### MAIN RENDER LOOP (found): FUN_18052e260 +- Signature (param_1=DSP obj, param_2=nSamples). iVar5=NFFT/2+1; loop per band + (count=0x2404d0): FUN_180536300(param_1+0x3d8, band_in_ptr, 0x540758+bufoff, band, n). + Uses full-STFT frame budget 0x2404d8; tail marks bands needing rebuild (0x240467 flags). +- => 52e260 = per-frame dual-band SIDE-CHAIN driver; twin = per-band. + +### FULL decomp FUN_18052e260 (decomp_funs2.txt:348915) — the frame driver: +``` +iVar5 = NFFT/2+1; iVar2 = min(iVar5, [0x2404d8]); [0x2404d8] = iVar2; param_2=min(iVar2,param_2) +for band in 0..[0x2404d0]: FUN_180536300(ctx+0x3d8, band_in + (iVar5-iVar2)*4, + [0x540758] + (iVar5-iVar2)*4, band, param_2) +[0x2404d8] -= param_2 +if [0x2404d8]<1 && [0x30]>0: fill(0x540788, 1.0, iVar5) // frame done -> reset dry/wet +``` +So 52e260 only DRIVES the per-band twin side-chain (536300); the LUT (563440) + +mask-apply (529fe0) + FFT-conv (52b550) run elsewhere (triggered when [0x2404d8] +drains, i.e. per STFT frame). Full pipeline assembly (P4) = wire these in order: +twin(536300) -> level -> LUT(563440/56e3e0/563a60) -> mask(529fe0) -> FFT-conv(52b550). + +### FUN_180536300 (twin caller, size=475) — audio path per band: +- lVar1 normalize = FUN_18052da00(scratch, in, scale_bin, n): pointwise in[i]*scale. +- band active (0x814): twin kernel FUN_180535880/180536f90(lVar2, base, band_i, lVar1, n), + else fill FUN_18052db50 (1.0 / 0.0). +- combine lVar4/lVar3 (52d990, 0x814>1), output copy 52dbc0(base, lVar4, n). +- TWIN KERNELS (535880/536f90): Ghidra emits ONLY the tail-call frame — + "WARNING: Removing unreachable block ..." => state shown = {pdVar1,*pdVar2} double pairs + read from param_3/param_4, then noreturn tail into thunk_FUN_181ba94b0. **The recursive + per-bin IIR level-smoothing (tatt/trel) lives INSIDE these two tail-calls, which static + decomp cannot recover** — same family as 0x540658 window (step-5 window). Confirmed + statically-invisible frontier = {532a715 twin kernels, 0x540658}. + +### Level-param setters (vtable stubs, auto-named .?AV?$Soothe2Module@M$01@@::vtbl_...): +- 18052bba0: 0x540870 = expf((p*DAT_1824c4348 + DAT_1824c44a4)*DAT_1824c3cd4) [gain/sens] +- 18052bb80/bb60: 0x540878 / 0x54087c = raw int [freq?/bandwidth?] +- 18052bb40/bb20: 0x540880 / 0x540884 = raw int [per-band level / dur] +- 18052bb00: 0x540874 = p (locked); 18052bad0/baa0: 0x540888 / 0x54088c = + expf(p*DAT_1824c3cd4) [attack / release coeff db] +- DAT_1824c3cd4 = 0.115129255 = ln(10)/20 => expf(p*0.11513) = 10^(p/20): **all these + setters convert dB-speed params to linear coeffs**. 0x540888/88c feed FUN_180529fe0 + step-5 dry/wet (mask peak) — the real attack/release coeffs. +- FUN_180530d30 (0x5406b8/6c8/6d8/6e8 weights): fVar9 = 2000.0/NFFT? base; w8=pow(base,0.25); + v = level*0.25*w8*factor(4.0); q=1/(1+v/(level*4)); dVar1=(sr/0x1a0)*0x1ac*0.001; + w = 0.1^(1/(max(q*v)*dVar1)); pairs (w,1-w). +- Numeric check (this session, real values): bin500 w~10^-6, bin2000 similar => 0x530d30 + does NOT tilt 2000>500. Tilt stays = warp (0x5406a8, K=7.942) + twin-mask curving. + Static decomp now COMPLETE up to the statically-invisible frontier confirmed above. + +## ============ 2026-08-19 (runtime capture SETTLED): registry heartbeat + live tables ============ +## **Window 0x540658 + freq-axis CAPTURED live. Internal DSP sample rate = 48000 (not 44100).** +- rtsnap_fast.py: spawn reaper render_long offline -> find host (soothe2 in /proc maps, not reaper + cmd) -> sleep 6s -> snapshot ALL readable maps chunked 8MB -> /tmp/snap_all.bin (318MB, 546 regs, + entries {lo,sz,bytes}+pad, idx in /tmp/snap_all.idx). Chunked pread REQUIRED: whole-region pread EIO. +- **registry heartbeat**: heap run of {u64 count, u64 ptr} pairs (stride 0x10) at **0x29b06c0** + (arena anon 0x2922000, 0x77e000), 36 entries — analog of GUI note 0x28b06c0. Every DSP buffer + base is a registry entry: [00] identity, **[01] = WIN_freq window 0.5->1.0 (saturates)** (this is + live 0x540658 8193 f32: 0.5, idx512=0.345?; idx1024=0.68, idx2048=0.8, tail 1.0), + [02] 0->~0.01, [03]=rwin_C0 0.596->0.126, [04]=0.404->0.874 complement, [05]=0.0435->~0, + [06]=0.9565->~1.0, [07] zeros+neg, [0d]=**freq-axis 0..23988.3 spacing 11.713 = 48000/4096**, + [0e]=2.017->0, [0f] doubled, [10]=1.2914->0, FFT work bufs 131072/32768/65536/16384. +- **CRITICAL**: freq-axis spacing 11.713 Hz => internal SR = 48000 regardless of project 44100. + Registry[01] window & weights are therefore the 48k tables; rwin_C0[03] byte-matches GUI 44100 file + (same table reused). Saved live tables: + - handoff/rtfreqaxis_48000_internal.npy (2048 f32, 0..23988.287, spacing 11.713) + - handoff/rtwin_freq_44100.npy (8193 f32 WIN window 0.5->1.0) <-- THE 0x540658 live capture + - handoff/rtwa_596.npy, rtwb_404.npy, rtwc_043.npy, rtwd_956.npy (registry [03][04][05][06]) +- vptr/44100-marker object-scan (rtobj/rtdump2/rtall) remains DEAD: no vtable 0x1824abb90 bank hit in + heap, fresh-render host has no 44100.0 constants (internal is 48k). The registry run is the beacon. +- twin IIR per-bin attack/release: NOT in registry (those are {att,rel} pair floats at 0x540888/88c, + expf(p*0.11513), per-note-band smoothing inside tail-call thunks). Live capture needs the twin band + object; not yet located. Registry gives per-bin WEIGHTS (done) — attack/release remain static-only. +- Registry dump + all raw tables saved: /tmp/rtcapt/registry.txt, /tmp/rtcapt/*.f32. +- Scripts captured into repo: rtsnap_fast.py (snapshot), findctx.py (locate), rtchunk.py (chunked IO). + +## ============ 2026-08-19 (STRUCTURAL LUT CURVE FUN_180563440 — EXACT FORMULAS) ============ +- Verified against f_563440.dis + PE constants 0x1824c3c54=0.0009775171(=1/1023? 1/0x3ff), + 0x1824c3ea4=1.0, 0x1824c41e0=2.0, 0x1824c4680=-1.0, 0x1824c3d8c=0.5, 0x1824c4f10=0x7fff.. (double |x| mask). +- Loop: 0x400 iterations (0..0x3ff), x = i*(1/1023)? const 0.0009775171 = 1/1023 → last bin x≈1.0. + (prior note said 1/1024 scale — corrected: constant value is 1/1023.) +- Curve config at ctx+0x188: word A(+0x00)=min, B(+0x04)=max, gamma(+0x0c), flag(+0x10), + callback(+0x50). Output written as double to ctx+0x198[i*8] (0x400 doubles). +- **Linear path (flag 0x10==0, 0x563595)**: t = x; if gamma!=1.0 and x>0: t = exp(log(x)/gamma); + val = A + (B-A)*t. (A=min of output, B=max). +- **Power-law path (flag!=0, 0x5635cd)**: t = 2x-1 (centered -1..1); if gamma!=1.0: + t = sign(t) * exp(log(|t|)/gamma) (abs-mask 0x24c4f10, log DIVSS gamma, exp, sign via xmm11/xmm8); + val = A + (B-A)*0.5*(1+t). When gamma=1.0 → val = A + (B-A)*x (identity linear). +- This is the exact runtime curve to replace Pchip/LUT empirical (roadmap Q1). Need live (A,B,gamma) + per band config from ctx+0x188 — requires DSP ctx base (registry doesn't own the curve struct). +- Level-tracker IIR (FUN_180563ce0) INIT confirmed: 341 bins x2?, state rows at +0x28/+0x40/+0x58 + init [1,0,0,0]/[-1,0,0,0], level coeff 0.1 (3dcccccd) = attack/release α; UPDATE loop remains + unmapped (field for future live capture; per registry tables stable between snapshots). + +## ============ UPDATE 2026-08-20i: REAL MASK CHAIN IN C++ (P4) ============ +Replaced empirical PCHIP detector with the live-calibrated mask chain: + +### Structure (mirrors FUN_180529fe0 mono path): + level_k = am_k * res_b[k] * level_scale + track += w_k * (level - track) per-bin attack/release (rt_weights.hpp) + acc_k = (level - track) + level accumulator (live peak ~10.08 at tone) + mask_k = (1/(1+K*acc))^n K=9.8026, n=0.25966 (fit to live mask) + final = min over bands + +### Live calibration zipped: +- live acc 0x5407c8 @1000Hz = 10.08 ; mask @1000 = 0.2976 (level_scale 1630 makes + am*res*1630*... hit acc ~78 at tone -> mask 0.178, matches ref -14.98dB). +- 678[0]=mask (0..1) is what we model as gain; warp/band-mult still missing. + +### Results (framed_test, N=2048 hop=512, sqrt-Hann OLA): +- t1kq single band (678.76,q~1,sens12,scale1630): ref -14.98 dB, out -14.47 dB, err +0.51 dB +- comb 4-band (678.76/q1/s12,1778.7/q3/2.24,195.1/q3/2.24,13408/q3/s12): per-tone err + 500:+5.8, 1000:+1.3, 1500:+1.8, 2000:-3.4, 3000:+5.6 dB. RMS err -16.8 dB (ref rms +4.3). +- Old PCHIP cut comb by ~6dB; new mask chain preserves comb (mostly ~1dB off). + +### Remaining: +- warp tilt (0x5406a8) + per-band 0x540768 mult + FFT-conv smoothing (step 6-8 decomp) +- exact BandConfig (A/B/gamma) for band LUT 0x563a60 +- rt_weights tables only cover bins 0..1024 (2049 len, rest zero); extend if higher bins matter + +## ============ UPDATE 2026-08-20j: FUN_180529fe0 MONO-PATH FULLY DECODED + TABLES EXTRACTED ============ +Read /tmp/consumers_out.txt:471-1277 (full decomp) + f529fe0.dis + live snap_rt.bin (ctx 0x2370040). + +### Exact per-band mono-path (0x5408b8==0), FUN_180529fe0: + 1. scale: 0x540678[band] *= (fVar30/0x1a0)·0x540870·0x54088c (0x9be0) [fVar30=PRNG, 0x540870=440.95] + 2. IIR1: FUN_18052d650(state 0x440518, out 0x5406f8, in 0x540678) = leaky: + y[i] = A[i]·acc + B[i]·x[i]; acc=y; x[i]=(float)y + A@0x4c0528 B@0x440528 (2049 doubles), B=1−A, A ramps 0→0.692 (fast attack at low bins) + 3. copy: 0x5406f8 <- 0x540678[band] (0x5160) — actually the IIR1 in/out feed; then + 4. IIR2: inline on 0x540678[band]: A@0x2c04f8 B@0x2404f8 (A ramps 0→0.086, SLOW release) + 5. mirror halves (0x11940) + 6. blend: 0x5406f8 = 0x540698·(1−mix) + mix·0.8; mask[band] = bigkernel(mask,0x5406f8) + bigkernel 0x26b820 = VECTORIZED exp2 (tables log2e=1.4424@0x1f31740, floor −708.9, + mantissa 2^(k/256)@0x1f38c80) — computes mask[i]=exp2-based op on (mask,0x5406f8) + 7. combine: 0x5407c8[band] = 0x540678 − 0x5406f8 (0x8d60 sub); mirror; + += 0x5406c8·upper (0x3c40 stride4); += 0x5406e8·lower (0x3c40); += 0x540678 (0x5a20) + 8. WARP: 0x540678 *= 0x540768[band] (0x8700); *= 0x5406a8 (0x8700) [TWO warp mults] + 9. IIR3: inline on 0x540678[band] TWICE: A@0x3c0510 B@0x340510 (=A2/B2 shape) +10. dry/wet: 0x540678 = mask·(fVar30·0x540888) + (1−fVar30); fVar30=0x540874−rnd +11. FFT-conv (0x535a70) -> FIR -> conv(audio param_2) + +### Runtime tables EXTRACTED -> dsp/rt_mask_tables.{hpp,cpp} (2049 doubles each, live): + - kIIR_A1 (0x4c0528): 0→0.692 rising; kIIR_B1 (0x440528) = 1−A1 + - kIIR_A2/B2 (0x2c04f8/0x2404f8): A 0→0.086 (slow); kIIR_A3/B3 (0x3c0510/0x340510) = same + - kWarp (0x5406a8): 0→3.899 (freqpath tilt, 8193 f32) + - kBand768 (0x540768[band0]): 2.017→0.271 (per-band warp mult, 2049 f32) + - kPRNGLut (0x5408b0 -> 0x152a3d0): 512 f32 (0.35..6.66, 189 nz) + - PRNG state 0x2404e0 = 112; DAT_18262b5c8=0.4552, b704=0.6089, b700=0.6070 (int32->float) + - scalars: 0x540870=440.95, 0x540874=1.0, 0x54087c=1.0, 0x540888=1.0, 0x54088c=1.0 + - 0x1a0=2048, 0x540868=16384, 0x54086c=8193, band count 0x2404d0=7, 0x30=2 (stereo) + +### NOTE (SR mismatch): internal DSP SR=48000, N=4096 (bin spacing 11.713 Hz). Host refs 44100. + IIR tables indexed 0..2048 (N/2+1). For bit-exact the detector must run N=4096@48k internally. + +## ============ UPDATE 2026-08-20k: bigkernel 0x26b820 = vectorized exp2 (semantics LOCKED) ============ +Read the SIMD loop (objdump 0x18026b820-0x18026bcaf). Key line 0x18026bba0: + vmulpd ymm12 (exp2 result) by ymm11 (loaded from rbp = blend buffer 0x5406f8). +=> mask[i] = exp2(x) * blend[i], where x is the mask value (0x540678[band]) and + blend = freqaxis*(1-mix)+mix*0.8 (=0.8 at mix=1.0). sign: to get attenuation + (mask<1) x must be negative, so mask = exp2(-level) * blend. Locked. +exp2 is standard log2e/poly/mantissa-table (0x1f34a80..., log2e~1.4424, floor -708.9). + +### Calibration (framed_test, structural chain now in framed_model.cpp): +- t1kq single band (678.76,q~1,sens12): level_scale=600 -> err -0.06 dB (ref -14.98). +- comb 4-band at scale 600: per-tone err 300:-5.1, 500:+7.8, 1000:+4.4, 1500:+3.9, + 2000:-1.3, 3000:+4.8 dB (max 7.8). Mask shape (warp tilt + FFT-conv smoothing) + still approximated. + +## ============ UPDATE 2026-08-20m: PRNG prologue transcribed (structure) ============ +FUN_180529fe0 prologue (decomp :515-583) = PRNG coefficient generation feeding +scale fVar30 and dry/wet, via LCG + PRNG LUT (0x5408b0 -> 0x152a3d0, 512 f32, live). + +LCG: state += (round offsets) & 0x8000007f with sign fixup. Constants: + DAT_18262b5c8=0.4552, DAT_18262b704=0.6089, DAT_18262b700=0.6070, DAT_1824c3c58=0.001 +Round products fVar27*LUT[..]*fVar28*LUT[..]+0.001 are ~0.28 at PRNG state 112 +(live), so (int) truncates to 0 => fVar30 can be 0..~ range frame-to-frame. +This is genuine per-frame RANDOMIZATION in the scale step (x *= (fVar30/0x1a0)*...). +structural model averages it via level_scale calibration; bit-exact requires the +exact LCG sequence per frame. scale already calibrated (t1kq -0.43 dB). + +### Remaining bit-exact gaps (documented, next sessions): +1. exact LCG/LUT per-frame fVar30 (scale + dry/wet randomization) +2. FFT-conv (0x535a70) mask smoothing before FIR +3. bit-exact vectorized exp2 (0x26b820) vs std::exp2 +4. combine/accumulator (0x5407c8) exact feedback (1500Hz comb err +6.4 dB) +5. internal SR=48000/N=4096 vs host 44100/N=2048 and twin per-bin IIR (statically + invisible, FUN_180535880 tail-calls) + +## ============ UPDATE 2026-08-20n: PRNG fVar30 LOCKED + CONSTANT FIX ============ +### CRITICAL CORRECTION: DAT_18262b5c8/b704/b700 == 1 (NOT 0.4552/0.6089/0.6070) +soothe_mem.bin is a VA-linear dump: file offset = RVA = VA - 0x180000000. +Earlier 0.4552/0.6089/0.6070 came from adding a spurious (+0x1e00-ish) section +adjustment that is NOT applicable. Verified at raw=RVA=0x262b5c8/b704/b700: +bytes 01 00 00 00 => int 1. Code does cvtdq2ps => (float)1 = 1.0. All three = 1. +Verified constants at raw=RVA: 0x24c3c58=0.001, 0x24c3e28=0.8, 0x24c4674=-0.7, +0x24c4670=-0.5 (match NOTES). + +### PRNG prologue (FUN_180529fe0 :515-583) TRANSCRIBED to C++ (prng_fvar30): + LCG state 0x2404e0 (live=112), round offsets: +0x3cdca,+0x140236,+0x10d56,+0xdf6b6 + fVar30(line554) = (int)(f32(LUT[s+1])*f32(LUT[s]) + 0.001f) [b5c8=b704=1] + => at live state 112, fVar30 == 1.0 deterministically over 300 frames. + => scale step: level *= (fVar30/0x1a0)*0x540870*0x54088c = /2048*440.95*1.0. + scale is NOT effectively randomized for this LUT/state. + dry/wet fVar30 (line1163) = 0x540874 - rnd, rnd from IAT stub 0x181a14cac + (jmp *0x181bab330, CRT import) - random dither, not statically lockable. + +### framed_model.cpp: scale coeff now computed via prng_fvar30() (prng_state_=112), + not a hardcoded constant. t1kq still -0.43 dB at level_scale=600 (unchanged). + +## ============ UPDATE 2026-08-20o: CRITICAL — level = am / res (not am * res) ============ +Found + fixed a sign/direction bug in framed_model.cpp level computation. + +res = |2B/A| (twin) is MINIMAL at band centre (0.117), NOT maximal. The model uses +xv = log10(A_k / res_k), i.e. LEVEL = am / res. My code had level = am * res which +inverted the fc-resonance: it cut MORE off-center and LESS at fc, producing a flat +inverted fc-response. + +Fix: level = am_ / res_[b][k] (with 1e-12 floor). Now the fc-scan (tone1kq, band fc +swept 800..1200, refs t1kq_only1_ = true single-band) tracks the reference shape +(deepest at fc==tone): + scale=42: fc900 +0.3, fc950 +1.3, fc1000 +0.9, fc1100 +0.2; mean|err| 2.46 dB, + max 5.3 dB (worst at edges fc800 +3.9, fc1200 +5.3 - ref cuts broader than model, + likely needs FFT-conv mask smoothing / wider effective notch). +Remaining under-cut off-center: real soothe reduces ~16-22 dB broadly across +800-1200 while model narrows; candidate = FFT-conv smoothing + warp + q shape. +DEFAULT level_scale for am/res path ~= 35-45 (was 600 for the wrong am*res path). + +## ============ UPDATE 2026-08-20p: Phase B — framed_model RE-TRANSCRIBED ============ +Re-transcribed dsp/framed_model.cpp per confirmed chain (NOTES:199-226, source +/tmp/consumers_out.txt:638-1111). Structural divergences from the old approximation: +- IIR1 (FUN_18052d650, state 0x440518) writes into the SHARED 0x5406f8 buffer, + then bridges into the band's 0x540678 (:731 0x5160); IIR2 (0x3404f8/0x2c04f8 on + the band mask) kept separate. +- mirror 0x11940: upper half = reversed lower (Hermitian), full-nfft scratch. +- blend step: f6f8 = 0x540698·(1−mix) + mix·0.8; mask = exp2(−mask)·f6f8. C_AXIS=1.3 + placeholder (offline 0x540698 = per-band scalar, NOT the exp-formula; NOTES:354). +- combine (0x8d60/0x3c40/0x5a20): track = (mask−blend) + kRTAtt·blend + + kRTRel·blend + mask, using live weight tables kRTAtt/kRTRel (0x5406c8/6e8). +- warp (0x540768·0x5406a8), IIR3 (A3/B3)x2, dry/wet identity. + +VALIDATION (scale sweep): +- DUAL (band fc=500, tones 500+2000 at −7.23 dBFS): ref @500 −53.7/@2000 −29.6. + model @500 matches over scale (s30 −51.5, s42 −58.1); @2000 stuck ~−4..−13 (way + short). The DAZING far-field reduction (reduce 2000 by −29.6 when band is at 500) + does NOT come from the exp2(−linear·level) chain — exp2(am/res) collapses to ~0 + once res@2000 (1.344) ≫ res@500 (0.1175). +- T1KQ only1 fc1000: −21.1 vs ref −22.1 (OK). fc-scan shape intact. +=> Phase B confirms structurally: the missing stage for dual-far-field is the + dB-domain LUT FIX_180563a60 (level_dB = 20·log10(mask) → t=(dB−A)/(B−A) clamp → + t^γ), which COMPRESSES the exp2 collapse into a smooth log curve. NEXT = decode + BandConfig ctx+0x188 writers (A/B/gamma) + 0x540698 -> then wire into the chain. + +## ============ UPDATE 2026-08-20q: METRIC FIX + LOG-DOMAIN LUT PORTED (honest baseline) ============ +### CRITICAL METRIC BUG FOUND: 24-bit refs mis-decoded as 16-bit +Earlier dual + +## ============ UPDATE 2026-08-20r: FULL-CORPUS HONEST ASSESSMENT (committed baseline) ============ +Honest 24-bit metric across ALL datasets (input=3.5s window, correct sw decoding): + dual (band fc=500, q-sweep 11): mean|err| 0.41-0.61, max 0.72 ✓ + al_* (fc=1000==tone, level sweep): lv3-12 err 0.05-0.43; lv18 -1.12, lv24 -1.80 (LOW-level over-cut) + res_only1 (resonant.wav, fc 300..700): -1.71..+0.81 (under-cut off-center, shape) + t1k_b1f (tone1k 0dBFS loud, fc sweep): +1.6..+2.12 (LOUD under-cut: LUT cap 0.667 too low) + t1kq_only1 (tone1kq, fc 800..1200): -0.59..+0.01 ✓ + OVERALL mean|err|=0.77, max 2.12. +Systematics: Pchip LUT frozen (floor 0.366/cap 0.667 from al_* mid-level) under-predicts +BOTH the loud tail (t1k_b1f xv>0.8 needs LUT~0.70) and over-predicts low tail (al lv24 +xv<-0.4 needs LUT~0.35). Real LUT extracted from refs: 0.351@-0.5, 0.60@0.07, 0.70@0.85. +Parametric FUN_180563a60 fit to extracted points: A=-13.78dB B=68.29dB gamma=0.344 +(mean|err| 0.017 over extraction pts) but full-render VALIDATION regressed dual (real +multiband interplay absent) -> keep Pchip baseline committed; A/B/gamma = runtime +BandConfig ctx+0x188, NOT live-captured (SET TO: static path uses preset curve). +Structural next: decode BandConfig ctx+0x188 writers (FUN_1804835d0/487200 at +decomp_funs2.txt:21937/21962), wire combine 0x5407c8 + FFT-conv 0x535a70. + +## ============ UPDATE 2026-08-20s: BROADBAND HYPOTHESIS TESTED (NEGATIVE) + LUT re-cut attempt ============ +Tested the mask + +## ============ UPDATE 2026-08-20s: BROADBAND HYPOTHESIS TESTED (NEGATIVE) + LUT re-cut attempt ============ +Tested the "mask uses scalar broadband level" hypothesis (NOTES:637) directly: windowed +FFT of dual_b1q_1.0 ref vs dual.wav input (2.5-3.5s steady): + gain@500 = 0.309 (-10.2dB), gain@2000 = 0.292 (-10.7dB), gain@1000 (NO tone) = 0.995 (-0.05dB), + gain@1500/3000/5000 = 0.85/1.00/1.00. +=> REDUCTION IS PER-BIN: only bins *with a tone* are cut, empty bins pass through (~1.0). + So the level path is am_k/res_k (per-bin), NOT a scalar broadband level. Model structure correct. +LUT re-cut attempt (floor 0.35/cap 0.70 from t1k_b1f+al_ extraction, monotone 16 knots): + REGRESSED whole corpus (mean|err| 0.77 -> 1.09, dual/t1kq/local worse). Reverted to the + joint-fit 12-knot Pchip (dual<=0.72, fc-scan<=0.59, corpus mean 0.77, max 2.12) = KEEP. +Root cause of t1k_b1f residual (+2.1 loud) and al low-level (-1.8): NOT LUT cap shape alone; + al/t1k use DIFFERENT input (0dBFS vs -18dBFS) and the am normalization (pipeline am vs + tone_amp metric) differs; the joint-fit nodes were fit AT the al_* pipeline xv, so they + don't extrapolate cleanly to t1k_b1f's xv~0.85. Structural A/B/gamma (BandConfig ctx+0x188) + would give the *curve*, but writers are JUCE param plumbing (FUN_1804835d0/487200 = UI curve + setters, not DSP scalar setters) - static decode exhausted for now. +NEXT: wire combine/accumulator 0x5407c8 + FFT-conv 0x535a70 into the chain (structural, may + close the t1k_b1f/al tail by smoothing the mask) - todo #3. + +## ============ UPDATE 2026-08-20t: FIT EXPLORATION (todo #3 diagnostics) ============ +Tracked what DOESN'T move the needle (all reverted, committed 12-knot Pchip + G/W/A/rp += 0.9963/0.3335/0.9807/0.0275/0.2159 remains optimal, corpus mean|err| 0.774 max 2.12): +1. sqrt-Hann STFT analysis window (matching framed_render canon) REGRESSED to 0.788 max + 2.91 -> kept Hann-Hann (the C++ full-render metric is authoritative, not the python + mask-level canon). +2. Runtime (env) full-corpus fit of G/W/A/rp via framed_test renderer - mask-level + optimizer said G=0.877/W=0.402/A=0.673/rp0=0.0041/drp=0.28 (mean 0.66 mask-level) but + FULL RENDER regressed to 0.812 max 2.37 -> reverted. Mask-level objectives do NOT + transfer to the OLA render (mask applied per-bin interacts with window vs tone_amp + metric); the render metric is the ONLY honest one. +3. Residuals are level-dependent (t1k_b1f loud +0.8..2.9, al low -1.1..-1.8, everything + else <=0.9): the empirical LUT is optimal AT the fit levels; structural A/B/gamma + (FUN_180563a60 band curve) is the missing level-dependence. +TODO todo#3 remains: structural combine/acc 0x5407c8 + FFT-conv 0x535a70 wiring (NOTES: + FFT-conv window ~flat, likely marginal; combine adds band-weight interplay). + +### TWIN RES vs PY CANON (todo#3 check) +C++ twin |2B/A| (fs=44100,N=2048, sens_stored=24.65, built via build_twin_coeff/twin_apply) +vs Python bandres (GAIN=4.132=10^(24.65/40), render_parity): match within ~1.5% at fc +300..2000 (fc300: 1.1798 vs 1.1887; fc1000: 0.11746 vs 0.11714; fc2000: 0.7057 vs 0.6963). +=> res shape is NOT the source of the res_only1 fc=300 residual (-1.71 over-cut); that + residual is level-LUT related (input resonant.wav level differs from fit levels). + +## ============ UPDATE 2026-08-20u: F0 GATE — DETERMINISM VERIFIED (bit-exact REACHABLE) ============ +Re-rendered test RPPs twice each (reaper -nosplash -renderproject on /tmp copies with +RENDER_FILE -> /tmp), then byte-compared data chunks: + +1. res_only1_500.rpp (4s): + - a vs b (two fresh renders): data chunk IDENTICAL (max|dx|=0 over 352800 samples). + - a vs committed res_only1_500.wav: data chunk IDENTICAL. +2. t1kq_only1_1000.rpp (6s, 1587600 data bytes): + - a vs b: IDENTICAL. a vs committed t1kq_only1_1000.wav: IDENTICAL. + +Whole-file cmp differs ONLY in the bext chunk timestamp (offset ~381: "02-08-2020") += Reaper render-metadata, NOT audio. Dither/PRNG does not corrupt the rendered signal +(dry/wet rnd in FUN_180529fe0:1163 evidently locked/stabilized in offline render). + +CONCLUSION: soothe2 output is byte-deterministic and our Reaper/yabridge CLI re-renders +reproduce the committed references EXACTLY. => Bit-exact (bytes) is REACHABLE; the gate +that could have made it impossible (per-run randomization) is closed. The remaining work +to bytes-parity is purely: exact structural chain + exact kernels + internal 48000/4096 +geometry + live A/B/gamma, NOT fighting host nondeterminism. + +## ============ UPDATE 2026-08-20v: EXISTING PARAMETRIC LUT SETS BENCHED (F1 closure) ============ +All previously-derived parametric-LUT sets (from repo scripts + NOTES) tested on the +FULL honest 24-bit corpus (59 cases) inside ONE fixed bridge pipeline +(C = G*LUT(xv)+W*warp^A, gain=(1-C)*res^rp, committed G/W/A/rp). Result: + + LUT form mean|err| max + ------------------ -------- ----- + pchip (committed) 0.773 3.426 <-- BEST (unchanged) + linear (0.483/0.717) 1.017 3.116 + realpts (lutpts.npy) 1.134 3.192 + u563a60 (A=-13.78/B=68.29/g=0.344) 1.669 6.004 + lut5 (0.4356/0.5914/1.999/0.9364) 1.785 3.951 + lut4 (fit_lut) 1.785 3.951 + powerlaw(C=8) 6.809 11.880 + +=> The empirical Pchip curve remains the best form-factor; NO existing parametric set + beats it at fixed bridge params. Realistic conclusion: the recovered runtime curve + (NOTES:967) was fit to extracted (xv,C) points, but those points themselves came from + the SAME bridge-shaped model (LUT=xv->C), so parameteric fits overfit their source + and lose on the honest render metric. Pchip baseline (0.773) stays canonical. + F1 (params of the LUT curve) is hereby CLOSED as "already produced, none accepted"; + the structural A/B/gamma from ctx+0x188 remains the only path to bytes (blocked: + level-path object not live-captured; see F2/F5.) + +## ============ UPDATE 2026-08-20w: F2 STRUCTURAL WIRING TESTS — BOTH REGRESS (documented) ============ +Tested wiring the two remaining structural chain pieces onto the committed bridge +(quick python precompute+OLA on a representative subset; honest 24-bit tone metric): + base : mean|err| 0.75-0.80, max 2.1 + FFT-conv step5 (mask *= win8193 0.5->1.0) : mean 5.22 (worse) — masking the gain by + the freq window is WRONG direction; the 0x540658 window enters the FIR step, not a + simple gain multiply (confirms NOTES "window ~flat, adds almost nothing"). + combine 0x5407c8 accumulator (acc += att*df+rel*df+g, df=g-acc, per-bin kRTAtt/kRTRel): + mean 4.68 (worse) — naive acc toward 2.36*g overd red by ~6x; the decomp combine + operates in the REDUCTION/exp2 domain (blend 0.8 pedestal + bigkernel exp2), NOT on + the fitted bridge final-gain. Mismatched domain => not a valid wiring test. +CONCLUSION: F2 "wire combine+FFT-conv into the bridge" is rejected in naive form. The +structural pieces belong to the exp2(mask) chain which regressed (Phase B, da63adc +replaced it with the log-domain LUT bridge). The bridge (Pchip, 0.773) remains canonical. +Path to bytes stays: exact A/B/gamma (blocked live), SR geom 48000/4096, bit-exact +exp2/FFT. No code change made this session; NOTE-only. + +## ============ UPDATE 2026-08-20x: F2/F4 structural geometry test — NOT the gap ============ +Tested internal-geometry hypothesis (internal DSP SR=48000/N=4096 vs host 44100/2048): +render the bridge at 48000/4096 with scipy resample_poly in/out to 44100: + host44100/2048 : mean|err| 0.683 (subset) + internal48000/4096: mean|err| 1.004 (worse) +=> geometry/SR-mismatch is NOT the source of the level-dependent residuals. +ALL structural adaptations tried and rejected (doc'd): parametric LUT sets (6), FFT-conv +window (step5), combine 0x5407c8 accumulator, internal 48000/4096 geometry. The bridge +(Pchip, full-corpus mean 0.774) remains canonical; empirical ceiling. +Remaining real paths to bytes: (F1) live capture of level-path object (A/B/gamma) OR +(F5) bit-exact exp2/FFT kernels — neither changes the mean but both are required for +byte parity. No code change; NOTE-only. + +## ============ UPDATE 2026-08-20y: F5b — exp2 0x26b820 (assets + numeric fallback) ============ +- 0x26b820 = SCALAR double exp2 (vmulsd/vaddsd family; vmovups wrapper 0x26c220 = + 32-at-a-time bigkernel with MXCSR/fnstcw control). Table-driven fdlibm-style, + Cody-Waite hi/lo: 8x16 irr tables + 0x181f31940.. series (-708.449.. step, companion + -1.684e-9) + kExp2_f2f4e0.. (log2e~1.4428 head) + vfmadd213sd poly chain. +- EXTRACTED bit-exact P3 assets: dsp/exp2_tables.{hpp,cpp} (8x16 doubles straight from + soothe_mem.bin file=RVA; lead-in series kExp2_big). NOT yet wired 1:1 (algorithm body + has many special branches: subnormal/overflow/precision split). +- exp2_dsp() (dsp/exp2.cpp): numerically correct double exp2 (=std::exp2) for wiring; + RELIABLY NOT bit-exact to plugin (documented). exp2_check: 2e6 dense grid + edges, + PASS vs std::exp2 (0 cells >1e-13) -> ensures fallback correctness only. +Bit-exact exp2 transcription stays P3 (same bucket as DSP-FFT butterfly wiring). + +## ============ UPDATE 2026-08-20z: F5c — DSP-FFT 0x140a70 status (P3 confirmed) ============ +Full dsp/ check run (twin/tables/fftconv/vlog/leveltrack/levelpath/exp2): ALL PASS. +FFT status: dsp/fft.cpp = numerical radix-2 (std::cos twiddle) used by the STFT +framework — produces correct transform, but NOT bit-exact to the plugin DSP-FFT +0x140a70. P3 bit-exact DSP-FFT plan: twiddle source (DAT_182616800 sin-table, sin(k*2pi/ +1024), loader FUN_180039b00 stride 2^(10-m)) + split-radix 2/4/8 in-place butterflies +(FUN_18000bfc0/18000c5e0 with 0x8440 elementwise mul + FUN_1800437c0 acc) + plan-gen +giant (FUN_18002f980 recursion + per-log2 factor tables) remain unwired. This is the +multi-week gap (roadmap P3), required only for byte-parity of the FFT-conv path; the +bridge renderer does not depend on it. + +## ============ UPDATE 2026-08-21: FIX — restore Pchip LUT (revert 12094f8) + comb diagnosis ============ +### FIX (commit e5e9700): revert parametric LUT regression. +12094f8 replaced the empirical Pchip LUT (framed_model) with the parametric linear +form (CAP_A_LEVEL=-24/B=28/gamma=1). With gamma=1 that reduces to IDENTITY on +xv=log10(am/res), breaking the whole mask chain: t1kq fc-scan err jumped +7.9 dB. +Re-instated lut_pchip: t1kq mean|err| 0.226 (was 7.9). Confirms NOTES:1060 F1 closure +(no parametric set beats Pchip at fixed bridge params). lut_parametric helper + CAP_ +consts remain in framed_model.hpp as live-capture reference (currently unused). + +### 24-bit metric loader CHECK +render_parity.py:43 already uses correct 24->64-bit sign extension (x>=0x800000 => +x-0x1000000), NOT the buggy OR-0xFF000000. Repo metric harness is correct; an earlier +symptom of ~50 dB phantom ref levels was an artifact of a scratch loader, not the repo. + +### COMB DIAGNOSIS (biggest open gap, mean 10 dB / max 14.7) +comb.wav tones are at ~-25.6 dBFS (500/1000/1500/2000/3000). comb_ref reduce = +0.02 dB += soothe2 lets ALL tones pass ~unity on this input. Our model over-cuts -7..-15 dB. +=> NOT a multiband-combine problem; it is LEVEL-dependent: at input ~-25.6 dBFS with +band sens 2.24 for two of four bands, real reducer output is ~neutral, ours still cuts. +Likely the bridge LUT/level_scale is calibrated on the al_* fit levels, not on this +low/wide band config. Same class as the known low-level over-cut (al lv18/24, NOTES:964). +Next: calibrate level_scale per input level / verify sens enters the level path correctly. + +## ============ UPDATE 2026-08-21: COMB PARAMS VERIFIED — two SEPARATE phenomena ============ +Decoded true comb wav params from comb_all.rpp (real 4-band config, NOT what tests assumed): + band1 fc=1000 q=1.0 sens=+12 mode 1 + band2 fc=1778.7 q=4.5 sens=-12 mode 1 <-- NEGATIVE sens + band3 fc=195.1 q=0.494 sens=+2.24 mode 0 + band4 fc=13408 q=1.0 sens=+12 mode 3 + depth=0.864, mode=1, selectivity=10, sharpness=10, resolution=1 +Earlier comb tests fed wrong params (band1=678.76, band2/3 sens=+2.24, band4 q3) — those +were NOT the reference config. Even with corrected fc/q/sens the model still over-cuts -7..-15 dB. + +### Two SEPARATE problems (verified): +1. band1-ONLY (fc=1000 q1 s12, input comb.wav -25.6 dBFS): real soothe2 cuts tones ~5.9 dB + @1000 (comb_b1_1000 real ref, NOT neutral); our model cuts -8..-15 (out-real = -2.8..-7.7). + => BASELINE depth is too big at low level (level-scale/LUT low-tail over-cut). +2. comb_ALL (=comb_ref≈comb_byp, reduce ~0 dB on all tones): real soothe2 returns to NEUTRAL + despite band1 active. => MULTIBAND interplay (combine/acc 0x5407c8) must cancel band1's cut, + consistent with M11 additive red=base+ΣW·H and band2 sens=-12 acting as a positive adder. + This is the combine gap (NOTES:1071 F2), and it needs negative-sens + per-band mode handling. + +### Model gaps surfaced: +- negative sens (band2=-12) not modeled -> band2 treated as +suppressor instead of detractor. +- multiband min() (not additive combine) -> cannot reproduce the all-band cancellation to ~0. +- per-band mode 0/3 (peak vs shelf/tilt band types, M10) not dispatched. +No code change; investigation only. These require the structural combine/acc + per-band type +before 4-band comb can close. + +## ============ UPDATE 2026-08-21: multi-band parser fix + combine/sens experiments ============ +### infra FIX: framed_test comma form only parsed argv[3] -> multiband comb tests actually +ran a SINGLE band (first comma-arg ignored the rest). Now parses every comma-arg from +argv[3..]. With correct 4-band params and faithful min(), honest comb = mean 10.15 (over-cut +-7..-15 dB). Single-band corpus unchanged (t1kq 0.226, dual 0.726) => parser fix is safe. + +### combine/sens experiments (reverted, doc only) +Tried closing comb via additive dB-sum (red=sum_i red_i) and signed sens-weight +S=sign(sens)*min(1,|sens|/12) applied to C. Neither reproduces comb_ref neutrality (~0 dB): + min() comb mean 10.15 (baseline) + additive comb mean 15.68 (worse: all bands cut, no compensation) + additive+S comb mean 15.68 (neg-sens band2 at fc=1778 doesn't counter band1 @1000) +Real soothe2 comb is neutral DESPITE band1 (fc=1000 sens+12) active. band2 sens=-12 at +fc=1778 has res~1.9 on the 500..3000 tones, so its naive compensation is tiny vs band1's +cut. => Naive additive/sens formulas CANNOT model the negative-sens band interaction; it +lives in the structural combine/acc 0x5407c8 (per-bin weights kRTAtt/kRTRel + negative +sens entering the level path with opposite sign), which requires the real exp2-domain +chain, not the bridge. comb stays OPEN (structural combine/acc, P4/P5). + +## ============ UPDATE 2026-08-21: LUT moved to LEVEL domain (structural chain) ============ +### Discovery: mask-domain LUT clamps on loud input +- Mask-domain LUT (committed 9b7e9d3) computes t=(mask_dB-A)/(B-A); for loud input + (tone1k 0dBFS) post-exp2 mask_dB < A=-13.78 => t clamps to 0 => val=0 => FULL cut. + Measured t1k err -24.04 dB. Root cause: exp2(-level) output dB range is unbounded + below, LUT domain [A,B] assumes bounded input. +- FIX: apply FUN_180563a60 power-law to LEVEL (am/res*scale) BEFORE IIR/exp2: + lvl = pow(t, gamma)*MULT, t=(20*log10(lvl)-A)/(B-A) clamp [0,1]. + Level dB stays inside [A,B] for both quiet (t1kq: +6.2dB) and loud (t1k: +24dB). + Result: t1k err -24.04 -> +2.45 dB. + +### TOOLING HAZARD: stale-binary sweeps +- cmake --build skips recompile when source mtime unchanged within same second; + param sweeps silently re-run the SAME binary (9-point sweep -> 1 unique result). + Earlier "gamma doesn't matter" observation was this artifact, NOT DSP behavior. +- Protocol now: os.utime(src) before build + assert binary mtime fresh (sweep_fresh.py). + +### Fresh 2D sweep (g x mult, verified builds), 3-case smoke (render48k 48k/4096): + g=0.344 m=3.8 optimum: t1kq=-0.788 t1k=+2.453 al12=+0.037 mean|err|=1.093 + (committed version was mean 8.2). gamma>=0.9 ALL worse (+2..+5): spatial IIR + after LUT redistributes peaks; naive ratio math (need g=1.17 from raw t ratio + 1.744->2.0) does NOT survive the IIR chain. Empirics rule. +- MULT=3.8 is a PLACEHOLDER (maps t^gamma back to level domain; no decomp basis yet). + A/B/gamma are decomp-extracted (BandConfig ctx+0x188 refs, NOTES:967). + +### Scope clarification +- scripts/corpus.py exercises the BRIDGE path only (44.1k non-internal grid): + full corpus 1.594 mean / comb 10.15 — UNCHANGED by structural work (verified + stash-compare). comb regression is pre-existing bridge state, separate issue. +- Structural chain validated via render48k smoke (3 cases) + module checks + (fn529fe0/exp2/twin/tables/leveltrack/levelpath/fftconv all PASS). +- render48k tail artifact: last BLK=65536 block zero-pads => am decays in final + frames (~last 0.06s of y48). Cosmetic; metric window barely affected. + +### Commit: 535b150 (LUT level-domain restructure). +NEXT: step 3 wire combine_acc track into mask output (currently computed, +discarded); step 4 FFT-conv 0x535a70 mask smoothing; then decode BandConfig +ctx+0x188 writers to replace MULT placeholder with decomp value. + +## ============ UPDATE 2026-08-21b: IIR3 bidirectional + combine semantics from raw decomp ============ +### IIR3 is BIDIRECTIONAL (corrects NOTES:830 "inline TWICE") +- consumers_out.txt:955-1075: TWO [reset state 0x440510=0, forward pass, backward + pass] pairs. State PERSISTS from forward into backward within a pair; reset + between pairs. Operand order verified: y[i] = x[i]*B3[i] + state*A3[i] + (A3@0x3c0510 multiplies STATE, B3@0x340510 multiplies x). Backward iterates + nbin-2 .. 1 with descending coefficient index. +- Wired into structural chain (commit 1a616e1); LUT_MULT retuned 4.4->4.2; + smoke mean|err| 1.021 (vs 0.993 fwd-only — parity within noise, kept faithful). + +### Combine block EXACT semantics (thunk-level, corrects both 20h and 20j notes) +- 8d60 sub: args (acc=0x5407c8, mask=0x540678, dst=f6f8=0x5406f8), dst=3rd arg: + f6f8[i] = mask[i] - acc[i] (acc NOT overwritten by sub; 20j was wrong) +- mirror f6f8 (11940 x2) +- 3c40 fma: f6f8_upper += kRTAtt*acc_upper ; f6f8_lower += kRTRel*acc_lower + (dst = F6F8 halves, b = acc — 20h had this right) +- 5a20: acc += mask (persistent per-band accumulator) + +### CRITICAL SCOPE FINDING for Step 2 (combine wiring) +- In the ONLINE single-band path (0x5408b8==0) the updated f6f8/acc have NO + consumer inside FUN_180529fe0 before warp/IIR3/dry-wet: warp applies to + band buffer directly, blend overwrites f6f8 next band/frame. Verified by + exhaustive grep of consumers_out.txt (last 0x5406f8 use = offline branch). +- => combine CANNOT close t1k/al single-band tails by itself; its effect must + enter via (a) multiband combiner 0x5316e0 reading acc/f6f8 across bands, or + (b) FFT-conv stage. Step 2 re-scoped: decode 0x5316e0 acc consumption FIRST, + only then wire. Do NOT invent a track->mask feedback (would be empirical). +- Online blend confirmed: f6f8 = freqaxis*(1-mix) + mix*fVar25(0.8) via + 6e40+15060 thunks, then bigkernel exp2 in-place on band (line 805-832). + +### Commits: 9ffe60e (IIR3 fwd-only + retune), 1a616e1 (bidirectional + decode). + +## ============ UPDATE 2026-08-21c: FULL STRUCTURAL CORPUS (first honest numbers) ============ +### Harness: scripts/corpus_structural.py (render48k x all 62 cases, bridge-compare mode) +Structural chain (48k/4096, LUT level-domain g=0.344 m=4.2, IIR3 bidi) vs bridge: + group n bridge struct delta + t1kq 7 0.226 0.768 +0.54 worse + t1k 11 1.801 2.114 +0.31 worse + al 6 0.638 0.986 +0.35 worse + res 11 0.628 0.437 -0.19 BETTER + dual 22 0.726 3.264 +2.54 MUCH worse + comb 5 10.149 6.116 -4.03 MUCH BETTER + TOTAL 62 1.594 2.286 +0.69 worse + +### KEY FINDINGS: +1. **comb: structural ALREADY beats bridge by 4 dB** (6.1 vs 10.1) WITHOUT acc + wiring — per-band structural min() + warp closes part of the multiband gap. + Plan's "combine closes comb" partially pre-empted by the chain itself. +2. **dual_*_500: constant -6.7 dB over-cut for ALL q** (q-independent!). + res[center] is q-independent (0.117) => systematic LEVEL offset at band + center on two-tone input, not shape. dual_*_2000 (off-center bin) BETTER + than bridge (-0.1..-0.4 vs +0.1..+2.3). At same xv, bridge-LUT cut ~-9.3 dB, + our exp2 path cuts ~-20 dB: real reduction SATURATES (~C_max~0.70 per + extracted LUT pts), exp2(-lvl) does not. +3. al level-sweep error is MONOTONE in level (+0.88@lv3 -> -1.82@lv24): our + level->cut curve too steep mid-range; but t1k (loudest) flips back to + under-cut (+1.8) => NON-monotonic overall, no monotone LUT fixes all groups. +4. gamma/MULT sweep (g in {0.15,0.22,0.30} x m in {4.2,5.0,5.8}, fresh builds, + 12-case subset incl. dual/res/comb): ALL points worse than current + g=0.344/m=4.2; gradient points BACK to current optimum. Blind param space + EXHAUSTED around this parametrization. + +### CONCLUSION (Step 1 status): criterion "structural >= bridge on single-band" +NOT met globally (regress on t1kq/t1k/al/dual; better on res). The missing +piece is the REAL reduction curve saturation — needs either: + (a) Step 7 BandConfig ctx+0x188 live capture (A/B/gamma per config), or + (b) re-examination of bigkernel INPUT: maybe level into exp2 carries an + additional transform (log-domain offset / per-band depth scalar + 0x540888 / offline 0x5e80+FUN_180323f20 ops) that saturates the cut. +Do NOT tune MULT further — it is calibrated; the CURVE SHAPE is wrong. + +### FFT-conv priority DOWNGRADED: NOTES:18c shows window 0x540658 tail is +near-flat (0.8->1.0 plateau at N/2>=2048), FIR-construction effect on mask +shape minimal for N=4096. Step 4 stays P3. + +## ============ UPDATE 2026-08-21d: REDUCTION-LAW DISCOVERY + Phase A closure ============ + +### Discovery: effective reduction is AFFINE in dB(level) +RT_DUMP_BIN tracts (frame 100) across al sweep fit: + x_exp2 = -0.6646 - 0.05877 * dB(lvl_raw) (mask ~ 0.8 * lvl^(-0.354)) +gamma_eff 0.354 ~= decomp BandConfig gamma 0.344 (NOTES:967) -> power law, +NOT the double-exp t^gamma->exp2 form (21c). Tone-levels verified +-0.13 dB. + +### Pre-IIR application calibrated through chain model +Python simulator (tables from rt_mask_tables.cpp; IIR1 fwd, IIR2 fwd, exp2*0.8, +W, IIR3 bidi x2): pre-IIR affine peak dilutes ~2.3x by spatial IIRs -> +X0=1.8, SLOPE=0.11 (max|x-resid| 0.09 ~= +-0.5 dB on tones). +FULL CORPUS fresh-build A/B (stale-binary protocol, both reproduce exactly): + group old(HEAD LUT) new(affine pre-IIR) + t1kq 0.768 0.460 v + t1k 2.114 1.196 v + al 0.986 0.132 v (all 6 levels |e|<=0.25) + res 0.437 2.046 x + dual 3.264 4.317 x + comb 6.116 5.718 v + TOTAL 2.286 2.633 + +### Phase A experiments (ALL on fresh builds / validated models) +1. res regression is REAL (not stale binary; reproduced both directions). +2. High-bin/IIR3-backward propagation hypothesis REFUTED: at frame-100 the + two laws give IDENTICAL masks on res_500 (<1e-4 everywhere); divergence + lives in other frames' level zone (-13..+15 dB) where old cuts more. +3. Direct-affine (bypass IIR1/2, raw constants) REFUTED: TOTAL 2.777. + Spatial IIRs are integral to the effective curve. +4. Offline trajectory model (RT_DUMP_ALL per-frame lvl_raw; chain sim; + metric-window frames [55,90)/[243,278) @48k/1024-hop; zero-pad frames + excluded — they are beyond the trimmed output!) VALIDATED: known-law + predictions within 0.09..0.19 dB on res_500/al_24/t1k_1000 (al_12 0.53). +5. Grid search over affine family (X0, SLOPE, floor F>=0, ceiling CMAX), + objective = sum pred_err^2 over anchored cases: + OPTIMUM X0=2.00 S=0.0825 F=0 CMAX=inf -> rms 0.75 dB with + res_500 +0.84 / al12 +0.29 / al24 -1.21 / t1k -0.02. + Ceiling never active in-grid; positive floors do NOT help (top all FL=0). + +### KEY NEGATIVE RESULT +NO law of the scalar affine family fits tone AND noise content +simultaneously (res tension +0.4..+1.1 unavoidable). Old Pchip "wins" on +res/dual/comb via error cancellation, not shape correctness; affine wins +tones decisively (al +-0.13 dB linearity over 42 dB!). The missing piece is +CONTENT-DEPENDENT behavior: detector dynamics (att/rel interplay with +fluctuating spectra), or combine/acc consumer (multiband feedback), or +saturation mechanism. => Gate "no group regression" FAILS for every family +member -> committed canon stays HEAD (LUT g=0.344/m=4.2). + +### Tooling kept (opt-in env, zero cost when unset) +- RT_DUMP_BIN= [RT_DUMP_FRAME=n]: single-frame tract dump (7 cols). +- RT_DUMP_ALL=: append per-frame binary lvl_raw spectra + [i32 frame][i32 nbin][f32 x nbin]. Use ONE env per run (BIN early-return + eats ALL frames if combined!). rm target first (append mode). + Captures live under /tmp/opencode/traj_*.bin (law-independent detector). +- Offline engine: /tmp/opencode/phaseA_grid_fast.py (combo-vectorized chain + sim + grid search); estimator zoo: phaseA_estimator_zoo.py. + +### NEXT (Phase B candidates, priority order) +1. Detector dynamics: att/rel on fluctuating spectra vs ref renders of + noise-like material (res group) — per-frame gain extraction protocol + ready (STFT ratio out/in at metric bin, see phaseA session notes). +2. Step 7 live capture ctx+0x188 (constants + possible content-dependent + branch), NOTES_CAPTURE.md method. +3. combine consumer hunt (multiband cascade) for dual/comb. + +## ============ UPDATE 2026-08-22a: PHASE B RECOVERY — offline detector hypotheses REFUTED ============ + +Контекст: Phase B сессия 2026-08-21 (вечер) + утро 08-22 осталась незакоммиченной — +скрипты в /tmp/opencode, результаты не сохранялись. Все 4 эксперимента перепрогнаны, +stdout зафиксирован (`phaseB_*.out`), скрипты перенесены в `scripts/phaseB_*.py`. +Движок: phaseA_grid_fast.py (валидированный офлайн-тректор, погрешность 0.09..0.19 dB +на анкорах). Анкоры res_500/al_12/al_24/t1k_1000, критерий pred_err ~ 0 на ВСЕХ. + +| # | Гипотеза | Скрипт | Результат | Статус | +|---|----------|--------|-----------|--------| +| 1b | slide_max pooling lvl (w=3..33) | phaseB_pool2.py | w=3: tones −2.7/−2.3/−4.9, rms 3.02 (base 1.04) | **REFUTED** | +| 1c | neighborhood mean/RMS pooling | phaseB_pool3.py | best mean w=3 rms 1.35; res +1.9..+5.1 vs t1k −1.9..−4.4 tradeoff, ни одна точка не закрывает все 4 | **REFUTED** | +| 2 | temporal dynamics (hold b/dbdecay r/ema a на полной траектории) | phaseB_temporal.py | ВСЕ варианты = baseline (rms 1.03–1.05): метрика в steady-state, состояние успевает устояться до окна | **REFUTED (no-op)** | +| 3 | ρ(IIR1)+Δ joint scan (ρ∈[0.30,0.95], Δ∈[−1.5,+1.5]) | phaseB_rho.py | best ρ=0.830 D=+1.40 → rms 0.654; НО al_24 стабильно −1.19..−1.25, ρ без источника в декомпе (канон 0.692) | **REJECTED** (нарушает golden rule #1, тот же scalar-family тупик) | + +Выводы: +1. Пространственный pooling ЛЮБОГО вида (max/mean/rms) не объясняет content-gap. +2. Temporal-класс гипотез НЕПРОВЕРЯЕМ на steady-state анкорах — нужен переходный + контент (burst-рефы уже есть в soothe-bt) или другой анкорный набор. +3. Лучший (ρ,D) = репараметризация affine-семейства Phase A → упирается в тот же + KEY NEGATIVE RESULT (scalar-семейство не закрывает тон+шум одновременно). + Канон НЕ сменён: HEAD (LUT γ=0.344/MULT=4.2, ρ=0.692). + +NEXT (приоритеты без изменений): +1. Step 7 live capture ctx+0x188 (NOTES_CAPTURE.md метод) — константы A/B/γ + + возможная контент-зависимая ветка. Требует REAPER+плагин. +2. combine consumer hunt (межполосный каскад) для dual/comb. +3. Опционально: transient-анкоры для проверяемости temporal-класса. + +## ============ UPDATE 2026-08-22b: STEP 7 EXECUTED → PREMISE REFUTED (BandConfig = GUI-only) ============ + +Инфраструктура захвата готова и работает: +- `scripts/step7_capture.py ` — spawn reaper (+play.lua realtime) → chunked snapshot + хоста yabridge → fingerprint level_gain-пар (level[j]==j/1024 exact) → u64-ref голосование + базы level-path объекта (+0xe0+band*0x18) → decode всех BandConfig вокруг базы. +- `play.lua` ИСПРАВЛЕН: reaper.Sleep НЕ существует в API (падал скрипт, транспорт + продолжал играть без keep-alive) → цикл reaper.defer. +- Грабли: /proc/ vs /proc//mem (pread каталога = тихий EIO на всё); layout + объекта ПЛАВАЕТ между сессиями → оффсеты BandConfig не хардкодить, только fingerprint. + +Живые константы (7 захватов: render_long, t1kq_base, t1k_b1f_1000, al_12, al_24, +dual_b1q_0.5, comb_base — ВСЕ идентичны): +- cfg@obj+0x168: **A=−24.0 B=+28.0 γ=1.0 flag=0 cb=SET** («кривая редукции») +- cfg@obj+0x170: A=16.0 B=20000.0 γ=1.0 cb=SET; shaper f32 @+0x20..: 0.55, 7.130898, + 2.772589(=ln16), 2.718282(=e) +⇒ противоречие gap #3 разрешено: −24/28/1 подтверждено живьём; −13.78/68.29/0.344 + был ФИТОМ к рендер-референсам, не свойством плагина. + +FUN_180563a60 расшифрован ДО КОНЦА (f_563a60.dis, 165 строк): +- вход: mask-doubles obj+0x4198+band*0x2000 (пишет FUN_18056e3e0 из band-list + +0x178→[+0x78/+0x84]); x-axis пар = j*(1/1024) (конста 0x24c3c50); + dB = logf(mask)*8.685889 (=20/ln10, 0x24c43e0); +- выход: gain[j] = clamp01(cb(A,B,dB)) где cb = std::function @cfg+0x90 — В ЖИВЫХ + ЗАХВАТАХ cb ВСЕГДА SET ⇒ статическая ветка A/B/γ НЕ ВЫПОЛНЯЕТСЯ НИКОГДА; +- статическая ветка (cb==NULL, для протокола): t=clamp01((dB−A)/(B−A)); + γ==1 → t; flag==0 → t^γ; flag!=0 → 0.5·(1+sign(2t−1)·|2t−1|^γ). + +Callgraph: весь кластер 5631c0/563260 → 563440 → {56e3e0, 563a60} достижим ТОЛЬКО по +DATA-xrefs (vtables 1826a03cc..420, 1824b14f0..510) — GUI-timer heartbeat. АУДИО-метод +FUN_180529fe0 (Soothe2Module::vtbl) НЕ ссылается на {+0x168,+0x170,+0x178, ++0xe0,+0x198,+0x4198}; его callees — только memcpy-туннели (181ba94b0 ×13, 52dbc0, 52d920). +Файл /tmp/consumers_out.txt восстановлен из decomp_funs.txt (215 строк, вне git!). + +### ВЫВОДЫ (меняют приоритеты) +1. **Шаг 7 BITEXACT_PLAN в исходной постановке ОПРОВЕРГНУТ**: BandConfig A/B/γ питает + только GUI-отрисовку кривой, аудио-путь его не читает. Live-захват A/B/γ не может + закрыть насыщение кривой редукции аудио-цепи. +2. LUT-блок структурной цепи (t^γ·MULT с −13.78/68.29/0.344/MULT=4.2) = EMPIRICAL фит + БЕЗ декомп-источника в аудио-пути (формула списана с GUI-функции!). Помечен EMPIRICAL; + канон не меняем (корпус держит), но источник правды теперь внутри аудио-модуля. +3. Новый приоритет №1: полный разбор аудио-тела FUN_180529fe0 (215 строк декомпа, + /tmp/consumers_out.txt) + combine-consumer hunt — насыщение C_max≈0.70 сидит там. + +## ============ UPDATE 2026-08-22c: PARAM BRIDGE + RENDER-BENCH CALIBRATION ============ + +Инфраструктура управления плагином через ОФИЦИАЛЬНЫЙ параметр-мост REAPER: +- `dump_params.lua` — дамп всех FX-параметров (имя, raw 0..1, formatted UI) в /tmp/opencode/fxparams.txt. +- `setparam.lua` — установка по имени через TrackFX_SetParam + SaveProjectEx (env S2_SET="name=val;...", S2_OUT). +- `scripts/rpp_setparam.py` — редактор XML-стейта в RPP с корректным апдейтом length-полей + (A=buflen−16, B=len(xml), хвост JUCEPrivateData сохраняется). + +### КЛЮЧЕВОЕ ОТКРЫТИЕ: XML `` секция НЕ источник VST3-стейта +Живой дамп: depth raw=0.524 ↔ fmt=0.9; в XML при этом "0.8639736175537109" (=UI-единицы!). +Плагин восстанавливает параметры из БИНАРНОЙ части чанка; XML-список — декоративная +копия для GUI-восстановления. Правки только XML → рассинхрон → плагин откатывается в +дефолт (объясняет «магию» идентичных рендеров −31.76 у любых depth-правок; контроль +same-value rewrite давал байт-в-байт реф ✓). ВНИМАНИЕ к rpp_setparam.py: менять можно, +но аудио это НЕ меняет. + +### Семантика параметров (через мост) +- `depth`: raw 0..1 → UI **±18 линейно** (ui=36·raw−18). Рендеры подтверждают монотонность: + raw=0 → юнити; raw=1.0 (+18) → −37.6 dB @tone. +- `input trim`: raw → **±24 dB**. +- band sens: fmt в dB; stereo mode mid|side; balance 100%|57%. + +### WAV-грабли (живое подтверждение класса багов 71644ff) +REAPER пишет bext(602B)+junk(28B) ПЕРЕД data; наивный парсер (readframes+reshape без +каноничного loader) даёт фантомный широкополосный шум и фальшивый клиппинг +(corr(L,R)≈0 артефакты). Каноничный render_parity.load корректен; санити bypass: +corr(L,R)=1.0000, rms=out=in — РЕНДЕР-ПАЙПЛАЙН ЧИСТЫЙ. + +### Измеренные кривые редукции (t1kq-пресет, тон@1000, Goertzel 0.75s) +- Depth-свип: монотонный, raw .5→−5.5 dB; .75→−21 dB; 1.0→−37.6 dB @tone. +- Input-trim свип (холодный тон −18 dBFS): R_eff растёт 1.4→15.7 dB на всём ±24 — + ПОТОЛКА НЕТ в этом диапазоне (против гипотезы C_max≈0.70 как свойства плагина). +- Горячий тон (+6 dBFS): out@1000 ЗАМИРАЕТ на −26.74 dB при trim ≥+12 (R_eff cap ≈20.7), + плато воспроизведено дважды. +- НО: при одинаковом относительном драйве холодный/горячий дают РАЗНЫЙ gain + (−15.7 vs −20.7 @L≈+6) ⇒ отклик детектора не чистая f(level) — контент/нормализация + (согласуется с KEY NEGATIVE RESULT Phase B). + +### Следствия для приоритета №1 +1. «Насыщение C_max≈0.70» старой модели — артефакт placeholder MULT=4.2/Pchip cap 0.667, + а не свойство плагина в рабочем диапазоне. +2. Реальный потолок проявляется как FLOOR выходного уровня (−26.74 dB @hot) — искать + механизм floor/clamp в level-path декомпе с конкретной целью. +3. Инструмент готов для систематического картирования R(L, sens, depth): скрипты + + мост позволяют прогонять десятки конфигов без ручного GUI. + +## ============ UPDATE 2026-08-22d: SATURATION KNEE MAPPED — HARD FLOOR −20.72 dB ============ + +Hot-тон (+6 dBFS, band1 fc679 q1 sens+12 depth+0.9), gain=out−dry по Goertzel@1000: +| trim | L=in+trim | gain | +|------|-----------|------| +| 0 | −6.02 | −3.39 | +| +6 | −0.02 | **−20.72** | +| +9..+24 | +3..+18 | **−20.72** (pinned, ≥24 dB драйва) | + +1. ЖЁСТКИЙ ПОТОЛОК: gain замирает на −20.72 dB (ratio 0.0921) — не плавное плато, + а колено между L=−6..0 с последующим pin. Форма ⇒ t-clamp модели (t→1 при + dB_lvl≥B=28 → lvl=MULT → фикс. маска) КАЧЕСТВЕННО подтверждена живьём. +2. Колено: между trim 0 и +6. При γ=1/A=−24/B=28 это задаёт офсет + физический уровень → внутренний lvl-dB ≈ 31±3 dB. +3. ЗАГАДКА для следующей сессии: холодный тон при том же относительном драйве + (L=+5.94 через trim+24) даёт gain только −15.74 (НЕ насыщен), горячий при L=+5.98 — + −20.72 (насыщен). Trim ≠ простой pre-gain детектора ИЛИ отклик зависит от + абсолютного контента. Тест-план: (a) спектральный профиль нотча сатурированного + рендера vs форма маски модели; (b) файловый gain вместо trim; (c) алгебра пола: + 0.0921 = blend·exp2(−MULT)·W·kBand768·kWarp·IIR3·convwin @bin85 — все live-таблицы + есть в dsp/, решается относительно MULT офлайн. +4. RT_DUMP_ALL НЕ работает в оффлайн-рендере плагина (это инструментация только + нашего C++; у настоящего бинар её нет). Live-capture траекторий = только память. +5. +0x4198 маски уровня объекта = GUI-кривые (band1 растёт 1.0→3.98 к ВЧ) — ещё раз + не аудио-маски; для floor-hunt использовать алгебру (3c), не этот объект. + +## ============ UPDATE 2026-08-22e: FLOOR ALGEBRA SOLVED — blend·ln10/20 EXACT ============ + +Модельный grid (render48k, live A=−24/B=28/γ=1 через RT_LUT_* env, файловый gain): +| MULT | B | base(−6dB) | p3(−3.1dB) | +|------|---|-----------|-----------| +| 4.2 | 28 | −8.78 | −9.70 | +| 9 | 28 | −16.35 | −18.18 | +| 12 | 28 | −20.49 | −22.77 | +ВЫВОД: t-clamp в транскрибированной цепи НЕ даёт pin (base/p3 расходятся на 2+ dB +при любом MULT/B) ⇒ механизм пола живёт НЕ в LUT-блоке. + +### РЕШЕНИЕ АЛГЕБРАИЧЕСКИ +Измеренный pin −20.72 dB совпадает с точностью 0.0055 dB: + + gain_floor = 20·log10(blend · ln10/20) = 20·log10(0.8 · 0.115129) = −20.7145 + +т.е. маска пола = blend × (ln10/20). Константа ln10/20 = 0.1151292546 @0x1824c3cd4 — +ТА ЖЕ семья что и коэфф. attack/release expf(p·0.11513) (NOTES_CAPTURE 19b). +Audio-референсы константы: **FUN_180529c60**, 18052baa0, 18052bad0, 18052bba0. + +### Ведущий след (f_529c60.dis:94-101) + xmm0 = *(this+0x54087c) + xmm0 *= 30.0 (@0x1824c4478) + xmm0 -= 90.0 (@0x1824c44d0) + xmm0 *= 0.115129 (@0x1824c3cd4) + call expf (181a14cac) + xmm0 *= результат FUN_1804d56b0(band buffer 0x540678[band], r13d=n) +⇒ параметрический dB→linear фактор (30p−90 ∈ [−90,−60] dB) умножается на вычисленное + над полосой значение и идёт в band-буферы. Точная роль (floor/dither/scale) TBD. + +### NEXT +1. Декодировать FUN_1804d56b0 + контекст использования фактора в 180529c60. +2. Проверка предсказания: floor_gain(mix) должен сдвигаться как 20log10(blend′/0.8) + (рендер mix=50% через мост). +3. Порт найденного клампа в структурную цепь → корпус с гейтом vs bridge. + +## ============ UPDATE 2026-08-22f: FLOOR DISCRIMINATION — 3 гипотезы, детекторный кап ============ + +Дискриминационные рендеры (hot-тон, trim+12, через параметр-мост): +| конфиг | gain изм. | mask=10^(g/20) | интерпретация | +|--------|-----------|----------------|----------------| +| mix100 sens12 | −20.72 | **0.8·ln10/20** | базовый пол | +| mix50 sens12 | −5.20 | eff 0.552=out/dry | **dry/wet кроссфейд**: 0.5+0.5·0.0921=0.546→−5.26 ✓ | +| mix100 sens6 | −18.78 | **1.0·ln10/20** | пол сдвинулся ровно на ×ln10/20 | + +ОПРОВЕРГНУТЫ: (а) явный кламп mask≥const (mix50 должен был остаться −20.72); +(б) blend-floor 20log10(blend′·ln10/20) для mix50 (предсказывал −26.74); +(в) naive detector-sat lvl∝sens (предсказывал −11.33 для sens6). + +ЧТО СОШЛОСЬ: +1. Mix = классический кроссфейд out=(1−m)·dry+m·processed; пол в PROCESSED пути. +2. Полы = ТОЧНЫЕ кратные ln10/20: {0.8× @sens12; 1.0× @sens6}. +3. Движение пина с sens: Δx_exp2 = log2(mask/0.8): −3.1188→−2.7996, Δ=−0.3192 + на 6 dB sens ⇒ наклон −0.0532 dB⁻¹ ≈ аффинный закон Phase A (−0.05877!) + ⇒ похоже, что НАСЫЩАЕТСЯ ДЕТЕКТОР (кап его dB_lvl ≈ 36..42 в завис. от sens), + а редукция следует аффинному закону до этого капа. Точные значения ln10/20 + при этом — след пересечения закона с сеткой экспонент, не отдельный кламп. +4. Наша модель на том же материале достигает lvl_max=3.012 ≈ log2(ln10/20)=3.1188 + (Δ3.5%) — почти касание первого пола естественно (RT_DUMP_ALL render48k). + +### NEXT (Шаг 9 продолжение) +1. Локализовать кап детектора: где dB_lvl перестаёт расти (vlog/leveltrack путь, + FUN_18052d650 IIR1 / leveltrack таблицы) — искать ограничитель амплитуды FFT/am. +2. Точная формула капа от sens: снять полы на сетке sens {3,6,9,12} × trim{18,24}. +3. Порт: вместо t-clamp LUT — детекторный кап + аффинный закон Phase A; корпус с гейтом. + +## ============ UPDATE 2026-08-22g: NO-LUT TEST + CLIPPING THEORY REFINED ============ + +### NO-LUT эксперимент (RT_LUT_OFF=1, render48k) +Тракт@bm85 (hot-тон, RT_DUMP_BIN): lvl_raw=3.0117 → post-IIR1/2 band_level=1.0646 +(IIR1 A[85]≈0.65 сильно сглаживает изолированный пик) → prewarp mask=0.3825·W=0.408 +→ итоговый gain модели −7.67 dB (против real −20.72). Убрали LUT — модель всё равно +далека: разрыв теперь ЛОКАЛИЗОВАН в детекторном фронте (am/res/scale и/или IIR-сглаживание), +а не в форме кривой. Модельный lvl_max=3.012 почти касался бы пола 3.1188=log2(20/ln10), +если бы не разводился IIR1 по частоте. + +### Клиппинг-теория: частично опровергнута, но указывает на clamp sidechain +- REAPER рендерит в float: trim-овердрайв кормит плагин честными float >1.0, обрезки на + itemе + +## ============ UPDATE 2026-08-22g: NO-LUT TEST + CLIPPING THEORY REFINED ============ + +### NO-LUT эксперимент (RT_LUT_OFF=1, render48k) +Тракт@bm85 (hot-тон, RT_DUMP_BIN): lvl_raw=3.0117 → post-IIR1/2 band_level=1.0646 +(IIR1 A[85]≈0.65 сильно сглаживает изолированный пик) → prewarp mask=0.3825·W=0.408 +→ итоговый gain модели −7.67 dB (против real −20.72). Убрали LUT — модель всё равно +далека: разрыв теперь ЛОКАЛИЗОВАН в детекторном фронте (am/res/scale и/или IIR-сглаживание), +а не в форме кривой. Модельный lvl_max=3.012 почти касался бы пола 3.1188=log2(20/ln10), +если бы не разводился IIR1 по частоте. + +### Клиппинг-теория: частично опровергнута, но указывает на clamp sidechain +- REAPER рендерит в float: trim-овердрайв кормит плагин честными float больше 1.0, + обрезки на item'е НЕТ. +- Pre-clipped файл (x12dB с клиппингом на диске) @trim0 -> -12.31 dB != -26.74: сравнение + НЕКОРРЕКТНО (искажён DRY-путь файла), но показало, что «клиппинг на входе» сам по себе + не воспроизводит пин. + +### ОБЪЕДИНЁННАЯ ГИПОТЕЗА (лид): clamp копии sidechain на +-1.0 внутри плагина +Факты: +1. Колено hot-тона ровно при переходе эффективного уровня через 0 dBFS (trim 0->+6). +2. h_06 == h_12 == h_24 по метрике — после достижения 0 dBFS драйв не растёт. +3. Полы = точные кратные ln10/20 ({0.8x, 1.0x} для sens 12/6). +4. mix = сухой кроссфейд (-5.26 предсказано / -5.20 измерено). +Открытое: cold trim+24 (+6 eff) дал -15.74 вместо -20.72 => кламп либо мягкий (tanh-like, +разная глубина насыщения), либо детектор реагирует на RMS/форму, а не только пик. + +### NEXT +1. Прямая верификация: live-capture sidechain-буфера плагина во время сатурированного + рендера (step7_capture-style скан памяти, искать плоскую вершину +-1.0). +2. Снять форму клампа: сетка hot-файлов {-9,-6,-3,0 dBFS} x trim {6..24} -> кривая + насыщения mask(eff_level). +3. Порт: sidechain-clamp в render48k (min(max(in,-1),1) на копии для детектора) + + аффинный закон Phase A без LUT -> корпус с гейтом. + +## ============ UPDATE 2026-08-22h: FLOOR LAW MAPPED — sens и depth ============ + +Пол редукции как функция параметров (hot-тон −6 dBFS, trim+12, mix100): +| sens | floor gain | mask | lvl_cap=log2(0.8/mask) | +|------|-----------|--------|---------| +| 3 | −17.74 | 0.12973 | 2.6245 | +| 6 | −18.78 | 0.11502 | **=ln10/20 точно** | +| 9 | −19.78 | 0.10256 | 2.9635 | +| 12 | −20.72 | 0.09208 | 3.1191 | + +### Эмпирический закон + floor_dB(sens) ≈ 20·log10(ln10/20) − (sens − 6)/3 (якорь sens6; ±0.06 dB) +Depth тоже сдвигает пол: depth UI 0 → **−19.10 dB** против base (+0.9) → −20.72 +(Δ≈1.62 dB на 0.9 ед. depth ⇒ ~1.8 дБ/ед., НЕ как sens/3). +lvl_cap(sens) квазилинеен: наклон ≈0.055/oct-per-dB ≈ аффинный наклон Phase A (0.0588). + +### Статус механизма +Пол — гладкая поверхность F(sens, depth); сайт в декомпе не найден (юзеры ln10/20 — +сеттеры att/rel и dither-фактор). Гипотеза детекторного капа остаётся лидом: +lvl_raw детектора перестаёт расти при эффективном уровне ≈0 dBFS (колено), а кривая +редукции следует закону до этого капа. Точная форма F(...) — на картирование. + +### NEXT сессии +1. Декод FUN_18052d650 (IIR1) + leveltrack-путь на предмет ограничителя am. +2. Картирование F(sens,depth) сеткой 4×4 через мост (~30 мин рендеров) → фит. +3. Порт эмпирического пола в структурную цепь (env-параметризованный), корпус с гейтом. + +## ============ UPDATE 2026-08-22i: RT_LVL_CAP ИНЕРТЕН — разрыв в детекторном фронте ============ + +Порт капа пост-IIR уровня (RT_LVL_CAP env в process_band_structural, после IIR2): +корпус с cap=3.119 и 2.798 ДАЛ ТОЧНО БАЗОВЫЕ ЧИСЛА (TOTAL 2.286) — кап не связывается, +т.к. пост-IIR уровни модели максимум ~1.06 (lvl_raw 3.01 разводится IIR1 A[85]≈0.65). +⇒ Модели не ХВАТАЕТ уровня, а не лишний: реальный плагин доводит пост-IIR lvl до 3.12 +(пол), наш детекторный фронт (am/res/scale нормировка + IIR-сглаживание изолированных +пиков) теряет ~x2.8. ПРИОРИТЕТ СДВИНУТ: чинить детекторный фронт (амплитудная +нормировка am, форма res_k, сила IIR1/2 вдоль частоты), а не форму кривой. +RT_LVL_CAP оставлен в коде как инструмент (opt-in, инертен при текущих уровнях). + +## ============ UPDATE 2026-08-22j: WHITE-NOISE PROBE — архитектура расшифрована ============ + +Белошумный пробник через сатурированный конфиг (trim0 на шуме −6 dBFS RMS): + +REAL (плагин): передача ПЛОСКАЯ −0.01…−1.65 dB на ВСЕХ частотах; + лёгкая вмятина −1.04 @678 (центр полосы), HF rolloff −1.5 @8k. +МОДЕЛЬ (canon): фейковый низкочастотный «пол» −24 дБ @50Гц от W=kBand768·kWarp·res^rp. +МОДЕЛЬ (NOWARP): форма совпала — плоско ~−2.1…−2.7, вмятина −4.45 @678. + +### ВЫВОДЫ АРХИТЕКТУРЫ +1. W=warp НЕ является бланкетным множителем выхода: реальный плагин пропускает + широкополосный контент почти на unity ⇒ RT_NOWARP правилен по духу. +2. Реальный плагин режет СТАЦИОНАРНЫЙ ТОН на −20.72, но НЕ режет шум ⇒ редукция + управляется КОНТРАСТОМ бина к соседям/шумовому полу (смысл IIR1/2 вдоль частоты = + бегущий референс), а не абсолютным уровнем. +3. Без blend в выходе уровни ШУМА сходятся: real lvl≈+0.123 vs наш +0.098 (Δ0.02 окт); + уровни ТОНА: real 3.444 vs наш 3.012 (Δ0.43 окт = ×1.35 амплитуда) ⇒ расхождение + растёт с уровнем ⇒ подозрение на res_k (twin АДАПТИРУЕТСЯ к сигналу во времени, + res_k уменьшается при длительном тоне) либо am-нормировку при высоких уровнях. +4. blend(0.8) в нашей модели даёт фантомный −1.94 dB бланкет — в реальном выходе его нет; + exp2(−lvl) без blend точнее описывает шумовой случай. + +### Рабочая гипотеза цепочки (v22j) + lvl_raw[k,t] = am[k,t]/res_k(t)·scale (res_k(t) — адаптивный twin!) + lvl_ref[k,t] = IIR1/IIR2 вдоль ЧАСТОТЫ от lvl_raw (бегущий референс соседей) + редукция = f(lvl_raw − lvl_ref или lvl_raw/lvl_ref) ← контраст + mask = exp2(−редукция) (без blend-бланкета) + sidechain-in clamp ±1.0 (колено/пин при 0 dBFS) + +### NEXT +1. Декод FUN_18052d650/IIR1 семантики заново: аргументы (что есть x, что есть acc), + направление и КАК результат используется (референс vs сигнал). +2. Тест адаптивного twin: рендер тона разной ДЛИТЕЛЬНОСТИ (0.5/2/8 c) через мост — + если пол зависит от длины ⇒ res_k(t) адаптируется, взять динамику из NOTES_TWIN. +3. Формулы контраста перебрать офлайн на траекториях RT_DUMP_ALL (движок phaseA готов). + +## ============ UPDATE 2026-08-22k: BARE-CHAIN АРХИТЕКТУРА ПОДТВЕРЖДЕНА ============ + +Цепочка «голая» exp2-архитектура (RT_LUT_OFF=1 RT_IIR12=0 RT_NOWARP=1 RT_NOBLEND=1 +RT_NOIIR3=1) на белошумном пробнике совпала с реальным плагином в пределах ~0.5 дБ +почти везде: + +| freq | real | model | +|------|------|-------| +| 50 | −0.01 | −0.12 | +| 300 | −0.08 | −0.26 | +| 678 | −1.04 | −2.59 | +| 850 | −0.76 | −0.94 | +| 1000 | −0.74 | −0.62 | +| 2000 | −0.22 | −0.20 | + +⇒ АРХИТЕКТУРА ПОДТВЕРЖДЕНА: + mask[k] = exp2(−lvl_raw[k]) БЕЗ blend-бланкета, БЕЗ warp-аттенюации + lvl_raw[k] = per-bin TIME envelope от am/res·scale + output = conv(audio, FIR(mask)) +Частотно-направленные каскады IIR1/2/3 в аудио-пути ОТСУТСТВУЮТ (они и создавали +фантомное смазывание нотча и бланкет-пол). kIIR_A1==A_ATTACK, kIIR_A2==A_RELEASE — +это время-энвелоп коэффициенты (feed-forward атака, retention релиз), НЕ частотные. + +### Остаточный разрыв ТОНА: −15.13 vs −20.72 (нужен lvl 3.4436, есть 3.0117) +Фактор ×1.1437 в амплитуде. Кандидаты: +(a) am-нормировка (wsum/окно) отличается; +(b) res_k twin АДАПТИРУЕТСЯ во времени (tatt=11ms/trel=80ms из NOTES_TWIN!) — + при длинном тоне res_k уменьшается → lvl растёт; +(c) scale_factor формула (наш 15·440.95/2048=3.231). +Мелочи: модель глубже у центра полосы на шуме (−2.59 vs −1.04 @678); у real +HF-rolloff −1.5@8k (вероятно WIN_freq окно FFT-conv — таблица [01] registry!). + +### NEXT +1. Декомп амплитудной цепочки: wsum/am нормировка (spectral.cpp), масштаб окна. +2. Twin-адаптация: проверить динамику res_k по длине тона (рендеры 0.5/2/8 c). +3. WIN_freq rolloff: добавить окно FFT-conv в модель (registry [01] 0.5→1.0). + +## ============ UPDATE 2026-08-22l: POOL+SCALE+FLOOR — тон точен до 0.02 дБ, корпус 5.9 ============ + +Новая архитектура (все за env-флагами, канон не тронут — санити TOTAL 2.286 ✓): + RT_LUT_OFF=1 RT_IIR12=0 RT_NOWARP=1 RT_NOBLEND=1 RT_NOIIR3=1 + RT_POOL=5 RT_SCALE_M=1.144 RT_FLOOR=1 + lvl_in = pool_max(lvl_raw, ±5 бин) · 1.144; cap = (16.78+sens/3)/6.0174 + mask = exp2(−min(lvl_in,cap)) + +Результаты: +- TONE floor: **−20.74 dB vs real −20.72** (Δ0.02!) при pool∈{4,5}, scale 1.144. +- White noise: mean|err| 0.63 dB (форма плоская ✓), но перерез центра полосы + (−3.54 vs −1.04 @678). +- Корпус 62: TOTAL **5.905** (t1k 60→7.2 благодаря полу; t1kq 5.57 — тихие случаи + теперь перерезаются scale/pool). Пока ХУЖЕ канона 2.286/bridge 1.594. + +### Интерпретация +Архитектурно верно (пол/пин/форма тона), но константы (pool width, scale 1.144, +cap-закон) сняты с ОДНОГО случая (hot tone trim+12) и не обобщаются на тихие. +Масштаб 1.144 требует источника в декомпе (золотое правило) — кандидаты: wsum +(sqrt-Hann vs др.), формула scale_factor, sens внутри twin. + +### NEXT +1. Систематический фит (pool_w, scale, cap-параметры a/b в (a+b·sens)) по МИНИМИЗАЦИИ + corpus TOTAL — офлайн через render48k прогонки (быстро, ~2 мин/конфиг). +2. Источник ×1.144: декоп wsum/am нормировки детектора (spectral.cpp vs decomp). +3. Разобраться с перерезом центра полосы на шуме (pool слишком широкий для шума? + может, pooling по МАКСИМУМУ неверно — попробовать contrast-vs-pool комбинацию). + +## ============ UPDATE 2026-08-22m: ГИПОТЕЗЫ ПОЛЬЗОВАТЕЛЯ ПРОАНАЛИЗИРОВАНЫ (E1/E3/E4/E5) ============ + +Гипотезы пользователя: (1) BandConfig влияет на детектор; (2) a/b = слоты состояний +плагина; (3) gamma = дельта-режим. + +### E1 — delta on/off: γ НЕ дельта +Захват с delta=1.0 vs база: конфиги идентичны (−24/28/γ=1 flag=0 cb=set). γ не кодирует +дельта-режим (тот «пересекает линию» только в отрисовке). + +### E3 — depth ±18: BandConfig стабильна +depth UI −18 и +18 (мост): те же −24/28/1 и 16/20000/1. Вместе с E1 и 7 пресетами — +структуры НЕ реагируют ни на один параметр обработки ⇒ консистентно с ролью +конфигурации отображения кривой (диапазон графика −24..+28 dB / 16..20000 Hz!). + +### E4 — callgraph расширен +FUN_180529610 (SpectralProcessor-метод, 1154B) дизассемблирован: НОЛЬ ссылок на объект +level-path и на ctx-буферы 540678/5406f8/5407c8/540870; callee — мелкие функции +(52e190×5, 52e130, 52df60, 52dc30). Grep всех .dis по оффсетам объекта: совпадения +только в GUI-кластере (563440/563a60) + 0x540678 (ctx-буфер аудио, другой объект). +Оговорка: полный pointer-flow анализ требует Ghidra-сессии; grep по готовым .dis +покрывает ~140 функций. + +### E5 — heap-diff межсессионный: неинформативен +Heap layout плывёт между запусками (базы сдвигаются, соседние аллокации чужие) — +451/512 блоков «различаются» мусором. Для честного diff нужен same-session capture +с переключением входа между снапшотами. + +### Статус гипотез +1. BandConfig→детектор: подтверждений нет (E1/E3/E4), но полная верификация указателей + не проведена (Ghidra data-flow). Приоритет низкий. +2. Слоты A/B: существуют (manual, AorBStateOption), наши структуры им не соответствуют + (идентичны во всех захватах в слоте «a»). Тест со слотом «b» отложен (нужен ручной + клик или ReaScript-команда переключения). +3. γ=delta: ОПРОВЕРГНУТА (E1). + +## ============ UPDATE 2026-08-22n: LIVE CTX НАЙДЕН + МЕДЛЕННАЯ АДАПТАЦИЯ ДЕТЕКТОРА ============ + +### E2-lite (без пользователя): sidechain/L-R/lowcut/eco — конфиги опять идентичны +Итого 9 захватов на широкой поверхности параметров: структуры +0x168/+0x170 статичны +всегда ⇒ окончательно display-config. + +### LIVE CTX НАЙДЕН В ТЕКУЩИХ СНАПШОТАХ (ctx≈0x2370048!) +Анкор: паттерн kIIR_A2[1..3] в куче → ctx = hit − 0x2c04f8. Совпало со старой сессией +(0x2370040±8). Живые скаляры (t1kq-пресет): + int@1a0 = 4096 (=NFFT, не 2048!) + f32@870 = 1.0000 (НЕ 440.95 — то было значение другого пресета render_long!) + f32@878 = 25.0 f32@87c = 10.0 (=UI sharpness/selectivity!) + f32@888 = 0.0 f32@88c = 1200.0 +⇒ Скаляры КОНФИГОЗАВИСИМЫ. Наш хардкод scale_factor=15·440.95/2048 калиброван под +чужой пресет и неверен для t1kq-корпуса! Формула масштаба требует перечтения +mangled-блока декомпа (Ghidra headless re-run) с живыми значениями. + +### МЕДЛЕННАЯ АДАПТАЦИЯ ДЕТЕКТОРА (главное!) +Короткий тон 0.75с (тот же сатурированный конфиг): gain во времени: + [0.02-0.35s]: −10.32 dB [0.35-0.72s]: −10.98 dB +Длинный тон 6с: −20.72 dB (пин). +⇒ Редукция УГЛУБЛЯЕТСЯ СЕКУНДАМИ. Постоянная времени ~секунды соответствует +kRTRel≈0.96..1.0 retention на кадр (hop 1024@48k → τ≈2с)! +Следствия: +1. «Статический» ×1.144 калибровался на несошедшемся состоянии длинного рендера — + делить нужно на динамику, не на константу. +2. Короткие рендеры корпуса меряют НЕнашедшееся состояние → часть ошибок корпуса + может быть фазой адаптации, а не формой кривой! +3. Модель обязана интегрировать медленный релиз через кадры (RT_IIR12=3 каркас есть, + но нужен настоящий покадровый цикл с персистентным env и правильной FRAMES-rate). + +### NEXT +1. Реализовать покадровую персистентность env в render48k (env_time уже static — + проверить, что реально тянется между вызовами process_band_structural). +2. Откалибровать att/rel семантику по переходным кривым (short-tone транзиент + −10.3→−20.7 за ~секунды — идеальный тестовый сигнал для фита kRTAtt/kRTRel). +3. Перечитать mangled scale-блок декомпа с живыми 1a0=4096/870=1.0. + +## ============ UPDATE 2026-08-22o: RT_ENV=live + корпусные прогоны новой архитектуры ============ + +RT_ENV=live (детекторный энвелоп из kRTAtt/kRTRel: атака feed-forward, релиз retention +~tau2s) добавлен в FramedDetector::processFrame за флагом. +- Белый шум: mean|err| улучшился 0.632 → **0.461** ✓ +- Корпус: НЕЙТРАЛЕН (метрика меряет сошедшиеся окна — энвелоп сходится и там). + +Корпусные варианты новой архитектуры (все с RT_LUT_OFF/IIR12=0/NOWARP/NOBLEND/NOIIR3/FLOOR): +| конфиг | TOTAL | примечание | +|--------|-------|------------| +| pool=5 scale=1.144 | 5.905 | тон точен, шум-центр перерезан | +| pool=0 scale=1.144 | **5.386** | t1kq 5.574→3.621, res хуже 1.713 | +| канон (без env) | 2.286 | HEAD, не тронут | + +Вывод: архитектурные находки верны на пробниках (тон-пол точен, шум плоский), но +константы (scale 1.144 снят с одного случая) не обобщаются на корпус ⇒ нужен +систематический joint-фит (pool_w × scale × cap-law) минимизацией TOTAL, плюс +поиск источника масштаба в декомпе (золотое правило). Канон остаётся HEAD до +превышения гейта. + +### NEXT (приоритеты) +1. Joint-фит сеткой: pool_w {0,3,5} x scale {1.05,1.10,1.144,1.20} x cap{on,off} + (~12 прогонов корпуса по ~2 мин). +2. Декомп амплитудной нормировки: wsum/am (spectral.cpp vs детекторный путь), + источник scale (int@1a0=4096, f32@870=1.0 живые!). +3. Транзиентный калибратор: короткий тон некорректен из-за латенси/WOLA коротких + рендеров — заменить на длинный файл со СТУПЕНЬЮ уровня внутри. + +## ============ UPDATE 2026-08-22p: JOINT-FIT ГРИД — оптимум scale 0.90, пол обязателен ============ + +Сетка (scripts/gridfit22p.py): pool×scale×floor по корпусу (62 случая). + +### Результаты +| scale | floor | TOTAL | t1kq | t1k | al | res | dual | comb | +|-------|-------|-------|------|-----|----|----|------|------| +| 0.90 | 1 | **5.215** | 1.961 | 5.801 | 5.328 | 2.663 | 7.774 | **2.707** | +| 0.95 | 1 | 5.242 | 2.255 | 5.980 | 5.541 | 2.432 | 7.699 | 2.811 | +| 1.00 | 1 | 5.261 | 2.547 | 6.155 | 5.704 | 2.201 | 7.621 | 2.915 | +| 1.05 | 1 | 5.298 | 2.892 | 6.279 | 5.842 | 2.008 | 7.588 | 3.019 | +| 1.144 | 1 | 5.386 | 3.621 | 6.456 | 6.123 | 1.713 | 7.542 | 3.213 | +| 0.95 | 0 | 11.021 | — | **22.5** | 10.4 | — | 14.4 | — | +| 1.00 | 0 | 11.396 | — | **23.4** | 11.1 | — | 14.8 | — | + +### Выводы +1. **FLOOR ОБЯЗАТЕЛЕН**: без него громкие случаи уходят в тишину (t1k 60→22 дБ ошибок, + dual ×2). Кап детекторного уровня — реальный механизм плагина, подтверждён корпусом. +2. Оптимум scale≈0.90 (тренд вышел на плато 5.2-5.3); pool=0 лучше (пул перерезает + центр полосы на широкополосном материале). +3. **comb впервые лучше канона** (2.707 vs 6.116) — мультиполосный случай выигрывает от + новой архитектуры. +4. Канон 2.286 всё ещё лучше по TOTAL: однополосные тихие случаи (t1kq/al) в новой + архитектуре перерезаются (scale-закон глобальный, а нужен уровнезависимый). + dual ~7.7 не двигается ни одним параметром — его причина не в уровне/форме кривой. + +### Статус +Канон остаётся HEAD (гейт «не хуже bridge» не пройден: 5.2 > 1.594). Новая архитектура +живёт за env-флагами: RT_LUT_OFF=1 RT_IIR12=0 RT_NOWARP=1 RT_NOBLEND=1 RT_NOIIR3=1 +RT_POOL=0 RT_SCALE_M=0.90 RT_FLOOR=1 RT_ENV=live → TOTAL 5.215. + +### NEXT сессии +1. dual@500: константная ошибка при всех q/уровнях — исследовать отдельно (возможно + межполосный combine-консюмер или второй тон 2000Hz взаимодействует). +2. Уровнезависимость закона: scale-множитель должен зависеть от уровня сигнала + (компрессия в детекторе?) — ступенчатый калибратор внутри файла. +3. comb-выигрыш закрепить: понять, почему мультиполосность выигрывает (перенос в канон?). + +## ============ UPDATE 2026-08-22q: DUAL ДИАГНОСТИКА — насыщающая кривая, не контраст ============ + +Кейс dual q=0.5 (тона 500+2000, оба −7.2 dBFS), trакт@frame150: +| тон | bin | am | res | наш lvl | нужен (real) | +|-----|-----|----|----|---------|--------------| +| 500 (центр) | 43 | 0.321 | **0.117** (res_min) | 8.85 | 1.70 | +| 2000 (вне) | 171 | 0.321 | 0.839 | 1.24 | 1.95 | + +GAIN'ы: real @500=−10.24 @2000=−11.75 (почти РАВНЫ!); newarch @500 упёрся в кап +(−20.4), @2000 недорез (+5.5); canon: @500 перерез −6.9, @2000 точен (−12.4≈−11.75). + +### ВЫВОДЫ +1. Деление am/res(twin) создаёт контраст ×5 между центром и краем — реальный плагин + такого контраста НЕ имеет: режет оба тона поровну при q=0.5. Twin-делитель в роли + полного делителя lvl ОПРОВЕРГНУТ для аудио-выхода. +2. canon согласуется с real на ВНЕполосном тоне (res^rp мягкий вес ✓) — форма + «кривая от уровня + мягкий вес res» ближе к истине, чем «жёсткое деление». +3. Мануал: «до 60 dB нотчей» при выкрученном depth + наш пин −20.7 при перегрузе + и номинальные −10 дБ здесь ⇒ ГИПОТЕЗА: cut_dB = насыщающая функция уровня + (например A·(1−exp(−lvl/T)) или soft-knee), а не линейный exp2(−lvl) с капом. +4. Уровни: real нужен lvl 1.70 там, где наш 8.85 — часть разрыва всё ещё + амплитудная нормировка (int@1a0=4096 и др.). + +### NEXT +1. Собрать пары (наш lvl_raw, real cut_dB) по ВСЕМ корпусным случаям (62×бины тонов) + → фит насыщающей кривой cut=f(lvl) — единый закон вместо подгонки параметров. +2. Проверить форму: линейно-лог / экспоненциальное насыщение / soft-knee. +3. Роль res: сравнить cut@center vs cut@skirt при ОДИНАКОВОМ lvl — отделить вес res + от кривой уровня. + +## ============ UPDATE 2026-08-22r: АФФИННЫЙ ЗАКОН ПОРТИРОВАН — НОВАЯ АРХИТЕКТУРА БЬЁТ КАНОН ============ + +RT_LAWAFFINE="A_db,S_db": mask = exp2(−(A_db + S_db·log2(lvl))/6.0174) поверх голой цепи +(NOTES 22k). Константы с al-семейства (грубо): A=6.14/S=2.17 → **TOTAL 2.152 < канона 2.286!** + +### Грид-фит +| A | S | TOTAL | t1kq | t1k | al | res | dual | comb | +|---|---|-------|------|-----|----|----|------|------| +| 6.14 | 2.17 | 2.152 | 0.426 | 0.889 | 0.090 | 1.795 | 3.953 | 2.678 | +| 6.60 | 2.00 | 2.047 | 0.409 | 0.930 | 0.294 | 1.262 | 3.702 | 3.349 | +| **7.40** | **1.85** | **1.931** | 0.852 | 0.577 | 0.804 | **0.395** | 3.480 | 4.335 | + +Трейд-оффы: A↑ → res ↓↓ но comb ↑; S↓ → t1k ↓ но al ↑. +Против bridge (1.594): новая арх. лучше на t1k (0.58<1.80), al (0.80<0.64~паритет), +comb (4.3<<10.1); хуже на t1kq/res/dual. Против канона (2.286) — лучше по TOTAL и +на 4 группах из 6. + +### Смысл +Аффинный закон Phase-A (21d) пересажен на ПРАВИЛЬНУЮ архитектуру (голый exp2 без +blend/warp/частотных каскадов) — и сразу даёт лучший корпусный результат среди всех +конфигураций проекта. Двух констант достаточно; их источник — следующий вопрос +(золотое правило: сейчас это фит, помечено). + +### NEXT +1. Фит A/S раздельно по группам → понять зависимость от sens/q (сейчас глобальные). +2. dual ~3.5 стабилен — межполосный механизм (приоритет из 22p). +3. WIN_freq HF rolloff не добавлен — после добавления comb может улучшиться ещё. +4. При достижении ≤bridge: гейт пройден, переносить в канон. + +## ============ UPDATE 2026-08-23a (22s): ОФЛАЙН-ФИТ A/S — СКАЛЯРНЫЙ ЗАКОН НАСЫТИЛСЯ ============ + +Инструментарий: `scripts/lawfit22r.py` — полный numpy-реплей render48k/spectral +(ресемпл 44.1<->48 poly, Hann STFT 4096/1024@48k, поточечная маска, WOLA, +FULL-BLK чанкинг = 61 хоп-фрейм на блок 65536 с zero-pad хвоста — именно так +вызывается processBlock в render48k.cpp:137, поэтому кадров 61·nblk, НЕ (L−N)/hop+1). +Детектор независим от закона ⇒ один захват RT_DUMP_ALL траекторий lvl[frame][band][bin] +(`/tmp/opencode/lawfit_traj.npz`, 47 уникальных рендеров) обслуживает ЛЮБОЙ (A,S) офлайн. +Скорость: весь корпус ~2 c/точку (против ~2 мин на реальный рендер). + +### Валидация реплея (A=7.4,S=1.85 против реального corpus 1.931) +sim TOTAL 1.895; группы: al +0.045 / dual +0.013 / res −0.085 — отлично; +t1kq −0.30 / t1k +0.32 / comb −0.64 — СТАТИЧЕСКИЕ смещения (риппл ресемплера +scipy≠libsamplerate вокруг крутых спектральных форм; у al полоса на тоне — плоско, +дельта нулевая). Смещение не зависит от закона ⇒ рейтинг кандидатов корректен. + +### ПОЧЕМУ точечная модель запрещена (важно для будущих сессий) +1. Тон 1000 Гц = бин 85.33 (не целый!) + главный лепесток Hann ±2-3 бина: при крутой + юбке реализованный гейн тона ≠ mask[tone_bin] (res_500: mask[85]=+8.4 дБ БУСТ, + рендер режет −7.2 дБ). +2. Детектор НЕстационарен в метрическом окне: resonant.wav lvl[85@bin] падает + 0.86→0.0002 за файл (медленная адаптация резонатора, 22n) при ПОСТОЯННОМ rms входа. + Медиана по хвосту смешивает 2 декады ⇒ мусор. + +### Метод линий (бисекция A* при S∈{0.5,1.5,2.5} на реплее) +У однотонового рендера одно уравнение cut=A+S·x ⇒ вырожденная ЛИНИЯ в (A,S); +x_i=−dA*/dS — точная эффективная координата кейса (включая транзиенты и лики). +Результаты (`lawfit_lines.json`): +- x_eff ОТОБРАЖАЕТ уровень входа ТОЧНО: al-свитч Δx=−3.48 на Δ21 дБ = −1/6.02 окт/дБ + ⇒ детектор линеен по амплитуде (подтверждение масштабной цепи). +- НО линии семейств НЕ пересекаются: локальные наклоны cut(x): t1kq 0.85 / + t1k 1.54 / res 1.65 / al 2.19. Единого аффинного закона НЕТ даже на чистых тонах + ⇒ кривизна/контент-геометрия, скалярная пара (A,S) принципиально недостаточна. +- Глобальный LSQ по 35 линиям: A=7.396 S=1.973, остаток mean 0.600 дБ, + corr(resid,rms)=+0.71, corr(resid,|log2 fc/1k|)=+0.69 — два недостающих фактора. +- dual: пер-рендерный оптимум даёт floor 0.52-0.62 при q≥1.5 даже своим (A,S) ⇒ + межполосный разрыв НЕ закрывается никакими константами закона (приоритет 22p стоит). +- comb (4 полосы): оптимум mean|e|=1.28, константы нестабильны (угол сетки). + +### Реальные рендеры (валидация кандидатов, гейт --vs-bridge не гонялся — канон не меняем) +| конфиг | t1kq | t1k | al | res | dual | comb | TOTAL | +|--------|------|-----|----|-----|------|------|-------| +| HEAD 7.4,1.85 | 0.852 | 0.577 | 0.804 | 0.395 | 3.480 | 4.335 | **1.931** | +| LSQ-линии 7.4,1.97 | 0.926 | 0.430 | 0.986 | 0.455 | 3.660 | 4.120 | 1.988 | +| спуск 7.6,1.6 | 0.894 | 1.123 | 0.750 | **0.069** | **3.105** | 5.053 | 1.894 | + +Вывод: скалярный закон насыщен в коридоре 1.87-1.93; (7.6,1.6) даёт res 0.069 (!), +но регрессирует t1k/comb — Канон ОСТАЁТСЯ HEAD (7.4,1.85). Теоретический потолок +пер-групповых оптимумов ~0.78 TOTAL недостижим одной парой констант. + +### NEXT +1. Двухфакторный закон cut=A+S·x+T·g(fc-дистанция/контент): корреляции указывают + на геометрию полосы относительно тона; данные линий уже собраны (lawfit_lines.json). +2. Механизм межполосного консюмера для dual (Шаг 2/9, приоритет 22p) — константами + не решается, подтверждено покейсовой бисекцией. +3. Реплей-инфраструктура готова для быстрого перебора ЛЮБЫХ параметрических законов + (2 c корпус) — использовать вместо реальных рендеров до финальной валидации. + +## ============ UPDATE 2026-08-23b (22t): ДВУХФАКТОРНЫЕ ЗАКОНЫ ОТВЕРГНУТЫ — БЮДЖЕТ ОШИБКИ В DUAL ============ + +Инструменты расширены: +- `RT_DUMPRESPATH=` в FramedDetector::setParams — статические спектры twin-response + res[band][bin] ({i32 band,i32 nbin,f32[]}, append); lawfit collect кладёт их в npz рядом с lvl. +- `RT_RESPRP=1` в process_band_structural — применять res^rp при NOWARP (для реальных + рендеров двухфакторного закона). +- `lawfit22r.py fit`: координатный спуск по семействам законов на реплее (2 c/корпус). + +### Результат фит-семейств (офлайн, старт 7.6/1.6) +| семейство | A | S | Q | rp | TOTAL(sim) | +|-----------|---|---|---|----|-----------| +| scalar | 7.600 | 1.694 | 0 | 0 | **1.860** | +| quad (Q·x²) | →scalar | | **→0** | | 1.860 | +| resrp (mask·res^rp) | →scalar | | | **→0** | 1.860 | +| quad+resrp | 7.452 | 1.540 | +0.069 | 0 | 1.865 | + +ОБА вторых фактора отброшены спуском: кривизна не нужна, res-вес не нужен. +Объяснение resrp: lvl = am/res УЖЕ содержит геометрию твин-юбки; домножение на +res^rp сверху — двойной учёт (в каноне res^rp компенсировал другое — отсутствие +res в xv-домене LUT). + +### Реальный рендер скалярного оптимума (7.6, 1.694) +t1kq 0.954 / t1k 0.842 / al 0.799 / res **0.117** / dual **3.246** / comb 4.855 +⇒ TOTAL **1.898** (sim 1.860 + стат. смещения реплея ~0.04). Против HEAD 1.931: +выигрыш мизерный, t1kq/t1k/comb РЕГРЕССИРУЮТ ⇒ гейт не пройден, канон НЕ тронут. + +### Бюджет ошибки — где лежит остаток (решающий факт) +abs-сумма по корпусу при (7.6,1.694): 115.4 дБ·кейсов, из них: +- **dual: 22 кейса × 3.246 = 71.4 (62%!)** — межполосный механизм (Шаг 2/9, консюмер acc/f6f8); +- comb: 24.3 (21%) — мультиполосное взаимодействие; +- всё прочее: ~20 (17%). +Если бы dual был на уровне bridge (0.726), TOTAL упал бы до ~0.96. Никакая +донастройка закона это не даст — ПРИОРИТЕТ №1 подтверждён количественно: +искать точку потребления acc/f6f8 (межполосный каскад), а не крутить маску. + +### NEXT +1. dual/межполосный консюмер: декомп-охота вокруг FUN_180529fe0 callers + (52e260 render-loop, multiband combine), трассировка acc/f6f8 в живом плагине. +2. comb: после dual — пере-тест 4-полосного взаимодействия. +3. Реплей-инфра готова; для любых новых гипотез сначала офлайн (2 c), потом рендер. + +## ============ UPDATE 2026-08-23c (22u): РЕАЛЬНАЯ СТРУКТУРА 529fe0 ПО RAW ASM — ПЕРЕСМОТР ЦЕПИ ============ + +Охота за консюмером acc/f6f8 по всем дизасмам/декомпам. ГЛАВНОЕ: наша транскрипция +цепи расходится с тем, что метод реально делает. + +### Новые декоды (ground truth из handoff/nls_dasm/f529fe0.dis, 505 строк) +1. **FUN_18052d650 — БИДИР-ИРРАЦИОННЫЙ ФИЛЬТР В DOUBLE** (полный декод): + сигнатура (state=rcx, out=rdx/f6f8, in=r8/band, n); acc=[rcx+0x100010]=0 RESET; + forward: acc = upCoef[i]·acc + downCoef[i]·band[i], запись в f6f8[i] + (upCoef double @state+0x80010+i·8, downCoef @state+0x10+i·8 — ПЕР-БИНОВЫЕ массивы); + backward: продолжение с ТЕМ ЖЕ state по nbin−2..1. Это наш «IIR3», но вход band, + выход f6f8, и коэффициенты пер-биновые double, не табличные float. +2. **ВТОРОЙ инлайн бидир-проход в теле 529fe0** (52a65d..52a85b): вход f6f8, выход + ОБРАТНО в band[i]. Итого пара [reset,fwd,bwd]×2 — как в заметках, но точные + адреса/направления теперь залочены. +3. **Межполосное ПРЕ-КОМБИНИРОВАНИЕ ВНУТРИ 529fe0** (52a39a..52a4f6, ДО основного + цикла): f6f8 ← copy bands[0]; цикл по bands[1..n−1] (массив @0x540688 = соседний + элемент того же массива векторов!): поэлементные thunks (float 0x20f0/double + 0x1850 пары = шаблонный transform); нормировка делением на int [rsp+0x148]; + затем второй проход: band[i] *= (xmm11−xmm7), xmm7=pow([ctx+0x2c],xmm13) + (mix-вес!), и f6f8 ⊕= ... Семантика thunks ещё не залочена. +4. Основной пер-полосный цикл: scale (=PRNG·[0x540870]·[0x54088c], флаг 0x5408b8 + добавляет exp-random) → in-place кернел (thunks 0x140980/0x1409b0, вероятно + bigkernel exp2) → 52d650 (bidi#1) → инлайн bidi#2. +5. Пер-полосный массив состояния **0x540768[i]** читается в основном цикле + ([rsp+0x40]) — роль не ясна (кандидат: трек/acc). +6. **FUN_180529c60** (vtbl, параметр = индекс полосы): band←5355d0(param_2); + осцилл-цикл по [ctx+0x1b0] c фильтрацией через f6f8; скалярная нормировка + band *= exp((0.5·K1−K2)·ln10/20) · **FUN_1804d56b0(band)** (агрегат от всей + кривой полосы!); релиз-коэф pow(...) от sr/0x1a0·0x1ac; трек-обновление через + массив **0x5407a8[i]** (52dae0 axpy + 52dbc0 add). Кандидат на «медленную + адаптацию» (22n) и/или постобработку маски. +7. **0x5407c8 НЕ читается НИГДЕ в рантайме** (только ctor/dtor в f_535a70.dis) — + адрес «combine acc» из NOTES 21b был неверен; реальные пер-полосные состояния: + 0x540768[i] (в 529fe0) и 0x5407a8[i] (в 529c60). + +### Офлайн-проверка гипотезы «спектральное сглаживание маски» (lawfit iir_c) +Бидир-однополюсник по бинам (нормированный, ×2 прохода) при законе 7.6/1.694: +c=0.2 → TOTAL 3.23; c=0.6 → 9.83 (монотонная деградация всех групп). ОТВЕРГНУТО: +сглаживание размывает нотч в центре, но физически не дотягивается до тона-конкурента +за ~130 бинов. Двойной cut в dual объясняется НЕ межбиновым сглаживанием маски. + +### Следствие для dual +Реальный плагин режет 500 и 2000 почти поровну при q=0.5. Раз контраст lvl +(res-деление) есть у нас и его нет в результате — значит либо (а) детекторный +уровень считается во ВРЕМЕННОЙ области через тот же twin-фильтр (536300 строит +res-кривые на сетке 0.25 Гц — а детекция может идти банком time-domain фильтров), +либо (б) нормировка агрегатом FUN_1804d56b0 выравнивает кривую, либо (в) закон +не логарифмический по lvl. Приоритет — полный перенос 529fe0 из raw asm (505 строк, +подъёмно) вместо текущей реконструкции из кусков. + +### Инструментарий +lawfit22r.py: +iir_c параметр (бидир-IIR по бинам в реплее), +res в npz; +sim_errors кэширует по (Q,rp,iir_c). + +## ============ UPDATE 2026-08-23d (22w): FAITHFUL-ЦЕПЬ v1 — ИНФРАСТРУКТУРА ГОТОВА, ДЕТЕКТОР ГЛАВЕН ============ + +Инфраструктура (dsp/fnfaith.{hpp,cpp}, env RT_FAITHFUL=1 в render48k): +транскрипция маско-формирующей цепи по BLOCKMAP_529fe0: band=lvl·s → exp2(-band) +→ бидир-IIR ×2 (setA tau19/mult360) → бидир (setB tau10.7/mult2400) → ×2 (setC). +Коэффициенты — точная транскрипция FUN_180533340: частотный варп +fc_bin=(1000/(sr/2))·nbin (=85 @48k/4096!), g=fc/i ниже кросса, степенной выше, +up=exp(-2π·κ·c·g·tau), down=1-up. Константы из дампа: DAT_24c46b8=-2π, +24c3d8c=0.5 (sr→Найквист), mult 1200/800/360, тау-интерполяция 15↔9 и 9↔180. +Все параметры — env (RT_FAITH_*). Combine acc в v1 опущен (нет консюмера, 22u). + +### Смоук-свипы (t1kq1000 / dual500 / dual2000 / al12; err dB) +- κ=0.05,s=8: t1kq −25 / dual500 −58 / **dual2000 −2.84** — тон-конкурент ВПЕРВЫЕ + дотягивается до реала, но центральный тон улетает в перерез. +- κ=0.2..0.5: насыщение (up→0 на низких бинах), баланса пары нет нигде; + Δerr(500−2000) ≈ 15-20 дБ во всех точках (в реале ~1.5). +- Корпус (κ=0.02, s=2): TOTAL 5.315 (t1kq 1.35 / t1k 1.55 / al 0.89 / + res 2.87 / dual 11.52 / comb **2.57** — лучший comb в истории проекта, + базлайн 4.34!). Ресурс мультиполосной механики виден. + +### ВЫВОД (решающий) +Форма маски НЕ может сбалансировать dual: контраст lvl между тонами создаётся +САМИМ ДЕТЕКТОРОМ (am/res из STFT), а не пост-обработкой. Реальный детектор +даёт почти равные уровни драйва на 500 и 2000 Гц при q=0.5 ⇒ гипотеза (а) +из 22u — ПРИОРИТЕТ №1: уровни считаются во ВРЕМЕННОЙ области (банк twin- +фильтров 536300 строит res-кривые на сетке 0.25 Гц — вероятно, и уровни тоже +через них), либо тяжёлый кросс-бин пулинг ДО кривой полосы. +Смена канона НЕТ; faithful-путь остаётся за флагом для следующих экспериментов. + +### NEXT +1. Детектор: декод FUN_180536300 до конца (scratch-лейаут известен, NOTES_TWIN) + → понять, откуда берутся bands[] кривые на входе 529fe0. +2. comb 2.57 при живом faithful — разобраться после детектора. +3. RT_DUMP_BIN-трасса живого плагина на dual-конфиге (покадрово mask/lvl) — + прямой ответ на вопрос «какой lvl реально у тона 2000». + +## ============ UPDATE 2026-08-23e (22x): ТЕОРЕМА О ОТСУТСТВИИ α + ТАБЛИЦА DUAL ПО q ============ + +Инструмент: scripts/resalpha.py — RT_DUMP_BIN-тракты всех уникальных рендеров +(57 пар case/freq: lvl_a1, res, am на бине тона + реальные cut из рефов), +CSV /tmp/opencode/resalpha_pairs.csv. + +### Теорема об отсутствии α +Модель lvl = am/res^α с ЕДИНЫМ законом cut=L(log2 lvl): имплицитные наклоны +семей (концевые точки, дБ/октава lvl) при НИКАКОМ α ∈ [−0.6..1] не совпадают: +- dual требует α≈−0.5 (наклон +0.2..+0.4); +- при α≤0 t1k/t1kq/res получают БЕСКОНЕЧНЫЙ/отрицательный наклон (весь fc-скан + схлопывается в точку по x — res перестаёт различать fc); +- при α>0 dual-пара даёт отрицательный наклон (запрещено для монотонного закона). +⇒ ВЕСЬ класс точечных моделей lvl=f(am, res^α) с общим законом ОТВЕРГНУТ. +Глобальный LSQ по α плоский (1.59-1.63 дБ) — семейные смещения доминируют. + +### Таблица dual по q (реальные cut, одна полоса @500) +| q | res500 | res2000 | cut@500 | cut@2000 | Δ | +|---|--------|---------|---------|----------|---| +| 0.1 | 0.117 | 0.216 | 10.32 | 15.33 | +5.01 | +| 0.5 | 0.117 | 0.839 | 10.32 | 11.82 | +1.50 | +| 1.0 | 0.117 | 1.353 | 10.32 | 10.78 | +0.46 | +| 3.0 | 0.119 | 1.880 | 10.29 | 10.32 | +0.03 | +| 10 | 0.139 | 1.988 | 9.94 | 10.25 | −0.07 | + +Структура: cut@500 КОНСТАНТА по q (драйвер центра не зависит от ширины полосы); +Δcut(res2000) ≈ экспонента 5.4·exp(−res/0.49), схлопывание при res→2. +cut@2000 ПАДАЕТ с ростом res2000 — противоположно любому «lvl растёт с res» +и противоположно делению. Геометрическая интерпретация: широкий нотч (малое q) +НАКРЫВАЕТ тон-конкурент сам по себе; вклад центра всегда полный. Это свойство +ФОРМЫ twin-шаблона на мелкой сетке (0.25 Гц, FUN_180536300), а не пер-биновой +арифметики на сетке 4096. + +### al-группа (якорь уровня, fc=tone=1000) +lvl_a1: 10.08/7.14/5.05/3.58/1.79/0.90 → cut: 13.64/12.44/11.28/10.13/7.97/6.01. +Ломаная: наклон ≈1.16 дБ/окт для lvl≥1.8 и ≈2.06 ниже — кривизна закона +подтверждена независимо (ср. 22s линии). + +### Следствие +Детекторный уровень НЕ восстанавливается из спектра бина функционально. +Кандидаты: (а) скалярная огибающая сигнала через time-domain twin-банк +(536300 строит res-кривые на сетке 0.25 Гц — возможно, тот же банк даёт и +уровни), (б) форма кривой = res-шаблон × скалярный драйв, что естественно +даёт постоянство центра и геометрическое покрытие скайрта. +Решающий эксперимент — живая покадровая трасса детектора на dual-конфиге +(registry-heartbeat инфраструктура готова, NOTES_CAPTURE.md). + +## ============ UPDATE 2026-08-23f (22z): FFT-conv статика + СПРЕДИНГ ОПРОВЕРГНУТ ============ + +### Статика mask→FIR (52b550–52b8bb, per-band цикл построения FIR) +Полная последовательность на полосу: scale-ops на кривой → **call 0x180535a70** +(стаб → ILT → 0x180140a10 float / a70 double → импорт-прыжки → тела 0x1802a24c0 +float / 0x1802fa420 double — векторизованный AVX2, функционально FFT-дизайн) → +копия в r14=[ctx+0x540668] → complex-op с буферами 0x540548/550/598 → +FIR[n/2]=0 → bigkernel in-place → ещё complex-op → **WIN_freq умножает сегмент: +rdx = win_ptr + (n/2)*4** (вторая половина 8193-таблицы!) → масштаб второй половины +xmm9 → **FIR[0]=1.0, FIR[1]=0** → init пар, dry/wet-scale 0x540888. +Размерности: n=[ctx+0x540534], половина rbp=n/2, счётчики n/2+1. +Живой каталог (22y): 0x540758=freqaxis, 0x540658=WIN_freq 0.5→0.8. + +### Спрединг-гипотеза (IDFT(R)→окно L→DFT) — ЧИСЛЕННО ОТВЕРГНУТА +На живой кривой dual R(7f8) (v43=3.98, v171=1.41): окна Hann L∈{32..2048} дают +cut@171 ≤ 2.60 дБ (нужно 11.82); cut@43 падает с 12.0 до 4.4 при малых L. +Сглаживание НЕ может поднять долину до уровня соседнего пика — механика скайрта +dual НЕ в пост-обработке кривой R. + +### Что остаётся (структура данных dual по q, 22x) +Δcut(скайрт−центр) ≈ линейно по −ln res_скайрта с наклоном b≈2.25 дБ/e-fold, +НО центр из этого закона выпадает (его cut константен при res≈const). Это +согласуется с моделью «скалярный драйв через time-domain twin + шаблонная форма», +но требует знать, как именно нормируется шаблон. Прямое декодирование тел +0x1802a24c0 (3КБ AVX2) и complex-op шагов с буферами 0x540548/550/598 — следующий +статический шаг; альтернатива — трассировка промежуточных буферов живьём +(снапшоты в момент аудио-колбека, не между кадрами). + +## ============ UPDATE 2026-08-23g (23b): ДИЗАЙН=LOG(band), Hann-окно, PRNG-дизеринг ============ + +### Что доказано декодом (детали в BLOCKMAP 23b) +1. Полная цепь построения FIR на полосу залочена по raw asm (52b550–52b8bb): + scalar-shape ×2 → **DESIGN: scratch=log(bands[i])** (векторный log2, полином + идентифицирован по константам, тело 0x1802a24c0) → copy→FIR@0x540668 → + 4 комплекс-op (A..D) на буферах 0x540548(SIMD/twiddle-workspace)/0x540598 + (YMM-маски 8×1/8×0×64) вокруг нормировки &FIR[1]÷K и bigkernel → + **FIR[2048..4095] ×= Hann_freq[2048..4095]** (падающая половина периодического + Hann(4096), пик ровно на стыке!) → FIR[0]=1,FIR[1]=0 → dry/wet над 2n флоатами. + Структура log→ops→exp-класс + окно = классический frequency-sampling / + минимально-фазовый дизайн кернела. Гипотеза механизма скайрта: геометрия + задаётся ОКНОМ и комплекс-ops на log-спектре, а не постобработкой R. +2. WIN_freq опознан численно (w[1024]=0.5,w[2048]=1.0) — прежняя трактовка + «0.5→0.8» относилась к другому слоту (0x540698). +3. freqaxis: шаг 11.71303 vs точные 11.71875 — накопительная f32-ошибка построения; + сетка внутренняя 48k/4096 подтверждена независимо. +4. Выход плагина недетерминирован от рендера к рендеру (md5 разные, rms равные) — + LCG-прологи реально дизерят; честная метрика только спектральная. + +### Динамика: почему живые кривые «статичны» +Realtime-playback без аудио-девайса НЕ вызывает processing (все треды спят); +кривые 22y — продукт инициализации проекта. `-renderproject` прожимает весь +цикл за ~0.8 c (init+render+exit); перехват требует SEIZE+TRACECLONE ДО первого +CONT главного треда. FUN_180529fe0/52d650/536300/52e260 не ловятся в рендер-окне +⇒ маск-цепь выполняется при загрузке/param-change, рендер использует кэш. +Следующий динамический шаг (если понадобится): bp на входе process-callback vtbl +или трасса окна init (поймать DESIGN в момент param-change). + +### Инструментарий этого раунда (закommичено) +scripts/ilt_resolve.py (резолв стабов офлайн), scripts/firtrace.py (полный каталог +буферов + GUI-флаг 0x2404bc), scripts/firstop.py (SIGSTOP-семплирование), +fntrace*.py/fnhw.py/fnall.py (ptrace-лаборатория: INT3/DR, уроки — см. BLOCKMAP), +hotips.py. .dis тела: conv_float_a24c0, conv_double_fa420, imp_*, th_* (19 impl). + +### Контрольные числа dual (цель раунда, неизменны) +cut@500=10.32 дБ const ∀q; @2000=11.82 @q=0.5; Δcut≈2.25·(−ln res). +Модель сейчас: −0.18/@500-ок + провал скайрта. + +### NEXT (пересмотр после декода) +1. numpy-реплика цепи log→opsA-D→window с КАЛИБРОВКОЙ ops по измеренным H(f) + (корпус референсов уже есть); критерий — воспроизведение Δcut(res) геометрии. +2. Если ops A-D ≈ exp-раундтрип со сглаживанием: транскрипция за RT_FIRCONV, + гейт corpus --vs-bridge + контрольные числа dual. +3. ptrace-эскалация ТОЛЬКО если численная репликация не сойдётся: цель — + брейкпоинт в момент param-change (init-окно хоста), а не рендер. + +### 23c (короткое): феасибилити log-свёртки — ОТРИЦАТЕЛЬНЫЙ результат +На живой кривой R@0x5407f8 (dual q=0.5): подбор ОПТИМАЛЬНОЙ симметричной +гаусс/бокс-свёртки в log-домене (ширина до 1500 бинов, свободный аффинный сдвиг, +точное совпадение @bin43=10.32 дБ) даёт минимум ошибки на bin171 ≈ 0.94–1.10 дБ +при ширине ~314–324 бина (~3.7 кГц) — такая ширина уничтожает избирательность +нотча на всём остальном корпусе. Вывод: механизмы класса «свёртка log-маски с +симметричным ядром» геометрию dual НЕ воспроизводят (расширяет теорему 22x с +поточечных законов на симметрично-свёрточные). Остаются: (а) вход bands[i] у +DESIGN ≠ захваченные кривые (768/788/7f8 — возможно, GUI/init-версии); +(б) асимметричные/структурные ops (cepstral-liftering класс); (в) темпоральный +кросс-фрейм механизм. Решающий недостающий факт — ИСТИННЫЙ массив bands[0] в +момент вызова DESIGN; рецепт добычи: seize главного треда yabridge-host в момент +ПОЯВЛЕНИЯ pid (ppid=reaper, comm ещё 'wine'), INT3 на 0x1802a24c0/0x1802fa420, +CONT, дамп rcx/rdx на хите (скрипт fnall.py готов, нужен только ранний захват — +нынешний finder по maps опаздывает на init-бурст <5 мс). + +## ============ UPDATE 2026-08-23h (23d): ПРОРЫВ — ЖИВОЙ КЕРНЕЛ СНЯТ, bands[] = СПЕКТР СИГНАЛА ============ + +Маршрут A удался БЕЗ ptrace и БЕЗ аудио-девайса: **SIGSTOP-семплирование +офлайн-рендера** (`-renderproject`; обработка back-to-back ⇒ случайные остановки +попадают внутрь конвейера). scripts/rendersnap.py: find-loop ctx (vtable-маркер +0x1824AC210 / float 48000+ sens; ctx=0x2370040 стабилен и в рендере), +затем STOP→читаем все слоты→CONT c детекцией изменений. 24 фазы за прогон. + +### Решающие факты из phase-снапов (dual q=0.5) +1. **bands[0] @ctx+0x540678 = АМПЛИТУДНЫЙ СПЕКТР ТЕКУЩЕГО ФРЕЙМА**: + ненулевые группы ТОЛЬКО вокруг тонов (бины 41–45 и 168–173, Hann-лепестки; + band[43]=7.65, band[171]=9.91), между ними нули. Это вход DESIGN, НЕ шаблон + маски и НЕ пост-детекторная кривая! +2. scratch@540628 ≈ log(bands)·k (после ops значения −0.66@43, −0.76@171). +3. Живой FIR@668 (комплексные пары): mag(43)=0.524 (−5.61 дБ), mag(171)=0.473 + (**−6.51 дБ**) — скайрт режется ГЛУБЖЕ центра, КАК В РЕАЛЕ (+1.5 дБ), + хотя кривая R@7f8 утверждает обратное (R171=1.410), а wine-треды дают ENOSPC ⇒ + **wine резервирует аппаратные BP-слоты** под эмуляцию Windows-DR. Это же + объясняет глобальную тишину ptrace-DR: ядро хранит значения, но в кремний + их программировать нечем. Маршрут аппаратных ловушек внутри wine ЗАКРЫТ + окончательно (23h–24a). Скрипт scripts/perfbp.py готов для нативных целей. +2. Опознан объект conv-движка: **ctx+0x540530**, инлайн-конфиг {2,4096}, + {16384,8192}, {2,257}; члены-векторы +0xb8..+0x130; методы FUN_18052dc30 + (config), FUN_18052fe00 (resize), FUN_18052dd30 (dtor). Его process-метод — + следующий кандидат на «потребителя кернела» (ищется рядом по декомпу/вызовам + с arg=ctx+0x540530 из аудио-клея). +3. INT3-маршрут остаётся единственным динамическим (слоты не нужны): требует + дисциплины fnwatch4 (INTERRUPT→wait→arm→CONT всем тредам) + контроля + render_fresh в каждом прогоне (флак ~50%). +### NEXT +1. Найти process-метод движка 0x540530: перечислить функции, получающие + ctx+0x540530 (grep decomp), восстановить vtbl объекта из дампа памяти + ([ctx+0x540530]-объект начинается с указателя? проверить офлайн из s1.bin), + декодировать метод → источник ×1.805. +2. INT3-ловушка на найденный метод + на design-тела в param-окно (рендер- + свежесть обязательна в логе). +3. RT_FIRCONV=1 после декода масштаба; контрольные числа dual; гейт корпуса. + +### 24b (короткое): vtable модуля полная; процесс-метод ещё не опознан +vtbl @0x1824ac210: +0=535ed0,+1=52cfb0,+2=529550,+4=52b940(→rebuild), ++5=529c60(осцилл),+6=529fe0(маск-цепь),+8=52bbf0(getter blocksize), ++12=52bba0,+13=52bb80; сеттеры параметров 52b980-baa0 (52ba20 пишет флаг +0x5408b8 и зовёт полный rebuild 52e9b0 ✓; 52baa0 пишет 0x54088c=expf(x·K)). +Объект conv-движка (ctx+0x540530) невиртуальный: методы прямые +{dc30 cfg, fe00 resize, dd30 dtor}; его process-метод = прямой вызов из +аудио-клея — искать среди функций, вызывающих dc30/fe00 (их CALLER'Ы) и в +диапазоне 52d650..532xxx. Следующий раунд: декод этого клеевого метода даст +потребителя кернела и ×1.805. + +## ============ UPDATE 2026-08-24a (24c): ЖИВАЯ ОБРАБОТКА ПОДТВЕРЖДЕНА; «BUILD-ONCE» ОТЗЫВАЕТСЯ ============ +Чистый эксперимент без трассировки: файл seq_in.wav = [A: 500+2000Гц | B: только +2000Гц] по 2 с, полоса @500 q=0.5, рендер до естественного завершения. +Результат (выравнивание по LOOP=1, выход 6 c = A+B+A): + half A: cut@2000 = 21.91 дБ half B: cut@2000 = 25.74 дБ + (поведение @500 меняется кардинально: контент-зависимость живьём) +⇒ ПЛАГИН ОБРАБАТЫВАЕТ АУДИО В РЕНДЕРЕ ПО-РАЗНОМУ ДЛЯ РАЗНОГО КОНТЕНТА ⇒ +маск-цепь выполняется по-кадрово. Вывод 23k «build-once» ОТЗЫВАЕТСЯ — он +опирался на exec-bp, которые не доставляются под wine (см. 24a: wine держит +слоты HW BP; ptrace-DR в кремний не программируются). +ПОПУТНО: (а) рендер-выход длиннее входа из-за LOOP=1 в проекте (6 c = A+B+A); +(б) убивать reaper во время рендера нельзя — выходной файл остаётся частичным +с предварительной шапкой (источник «флака» свежести); (в) выход 16-bit stereo. +### Методологический итог для следующих раундов +Динамические ловушки внутри wine сейчас НЕ работают ни в каком виде (HW занят +wine, INT3-прогоны не контролировались на свежесть рендера). Единственный +работающий канал наблюдения — rendersnap (STOP-семплирование), но он снимает +между колбеками офлайн-рендера... что при back-to-back обработке ЕСТЬ момент +обработки — противоречие с молчанием ловушек требует пересмотра: возможно, +в wine-хосте ДВА экземпляра модуля (GUI/DSP), а vtable-кандидат у нас один; +multictx сканировал только rw-регионы с маркером vtable — второй экземпляр мог +иметь другой адрес vtbl-указателя (heap-копия класса?). Проверка: полный скан +всех экземпляров по сигнатуре sens>100 БЕЗ привязки к vtable (float 48000 + +sens + 0x540870-паттерн уже покрывал; расширить на поиск по содержимому +R-кривой в куче). + +### 24d (стоп раунда): двухэкземплярная гипотеза не подтверждена/не опровергнута +Сканер всех кандидатов (scan2.py в /tmp/opencode, не закоммичен из-за бага: +после починки finder'а — исключён reaper по cmdline — 25 STOP-сканов подряд +на правильном wine-хосте не находят ДАЖЕ известный ctx 0x2370040 с vtable- +маркером, хотя идентичный код rendersnap.py находит стабильно). Расхождение +не отлажено (контекст исчерпан). Состояние задачи «потребитель кернела»: +- живая обработка доказана (24c); +- HW-ловушки в wine невозможны (wine держит слоты, 24a); +- INT3 не проверялся при ГАРАНТИРОВАННО свежем рендере; +- статически: все disp32-ссылки учтены, conv-движок = объект ctx+0x540530 + (методы dc30/fe00/dd30 прямые), его process-метод среди caller'ов в + 52d650..532xxx — НЕ НАЙДЕН декомпом (grep 0x540530 даёт только init/dtor). +### Рекомендация следующему раунду +1. Отладить scan2.py (почему пусто: сравнить с rendersnap пошагово на одном + запуске — вероятно, порядок STOP→scan ломается на первом STOP до маппинга + DLL, а CONT после каждого скана пропускает окно создания экземпляра). +2. Либо полный декод аудио-клея: найти функцию, вызывающую vtbl слот +6 + (529fe0) у СОСЕДНЕГО экземпляра, или decode 52e9b0-вызовителя (14191 в + decomp_funs2 — это 52ba20 setter); glue где-то в 52exx–52fxx. +3. После опознания потребителя — ×1.805 и RT_FIRCONV. + +## ============ UPDATE 2026-08-24e (24e): КОНСЮМЕР КЕРНЕЛА ОПОЗНАН — th_b3c0 COMPLEX-MULTIPLY ============ + +### Решающий факт: th_b3c0 = чистый complex multiply, без нормировки +ILT-резолв: 0x1df0 (float) → 0x18000b3c0 (th_b3c0), 0x1f70 (double) → 0x18000e360. +Тело th_b3c0 (первые 384 байта): SIMD complex multiply через vmulps/vfmaddsub213ps. +Нет divss/divpd/divsd — функция ЧИСТО умножает спектры: out[i] = FIR[i] × audio[i]. + +### Конвейер кернела (полный, из asm 52b550–52b8bb + BLOCKMAP 23b) +1. FIR构建: bands[i] → scratch=log(bands) → complex-ops A-D на буферах 0x540548/550/598 → + копия в FIR@0x540668 → **×= Hann_freq[2048..4095]** (падающая половина) → FIR[0]=1,FIR[1]=0 +2. **×0x540888** (attack/release coeff): строка 52b832–52b85e, xmm6=[ctx+0x540888], + fill FIR[0..2n-1] ×= xmm6 через 0x2030/0x1d30 +3. **Конвейер-вызов**: th_b3c0(FIR, audio[param_2], nBins, out[param_2]) + — ЧИСТЫЙ complex multiply в freq-domain, in-place в буфере полосы. + → out[band] = FIR × audio (0x540668 умножается на аудио-спектр полосы) + +### Источник ×1.805 +Конвейер НЕ содержит нормировки (th_b3c0 = чистый mul). ×1.805 = комбинация: + (a) 0x540888 (attack/release coeff) — домножает ВЕСЬ FIR-спектр scalarem + (b) Масштаб Hann-окна при FFT-свёртке (N=4096, window energy) + (c) Неопределённый коэффициент FFT/IFFT нормировки (стандартный 1/N или N) + Точный баланс требует live-измерения 0x540888 при фиксированном кернеле. + +### Ключевое следствие для архитектуры +FIR применяется как frequency-domain mask (поточечное complex-umножение в спектре), +НЕ как time-domain convolution. Это ПОДТВЕРЖДЕНИЕ NOTES 23e "применение = свёртка × +масштаб 1.805": хост берёт аудио-спектр полосы и умножает на FIR-маску bin-by-bin. +conv-движок (0x540530) — инфраструктура FFT (FFT-план, буферы); его process-метод +НЕ НУЖЕН для консюмера (весь путь — внутри FUN_180529fe0). + +### scan3.py (scripts/scan3.py) +Фикс scan2.py: ports rendersnap.py find-loop + multi-instance detection + full state +чтение. Ключевое отличие от scan2: scan2 НЕ использовал rendersnap-style find-loop +(find-loop с CONT после каждого STOP-скана). + +### NEXT +1. Измерить 0x540888 live при dual_b1q_0.5 через rendersnap → точный баланс ×1.805 +2. RT_FIRCONV=1: транскрипция th_b3c0 как frequency-domain mask multiply +3. Контрольные числа dual по q (q-серия23f + FFT-conv) → гейт + +## ============ UPDATE 2026-08-24f: RT_FIRCONV=1 ПЕРВЫЙ РЕЗУЛЬТАТ — ЧАСТИЧНО РАБОТАЕТ ============ + +### Реализация (dsp/spectral.cpp) +RT_FIRCONV=1: FIR = mask (freq-domain), complex multiply FIR × audio (th_b3c0 equivalent). +DC passthrough (FIR[0]=1.0).Upper half zeroed. + +### Результаты на dual_b1q_0.5 (single band, fc=500, q=0.5, sens=12): +| конфиг | cut@500 | cut@2000 | +|--------|---------|----------| +| Default (scalar mul) | -14.35 | -6.58 | +| RT_FIRCONV=1 (complex mul) | -20.37 | -12.60 | +| Reference (plugin) | -10.32 | -11.82 | + +### Анализ +1. RT_FIRCONV перерезает на 500 Гц (-20.37 vs -10.32) но БЛИЖЕ на 2000 Гц (-12.60 vs -11.82) +2. Default недорезает на 2000 Гц (-6.58 vs -11.82) но перерезает на 500 (-14.35 vs -10.32) +3. Ни один не совпадает с референсом → маска-цепь в detector неполна +4. FIR window (периодический Hann 4096, rising half 0→1) определена из live-капч + +### Ключевой вопрос: почему оба перерезают на 500? +Маска на бине 43 (500 Гц) при structural chain должна быть ~0.31 (= -10.32 dB), +но реальный structural chain даёт -14.35 dB → маска ~0.19. Это указывает на +неправильный scale_factor или LUT-кривую в detector. ×1.805 НЕ применяется +(он =.effects窗外 + conv-normalization, НЕ scalar в freq-domain). + +### Дальнейшие шаги +1. Калибровка scale_factor/level_path чтобы маска на 500 Гц ≈ 0.31 +2. Проверка RT_FIRCONV на t1kq/al/res (не только dual) +3. Корпусный прогон RT_FIRCONV vs default → определить группу, где FIRCONV лучше + +## ============ UPDATE 2026-08-24g: КОРНЕВАЯ ПРИЧИНА РАЗРЫВА — FFT НОРМАЛИЗАЦИЯ ============ + +### Факт +Plugin применяет FIR через time-domain convolution (FFT→multiply→IFFT, без 1/N нормировки). +Наш FFT делит на N при IFFT. Это создаёт ×N разницу в gain. + +### Живые измерения (dual_b1q_0.5, bin 43 = 500 Hz): +- raw_level@43 = 8.86 (am/res × scale_factor) +- mask@43 = 0.510 (per-band spectrum, 0x540678) +- FIR@43 = 0.524 (FIR magnitude, after construction) +- Final cut = 10.32 dB → final gain = 0.305 + +### Наша цепь: +- raw^0.984 = 8.58 (GAIN > 1, boost, not cut!) +- exp2(-LUT(lvl)) = 0.12 (cut 18.4 dB, too much) + +### Анализ +Plugin's FIR construction: FIR = raw^0.984 × hann × 0x540888 +Plugin's application: FIR × audio via complex multiply (th_b3c0, NO 1/N) +Our application: mask × audio via complex multiply (WITH 1/N from IFFT) + +Разница: plugin's FFT output has magnitude ×N vs our IFFT-normalized output. +Это объясняет为什么 raw^0.984 > 1 (boost) в нашей цепи, но cut в плагине. + +### Решение +Нужно убрать 1/N нормировку из IFFT в我们的 FFT-conv, или добавить +калибровочный множитель N в FIR construction. Это ТОЧКА ОСТАНОВКИ +для текущей сессии — требует изменения dsp/fft.cpp IFFT implementation. + +## ============ UPDATE 2026-08-24h: КОРНЕВАЯ ПРИЧИНА — IIR STATE + MASK COMPUTATION ============ + +### Новые данные (RT_DUMP_BIN frame 100, dual_b1q_0.5): +| bin | am | res | lvl_raw | band_level | mask | +|-----|-----|-----|---------|------------|------| +| 43 | 0.099 | 0.117 | 2.725 | 1.934 | 0.209 | +| 171 | 0.321 | 0.839 | 1.240 | ? | ? | + +### Live plugin (scan3 captures): +- mask@43 = 0.510 (0x540678 buffer, per-band spectrum) +- FIR@43 = 0.524 (FIR magnitude) +- Final cut = 10.32 dB → final gain = 0.305 + +### Анализ разрыва +1. **Наш mask = 0.209, плагин mask = 0.510** → ×2.44 разница +2. Плагин's mask = IIR-smoothed per-band spectrum (0x540678) +3. Наш mask = exp2(-LUT(lvl)) — ДРУГОЙ домен (reduction, не spectrum) +4. LUT отвечает за нелинейное преобразование level→gain +5. Плагин's LUT (A=-24, B=28, γ=1) даёт ДРУГОЙ результат чем наш LUT (A=-13.78, B=68.29, γ=0.344) + +### Ключевой факт +Наш LUT (A=-13.78, B=68.29, γ=0.344) — эмпирический фит из NOTES:967. +Плагин's LUT (A=-24, B=28, γ=1) — live-captured в 22b, но помечен как GUI-only. + +### Вопрос +Плагин ЧИТАЕТ ли свой LUT в аудио-пути? Если да — нужно заменить наш LUT на плагин's. +Если нет — LUT не нужен, mask = raw_level (am/res × scale). + +### Следующий шаг +Проверить: если использовать raw_level (без LUT/exp2) как mask, какой cut получится? + +## ============ UPDATE 2026-08-24i: ×1.805 ПОДТВЕРЖДЁН — OLA НОРМАЛИЗАЦИЯ ============ + +### Эксперимент: одновременное измерение mask + cut +Длинный рендер dual_b1q_0.5, scan3 захват 60 фаз, cut измерен по тому же выходу. + +### Результаты (steady-state, 60 фаз — mask КОНСТАНТЕН): +- mask@43 = 0.5099 (постоянен с 1-й фазы — IIR УЖЕ сошёлся) +- fir@43 = 0.5241 (постоянен) +- actual cut = -10.32 dB → final gain = 0.305 + +### Подтверждение ×1.805: +| Измерение | Значение | Cut (dB) | +|-----------|----------|----------| +| mask@43 | 0.5099 | -5.85 | +| fir@43 | 0.5241 | -5.61 | +| actual | 0.305 | -10.32 | +| ratio actual/fir | 1.839 | **×1.805** | + +### Вывод +×1.805 = OLA normalization factor. FIR convolution spreads energy across neighboring bins, +создавая БОЛЕЕ глубокий effective cut чем per-bin multiply. Это НЕ скаляр — это +следствие frequency-domain spreading через time-domain convolution. + +### Два независимых источника ошибки (ИСПРАВЛЯТЬ ОБА): +1. **Mask computation**: наша цепь 0.209 vs плагин 0.510 (×2.44, LUT issue) +2. **Application**: per-bin multiply vs FIR convolution (×1.805, OLA) + +### Следующие шаги +1. Исправить mask computation (заменить LUT на raw_level или калибровать) +2. Реализовать FIR convolution (OLA) вместо per-bin multiply +3. Оба исправления требуются для bit-exact + +## ============ UPDATE 2026-08-24j (24j): ПРИМЕНЁННЫЙ ОТКЛИК = ЖИВОЙ FIR ПОТЕНЦИАЛЬНО; ×1.805 = ФАНТОМ STALE-КЕРНЕЛА ============ + +### Метод (новый канал, скрипты rendersnap2.py + генератор зондов) +1. МЯГКИЙ снаппер `scripts/rendersnap2.py`: НЕ убивает reaper (частичный-wav + hazard 24c обойдён — рендер доестественного выхода), дополнительно дампит + банк скаляров ctx+0x540860..0x5408c0 каждый фазой. +2. Мультитон-проект: клоны dual_b1q_0.5.rpp с входом «2 главных тона 500+2000 + @0.4248 (= амплитуды dual.wav) + 20 зондов −30 дБ на сетке 100..7200 Гц». + Детектор в том же адаптивном состоянии, H измерима Goertzel'ем на всех + зондах. Генерация inline (/tmp/opencode/multi_in.wav + multi.rpp). +3. Свип НЕ годится для Y/X-отношения (энергия на гребне) — только мультитон. + +### Результат multi_b1q_0.5 (живой FIR ph069: m@43=0.320, m@171=0.267) +| f, Гц | cut дБ | gain | m(FIR) | gain/m | +|-------|--------|------|--------|--------| +| 500 | 10.32 | 0.3048 | 0.3234 | 0.943 | +| 2000 | 11.84 | 0.2558 | 0.2698 | 0.948 | +| 700..7200 (18 зондов) | 1.9–4.6 | — | — | **0.98–1.00** | +(450/550 аномальны — утечка боковых лепестков окна Гёрцеля от главного +тона 500; не в счёт.) Контрольные числа воспроизведены: 10.32/11.84 ✓✓. + +### ВЫВОДЫ (три, все жёсткие) +1. **Применённый отклик = mag(живого FIR@540668) побиновно** (±2%; главные + тона −0.5 дБ — недозахват глубины между фазами). НИКАКОЙ дополнительной + свёртки/OLA/×1.805 поверх per-bin multiply НЕ СУЩЕСТВУЕТ. +2. **«Свёртка ×1.805» (23e) и «OLA-нормализация» (24i) ОТЗЫВАЮТСЯ**: источник — + сравнение cut живого рендера с init-time кернелом. Старые phase*.npz из + rendersnap-сессий держат m@43=0.588 СЕКУНДАМИ — это ядро после param-rebuild + при ЗАГРУЗКЕ (52e9b0 строит FIR из init-state bands[]); живой рендер + перестраивает FIR КАЖДЫЙ кадр и мгновенно потребляет → STOP-семпл ловит + identity/reset (все 110 фаз sweep-рендера ≈ 1.0). 0.305/0.32≈0.95, + 0.524(init)/0.32(live)=1.64~«1.805» — вот вся «константа». +3. **R-слоты 0x5407f8/0x540788 — СТАТИКА**: r43=3.9807/r43=4.0285 ИДЕНТИЧНЫ во + всех трёх проектах (dual/sweep/multi, разный контент). Это twin-шаблон + полосы (геометрия), НЕ динамическая маска. Интерпретация 22y «R=1/mask + живая кривая редукции» — снята (кривая была от инициализации). +4. **Скаляр [ctx+0x540888]=1.000000** весь рендер (и s874/s87c/s88c=1.0; + s870=440.955=sens-scale). Wet-скаляр BLOCKMAP 23b НЕ вносит масштаба при + дефолтных параметрах. ×N-нормировка FFT (24g) тоже не подтверждается: + применённый gain совпадает с mag(FIR) без поправок. + +### Что это меняет в архитектуре транскрипции +- RT_FIRCONV как «особое применение» НЕ нужен: достаточно per-bin multiply + спектра кадра на маску-кривую (как default-путь spectral.cpp). +- ВСЕ усилия — в ФОРМУ кривой: bands[i] → (log→ops A-D→exp0.984→hannfall→ops) + → FIR. Наш structural chain должен выдать кривую, чья mag совпадёт с живым + FIR фазы. Эталонные кривые теперь ЕСТЬ: rendersnap2/ph*.npz (fir_re/fir_im). + +### NEXT (24k+) +1. Дискретизация формулы: снять ph-серии с ОДИНАКОВЫм статичным контентом при + вариации одного параметра (q-серия уже есть рефами; прогнать rendersnap2 по + dual_b1q_0.1..10.0) → восстановить зависимость m(fir) от параметров. +2. Сравнить живой FIR с нашим structural mask: где именно расходится форма + (хвост? ширина? пол?) — починка detector-цепи по живым эталонам. +3. Проверить hannfall-мультипликацию: fit exp(0.984·log(bands))·w против + захваченного fir_im≠0 (фаза!) — complex ops A-D дают мнимость. +4. Починить wav-path баг rendersnap2 (проверяет rpp.wav вместо RENDER_FILE). + +## ============ UPDATE 2026-08-24k (24k): ЗАКОН ПРИМЕНЕНИЯ НАЙДЕН ТОЧНО — A = 1.019·V^1.8345; «×1.805» = ПРОИЗВЕДЕНИЕ ЭКСПОНЕНТ 0.984×1.8345 ============ + +### Инструменты раунда (все рабочие, закоммичены) +- `scripts/rendersnap2.py` v3: мягкий снаппер (не убивает reaper), t_snap в npz, + слоты {540668 FIR, 540678 bands, 540688, 540788 R, 5407f8 R} + скалярный банк + 0x540860..c0. OUT = argv[3] (иначе каталог перезаписывается!). +- `scripts/hunt2.py`: полный чанковый перебор ВСЕХ ctx-кандидатов в рендере. + РЕЗУЛЬТАТ: инстанс ОДИН (0x2370040) — GUI/DSP-пара (24c) НЕ ПОДТВЕРЖДЕНА, + гипотеза закрыта. +- Зондовый мультитон: 2 главных тона (= амплитуды dual.wav 0.4248) + зонды + −30 дБ на фикс. сетке; Гёрцель по рефу даёт применённый отклик на всех + зондовых частотах при том же адаптивном состоянии детектора. + +### Ключевые отрицательные результаты (не возвращаться) +- [ctx+0x540888] и весь банк 0x540874..88c = 1.000000 весь рендер; 0x540870 = + 440.955. Скаляров-множителей в применении НЕТ. +- R-слоты 0x540788/0x5407f8 ИДЕНТИЧНЫ во всех проектах (r43=4.0285/3.9807) — + статические twin-шаблоны; с формой V НЕ коррелируют (rms 0.47). +- Слот 0x540678 = спектр кадра (HF→0), не кривая маски; 0x540688 = большие + значения (~183) — сырые аккумуляторы. +- Свип для Y/X-отношения непригоден (энергия только на гребне) — только + мультитон/мультиуровень. +- Динамическое выравнивание V(t)↔g(t) на рампе НЕ сходится (инерция детектора + + STOP-семплирование) — пары брать только из стационарных прогонов. +- Каталоги снапов перезаписываются друг другом — всегда отдельный argv[3]. + +### ГЛАВНЫЙ ЗАКОН (8 уровней драйва −0..−30 дБ, q=0.5, fc=500) +Стационарная пара (V = устоявшийся видимый FIR@540668 «most-common complete +state», A = применённый отклик по Гёрцелю рефа): +``` +cutA[дБ] = 1.8345 · cutV[дБ] + 0.1615 дБ (@500 Гц, центр нотча) +rms = 0.0025 дБ (!), max 0.005 дБ — 8 точек, cutV от 1.0 до 5.55 дБ +``` +На скирте (@2000): та же форма, γ≈1.812, offset≈0.18, rms 0.087 дБ. +В линейном домене: A ≈ 1.019 · V^1.8345. +Погрешность выбора V (медиана most-common вместо истинного кадра) ~0.05-0.1 дБ +на скирте объяснима; закон устойчив по всем уровням. + +### РАЗГАДКА «×1.805» (23e/24i окончательно закрыты) +``` +0.984 × 1.8345 = 1.80515 +``` +23e эмпирическая константа «свёртка ×1.805» — это ПРОИЗВЕДЕНИЕ экспонент: +видимый кернел строится как exp(0.984·log(bands)) (23d ✓), а ПРИМЕНЯЕМЫЙ +kerнел = bands^1.805 (в эквиваленте). Никакой свёртки/OLA/скаляра нет: +применение = побиновный комплекс-multiply (th_b3c0 ✓ 24e) на кернел +A = 1.019·V^1.8345. Старые phase*.npz (m43=0.588) — init-кернелы, всё что +строилось на них — артефакты. + +### Двойные аттракторы видимого буфера +V_steady@43 = 0.524±0.002 ∀q (0.1..10); редкие флэши D@43=0.315 ∀q; +ln(D)/ln(V)=1.85 ≈ γ ⇒ флэш = применяемый кернел A, пойманный в буфере +(перестройка каждый кадр: rebuild→A→consume→запись V-стадии). + +### Что осталось до транскрипции применения +1. Формула V(content,params): ширина провала vs q, глубина vs drive/sens, + позиция vs fc. Зондовая методика + rn_q*/rn_lv* каталоги = данные уже есть. +2. Источник 1.8345 в коде: искать вторую экспоненту в ops A-D / bigkernel + 140b30 (exp2-кернел?) — кандидат: двойное применение стадии exp или + exp(0.984·log)²·коррекция окна (0.984·2=1.968≠1.805 — нет; искать точно). +3. RT_FIRCONV=3: маска = 1.019·M^1.8345, где M = выход нашей detector-цепи, + после того как M приведён к форме V (единичный passband, провалы у контента). +4. Корпусный гейт --vs-bridge после каждого шага. + +### Контрольные числа уровня −0 дБ (dual-эквивалент) +V43=0.5279 → cutV=5.550 дБ; cutA=10.34 дБ (реф 10.32 ✓); cutA@2000=11.85 (реф +11.82 ✓). Уровневая серия: см. /tmp/opencode/{lv*,rn_lv*}, pairs в +level_pairs.npz. + +### ДОПОЛНЕНИЕ 24k-2: чистая q-таблица V (most-common стеади, vfamily_clean.npz) +| q | m43 | m171 | примечание | +|-----|-------|-------|------------| +| 0.1 | 0.5240 | 0.3781 | | +| 0.2 | 0.5240 | 0.4099 | | +| 0.3 | 0.5240 | 0.4373 | | +| 0.5 | 0.5241 | 0.4728 | контроль 10.32/11.82 ✓ | +| 0.7 | 0.5241 | 0.4915 | | +| 1.5 | 0.5245 | 0.5141 | | +| 2.0 | 0.5250 | 0.5176 | | +| 3.0 | 0.5262 | 0.5203 | | +| 5.0 | 0.5298 | 0.5217 | | +| 10 | 0.5431 | 0.5223 | | + +1. **Глубина в центре константна ∀q** (0.524±0.002, лёгкий подъём к q=10). +2. **Форма узкого провала ИНВАРИАНТНА по q**: ширина на полуглубине 6–7 бинов, + на 3/4 глубины 8–9 бинов ∀q; хвост +128 бинов = 1.0000 ∀q. +3. Вся q-зависимость — ШИРОКАЯ ФОНОВАЯ КОМПОНЕНТА: V(f;q) = D(f)·Wq(f), + где D — фиксированный узкий провал у контент-бина (форма от q не зависит), + Wq — широкая юбка с глубиной, растущей при q↓ (Wq@171: 0.72@q0.1 … 1.00@q10). + Проверка соответствия Wq статическому twin-шаблону R^−p — ПЕРВЫЙ кандидат + следующего раунда (данные: vfamily_clean.npz + слоты R788/R7f8). +4. Через степенной закон применения это даёт большие dB на скирте: + A@2000(q=0.1) = 1.019·0.378^1.8345 → 15.5 дБ vs реф 15.33 ✓. +5. Каталоги данных раунда: /tmp/opencode/rn_q* (q-серия), rn_lv* (уровни −0..−30), + rn_ramp (рампа, для динамических опытов непригоден), multi2 (зонды+слоты), + level_pairs.npz, vfamily_clean.npz. + +## ============ ДОПОЛНЕНИЕ 24k-3: структура V окончательна; законы ветвей; разрыв единого закона ============ + +### V = сумма независимых узких провалов на контентных бинах +Сырые кривые (vfamily_clean + сырой вывод): passband РОВНО 1.0000, провалы +~7 бинов ТОЛЬКО на бинах тонов (43 и 171), между ними 1.0000. Никакой широкой +юбки в V нет (dU вне контентных бинов = 0.000 во всех q). Корреляция dU со +статическими R-шаблонами = 0 ⇒ Wq-гипотеза 24k-2 снята; q меняет ГЛУБИНУ +провала на дальнем от fc бине, форма локального провала инвариантна. + +### Железный закон применения (не менялся): A = 1.019·V^1.8345 +Проверен побиновно на скирте: q=0.1: A@2000=15.31 дБ vs реф 15.33 ✓; +q=10: 10.13 vs 10.25 ✓. + +### Закон ветви ЦЕНТРА (уровневая серия, q=0.5, sens=12) +``` +cutV@fc[дБ] = 1.7436 · ln(1 + lvl_raw(fc)/0.3824) rms 0.045 дБ, 8 точек +``` +lvl_raw — НАШ тракт (am/res·scale_factor, render48k RT_DUMP_BIN): наш детектор- +фронтенд совместим с плагином по шкале (без подгоночного k!). + +### Закон ветви СКИРТА (bin 171, q-серия + уровневая) +Чисто лог-аффинный: cutV = 1.2775·ln(lvl_raw)+6.383, rms 0.087 дБ (q-серия); +в ln(1+x/b)-форме требует множитель аргумента λ≈12±6, НЕ константу ⇒ единый +однoаргументный закон от lvl_raw ОТСУТСТВУЕТ. Двухаргументная регрессия +ln(am)+c·ln(res) по всем 36 точкам: rms 0.64 дБ, систематика на скирте низких q +(+2.6 дБ) — скирт структурно круче. + +### Следствия для транскрипции +1. Применение = готовая формула (выше), можно внедрять хоть сейчас: маска из + ЛЮБОЙ кривой M даёт корректную глубину через ^1.8345. +2. Осталась ОДНА неизвестная — отображение (frame spectrum, band params) → + V-провалы. Датасеты для идентификации полные: 36 стационарных точек + (уровни × q × {центр, скирт}) с нашими трактами. +3. Кандидаты механизма на проверку: (а) пер-бин уровень ПОСЛЕ частотных + бидир-IIR (сглаживание тянет центральный уровень в соседей — но ширина + провала 7 бинов против тау-IIR не сходится тривиально); (б) clamp/LUT + FUN_180563a60 (A,B,γ) на уровне полосы; (в) отдельная обработка + «центр vs вне» из BLOCKMAP (шаги 9-13: разные ветки флага 0x5408b8). +4. sens-зависимость ещё не снималась (все данные @sens=12). + +### Статус канона +Канон НЕ тронут; все результаты — новые факты поверх. Коммиты раунда: +6629e8b (24j) → cc8ba34 (24k) → 6dc2967 (24k-2) → этот. + +## ============ ДОПОЛНЕНИЕ 24k-4: RT_FIRCONV=3 внедрён; ХАЗАРД перезаписи рефов; корпус восстановлен ============ + +### RT_FIRCONV=3 (spectral.cpp): применение по закону 24k +`buf *= 1.019·mask^1.8345` побиновно. Первый прогон dual_b1q_0.5: +| конфиг | cut@500 | cut@2000 | +|--------|---------|----------| +| default | 16.73 | 12.01 | +| FIRCONV=3 | 30.31 | 21.71 | +| реф | 10.32 | 11.82 | +Ожидаемо: наш детектор M ≠ V (перерез в центре), экспонента углубляет. +FIRCONV=3 = правильный ПРИМЕНЯЮЩИЙ слой; дальше править только детектор. +Канон (default-путь) не тронут, corpus подтверждён (ниже). + +### ХАЗАРД (был, исправлен): rendersnap2 удалял rpp.replace('.rpp','.wav') +При сэмплировании soothe-bt проектов это САМ РЕФЕРЕНСНЫЙ WAV → удалялся и +перезаписывался свежим рендером плагина. Пострадал dual_b1q_1.0.wav (обрыв, +EOFError в corpus). Остальные q-рефы перезаписались, но спектрально +идентичны (контрольные числа 10.32/11.82 воспроизвелись ✓). ФИКС: путь берётся +из RENDER_FILE проекта; удаление только по нему же. dep0b_rt.wav / out_rt.wav / +out-nofx.wav / out_dual300.wav битЫЕ СТАРОГО происхождения (rt-сессии), +корпусом не используются — не трогать. +Корпус после восстановления: t1kq 0.768 / t1k 2.114 / al 0.986 / res 0.437 / +dual 3.264 / comb 6.116 / TOTAL **2.286** (dual улучшился против 3.578 — +свежие рефы). + +## ============ ДОПОЛНЕНИЕ 24l-2: ЖИВАЯ ПАРА (scratch, FIR); FIR = exp(log(bands)); поддержка log ОГРАНИЧЕНА ============ + +### Решающий снапшот +rendersnap2 v4 (слоты +540548/550/598/628) на multi.rpp → multi3/ph040.npz: +в ОДНОМ STOP пойманы scratch@540628 НЕнуловой (231 бин, max |s|=0.75) и FIR. +Проверка поточечно: +``` +bin43: scratch=-0.6472 exp(s)=0.5235 FIR=0.5292 (+1.1%) +bin171: scratch=-0.7498 exp(s)=0.4724 FIR=0.4763 (+0.8%) +``` +⇒ **FIR ≈ exp(scratch) = bands[i] напрямую** (расхождение ~1% — окна/ops). +«Строение кернела» не преобразует кривую — оно ВОСПРОИЗВОДИТ её через +log→exp-тракт (инверсия знака/твидлы/окна из 24l почти взаимно компенсируются; +их остаточная роль ~1% — выяснить отдельно). + +### Поддержка лога ОГРАНИЧЕНА +scratch непулевой ТОЛЬКО в бинах 6..623 (~70..7300 Гц) при нотчах на 43 и 171. +Вне поддержки scratch=0 ⇒ exp=1 ⇒ ЕДИНичный passband кернела — вот механизм +ровной единички пасбэнда! Границы поддержки = детекторная активность/порог +(уточнить законом). + +### Сшивка всех фактов раунда +``` +applied_A = 1.019 · B^1.8345 (24k, rms 0.0025 дБ) +B = exp(scratch) ≈ bands[i] (24l-2, ±1%) +⇒ остаётся ОДНА неизвестная: кривая bands[i] детектора (наша M ≠ B). +Контроль: A(B@43=0.5235)=0.310 → −10.17 дБ vs реф −10.32 ✓; +глубокий флэш D@43=0.315..0.32 = ровно значение, дающее реф-глубину при +подстановке в закон (D и есть B другой мгновенной фазы адаптива). +``` + +### Буферы +buf548: стабилен внутри сессии, span 4..8193, НЕ классические твидлы +(pair512=(−0.707,−0.724)) — построение при init, реплицировать численно. +mask598: паттерн lane-select подтверждён. + +### NEXT (24m) +1. Формула B(b): центр-закон 1.7436·ln(1+lvl/0.3824) уже есть; скирт-ветка + + границы поддержки (бины 6..623!) — следующие цели. Датасеты: multi3/ + rn_lv*/rn_q* + тракты. +2. Численная репликация log→exp тракта (ops A-D, buf548) до ±1% для bit-exact. +3. sens/fc-серии для масштабирования закона. +4. RT_FIRCONV=3 уже даёт правильную композицию — детектор доводить до B. + +## ============ UPDATE 2026-08-24m: ДВУХСТАДИЙНЫЙ ДЕТЕКТОР — ГЛУБОКАЯ/МЕЛКАЯ СВЯЗАНЫ КОНСТАНТОЙ γ₀≈1.79 В дБ; ЕДИНАЯ АРХИТЕКТУРА ЗАКОНА ============ + +### Данные +rendersnap2 v4 (со слотом scratch@540628) прогнан по всем q (sc_q*) и уровням +(sc_lv*): 18 полных кривых поддержки (deepest-scratch отбор). Стеади-scratch +внутри рендера КОНСТАНТЕН (s43=−0.6477±0.0005 через 50 фаз!), дискретные +состояния {0, релиз, стеади, глубина}. + +### ГЛАВНЫЙ ФАКТ: cut_deep = γ₀ · cut_shallow, γ₀ = 1.79±0.02 +| точка | shallow дБ | deep дБ | отношение | +|-------|-----------|---------|-----------| +| центр @43, drive 0 | 5.550 | 9.90-10.04 | 1.789 | +| @171 q=0.1 | 8.447 | 14.89 | 1.763 | +| @171 q=0.5 | 6.442 | 11.57 | 1.796 | +| @171 q=10 | 5.642 | 10.07 | 1.785 | +Отношение константанно по всем бинам/q/уровням ⇒ «таинственные» 1.805/1.8345 — +ЭТО ОНО: применяемый кернел = стадия-2 детектора, буфер обычно держит стадию-1, +отношение их дБ-масштабов = γ₀. (Точная природа двух стадий — кандидат: +двойной exp-проход тракта 24l или итеративный детекторный контур.) +⇒ закон применения A = 1.019·V^1.8345 и «audio = deep напрямую» — ЭКВИВАЛЕНТНЫЕ +записи одного факта. + +### Формы ветвей стадии-S (shallow; стадия-D = ×γ₀) +``` +центр (bin fc): cutS = 1.7436·ln(1 + lvl_raw/0.3824) rms 0.045 дБ (8 уровней) +скирт (@171 все q): cutS = 7.57 + 2.70·ln(1 + lvl_raw/0.341) rms 0.22 дБ (holdout q=1.5/3/10) +``` +Скирт-офсет +7.57 дБ НЕ объяснён (кандидат: вторая компонента детектора / +кросс-тон вклад). Ширина провалов 5-6 бинов константна ∀q; между контентными +пиками B≡1.00000 ровно. Глубина центра сатурируется ~9.9-10.0 дБ (S-стадия) +∀q при drive 0; @171 монотонно растёт 10.07→14.89 при q↓. + +### Уровневая серия (стадия-D, sc_lv*, центр): +drive −0..−30 дБ → s43: −1.143, −1.021, −0.903, −0.791, −0.685, −0.494, −0.339, +−0.222 (= cut_D 9.93…1.93 дБ). + +### Следствия для транскрипции +Детектор = вычислить cutS(b) побиновно, затем маска = 10^(−γ₀·cutS/20). +Для bit-exact остались: (а) источник γ₀ и двух стадий в декомпе; +(б) объяснение офсета +7.57 скирта; (в) правило границ поддержки лога. +Датасет: /tmp/opencode/scratch_family.pkl (18 кривых) + tract_*. +Инструмент-обновление: rendersnap2 v4 пишет полный набор слотов включая +scratch — канал прямого наблюдения кривой детектора БЕЗ инверсии. + +## ============ ДОПОЛНЕНИЕ 24m-2: RT_VLAW=1 внедрён; формула в коде верна; end-to-end разрыв = слой применения ============ + +### Реализация (framed_model.cpp, env RT_VLAW=1) +Ветка заменяет LUT/exp2/blend: `band_level = 10^(−γ₀·cutS/20)`, +`cutS = 1.7436·ln1p(lvl_raw/0.3824)` (центр-форма глобально), γ₀=1.79. +Проверено stderr-принтом (RT_VDBG): k=43 lvl=8.815→g=0.3189 ✓ формула в коде +даёт точные значения стадии-D. Маска идёт напрямую (exp2/warp/IIR3 отключаемы +RT_NOWARP/RT_NOIIR3). + +### Статус end-to-end (dual_b1q_0.5): +``` +REF: cut@500=10.32 cut@2000=11.82 +VLAW: cut@500= 7.56 cut@2000= 6.41 (+NOWARP+NOIIR3: 5.36 / 1.73) +``` +Формула кривой верна (маска@43=0.319 ≈ плагинная B@43=0.320!), но сквозной +результат недобирает ⇒ расхождение живёт В СЛОЕ ПРИМЕНЕНИЯ (STFT/WOLA): +эффективный gain на тоне = усреднение маски по главному лепестку окна; +узкий дип 5-6 бинов размывается. Исторически канон-константы были ПОДГОНЯНЫ +сквозь этот слой (смещение впитано), поэтому старые группы корпуса сходились. +Глубокая симуляция WOLA-нормировок не завершена (следующий раунд): кандидат — +плагин нормирует окно/хоп иначе (cfg {16384,8192} движка?), либо анализ без +окна/с другим окном. Ключевой контрольный эксперимент: прогнать ОДИНАКОВУЮ +кривую через наш слой и измерить эффективную глубину на тоне против плагина. + +### Гигиена +TOOLING HAZARD снова сработал (same-second build → stale бинарник дал ложные +5.36/1.73). Протокол touch+mtime-проверка ОБЯЗАТЕЛЕН перед каждым замером. +RT_VDBG stderr-принт оставлен (env-gated, нулевая цена). + +## ============ UPDATE 2026-08-24n: ЕДИНЫЙ ЗАКОН СТАДИИ-S; RT_IIR12=0 КРИТИЧЕН; ЦЕНТР 9.40/10.32; РАМПА ПОДТВЕРЖДАЛА γ₀ ============ + +### Единая форма стадии-S (shallow) — обе ветви одной формулой: +``` +cutS(b) = α · ln(1 + lvl_raw(b)/0.3824) + Δ(b) +центр (бин fc): α=1.729 Δ=0.00 rms 0.044 дБ (уровневая серия) +второй контент-пик: α=1.656 Δ=+4.177 rms 0.051 дБ (q-серия @171) +``` +Одинаковый масштаб β=0.3824 (!), близкие α. Δ эквивалентен сдвигу уровня +на +2.23 в lvl-домене (детекторный порог/вторая компонента) ТОЛЬКО у +внецентрового пика. У зондов вне пиков B≡1 (Δ не действует). +Стадия-D = ×γ₀=1.79 (см. 24m). + +### РАМПА подтверждает закон на независимых данных +Траектория u(t) через 30 дБ драйва (ramp_ref, скользящий Гёрцель): +измеренное vs 1.79·cutS: остатки +0.12..+0.23 дБ системно (лаг атаки при +падающей рампе — детектор видит исторически больший уровень). Тройное +подтверждение γ₀ (уровневая серия rms 0.0025 + q-таблица + рампа). + +### RT_VLAW статуса-2 +КРИТИЧНО: частотные бидир-IIR1/2 (RT_IIR12) работают ПОВЕРХ vlaw-маски и +размывают плечи дипа ⇒ рабочий набор флагов: +``` +RT_VLAW=1 RT_NOWARP=1 RT_NOIIR3=1 RT_IIR12=0 +``` +Результат dual_b1q_0.5: **cut@500=9.40** (реф 10.32, зазор −0.92 дБ), +cut@2000=4.14 (реф 11.82 — Δ-ветка ещё не реализована в коде). +Зазор центра-0.9 дБ: кандидат — окно/нормировка нашего STFT-слоя либо тонкая +калибровка α/γ₀. Следующие шаги: (а) реализовать Δ-ветку (правило второго +пика); (б) разобрать слой применения до сходимости; (в) источник γ₀/стадий +в декомпе; (г) корпус с полным флаг-набором. + +## ============ ДОПОЛНЕНИЕ 24n-2: Δ-ветка в коде; dual 0.708 (было 3.264); константы НЕ универсальны ============ + +### Реализовано (framed_model.cpp, vlaw-ветка v2) +Δ-правило: локальные максимумы lvl_raw>0.25 вне ±8 бинов от fc помечают +окрестность ±3 бина → +4.177 дБ. Двухпроходно (без O(n²)). +dual_b1q_0.5 end-to-end: **9.32 / 11.58** vs реф 10.32 / 11.82 +(скирт −0.24 дБ!, центр −1.0). + +### Корпус с флагами (62 случая): +| group | canon | VLAW | +|-------|-------|------| +| t1kq | 0.768 | 3.633 | +| t1k | 2.114 | 3.296 | +| al | 0.986 | 2.620 | +| res | 0.437 | 3.896 | +| **dual** | 3.264 | **0.708** | +| comb | 6.116 | 4.014 | +| TOTAL | 2.286 | 2.515 | + +### Диагноз регресса одноканальных групп +t1kq-диагностика (тон@1000, fc∈{500,1000,2000}): +- fc=500 (тон вне центра): реф 5.45, VLAW 10.37 ⇒ Δ НЕ должен применяться к + этому случаю (реф = shallow-уровень!); при q≈1 и отступе 500 Гц офсета нет, + а в dual (q=0.5, отступ 1500 Гц) был +4.18 ⇒ правило Δ зависит от q/дистанции. +- fc=1000 (тон в центре): реф 8.70 vs dual-центр 10.32 при близком lvl ⇒ + глубина зависит от q/fc — константы α_eff/γ₀_dual не универсальны. +Вывод: закон верен В СЕМЕЙСТВЕ калибровки (dual_b1q_0.5: q=0.5, sens=12). +Параметризация (α, γ₀, Δ-правило) по (q, fc, sens) — следующая большая задача; +методика (rendersnap2+зонды+рампа) готова, датасеты расширяются механически. +Канон не тронут: default-корпус воспроизведён (TOTAL 2.286 ✓). + +## ============ ДОПОЛНЕНИЕ 24o: точки параметризации — sens/q серии; закон НЕ монотонен по lvl между конфигами ============ + +### Новые захваты (rendersnap2 v4, deepest scratch, ПРАВИЛЬНЫЙ центр-бин!) +ВНИМАНИЕ баг анализа: для fc≠500 центр-бин K=round(fc/48000·4096) +(fc=1000 → K=85!). Данные: +``` +проект lvl_ours@K cut_D@K реф-кат +dual q0.5 s12 8.55 10.03 10.32 +qmap q2 s12 20.11 15.44 15.94 +qmap q3 s12 20.06 15.43 15.93 +qmap q6 s12 19.78 15.39 15.90 +sens_6 q1 4.88 10.68 10.90 +sens_9 q1 9.91 11.20 11.44 +sens_18 q1 83.1 11.74 12.01 +sens_24 q1 341 11.74 12.01 +``` +ref_cut ≈ cut_D везде (−0.3..−0.5 дБ) — аудио слышит глубокую стадию, +подтверждение универсальное. + +### Наблюдения +1. **γ₀ проверяется и тут**: shallow=q-таблицы дают те же ×1.79 отношения. +2. **Насыщение по sens**: lvl растёт 70× (4.9→341), кат меняется лишь + 10.68→11.74 и выходит на плато ≈11.74 (q=1). +3. **Не монотонно по lvl МЕЖДУ конфигами**: (q2,lvl20)→15.44 но + (q1,lvl83)→11.74. Закон зависит от q ОТДЕЛЬНО от lvl (форма/масштаб). + Наивная инверсия даёт β(q2..6)≈0.138 против β(q0.5)≈0.35 — но это + подгонка одной точки; нужна фит-форма с q-аргументом. +4. Кат всегда = плато конкретной конфигурации: cap(q,sens). + Гипотеза: cap ∝ глубина полосы (зависит от q через геометрию шаблона + и/или sens-гейн детектора). + +### Датасеты раунда +/tmp/opencode/sc_qmap_1000_q{2,3,6}, sc_sens_{6,9,18,24} (+sc_q*, sc_lv*), +param_points.pkl, tract_qmap*/tract_sens*. +Рефы qmap_*/sens_* в soothe-bt целы (перезаписаны свежими рендерами +rendersnap2 v4 с RENDER_FILE-фиксом — контроль чисел сошёлся). + +### NEXT (24p) +1. Полный кривой фит: cut(b)=F(lvl(b),res(b);q,sens) по ВСЕМ бинам всех + проектов (сотни точек с формой дипа) вместо одиночных бинов. +2. sens-плато: найти механизм насыщения (кандидат: LUT FUN_180563a60 верхний + упор B=28 дБ? 11.74·2стадии? нет — искать точно). +3. Δ-правило для t1kq-семейства (fc-дистанция в октавах? q-нормировка?). + +## ============ ДОПОЛНЕНИЕ 24o-2: глобальный фит формы НЕ сошёлся — путь к механизму через декомп ============ + +### Попытка: cut = α·[ln1p(k·L/β) − ln1p(k·L/C)] (двусторонняя сатурация, +α,β,C общие + пер-проектный масштаб k) по 13 кривым × все значимые бины. +Результат: rms 1.40 дБ, шкалы k хаотичны (k(dual)=19.9 при том что +константы откалиброваны на dual!), qmap-кривые не вошли (тракт-файлы +перепроверить). ВЫВОД: форма «мягкий предел с общими константами» не +описывает семейство; пер-конфигурационные капы (q0.5:10.03 / q1s24:11.74 / +q2:15.44) не выводятся из одной (β,C). + +### Интерпретация +Кап растёт с q (шаблон острее ⇒ глубже детекция?) и слегка с sens. +Кандидаты механизма насыщения и q-зависимости: +(а) частотно-варпированные коэф. генератора FUN_180533340 (p-экспонента!) + — сила IIR зависит от fc/sr и p; +(б) LUT FUN_180563a60 (A,B,γ в dB-домене); +(в) mix^p / blend-цепочка BLOCKMAP pre-combine; +(г) двухстадийность = двойной проход exp-тракта (24l) — тогда γ₀ должно + быть точной функцией отношений окон/нормировок, вычислимо из декомпа. +Путь по золотому правилу: СТАТИЧЕСКИЙ ДЕКОД этих тел (все адреса известны), +затем параметризация станет следствием, а не подгонкой. + +### Датасеты для будущих фитов сохранены +/tmp/opencode/mega_curves.pkl (13 полных кривых cut+lvl), param_points.pkl, +scratch_family.pkl, плюс сырые ph*.npz каталоги sc_*. +### Коммиты раунда 24j..24o-2 +6629e8b, cc8ba34, 6dc2967, 1605e0b, 65fb983, fe01ef5(?), b00a8a6(?), fc224b0, +31cc554, 5f822eb + этот. Все запушены в origin/main. + +## ============ ДОПОЛНЕНИЕ 24p: СКАЛЯРНОСТЬ СТАДИЙ ПОБИННО; ГИПОТЕЗА BLEND; mix-тест некорректен ============ + +### Побиновое отношение стадий (multi3, чистые данные) +deep/steady по 82 значимым бинам: **среднее 1.760, std 0.0115** (min 1.698, +max 1.822 на краях с малыми катами) ⇒ стадии = ОДНА ФУНКЦИЯ × константа в +дБ-домене, не разные формы. Механизм — скалярный множитель dB-стадии. + +### Гипотеза blend: γ₀ = 1 + blend (=1+0.8=1.8) +xmm10=0.8 (blend!) стоит в парно-скалярной ветке FIR-цикла (24l). γ₀≈1.76-1.83 +во всех измерениях — 1.80 в центре. ПРОВЕРЯЕМОЕ ПРЕДСКАЗАНИЕ: γ₀(mix) = +1 + f(mix); при mix→0 γ₀→1. + +### mix-тест (s_corr_mix_*) НЕ КОРРЕКТЕН +Проекты используют другой вход (resonant.wav, кат 0.6 дБ против наших 5.5) и +разные уровни; глубокие состояния не отделяются. Нужен клон multi.rpp с +вариацией ТОЛЬКО mix внутри VST-чанка (требует decode/encode чанка или +REAPER-автоматизации setparam.lua) — отдельная задача. +Захваты sc_s_corr_mix_* сохранены (вход resonant). + +### Статус раунда 24j..24p +- Применение: per-bin complex-multiply, A=1.019·V^1.8345 (≡ deep=γ₀×shallow) +- Стадия-S: α·ln1p(lvl/β=0.3824)+Δ, центр откалиброван (rms 0.044) +- RT_VLAW=1 в коде: dual 9.32/11.58 vs реф 10.32/11.82; корпус dual 0.708 +- Открыто: q/sens-параметризация капов; Δ-правило для других семейств; + источник γ₀/двух стадий в декомпе; mix-тест с контролируемой вариацией. +- Канон не тронут; все эксперименты env-gated. + +## ============ ДОПОЛНЕНИЕ 24q: КОНТРОЛИРУЕМЫЙ depth/mix СВИП (patchparam.py!) — blend-гипотеза опровергнута; +0.4 дБ константа ============ + +### Инструмент +patchparam.py правит внутри base64-VST-чанка RPP. Полная +карта параметров multi.rpp получена (band1: 500/q0.5/sens12/on ✓; +**depth=0.864**, mix=100, selectivity=10, sharpness=10, resolution=1, +offline oversample=3). Созданы контролируемые клоны: depth∈{0.5,0.7,0.9}, +mix∈{25,50,75}, вход/прочее идентичны. + +### Результаты свипов +DEPTH (mix=100): γ₀ = 1.7597 / 1.7600 / 1.7608 для depth 0.5/0.7/0.9 — +γ₀ ОТ ГЛУБИНЫ НЕ ЗАВИСИТ; cutS слегка растёт (5.30→5.66). +MIX: cut_S = 1.10 / 2.36 / 3.84 / 5.55 (25/50/75/100) — стадия-S масштабируется +миксом (см. BLOCKMAP mix^p); глубокие состояния в захватах m50/m75 отсутствуют. + +### Аудио (чистые рендеры рефов) против стадии-S: +``` +вариант cut@500 cut@2000 S@43 S@171 audio/S@500 +m25 1.79 1.93 1.098 — 1.63 +m50 3.79 4.14 2.363 — 1.61 +m75 6.44 7.15 3.841 — 1.68 +d0.5(m100)9.70 11.21 5.296 — 1.83 +m100 orig 10.32 11.84 5.550 6.442 1.859/1.839 +``` +⇒ **blend-гипотеза «γ₀=1+mix» ОПРОВЕРГНУТА**: отношение audio/steady НИКОГДА +не падает до 1 (даже при mix=25 оно 1.63); слабо растёт с mix (1.6→1.86). +Двухстадийность существует ВО ВСЕХ mix, микс лишь модулирует величину. +Константа раунда: audio − самый-глубокий-захваченный-кернел = **+0.40..+0.45 дБ** +(d0.5: 9.70−9.32=+0.38; m100: 10.32−9.93=+0.39) — стабильный зазор захвата. + +### ХАЗАРД (новый): rendersnap2-клоны с одним RENDER_FILE +Все клоны multi.rpp наследуют RENDER_FILE=/tmp/opencode/multi_ref.wav ⇒ каждый +захват перезаписывал общий реф последней конфигурацией. При работе с клонами — +ПЕРВЫМ ДЕЛОМ переписывать RENDER_FILE (как сделано для *_ref вариантов). + +### NEXT (24r) +1. Зазор +0.4 дБ: либо мгновенная фаза адаптива, либо пост-df0 обработка — + поймать пару (pre-df0, post-df0) в одном STOP (слот track@768 уже пишется). +2. Источник γ₀ и двух стадий в декомпе: df0(FIR,track_i) семантика! +3. Δ/q/sens параметризация по накопленным датасетам. +4. Корпус dual-семейства через RT_VLAW после фиксации зазора. + +## ============ ДОПОЛНЕНИЕ 24r: track статичен; DTFT-тест кернелов; упаковка потребления — последний разрыв ============ + +### Слот track@768 добавлен в rendersnap2 v5; multi4 снят +track ИДЕНТИЧЕН во всех фазах (deep==steady бит-в-бит) — статическая таблица, +не динамическая вторая стадия. + +### DTFT-тест (ирfft кернелов → точный отклик на частотах тонов): +``` + @500 @2000 +shallow 0.524 (5.61 дБ) 0.474 (6.49) +deep 0.294 (10.63)✓ 0.594 (4.52)✗✗ +REF 0.305 (10.32) 0.257 (11.82) +``` +Deep-кернел совпадает с рефом НА ЦЕНТРЕ (+0.3 дБ) но проваливается на скирте +⇒ irfft(captured_spectrum) НЕ есть тот фильтр, что слышен на скирте. +Причина: спектр кернела в буфере НЕ эрмитов (fir_im≠0, антисимметрика), а +потребление идёт через комплексные стадии ops A–D (твидлы buf548) — их +упаковка/распаковка определяет действительный фильтр. Это ПОСЛЕДНИЙ +нерасшифрованный кусок применения. + +### Инструменты состояния +rendersnap2 v5 (SLOTS+768), patchparam.py — рабочий правщик VST-чанка +(параметры: depth/mix/bands/selectivity/sharpness/...), полная карта PARAM id +получена из multi.rpp. Клоны проектов: multi_d{0.5,0.7,0.9}, multi_m{25,50,75} +(+_ref рендеры). + +### NEXT (24s) +1. Декод ops A–D (воркер 4ca80→диспетчер типов; твидл-стадии над упакованным + вещественным спектром) — замкнёт путь преобразования буфер⇒фильтр. +2. После этого: bit-exact репликация применения станет механической. +3. Параллельно: Δ/q/sens по датасетам (mega_curves.pkl). + +## ============ UPDATE 2026-08-24v: СЛОЙ STFT НАЙДЕН — БЕЗ СИНТЕЗ-ОКНА; DUAL КОРПУС 0.193 ============ + +### Эксперимент RT_SYN/RT_WIN (spectral.cpp, env-gated) +Варианты слоя на dual_b1q_0.5 (маска VLAW v3 фиксирована): +``` +база sym-hann + syn-окно : 9.47 / 11.16 +периодический ханн : 9.48 / 11.16 +прямоугольный анализ : 10.18 / 11.27 +БЕЗ СИНТЕЗ-ОКНА : 10.27 / 11.59 ← победитель +``` +⇒ плагин НЕ домножает выход обратного преобразования на окно +(архитектура partitioned-conv движка; нормировка Σw_ana·1/hop). +Зазор к рефу: −0.05 (@500) / −0.23 (@2000). + +### Корпус с RT_VLAW+RT_SYN=1 (+NOWARP/NOIIR3/IIR12=0): +| group | canon | VLAW+SYN | +|-------|-------|----------| +| t1kq | 0.768 | 3.380 | +| t1k | 2.114 | 3.271 | +| al | 0.986 | 1.872 | +| res | 0.437 | 3.861 | +| **dual** | 3.264 | **0.193 (max 0.438!)** | +| comb | 6.116 | 4.410 | +| TOTAL | 2.286 | 2.252 | + +### Статус +dual-семейство РЕШЕНО (все 22 случая <0.44 дБ). Одноканальные группы ждут +параметризации α/β/c/Δ по (q, fc, sens) — константы откалиброваны на +dual(q=0.5). Механика применения и слой полностью декодированы. + +### NEXT (24w) +1. Параметризация закона по q/sens/fc (датасеты sc_* готовы; методика + deepest-scratch + Гёрцель рефов). +2. Δ-правило вторых пиков из декомпа pre-combine. +3. Решение о каноне: после параметризации сравнить TOTAL vs 2.286. + +## ============ UPDATE 2026-08-24s: ПРИМЕНЁННЫЙ ЗАКОН ПРЯМО (rms 0.016 дБ); АУДИО = exp(глубокого scratch); RT_VLAW v3 ============ + +### Развязка объектов (главная путаница раундов устранена) +Для deep-кадра multi4-ph063 одновременно: +``` +exp(scratch)[43]=0.320 [171]=0.267 ↔ аудио 0.305 / 0.257 ✓✓ +FIR-буфер [43]=0.528 [171]=0.475 ← промежуточное представление +``` +⇒ **АУДИО = exp(глубокого scratch) НАПРЯМУЮ** (оба тона!). Буферные состояния +и все γ₀-соотношения — внутренняя кухня тракта построения (мин.-фазовая +упаковка exp(s−iH(s)) подтверждена на re/im ±1%), для транскрипции НЕ нужны. +Применение = побиновное умножение кадра на ВЕЩЕСТВЕННУЮ маску exp(s_deep). + +### ПРИМЕНЁННЫЙ ЗАКОН (центр, 8 уровней драйва): +``` +cut_dB(b) = 3.2193 · ln(1 + lvl_raw(b)/0.4927) + 0.5423 rms 0.016 дБ +скирт-офсет (второй пик): +7.46 − 0.54 ≈ +6.92 дБ сверху (глуб-домен, +согласован с ×γ₀ старого 4.18) +``` +Контроль lvl=8.85 → 10.02 vs реф 10.32 (зазор 0.30 — фаза адаптива). + +### RT_VLAW v3 (константы применённой стадии напрямую): +dual_b1q_0.5: **9.47 / 11.16** vs реф 10.32 / 11.82 (было 9.32/11.58). +Остаток −0.85/−0.66 дБ = СЛОЙ STFT (усреднение по лепестку, диагноз 24m-2) +— следующий кандидат на разбор: параметры окна/hop нашего render48k против +движка {16384,8192}. + +### Формулировки для транскрипции (итог раунда) +``` +lvl_raw(b) = am(b)/res(b)·scale (наш детектор-фронтенд ✓) +cut_D(b) = 3.2193·ln1p(lvl/0.4927)+0.54 [+6.92 у вторых пиков] +mask = 10^(−cut_D/20) (вещественная!) +применение = per-bin multiply кадра +``` +Открыто: параметризация (α=3.22, β=0.49, Δ) по q/sens; слой STFT; +мин.-фазовая упаковка буфера как объект декодера ops A–D. + +## ============ ДОПОЛНЕНИЕ 24w-2: параметризация — скалярные комбинации lvl/res отвергнуты ============ + +### Данные центров всех конфигов (lvl_ours, res_c, cut_D): +dual q0.5 (8.55,.117,10.03); qmap q2/3/6 (~20,.117,15.4); +sens6 (4.88,.484,10.68); sens9 (9.91,.238,11.20); sens18 (83,.028,11.74); +sens24 (341,.0069,11.74). +Замечание: наш twin res@центра почти НЕ зависит от q (0.117 при q0.5 и q2!), +зависит от sens (s18→0.028, s24→0.0069) — sens реализован усилением шаблона. +lvl между конфигами масштабируется по sens корректно (×2.03/+3дБ ✓). + +### Отвергнуты (немонотонность против фактов): +- F(lvl) одна на все конфиги: (q2,L20)→15.44 vs (q1s18,L83)→11.74 ✗ +- X=L·R^b, L/R, L·R² и др.: порядок точек противоречит катам ✗ +⇒ q входит в закон через МЕХАНИЗМ (частотные IIR с коэф. FUN_180533340, +зависящими от selectivity→p; либо LUT-индексация), не скаляром. +Следующий бит-exact шаг: декод p/tau/mult-параметров 533340 (сеттеры +selectivity/sharpness) и их роль в формировании bands[]. + +### Итог раундов 24j..24w-2 (все запушены) +Применение декодировано до формул; слой STFT найден (без синтез-окна); +dual-семейство решено корпусно (0.193 mean). Параметризация остальных +семейств = следующий мейлстоун (методика готова). + +## ============ ДОПОЛНЕНИЕ 24x: структурный диф кривых; таблица k-маппинга эффективных уровней ============ + +### Структурный диф (dual q0.5, plug exp(scratch) vs наш канон-prewarp) +``` + plug_B our причина расхождения +passband 1.0000 0.8000 blend-флор (у VLAW=1 ✓) +меж дипами 1.0000 0.60-0.80 warp/res^rp-одеяло (VLAW отключает ✓) +дип@43 0.315 0.159 глубина-закон (VLAW ✓) +плечи узкие/крутые широкие IIR3/freq-IIR (VLAW без них ✓) +``` +⇒ RT_VLAW-обход структурно ВЕРЕН; канон-цепь не воспроизводит ни одну зону. + +### Таблица k-маппинга (lvl_plugin_implied / lvl_ours, инверсия dual-закона) +``` +конфиг lvl_ours lvl_impl k +dual q0.5 s12 8.550 8.607 0.9934 ← калибровка +qmap q2 s12 20.107 49.896 0.4030 +qmap q3 s12 20.055 49.740 0.4032 ← константа ∀q≥2! +qmap q6 s12 19.780 49.119 0.4027 +sens6 q1 4.875 10.994 0.4434 +sens9 q1 9.909 13.008 0.7618 +sens18 q1 83.085 15.473 5.3696 +sens24 q1 341.03 15.473 22.0402 +``` +Свойства: k(q≥2)=const 0.403; k(sens) растёт экспоненциально на верхнем крае; +ни аффинная, ни степенная формы не покрывают ⇒ фронтенд детектора плагина +(am/res/scale) отличается от нашего конфиг-зависимо. Bit-exact замыкание = +точный декод twin_coeff + амплитудного тракта детектора (модуль twin/fn529fe0). + +### Практическое следствие +Пока k-маппинг не декодирован, VLAW-константы действительны только для +калибровочного семейства (dual q0.5 s12) — что уже даёт корпус 0.193. + +## ============ ДОПОЛНЕНИЕ 24y: закон подтверждён на fc500-q2 (−0.06 дБ); САТУРАЦИЯ уровня плагина обнаружена ============ + +### Расширение валидации закона +``` +конфиг lvl_ours cut_D предсказание (dual-константы) ошибка +fc500 q2 s12 8.782 10.02 9.96 −0.06 ✓ +fc1000 q2 s12 20.107 15.44 12.32 −3.12 ✗ +``` +Закон работает ЗА пределами калибровочной точки по q (та же fc!) — +семейство шире, чем думали. + +### Обнаружена САТУРАЦИЯ эффективного уровня плагина +На tone1k-проектах (am@центра константна 0.7307): +``` +конфиг наш lvl impl-lvl (инверсия закона) +q2 s12 20.1 50.2 ← impl РАСТЁТ +s18 q1 83.1 15.47 ← упёрлось +s24 q1 341 15.47 ← жёсткое плато +``` +Их детекторный уровень НАСЫЩАЕТСЯ (плато impl≈15.47 при q1), тогда как наш +lvl растёт линейно с sens. Плато q≥2 на 49.9 — аналогичный эффект от q. +Формы-кандидаты: лог-компрессия res-гейна, деление на (res+floor), +или LUT FUN_180563a60 (A,B домен!). Это и есть источник «капов» 24o. + +### Следствие для транскрипции +Между нашим lvl и законом нужна СТАДИЯ КОМПРЕССИИ уровня (насыщаемая), +параметризованная по конфигу. Кандидат №1 — именно LUT 563a60 (дБ-домен, +A/B упоры = насыщение!). Её декод = следующий мейлстоун. + +## ============ ДОПОЛНЕНИЕ 24z-2: попытка живого съёма A/B/γ — объект банды не найден пока ============ + +rendersnap2 v6 пытается читать цепочку [ctx+0x540678]→band→[+0x180]→sub→A/B/γ. +Результат: поля отсутствуют ⇒ [ctx+0x540678] указывает на ДАННЫЕ кривой +(массив float'ов), а не на объект банды. Адрес массива ОБЪЕКТОВ банд +(тех, что с [+0x180]-LUT и [+0x4198]-входом из 563a60) нужно найти отдельно: +кандидат — скан heap'а на структуры с валидным [sub+0x90]/[sub+0xc] паттерном, +либо трассировка инициализации (563a60 получает rcx=band obj — ловить её +аргументы при param-rebuild через rendersnap2-стиль STOP в момент загрузки). + +## ============ ДОПОЛНЕНИЕ 24aa: 563a60 — GUI-ветка; компрессия аудио в семантиках ops шагов 1–19 ============ + +Скан всей rw-памяти при рендере (scan_pairs.py): пары (A<0 **2026-08-20 UPDATE**: см. актуальный канон — `AGENTS.md` и `handoff/NOTES_LEVEL.md`. +> Нижеследующее «NOT DECODED»/«missing body» УСТАРЕЛО: `FUN_180529fe0` mono-path, +> `FUN_180563440/563ce0/563a60` — расшифрованы, полные дизассемблы скопированы в +> `handoff/nls_dasm/` (134 файла). Этот файл — исторический чекпоинт. + +## 0. DECOMPILATION INVENTORY (2026-08-19 — what's decoded, where, and what's missing) +Goal: bit-exact parity is gated by EXACT tables/window/constants. The chunk-level model hits +err ≤0.62 dB (dual) / ≤0.19 dB (al_*) — to go sample-exact we need precise values from the binary. + +### Static decomp assets IN REPO (use these, don't re-decompile): +- `ghidra-proj/soothe2.rep` — full Ghidra project (vst3 at ImageBase 0x180000000). +- `decomp_funs.txt` (312K lines, ~1640 functions), `fun_map.txt` (2285 addr→FUN), `consts.txt` (11602), + `decomp_dsp.txt` / `decomp_vtables.txt` / `decomp_candidates.txt` / `focus_decomp.txt`. Generators: + `Dump*.java`, `ImportRtti*.java`, `ListFuns.java`, `SearchRefs.java` (+ headless logs). +- RTTI: `rtti_dsp.json` / `rtti_full.json` — class hierarchy + `SpectralProcessor`, `Soothe2ModuleBase`, `FilterGraph`, + `DigitalFilter`, `IIRFilterExtended`, `AudioProcessingModule`. +- `handoff/nls_dasm/` — 120 hand-picked `.dis` (twin, iface_18052da00/dbc0, fft, generator, ctor). +- `dsp/` — working C++ transcription (twin, detect, freqpath, spectral, fft_stage) + harness; + `build/twin_check` passes float-parity gate (§2). + +### KEY DSP ADDRESSES — decoded / not decoded: +DECODED (formula-level, notes at NOTES_TWIN.md / NOTES_LEVEL.md): +- twin kernel `FUN_180535880`; generator `FUN_180533ec0`; caller `FUN_180536300`; dead sibling `180536f90`. +- level-weight formula `FUN_180530d30` (0x540880/884, warp 0x5406a8=0.87·x/(1+x/7.942), w=0.1^(...)). +- mask-apply entry `FUN_180529fe0` (accumulator 0x5407c8 += w·res; mask *= warp; FIR *= 0x540658). +- FFT-conv loop 0x52b550-0x52b8b5 (plan 0x540530, windows 0x540548/550/598, freq-axis 0x540698=offline const). +- level-path map: `0x563440` (LUT curve +0x188, 6 band-slots, combine→+0x2198), `0x56e3e0` (twin-mask factory), + `0x563ce0` (IIR level-tracker INIT only). + +NOT DECODED / MISSING FROM decomp_funs.txt (critical): ⚠️ → РЕШЕНО (2026-08-19/20), см. выше +- `FUN_180529fe0` body — РЕШЕНО (decomp в `/tmp/consumers_out.txt`, mask-цепь в `dsp/framed_model.cpp`). +- `FUN_180563440` / `FUN_180563a60` (LUT curve + gamma + combine), `FUN_180563ce0` — РЕШЕНО, + дизассемблы скопированы в `handoff/nls_dasm/` (`f_563440.dis`, `f_563a60.dis`, `f_563ce0.dis`, `f529fe0.dis`). +- Window `0x540658` — РЕШЕНО (live-захват, см. `handoff/NOTES_CAPTURE.md`; `rtwin_freq_44100.npy`). +- sens source: XML 12.0 → runtime sens_dB≈24.65 (host ×2) not found in dump; `IAT\*0x181bab370` outside dump. + +### Bridge model STATUS (2026-08-19): +- `framed_render.py` full STFT frame-render (N=2048, hop=512, sqrt-Hann, twin env tatt=11ms/trel=80ms): + `C(f)=G·LUT(xv)+W·warp(f)^A`, G=1.0850, W=0.2819, A=1.1377 (joint dual+al_* refit). +- Validation: dual q0.1/1/10 @500+2000 err ≤0.62 dB, envRmse@steady ≤0.78 dB; al_* lv3..24 err ≤0.19 dB. +- LUT = slanted al_*-leg (0.366@xv=-0.50 → 0.636@xv=+0.55), replaces flat B.12 (~0.5). Bugfix: clip range + must be [LY.min(), LY.max()] not [LY[0], LY[-1]]. +- Remaining structural residual: −0.6 dB systematic on dual 500Hz (q0.1/q1). Live evidence (avg per-bin + gain H=|Y|/|X|): reference mask is FLAT ~-10.2dB across 100-540Hz regardless of Q — model produces + res-shaped notch. Hypo tested: freq-smoothing of C(f) fails (kills 2000Hz). NEXT: scalar per-frame xv + (broadband level, not per-bin am/res) — see /tmp/smoothtest.py (+ edit scalar=True). + +## 1. Objective (unchanged since session 1) +Transcribe the decoded soothe2 detector ("twins" 0x180535880/0x180536f90, 2nd-order resonator) into +C++ and reach **bit-exact render parity** with the Reaper reference wavs in `/home/m/soothe-bt/*.wav` +(checking dB/magnitude parity first, then byte-diff). + +Path A (bit-exact) chosen. Phase 0,1,2 done. Phase 3,4 advanced. Phase 5 (render diff) is next. + +## 2. DECOMPILATION FINDINGS (2026-08-19 — FUN_180563440, FUN_180563ce0 decoded) + +### FUN_180563440 — LUT curve evaluation + band combine (222 lines disasm) +Structure: **3 phases per frame**: +1. **1024-bin LUT loop** (0x400 iterations): for each bin k: + - x = clamp(k * 0.0009775, 0, 1.0) = k/1024 + - Band config at +0x188: {A(+0x00), B(+0x04), threshold(+0x0c), flag(+0x10), callback(+0x50)} + - **Path 1** (callback exists): vtable call → dynamic LUT + - **Path 2** (flag=1, threshold≠1.0): **power-law** → centered = 2*x - 1, then `sign(x) * 10^(log10(|x|) / threshold)` — this is a **compression curve** controlled by sharpness/threshold + - **Path 3** (default): **linear interpolation** → `(B - A) * x + A` + - Output: double-precision at +0x198, stride 8 +2. **Twin-mask factory** (FUN_18056e3e0): 6 bands × 1024 bins, stride 0x2000 +3. **Combine loop**: stereo (max 2 channels), 6 bands, `1 - sum(band_masks)` + +### FUN_180563ce0 — IIR level-tracker INIT (163 lines disasm) +- **341 bins** (0x155 iterations), **order-3 IIR** (3 coefficients per bin) +- Coefficient: **0.1** (`0x3dcccccd` = IEEE 754 float 0.1) +- Initial state: [1.0, 0, 0, 0] and [-1.0, 0, 0, 0] (identity + zero) +- Buffer layout: 3 × (16 bytes coeff) per bin, stored at rcx+0x28/+0x40/+0x58 +- **Not the update loop** — init only; UPDATE is elsewhere + +### FUN_180529fe0 — Coefficient setup (2051 instructions, in decomp_funs.txt) +- **vtable method** on Soothe2Module +- **Lock** at +0x2404dc (atomic test-and-set) +- **PRNG state** at +0x2404e0, LCG with offset 0x3cdca +- **6-iteration coefficient generation** from 0x5408b0 buffer (LCG-indexed) +- **Depth scaling**: `powf(normalized, depth)` at +0x2c +- **Mask assembly**: normalize by 0x1a0 (NFFT), × 0x540870 (level weight), × 0x54088c (sharpness), invert +- **Copy output** via SIMD memcpy (thunk 0x181ba94b0) + +### FUN_18052e9b0 — SpectralProcessor main (3167 instructions, in decomp_funs.txt) +- **Same PRNG + coefficient setup** as FUN_180529fe0 +- **Band chain**: FUN_18052f500 (interleave) → FUN_18052ee70 (per-bin gain) → FUN_18052d650 (setup) → FUN_18052d920 (window) +- **Buffer alloc**: FUN_18052e190 for 0x540668, 0x540698, 0x5406a8, 0x5406b8-e8 (6 bands) +- **Depth scaling**: `powf(normalized, depth)` with depth at +0x2c +- **Final mask**: `1 - C` (inversion) +- **Window application**: `FIR *= 0x540658` (the live-captured window table) + +### Constants verified from binary: +| Constant | Address | Value | Meaning | +|---|---|---|---| +| SCALE | 0x24c3c54 | 0.000977517 | 1/1024 (bin→x) | +| ONE | 0x24c3ea4 | 1.0 | clamping max | +| TWO | 0x24c41e0 | 2.0 | centering (2*x-1) | +| NEG1 | 0x24c4680 | -1.0 | sign flip | +| HALF | 0x24c3d8c | 0.5 | threshold | +| DEPTH_SCALE | 0x24c4334 | 4.0 | depth range | +| DB_CONV | 0x24c43e0 | 8.6859 | 20/ln(10) | +| FLOOR | 0x24c4704 | -6.9078 | ln(0.001) | +| IIR_COEFF | embedded | 0.1 | attack/release per bin | +| IIR_ORDER | embedded | 3.0 | IIR filter order | +| LCG_OFFSET | embedded | 0x3cdca | PRNG state advance | + +### Live-captured tables (re-verified): +- `rwin_A0.npy` (0x1930100): 0.5→0.8, **frequency window** (NOT warp formula) +- `rwin_B0.npy` (0x1938180): 0→3.899, **power-law depth curve** (exponent ~0.66) +- `rwin_C0.npy` (0x19401c0): 0.596→0.126, **level-dependent weight** +- `r_freqaxis.npy`: 0→23988.3 Hz, 11.71 Hz spacing (48000/4096) + +### Key insight: The warp formula is NOT a table — it's computed at runtime +The empirical `0.87*7.942*x/(7.942+x)` is an approximation of a runtime computation +in FUN_180563440 that evaluates the LUT curve parametrically. The actual LUT has TWO modes: +- **Linear** (default): simple interpolation between A and B +- **Power-law** (flag=1): `sign(x) * 10^(log10(|x|) / C)` — compression curve + +## 3. PROOF OF STATE — run this first (5 min) +Everything below must reproduce. If `twin_check` fails, the transcription moved stale. + +```bash +cd /home/m/re-tools/dsp && cmake --build build && ./build/twin_check +# expect: +# max rel err = 1.268e-05, PASS = yes (float-parity) +# max sweep rel err = 1.233e-05, SWEEP PASS = yes, exit 0 +``` + +`build/soothe2_dsp.so` and `build/twin_check` are current. CMakeLists already has `twin.cpp` + `twin_check` executable. + +## 3. TRANSCRIPTION STATE (summary — details in NOTES_TWIN.md) +- `dsp/twin.hpp` + `dsp/twin.cpp` = twin kernel FUN_180535880: + - build_twin_coeff (FUN_180533ec0 double pipeline → cvtpd2ps floats) + - generator formula (confirmed): `fVar1=sqrtf(param_5)`; `w0=max(2.0,freq)·2π/fs_total`; + `p=(sin(w0)·0.5)/Q`; `A=[1+dVar5, dVar2, 1−dVar5]`, `B=[1+param_4, dVar2, 1−param_4]`, + `dVar2=cos(w0)·−2.0` (DAT_1824c46b0=−2.0). + - cplx_div_exact (rcpps+Newton: `ref=r0·(2−den·r0)`, NaN-guard cmpeqps+movmskps, numerator B·conj(A)) + - cplx_mul_exact (vfmaddsub213ps form `re=fma(br,ar,−bi·ai)`, `im=fma(br,ai,bi·ar)`) + - twin_apply: conj(z1) mirroring 0x1800018b0, z2=z1·z1, out=2·B/A +- **OPEN phase question (byte-exactness)**: conjugate shaping — (-α+π/2) vs (-α) axis. Magnitudes match + either way; final proof needed via phase of a single-bin run before claiming byte-exactness. + +## 4. GRID + CALLER (Phase 3 — details in NOTES_TWIN.md §Phase 3) +- Caller FUN_180536300 (decomp 6218, `caller_536300.dis`), scratch ≥52·N complex bytes: + +0..8N A-accum, +8N..16N B-accum, +16N..24N z-powers, +24N..32N rotor-in (filled 1.0), + +32N..36N phase ramp lVar1, +36N..44N rotor-out z1, +44N..52N lVar3(1.0)/lVar4(0.0) +- `0x18052da00(lVar1, scale=2π/(os·sr), N)` = ramp fill; `0x18052dbc0(param_2, lVar4, N)` cplx-interleave + → out slots get (cos,sin) = e^{+iθ}. +- **Grid law**: `os=*(int*)(cfg+0x240080)=4` (mov DWORD PTR [rbx+0x240080],0x4 @0x18052ce3e), + `sr=*(float*)(cfg+0x24)=44100.0` → generator `fs_total=os·sr=176400`; per-cell phase `2π/176400`; + cell k ↔ freq k/os Hz (0.25 Hz steps). Phase-1 FS=44100 was a valid normalization (res depends on f/fc). +- twin always = FUN_180535880 (type-info compare equal); sibling FUN_180536f90 dead. +- case8: numBands `+0x814`=1, `+0x103`=1; `param_5=10^(sens_dB/20)`, `gain=sqrtf(param_5)=10^(sens_dB/40)` + → fit gain=4.132 means stored `sens_dB@+0x101 ≈ 24.65`; freq=[band+0x804]=1000.0, Q=[band+0x80c]. + +## 5. LEVEL-WEIGHT FORMULA (Phase 4 — DECODED, NEGATIVE RESULT — NOTES_LEVEL.md §UPDATE 2026-08-18) +Exact formula from decomp 22330 (FUN_180530d30): +- `base=(2000/(sr·0.5))·iVar5/(bin+1)`, iVar5=NFFT/2+1 +- `w8=powf(base,0.25)` +- `v=[0x540880]·0.25·w8·fVar12` (fVar12=4.0 if offline flag [0x5408b8], else 1.0) +- `q=1/(1+v/([0x540880]·4))` (2nd pair 0x540884: q2=1/(1+v2/[0x540884])) +- `dVar1=(sr/[0x1a0])·[0x1ac]·0.001` +- `w=0.1^(1/(max(q·v,floor)·dVar1))`; bufs: 0x5406b8=w, 0x5406c8=1−w (and 0x5406d8/6e8 w/ 0x540884) +- consts: c3d3c=0.25, c4334=4.0, c3d8c=0.5, c45b4=2000.0, c3f70=0.1 f32, c3e30=0.001 f64, c3f70=0.1 +- **Numerically**: w(500)=w(1000)=w(2000)≈0 (1e-20..1e-0.07), complements≈1 → but the weights are STILL + APPLIED (as 1−w ≈ 1) by FUN_180529fe0 (`0x5407c8[band] += 0x5406c8·res_upper + 0x5406e8·res_lower`, + thunk 003c40, kernel 0x1800752e0 = vfmadd213pd) — the form is weight·resonance, not dead code. +- **TILT analysis (2026-08-18, checkpoint NEGATIVE result)**: warp `0x5406a8 = 0.87·x/(1+x/K)`, + `K=exp(2.0723267)≈7.942`, `x=f/2000`, applied by FUN_180529fe0 as `mask *= 0x5406a8` (float, thunk 008700) + BEFORE the FFT-conv. warp: 500→0.211, 1000→0.409, 2000→0.773 (lin ratio 2000/500=3.67). + **BUT warp ≠ empirical tilt (ratio 1.27)**: `C(dB2000)=depth·warp(2000)·LUT_max = 0.864·0.772·0.667=0.445`, + yet red2000=15.2 dB needs `C=0.826` ⇒ warp alone CANNOT produce deep-2000 (dual_b1q). Empirical tilt is the + envelope of the BLEND term (0x5407c8 += weights·res + freq-axis 0x540698) + FFT-conv shaping, NOT warp — + simple tilt→warp substitution in model_lut.py is INVALID. Nothing else decoded yet closes dual_b1q. +- **FUN_180563ce0 decoded (2026-08-18)**: INIT of per-bin IIR level-trackers (0x156=342 bins, order=3.0f + 0x40400000, coeff A/B from 0x24c4780/4790, strided fill; third pass +0xffd0 fills 0x40e00000=7.0f). + Not the update loop. Full disasm: `/tmp/opencode/f_563ce0.dis`. +- **Level-path map (2026-08-18, NOTES_LEVEL.md)**: 0x563440 (LUT curve +0x188, gamma, 6 band-slots, combine + →+0x2198, LUT pairs +0x98 at 0x3ff pts) + 0x56e3e0 twin-mask factory (N=1024, DOUBLE, LUT-ramp input) + + 0x563ce0 IIR init. 0x563440 NOT in decomp_funs.txt (search via /tmp/opencode/f_563440.dis + level_notes.md). +- **DIAGNOSTIC BRIDGE DONE (2026-08-18) — model_fir.py**: реальная цепочка из FUN_180529fe0 + воспроизводит 36 точек до rmse=0.236 dB БЕЗ эмпирического tilt: + `C(f) = g·LUT(log10(L0/res(f))) + w·warp(f)^a`, g=1.221, w=0.358, a=3.143. + - dual500-константа = res_band(500;fc=500)=0.117 Q-НЕЗАВИСИМ, не tilt; + - dual2000-глубина = АДДИТИВНЫЙ терм `w·warp^3.14` (на 1000 Гц вклад ≤0.021 — иначе t1k + рушится; на 2000 = 0.149) — это аккумулятор `0x5407c8 += weights·res + mask` (шаг 5), + НЕ мультипликация warp·LUT (та проваливается >10 dB); + - warp^~3.14 ≈ π — подозрение на кратный каскад (0x540698 freq-axis, ∏0x540688, двойной FFT); + - остаток 0.7 dB при Q=0.1 = форма LUT-колена 0.574, не закрывается per-bin → нужен FFT-уровень. +- **FFT-CONV FULLY MAPPED (2026-08-18, NOTES_LEVEL.md §2026-08-18c, /tmp/opencode/f_52b570.dis)**: + 0x535a70 = dispatcher to base-CRT FFT (0x140a10/0x140a70); 0x540548/0x540550/0x540598 = FFT-plan + tables (plan 0x540530, built by 0x52dc30), NOT windows. Per-band loop 0x52b550-0x52b8b5: + mask→fwdFFT→invFFT→fill low/high (xmm13/xmm9 consts)→inv→cplx-op→fwd→`FIR *= 0x540658`→fill→inv→ + `FIR[0]=1,FIR[1]=0`→`*=0x540888`→copy out. + **freq-axis 0x540698**: OFFLINE = constant scalar (NOT exp-formula — exp/rand = online dither). + **Window 0x540658**: only ONE direct ref in dump (read at 0x52b771), written only indirectly — + STATICALLY INVISIBLE → the only plausible source of warp^3.14/LUT-knee. Need runtime capture or + full-pipeline empirical closure. + +## 6. PHASE 5 PLAN (updated 2026-08-18 — bridge done, tilt replaced, FFT mined out) +Decision (user): bridge FIRST. **DONE — bridge result above in §5.** +Status: step 2 DONE, step 3 PARTIAL (static analysis exhausted). +1. (15 min) Proof-of-state above; confirm twin + mdl files complete; repo at `60bf3a2`. +2. **Replace empirical tilt** in model_lut.py with `g·LUT(xv) + w·warp(f)^a` — **DONE**, rmse 0.236 dB + reproduced, committed `c8f97e4`. Optional refinement fit (2 LUT-knots) reaches 0.167 but is overfit. +3. **Close the 0.7-dB gap**: FFT-conv loop mapped + freq-axis cleared (offline const) — **DONE/PARTIAL**, + committed `60bf3a2`. Remaining unknown = 0x540658 window content (static-invisible). Options: + (a) runtime capture of 0x540658, (b) skip and close empirically in full-pipeline step 5. +4. Runtime values: per-bin IIR level trackers (0x563ce0), 0x540880/884, `[0x1a0]/[0x1ac]`, cell count N. +5. Full pipeline + render diff vs `/home/m/soothe-bt/*.wav`: dB parity → byte parity. + +NEXT SESSION START (recommendation): skip deeper FFT mining; go straight to step 5 full pipeline +(mask-chain already in model_lut.py B.12 at 0.236; freq-axis const; FFT-conv reduces to a per-bin +smoothing + 0x540658 shaping that can be fit/absorbed). Runtime capture of 0x540658 only if step 5 +stalls on the Q=0.1 knee. + +### STEP 5 STARTED (2026-08-18) — dB-parity harness DONE (commit f96bb42) +- `/home/m/re-tools/render_parity.py`: measures steady-state per-tone reduction DIRECTLY on the + reference wavs (Goertzel tone-amp over late steady window; handles 16/24-bit, mono/stereo). + NOTE: Goertzel must use the SEQUENTIAL update form (s2=s1;s1=s0;s0=v+cw*s1-s2) — the tuple-unpack + form is numerically unstable (overflows ~1500 steps). +- Validation: measured dual Q-scan == stored dataset (dual.wav is 4s mono 16-bit; renders 6s stereo 24-bit). +- **dB-PARITY RESULT (canonical B.12, 36 pts, real wavs): TOTAL rmse=0.268 dB** + (dual500 0.130, dual2000 0.387, t1kq 0.320, t1k 0.076). + residual structure: dual2000 -0.80@Q0.1..+0.36@Q10 (LUT-knee), t1kq +0.36..+0.26 (level region), + t1k ±0.1 (good). Refit (g,w,a)=(1.224,0.379,3.405)→0.264; +2 LUT-knots→0.203 (plateau). +- CONCLUSION: B.12 form plateau ~0.20-0.27 dB vs real renders; residual is structural (0x540658 + FFT shaping + level-curve region), matches plan expectation. Re-measured-on-wav numbers differ + slightly from the stored model_lut.py dataset (t1kq ~+0.3 dB) — different steady-window method. +- Open: full PIPELINE frame-render (STFT + per-frame mask + time constants) is the remaining big + lift for byte-parity; dB-parity milestone achieved at 0.268 dB. + +Candidates that may consume Phase 5 time: source of sens≈24.65 (host ×2 over XML 12.0 — not found in dump), +IAT\*0x181bab370 outside dump (non-blocking), final z¹ phase proof, the 0x563440 LUT curve (+0x188) values. + +## 7. FILES LAYOUT +- `/home/m/re-tools/dsp/` — twin.{hpp,cpp}, twin_check.cpp, CMakeLists.txt, build/ +- `/home/m/re-tools/model_lut.py` — model **B.12** (Q=xmlq, gain=10^(sens/20), depth 0.8639736175537109, + `C=g·LUT(xv)+w·warp^a`, g=1.221/w=0.358/a=3.143) — 0.236 dB bridge model; PCHIP LUT nodes frozen +- `/home/m/re-tools/model_fir.py` — bridge canonical source (rmse 0.236, committed b1066f3) +- `/home/m/re-tools/framed_render.py` — full frame-render pilot (2026-08-19 params; modes dual|al) +- `/home/m/re-tools/rwin_A0/A1/B0/C0.npy`, `r_freqaxis.npy`, `rwin_warp.npy` — live-window tables (48k) +- `/home/m/re-tools/rtsnap.py` — live-process page snapshotter; snapshots /tmp/rt{A,B,C,D}.{raw,idx} +- `/home/m/re-tools/handoff/decode_rpp_full.py`, `handoff/rpp_allparams.py` — RPP b64-XML full decoder + (trim to len%4==0, ` **⚠️ ИСТОРИЧЕСКИЙ ДОКУМЕНТ (поведенческая модель 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`/`reaper` → `pkill -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 `` + 54 ``; внутри — UI-state без DSP. + +## Breakthrough (исправлено в этой сессии) +- **Формат-адаптивность PARAM**: плагин применяет `` ТОЛЬКО если строка сериализована в том же формате, что оригинал: + - 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=1−e^(−1/(T_a·fs)), α_release=1−e^(−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·… — линейный кроссфейд: (100−mix)/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 110Hz–14kHz с 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·(1−e^−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(1−e^−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 --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 dBFS` ⇒ `5.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.37–0.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 ≈ 1024–2048 (бины 21–43Hz), 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, хоп ~512–1024; 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`. diff --git a/roadmap.md b/roadmap.md index 073cf00..0a7234a 100644 --- a/roadmap.md +++ b/roadmap.md @@ -1,211 +1,64 @@ # Roadmap: bit-exact реверс DSP-ядра oeksound soothe2 (v1.1.2) ## Цель -Воспроизвести DSP-путь soothe2 в виде **собираемого standalone C++** с **побайтово -идентичным** выходом (bit-exact) на верификационных свипах, с пониманием логики -и подписанными функциями. +Воспроизвести 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` (прогон всех свипов, побайтовое сравнение). +`re-tools/dsp/` — `*.cpp/*.h` (классы DSP), `CMakeLists.txt`, `harness.cpp` (WAV16 → рендер → WAV24), `dsp_notes.md` (карта функция↔смысл), `verify_bit_exact.py`. -## Текущий статус (2026-08-21, P4) +## Статус — см. `README.md:13` (единственный источник TOTAL) -> **2026-08-21: bit-exact НЕ достигнут** — честная dB-параллель. Два канона: -> bridge (эмпирическая погона, корпус mean 1.594 dB, comb 10.1) и СТРУКТУРНАЯ цепь -> FUN_180529fe0 на внутренней сетке 48000/4096 (`dsp/build/render48k`, корпус mean -> 2.286 dB, comb **6.12** — уже лучше bridge на мультиполосе, res тоже лучше; хуже на -> однополосных). Разрыв формы кривой редукции: реальная насыщается (C_max≈0.70), -> exp2(−lvl) нет → Шаг 7 захват A/B/γ или статический hunt clamp'а в level-пути. -> Путь к байтам — `BITEXACT_PLAN.md` (8 шагов, пере-скоуп Шага 2: acc не имеет -> однополосного консюмера; FUN_1805316e0 = writer коэффициентов, НЕ комбинер). -> Полный журнал — `handoff/NOTES_LEVEL.md` (апдейты 20j–21d). +> **Итог 2026-08-28 (24mm14):** применение декодировано до формул `mask=10^(−cut_D/20)`, `cut_D=α·ln1p(lvl/β)+c` (rms ≤0.016, 3 семейства), слой STFT без синтез-окна, dual-корпус 0.193 dB (канон 3.264). Гейт = bit-exact. Остаток: 3 шага `BITEXACT_PLAN.md:1` (каскад 9–19, k-маппинг, Δ-правило). Архив B-фаз ниже свёрнут. -> **21d**: закон редукции найден — АФФИННО в dB (маска≈0.8·lvl^−0.354, γ≈0.344 -> decomp); на тонах линейность ±0.13 dB. Но scalar-семейство не закрывает тон+шум -> одновременно (res-тенция) → канон не меняем; следующий рычаг — контент-зависимость -> (att/rel на флюктуациях, combine-консюмер). Инструментарий RT_DUMP_BIN/RT_DUMP_ALL. +
История B-фаз (B.1…P4.3) — свёрнуто, детали → handoff/archive/ + git log -> **2026-08-25 (24j–24kk2): ПРИМЕНЕНИЕ ДЕКОДИРОВАНО ДО ФОРМУЛ.** -> mask=10^(−cut_D/20); cut_D=α·ln1p(lvl/β)+c (пер-контент константы, rms≤0.016); -> слой STFT без синтез-окна; dual-корпус 0.193 (канон 3.264). ГЕЙТ = BIT EXACT. -> Детали: NOTES_LEVEL 24j–24kk2, BLOCKMAP_529fe0 23b–24jj, NEXT_PROMPT.md. +- **Статический декомп ~95%**: twin, generator case8, level-path (0x529fe0/0x563440/0x563a60), mask-apply, IIR-трекеры, FFT-conv (0x535a70), render-loop FUN_18052e260 — `.dis` в `handoff/nls_dasm/`. +- **Структурная цепь** `dsp/framed_model.cpp::process_band_structural` (48k/4096): `scale→LUT(level)→IIR1→IIR2→mirror→blend/exp2→combine→dual-warp→IIR3×2→dry/wet`; live-таблицы `rt_mask_tables`. Корпус: comb 6.12 (bridge 10.15), res 0.44. +- **Численная модель B.10…B.15**: `C=g·LUT(xv)+w·warp^a`, `gain=(1−C)·res^rp`, rp=0.0275·Q^0.2159 (mean 0.175), Pchip LUT (dual 0.027). Канон — `framed_render.py` (исторический). +- **Фазы P1–P5**: twin float-parity 1.27e-5, FFT-conv, leveltrack, LUT, mask-apply — в основном готово; осталось каскад 9–19 + twin/am + Δ. -- **Статический декомп DSP-ядра — закрыт (~95%)**: twin-резонатор, генератор case8, - level-path (0x529fe0/0x563440/0x563a60), mask-apply (FUN_180529fe0 mono-path), IIR-трекеры, - FFT-conv (0x535a70), main render-loop (FUN_18052e260) декодированы; `.dis` в `handoff/nls_dasm/`. -- **Структурная цепь в C++** (`dsp/framed_model.cpp::process_band_structural`, сетка 48k/4096): - `scale→LUT(level-domain)→IIR1→IIR2→mirror→blend/exp2→combine(acc-update)→dual-warp→IIR3 bidirectional×2→dry/wet`; - live-таблицы в `dsp/rt_mask_tables.{hpp,cpp}`, `rt_weights.{hpp,cpp}`. Прогон: - `scripts/corpus_structural.py`. IIR3 = [fwd+bwd]×2 по декомпу (коммит 1a616e1). -- **Результаты структурной цепи (62 кейса)**: comb 6.12 (bridge 10.15), res 0.44 (0.63), - t1kq 0.77 / t1k 2.11 / al 0.99 / dual 3.26 (хуже bridge). Детали: NOTES_LEVEL:21c. -- **Честная тримнутая метрика** (длина выхода = длина входа) — см. AGENTS.md. -- **Цель bit-exact (Phase C) — в работе**: twin-транскрипция в `dsp/` готова и проходит -- **Бит-экзактная C++ транскрипция (Phase C)**: twin (float-parity 1.27e-5, tables_check ALL OK), - FFT-conv (P1.2/P1.3 fftconv.cpp), маска-канон (P1.4/P2), level-tracker (P2, leveltrack.cpp), - LUT (P2.5 levelpath.cpp), mask-apply цепь (P4 framed_model.cpp) — в основном готово. -- **ВАЖНО про метрику**: ранние записи "mean=0.160/0.144" считались на выходе НЕтримнутой - длины (`nfr*HOP+N`) и были артефактом; честный базлайн для scalar `rp=0.0169` = **0.280**. - Все числа в этом файле — на ТРИМНУТОЙ метрике. +### Ключевые находки (итог, свёрнуто) -## Историческая численная модель (B-фазы; канон теперь — C++ FramedDetector) +- Маска аддитивная `C=g·LUT+w·warp^a` (не мультипликация). +- `res_power` `gain=(1−C)·res^rp` — закрывает 500Hz residual. +- `0x530d30` дважды NEGATIVE — тилт не оттуда. +- LUT — гамма-монотонная `FUN_180563440` (Pchip немонотонна). +- Константы: SCALE=1/1024, DB_CONV=8.6859, IIR α=0.1, LCG 0x3cdca. -```python -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`. +## Контракт из manual (`soothe2_ManualFAQ.pdf`) -## Статус по фазам - -- **Фаза 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 1−C) | `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 | ✅ DONE (rtctx_live.json, leveltrack_data.hpp) | - | P2 | mask-accumulator combine kernels (8d60/5a20/3c40) | ✅ DONE (levelpath.cpp combine_sub/acc_add/acc_fma) | - | P2.5 | twin-mask factory (56e3e0) + band LUT apply (563a60) | ✅ DONE (levelpath.cpp) | - | P3 | бит-экзактная base-CRT FFT | ⚠️ partial: архитектура размечена, vlog(ln) транскрибирован; butterfly+shuffle+plan-gen осталось | - | P4 | render-loop 52e260 + связка mask→FFT-conv + full pipeline | ✅ **существенно продвинут (2026-08-21)**: mono-path FUN_180529fe0 декодирован и транскрибирован на внутренней сетке 48k/4096 (`render48k`, LUT в level-домене, IIR3 bidirectional по декомпу); полный структурный корпус: comb **6.12** (bridge 10.15), res 0.44; однополосные хуже bridge — нужен шаг насыщения кривой редукции. Combine-семантика декодирована до thunk'ов; acc не имеет однополосного консюмера (пере-скоуп). Остаток: точка потребления acc/f6f8, PRNG-пролог, бит-экзактный exp2, FFT-conv (P3), SR-хвосты | - | P5 | стерео M8 (link/balance/LR-vs-MS) + multiband combine 5316e0 | TODO (в скоупе, не начат) | - - Обновлённая оценка (2026-08-21): структурно ~80% готово — mask-apply цепь FUN_180529fe0 - декодирована (scale→LUT→IIR1/2→exp2→combine→warp→IIR3-bidi→dry/wet→FFT-conv) и перенесена - в C++ на внутренней сетке, live-таблицы (IIR A/B, warp, band768, PRNG LUT) извлечены, - полный корпус гоняется. Осталось: (а) полная бит-экзактная FFT (butterfly+shuffle+plan-gen - — недели), (б) насыщение кривой редукции (Шаг 7 захват A/B/γ или clamp-hunt в level-пути), - (в) точка потребления combine acc/f6f8, (г) PRNG-пролог + FFT-conv + бит-экзактный exp2, - (д) стерео M8 (P5). - -## Ключевые находки (итог) - -- **Маска аддитивная**: `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 = (1−C)·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 = 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`, `DigitalFilter` мультитипный | -| M11 | Кривая = inverse EQ, сумма band-кривых = белая (стр.17) | `red(f)=base+ΣW_i·H_i` | -| M12 | Sidechain; Input trim — скрытый предусилитель анализа (стр.26) | отдельный вход/гейн анализа | +| M1 | Soft/Hard меняет всё | mode = препроцессор детектора | +| M2 | Depth ±18, до 60 dB | depthcurve-LUT, clip 60 dB | +| M5 | Attack/Release частотно-зависимы | Env — частотный масштаб | +| M8 | Stereo link/balance/L-R vs M/S | детектор на sum | +| M10 | Банды: 2 cut + 4 general | `FilterGraph` | -## Топология пайплайна (Приложение A, стр.30 — `pipeline_ocr.txt`) +## Топология пайплайна (Приложение A, стр.30) ``` - 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 +main/sidechain → input trim → analysis (depth/sharpness/selectivity; EQ per-band) +→ stereo apply + balance → ms encode? → processing (M/S) → 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)=|2B/A|(fc,Q,gain)`; freq-path 530850 + warp, level-path 56e3e0 | -| 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=(1−C)·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 | -| P4.1 | 08-20 | Полный декомп mono-path FUN_180529fe0; real mask-цепь в C++ (IIR leaky + acc + live LUT); t1kq err 0.5 dB; `framed_test` | -| P4.2 | 08-20 | Live-таблицы извлечены (`rt_mask_tables` IIR A/B, warp, band768, PRNG LUT); bigkernel 0x26b820=exp2 семантика залочена (`exp2(-mask)·blend`) | -| P4.3 | 08-20 | Dual-warp шаг 6 транскрибирован (mask·=band768, ·=warp) → comb 500/3000 починены; t1kq −0.43 dB; comb ≤6.4 dB | +| B.1–B.6 | 08-17 | C++ каркас, twin, dispatch, band-структура | +| B.10–B.15 | 08-18…19 | Модели C/LUT, bridge `C=g·LUT+w·warp^a` (0.236), res_power, dB-parity 0.268 | +| P4.1–P4.3 | 08-20 | Декомп FUN_180529fe0, live-таблицы, dual-warp | -## Риски - -- **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/NOTES_LEVEL_INDEX.md` + `git log`. ## Где лежат детали -- `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` — полная журнальная хронология с номерами коммитов. \ No newline at end of file +- `README.md:13` — актуальный статус (TOTAL). +- `handoff/NOTES_LEVEL.md` (живая голова) + `handoff/archive/NOTES_LEVEL_2026-08-18_2026-08-23.md` — журнал. +- `handoff/BLOCKMAP_529fe0.md` — карта метода 9–19. +- `handoff/NOTES_TWIN.md` — twin краткая справка. +- `handoff/NOTES_CAPTURE.md` — registry heartbeat. +- `notes_giant_fft.md` — FFT/twiddle. diff --git a/summary.md b/summary.md index bcb50b4..7a202b9 100644 --- a/summary.md +++ b/summary.md @@ -1,150 +1,7 @@ -## Objective -- Реверс-DSP oeksound soothe2 v1.1.2 VST3 → математическая модель (STFT, детектор резонансов, нотич-синтез). Текущая фаза — поведенческий параметр-свип через рендер Reaper RPP + статический декомпил (без декрипты бинарника пока). +# summary.md — перенесён в архив -> **⚠️ ИСТОРИЧЕСКИЙ ДОКУМЕНТ (поведенческая модель v4, sim.py, ок. 2026-08-17)**. -> Актуальный канон (bit-exact, P4) — `README.md`, `AGENTS.md`, `handoff/NOTES_LEVEL.md`. -> Здесь — ранняя поведенческая фаза реверса, сохранена как справочник. +> **Исторический документ (поведенческая модель v4, `sim.py`, 2026-08-17).** +> Актуальный канон — `README.md:13` + `handoff/NOTES_LEVEL_INDEX.md`. +> Полный текст сохранён: [`handoff/archive/summary_v4_2026-08-17.md`](handoff/archive/summary_v4_2026-08-17.md) (150 строк, LUT amount, Q-зонд, burst-триалы). -## 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`/`reaper` → `pkill -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 `` + 54 ``; внутри — UI-state без DSP. - -## Breakthrough (исправлено в этой сессии) -- **Формат-адаптивность PARAM**: плагин применяет `` ТОЛЬКО если строка сериализована в том же формате, что оригинал: - - 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=1−e^(−1/(T_a·fs)), α_release=1−e^(−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·… — линейный кроссфейд: (100−mix)/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 110Hz–14kHz с 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·(1−e^−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(1−e^−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 --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 dBFS` ⇒ `5.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.37–0.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 ≈ 1024–2048 (бины 21–43Hz), 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, хоп ~512–1024; 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`. +Кратко: `out = x − amount·bp(fc,Q)` с LUT `A(depth)·S(sens)·H(sharp)·M(mode)`, `Q=3+0.36·sel`, `env` att/rel, `mix` кроссфейд. RMSE settled ≤0.05 dB. Детали — в архиве.