116 lines
5.4 KiB
Python
116 lines
5.4 KiB
Python
#!/usr/bin/env python3
|
|
"""model_lut.py — КАНОНИЧЕСКАЯ модель B.12 (2026-08-18).
|
|
|
|
Диагностический мост (model_fir.py) ЗАМЕНИЛ эмпирический tilt реальной цепочкой
|
|
FUN_180529fe0: C(f) = g·LUT(xv) + w·warp(f)^a (АДДИТИВНЫЙ аккумулятор 0x5407c8).
|
|
|
|
МОДЕЛЬ (rmse=0.236 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) = g * LUT(log10(L0 / res(f))) + w * warp(f)^a
|
|
res(f) = |2·B/A|(f; fc, Q, gain) case8/m2c (freq-path, близнец 0x180535880)
|
|
warp(f)= 0.87·K·x/(K+x), K=exp(2.0723), x=f/2000 (0x5406a8, FUN_180530850)
|
|
LUT = PCHIP-узлы B.11 (заморожены), таблица ниже
|
|
g=1.221 w=0.358 a=3.143 (фит, bounds [0.9..1.5],[0.2..1.2],[2..6])
|
|
L0 = линейный уровень входа (dual 10^(-7.142/20), t1kq 10^(-18.063/20), t1k 1.0)
|
|
|
|
СВОЙСТВА (открытие B.12, bridge):
|
|
- C500-константа dual = res_band(500; fc=500)=0.117 Q-НЕЗАВИСИМ (твин в центре полосы)
|
|
-> крас500 const по Q БЕЗ tilt; C500 = g·LUT(0.574)·warp^a(500≈0) = 0.692.
|
|
- Глубина dual2000 = АДДИТИВНЫЙ терм w·warp(f)^3.14: на 2000 Гц +0.149, на 1000 <=0.02
|
|
(иначе t1k/t1kq рушатся) — это шаг 5 декомпа `0x5407c8 += веса·res + mask`, НЕ
|
|
мультипликация warp·LUT (та проваливается >10 dB).
|
|
- xv(2000)=log10(L0/res): 0.308(Q=0.1)..-0.656(Q=10) -> C2000-Q-зависимость совпадает.
|
|
- Остаток 0.7 dB (Q=0.1) = тонкая форма LUT-колена 0.574; warp^3.14~pi => подозрение
|
|
на кратный каскад (freq-axis 0x540698 / ∏0x540688 / двойной FFT-проход 0x535a70).
|
|
- Структура level-path подтверждена: per-bin уровень (0x540678 IIR) -> кривая +0x188
|
|
(FUN_180563440) -> аддитивный аккумулятор 0x5407c8 -> warp -> FFT-conv.
|
|
"""
|
|
import numpy as np
|
|
from scipy.interpolate import PchipInterpolator
|
|
|
|
FS = 44100.0
|
|
GAIN_FIT = 4.132
|
|
|
|
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
|
|
|
|
# bridge-фит (model_fir.py): C = g*LUT(xv) + w*warp^a
|
|
G_FIT, W_FIT, A_FIT = 1.221, 0.358, 3.143
|
|
|
|
# узлы LUT (x=log10(L0/res), y=норм. маска) — из B.11, заморожены
|
|
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])
|
|
_LUT = PchipInterpolator(LUT_KNOTS_X, LUT_KNOTS_Y)
|
|
|
|
|
|
def lut(x):
|
|
v = _LUT(np.asarray(x))
|
|
return np.clip(v, LUT_KNOTS_Y[0], LUT_KNOTS_Y[-1])
|
|
|
|
|
|
def warp(f):
|
|
x = np.asarray(f) / 2000.0
|
|
return 0.87 * 7.942 * x / (7.942 + x)
|
|
|
|
|
|
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, g=G_FIT, w=W_FIT, a=A_FIT):
|
|
r = res_at(f_tone, fc, Q, gain)
|
|
C = g * lut(np.log10(L0 / r)) + w * warp(f_tone) ** a
|
|
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)
|
|
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)
|
|
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)
|
|
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)}) [B.11 эмпирик: 0.072; warp-одиночный: >10]')
|
|
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()
|