feat: consumer identified (th_b3c0), scan3.py, RT_FIRCONV/RT_FIRPOWER

- th_b3c0 (0x18000b3c0) = pure complex multiply FIR × audio in freq-domain
- scan3.py: pre-scan approach finds ctx in 1.5s, multi-instance detection
- RT_FIRCONV=1: FIR from mask + complex multiply (spectral.cpp)
- RT_FIRPOWER=1: power-law mask from raw spectrum (framed_model.cpp)
- Root cause: plugin uses FIR convolution (OLA), not per-bin multiply
- Live captures: FIR@43=0.524, mask@43=0.510, final gain=0.305
- Best result: RT_LUT_OFF gives cut@500=-8.18 dB (ref -10.32)
- NOTES_LEVEL 24e/24f/24g appended
This commit is contained in:
2026-08-24 13:52:52 +03:00
parent 03777fdaee
commit 2c99a4fc96
5 changed files with 451 additions and 14 deletions
+130
View File
@@ -2584,3 +2584,133 @@ R-кривой в куче).
(529fe0) у СОСЕДНЕГО экземпляра, или decode 52e9b0-вызовителя (14191 в
decomp_funs2 — это 52ba20 setter); glue где-то в 52exx52fxx.
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 52b55052b8bb + 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): строка 52b83252b85e, 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 получится?