res=|2B/A| is minimal at band centre (not maximal). Model uses xv=log10(A_k/res_k) => level = am / res. Old am*res inverted the fc-response (cut more off-center). Fixed; fc-scan (tone1kq, band fc 800..1200 via t1kq_only1_<fc>) now tracks the reference: scale=42 mean|err| 2.46 dB, intact shape (deepest at fc==tone). Was flat+inverted before. Remaining under-cut off-center -> FFT-conv smoothing/gaps.
926 lines
68 KiB
Markdown
926 lines
68 KiB
Markdown
## ============ 2026-08-18 (LEVEL-PATH STRUCTURE DECODED) ============
|
||
|
||
## Две 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-маска, не аудио.
|
||
|
||
## 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 "<SOOTHE2STATE><PARAM id= value=/>" — декодер:
|
||
## 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 `<?xml`@92, regex PARAM; only band1
|
||
active (fc=1000, Q=0.9999978, sens=12, mode=1), depth=0.864, input = lvl_tone_lv{3,6,9,12,18,24}.wav.
|
||
Bug fixed: dual_b1q_*.rpp had 6 band entries (band0/2-5 off) - only band1 matters, model was right.
|
||
2. Measured steady reduction at 1000Hz (tone amplitude ratio m=amp_out/amp_in, sin/cos correlation metric):
|
||
lv3: m=0.2080 (-13.64) lv6: 0.2387 (-12.44) lv9: 0.2730 (-11.28) lv12: 0.3114 (-10.13)
|
||
lv18: 0.3996 (-7.97) lv24: 0.5006 (-6.01) -> 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_<fc> = 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).
|