B.11: LUT-кривая уровень->маска извлечена (rmse 0.072 dB, 36 точек, 3 уровня): коллапс dual+t1kq+t1k на ОДНУ кривую x=log10(L0/res); степенной закон C~L0^p НЕ работает на 0dB (p_eff 0.19 vs 0.085) - LUT насыщающая gamma из FUN_180563440 (param_1+0x188); форма резонанса стабильна; model_lut.py канонический + roadmap B.11

This commit is contained in:
2026-08-18 13:02:56 +03:00
parent 9abecb60d5
commit 32a63955f8
13 changed files with 1021 additions and 0 deletions
+108
View File
@@ -0,0 +1,108 @@
#!/usr/bin/env python3
"""model_lut.py — КАНОНИЧЕСКАЯ модель B.11 (2026-08-18).
Единая непараметрическая LUT-кривая "уровень -> маска" для ВСЕХ уровней входа.
МОДЕЛЬ (verify, rmse=0.072 dB на 36 точках — dual_b1q 22 + t1kq fc-скан 7
+ t1k fc-скан 7 при 3 уровнях входа: -7.14 / -18.06 / 0 dBFS):
red(f) = -20*log10(1 - C(f))
C(f) = depth * tilt(f) * LUT(log10(L0 / res(f)))
res(f) = |2·B/A|(f; fc, Q, gain) case8/m2c (freq-path, близнец 0x180535880)
LUT = PCHIP-узлы (x=log10(L0/res), y=нормированная маска), таблица ниже
depth = 0.8639736175537109
L0 = линейный уровень входа (dual 10^(-7.142/20), t1kq 10^(-18.063/20), t1k 1.0)
tilt(f)= 1 - w(f) per-bin level-вес FUN_180530d30 (500/1000/2000: 1.414/1.454/1.795)
СВОЙСТВА (открытие B.11):
- Степенной закон C = D0·(L0/res)^p (B.10, p=0.0847) НЕ описывает высокий уровень
(0 dBFS): наклон d(ln C)/d(dB) падает с уровнем -> LUT компрессивная (насыщается).
- Кривая монотонна, с "коленом" при x≈0.58 (скачок 0.56 -> 0.65) и асимптотами
y->0.44 (низкий уровень) / y->0.67 (высокий уровень).
- Форма резонанса (Q=0.900, gain=4.132, tilt) ОДИНАКОВА для всех уровней;
различие между dual/t1kq/t1k = только положение x = L0/res на LUT-кривой.
- Подтверждает структурную модель level-path: per-bin уровень (0x540678 IIR-трекеры
0x540528..) -> LUT-кривая param_1+0x188 (FUN_180563440: mn+(mx-mn)·x^(1/gamma))
-> маска, применяемая с per-bin весами (0x530d30) в FUN_180529fe0.
"""
import numpy as np
from scipy.interpolate import PchipInterpolator
FS = 44100.0
DEPTH = 0.8639736175537109
QS = [0.1, 0.2, 0.3, 0.5, 0.7, 1.0, 1.5, 2.0, 3.0, 5.0, 10.0]
DUAL = np.array([(10.220, 15.224), (10.219, 13.963), (10.219, 12.947),
(10.219, 11.725), (10.218, 11.119), (10.216, 10.689),
(10.210, 10.412), (10.203, 10.305), (10.182, 10.225),
(10.113, 10.183), (9.822, 10.165)])
FCS = [800.0, 900.0, 950.0, 1000.0, 1050.0, 1100.0, 1200.0]
T1KQ = np.array([7.868, 8.536, 8.726, 8.788, 8.729, 8.575, 8.115])
T1K = np.array([14.548, 15.332, 15.553, 15.626, 15.557, 15.378, 14.840])
L0_DUAL = 10 ** (-7.142 / 20)
L0_T1KQ = 10 ** (-18.063 / 20)
L0_T1K = 1.0
TILT = {500: 1.414, 1000: 1.454, 2000: 1.795}
Q_FIT, GAIN_FIT = 0.900, 4.132
# узлы LUT (x=log10(L0/res), y=норм. маска) — фит по 36 точкам, rmse=0.072
LUT_KNOTS_X = np.array([-0.750, -0.500, -0.250, 0.000, 0.250, 0.500, 0.574, 0.610, 0.750, 1.000])
LUT_KNOTS_Y = np.array([0.4402, 0.4552, 0.4813, 0.5072, 0.5329, 0.5332, 0.5645, 0.6471, 0.6562, 0.6670])
def lut(x):
p = PchipInterpolator(LUT_KNOTS_X, LUT_KNOTS_Y)
v = p(np.asarray(x))
return np.clip(v, LUT_KNOTS_Y[0], LUT_KNOTS_Y[-1])
def res_at(ft, fc, Q, gain):
w0 = fc * 2 * np.pi / FS
c, s = np.cos(w0), np.sin(w0)
p = (s * 0.5) / Q
a, a2 = p * gain, p / gain
A = [a + 1, -2 * c, 1 - a]
B = [a2 + 1, -2 * c, 1 - a2]
w = 2 * np.pi * ft / FS
z = np.exp(-1j * w)
return np.abs(2.0 * (B[0] + B[1] * z + B[2] * z * z) /
(A[0] + A[1] * z + A[2] * z * z))
def red(f_tone, fc, Q, gain, L0, tilt):
r = res_at(f_tone, fc, Q, gain)
C = DEPTH * tilt * lut(np.log10(L0 / r))
return -20 * np.log10(max(1 - C, 1e-9))
def run():
preds, meas = [], []
print('--- dual_b1q (tones 500+2000, band fc=500, -7.142 dBFS) ---')
for i, q in enumerate(QS):
for f, m in ((500.0, DUAL[i, 0]), (2000.0, DUAL[i, 1])):
p = red(f, 500.0, q, GAIN_FIT, L0_DUAL, TILT[f])
preds.append(p); meas.append(m)
print(f'q={q:5.1f} 500 {DUAL[i,0]:7.3f}/{preds[2*i]:7.3f} '
f'2000 {DUAL[i,1]:7.3f}/{preds[2*i+1]:7.3f}')
print('--- t1kq fc-скан (-18.06 dBFS, q=0.9999978) ---')
for i, fc in enumerate(FCS):
p = red(1000, fc, 0.9999978, GAIN_FIT, L0_T1KQ, TILT[1000])
preds.append(p); meas.append(T1KQ[i])
print(f'fc={fc:5.0f} {T1KQ[i]:6.3f}/{p:6.3f} ({p - T1KQ[i]:+.3f})')
print('--- t1k fc-скан (0 dBFS, q=0.9999978) ---')
for i, fc in enumerate(FCS):
p = red(1000, fc, 0.9999978, GAIN_FIT, L0_T1K, TILT[1000])
preds.append(p); meas.append(T1K[i])
print(f'fc={fc:5.0f} {T1K[i]:6.3f}/{p:6.3f} ({p - T1K[i]:+.3f})')
preds = np.array(preds); meas = np.array(meas)
rmse = np.sqrt(np.mean((preds - meas) ** 2))
print(f'\nTOTAL rmse={rmse:.4f} dB (n={len(meas)})')
print(f'dual-only rmse={np.sqrt(np.mean((preds[:22]-meas[:22])**2)):.4f}')
print(f't1kq rmse={np.sqrt(np.mean((preds[22:29]-meas[22:29])**2)):.4f}')
print(f't1k rmse={np.sqrt(np.mean((preds[29:]-meas[29:])**2)):.4f}')
if __name__ == '__main__':
run()