B.7: XML mode=1 исполняет case8 (Q=+0x80c, fvar=sens/20), не case1; Q_eff-маппинг
This commit is contained in:
@@ -0,0 +1,84 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""bandshape.py — эмпирическая форма полосы детектора из рендеров soothe2.
|
||||||
|
|
||||||
|
Тон 1 кГц (tone1kq.wav / tone1k.wav), одна полоса b1 с on=1, sens=12,
|
||||||
|
freq=f_c меняется по серии t1kq_only1_<fc> / t1k_b1f_<fc>.
|
||||||
|
Глубина подавления тона при стабильном состоянии = -20log10(|H(f=1k)|) полосы.
|
||||||
|
Кривая D(f_c) = форма |H(f_c)| (Q-ширина), нормируется на D(1000).
|
||||||
|
|
||||||
|
Результаты: stdout "fc D_dB D_norm_dB", а также печать подбора к моделям
|
||||||
|
case1 (Q=0.707) и case8 (Q=0.9999978542327881) с хелпером cfa.
|
||||||
|
"""
|
||||||
|
import numpy as np, wave, glob, os, sys, re
|
||||||
|
|
||||||
|
SR = 44100
|
||||||
|
|
||||||
|
def read_wav(path):
|
||||||
|
w = wave.open(path, 'rb')
|
||||||
|
sw, nc, n = w.getsampwidth(), w.getnchannels(), w.getnframes()
|
||||||
|
d = np.frombuffer(w.readframes(n), dtype=np.uint8).reshape(n, nc, sw)
|
||||||
|
w.close()
|
||||||
|
ch = d[:, 0, :]
|
||||||
|
if sw == 3:
|
||||||
|
v = (ch[:, 0].astype(np.int64) | (ch[:, 1].astype(np.int64) << 8) |
|
||||||
|
(ch[:, 2].astype(np.int64) << 16))
|
||||||
|
v = (v ^ (1 << 23)) - (1 << 23)
|
||||||
|
return v.astype(np.float64) / (1 << 23)
|
||||||
|
v = (ch[:, 0].astype(np.int64) | (ch[:, 1].astype(np.int64) << 8))
|
||||||
|
v = (v ^ (1 << 15)) - (1 << 15)
|
||||||
|
return v.astype(np.float64) / (1 << 15)
|
||||||
|
|
||||||
|
def rms_db(x, t0, t1):
|
||||||
|
s0, s1 = int(t0 * SR), int(t1 * SR)
|
||||||
|
seg = x[s0:s1]
|
||||||
|
if seg.size == 0: return -999.0
|
||||||
|
return 20.0 * np.log10(np.sqrt(np.mean(seg ** 2)) + 1e-12)
|
||||||
|
|
||||||
|
def band1_freq(rpp):
|
||||||
|
data = open(rpp, 'rb').read()
|
||||||
|
s = data.decode('utf-8', 'replace')
|
||||||
|
i = s.find('VST3: soothe2')
|
||||||
|
line = s[i:].split('\n', 1)[1]
|
||||||
|
j = line.find('\n >')
|
||||||
|
import base64
|
||||||
|
b = re.sub(r'[^A-Za-z0-9+/=]', '', line[:j])
|
||||||
|
d = base64.b64decode(b)
|
||||||
|
for m in re.finditer(r'<PARAM id="([^"]+)" value="([^"]+)"', d.decode('utf-8', 'replace')):
|
||||||
|
if m.group(1) == 'band1 freq':
|
||||||
|
return float(m.group(2))
|
||||||
|
return None
|
||||||
|
|
||||||
|
def measure_series(prefix, dry_path, freqs, t0=1.6, t1=5.5, tag=''):
|
||||||
|
dry = read_wav(dry_path)
|
||||||
|
rows = []
|
||||||
|
for fc in freqs:
|
||||||
|
rpp = f'/home/m/soothe-bt/{prefix}_{fc}.rpp'
|
||||||
|
wav = f'/home/m/soothe-bt/{prefix}_{fc}.wav'
|
||||||
|
if not (os.path.exists(rpp) and os.path.exists(wav)):
|
||||||
|
continue
|
||||||
|
fcr = band1_freq(rpp)
|
||||||
|
fx = read_wav(wav)
|
||||||
|
n = min(dry.size, fx.size)
|
||||||
|
red = rms_db(dry[:n], t0, t1) - rms_db(fx[:n], t0, t1)
|
||||||
|
rows.append((fcr if fcr else fc, red))
|
||||||
|
rows.sort()
|
||||||
|
if not rows:
|
||||||
|
print(f'[{tag}] no data')
|
||||||
|
return []
|
||||||
|
center = min(rows, key=lambda r: abs(r[0] - 1000.0))
|
||||||
|
for fc, red in rows:
|
||||||
|
print(f'[{tag}] fc={fc:10.3f} D={red:7.3f} dB norm={red-center[1]:7.3f} dB')
|
||||||
|
return rows
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
measure_series('t1kq_only1',
|
||||||
|
'/home/m/soothe-bt/tone1kq.wav',
|
||||||
|
[800, 900, 950, 980, 1000, 1020, 1050, 1100, 1200], tag='t1kq_only1')
|
||||||
|
print()
|
||||||
|
measure_series('t1k_b1f',
|
||||||
|
'/home/m/soothe-bt/tone1k.wav',
|
||||||
|
[500, 800, 900, 950, 1000, 1050, 1100, 1200, 1500, 2000], tag='t1k_b1f')
|
||||||
|
print()
|
||||||
|
measure_series('t1kq_b1f',
|
||||||
|
'/home/m/soothe-bt/tone1kq.wav',
|
||||||
|
[500, 800, 900, 950, 1000, 1050, 1100, 1200, 1500, 2000], tag='t1kq_b1f')
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""fit_bandshape.py — подбор модели формы полосы к измеренной кривой.
|
||||||
|
|
||||||
|
Модели (из декомпиляции, /tmp/det/detector_decoded.md):
|
||||||
|
case1 (mode=1 bell): FUN_1805343e0(fs, fc, Q=0.707)
|
||||||
|
w = 1/FUN_181a14cfa(fc*pi/fs); k = (1/Q)*w; w2=w*w; a=1/(k+1+w2)
|
||||||
|
A=[a,2a,a]; B=[1,(1-w2)*2a,(1-k+w2)*a]
|
||||||
|
case8 (RBJ bell+gain): FUN_180533ec0(fs, fc, Q=0.9999978542327881, gain=10^(sens/20))
|
||||||
|
w0=fc*2pi/fs; c=cos(w0); s=sin(w0); p=(c*0.5)/Q
|
||||||
|
alpha=p*gain; alpha2=p/gain
|
||||||
|
A=[alpha+1, s*(-0.5), 1-alpha]; B=[alpha2+1, s*(-0.5), 1-alpha2]
|
||||||
|
Передаточная функция H(f) = |(A0+A1 z^-1+A2 z^-2)/(B0+B1 z^-1+B2 z^-2)|, z=e^{j2pi f/fs}.
|
||||||
|
Кривая подавления тона f_tone при полосе на fc: D(fc) = -20log10(|H(f_tone)|), норм.
|
||||||
|
"""
|
||||||
|
import numpy as np
|
||||||
|
from bandshape import measure_series
|
||||||
|
|
||||||
|
FS = 44100.0
|
||||||
|
|
||||||
|
def cfa_candidates(x):
|
||||||
|
return {
|
||||||
|
'sin': np.sin(x),
|
||||||
|
'sqrt': np.sqrt(x),
|
||||||
|
'tan': np.tan(x),
|
||||||
|
'asin': np.arcsin(np.clip(x, -1, 1)),
|
||||||
|
'csc': 1.0/np.sin(x),
|
||||||
|
'cot': 1.0/np.tan(x),
|
||||||
|
'none': x,
|
||||||
|
}
|
||||||
|
|
||||||
|
def cfa_fn(name):
|
||||||
|
return cfa_candidates(1.0)[name].__class__ if False else {
|
||||||
|
'sin': np.sin, 'sqrt': np.sqrt, 'tan': np.tan,
|
||||||
|
'asin': lambda x: np.arcsin(np.clip(x, -1, 1)),
|
||||||
|
'csc': lambda x: 1.0/np.sin(x),
|
||||||
|
'cot': lambda x: 1.0/np.tan(x),
|
||||||
|
'none': lambda x: x,
|
||||||
|
}[name]
|
||||||
|
|
||||||
|
def h_mag(A, B, f, fs=FS, zsign=-1.0):
|
||||||
|
z = np.exp(zsign*2j*np.pi*f/fs)
|
||||||
|
num = B[0] + B[1]*z + B[2]*z**2
|
||||||
|
den = A[0] + A[1]*z + A[2]*z**2
|
||||||
|
return np.abs(2.0*num/den)
|
||||||
|
|
||||||
|
def model_case1(fc, f_tone, Q, cfa, fs=FS):
|
||||||
|
x = fc*np.pi/fs
|
||||||
|
w = 1.0/cfa(x)
|
||||||
|
k = (1.0/Q)*w
|
||||||
|
w2 = w*w
|
||||||
|
a = 1.0/(k+1.0+w2)
|
||||||
|
A = [a, 2*a, a]
|
||||||
|
B = [1.0, (1.0-w2)*2*a, (1.0-k+w2)*a]
|
||||||
|
return h_mag(A, B, f_tone, fs)
|
||||||
|
|
||||||
|
def model_case8(fc, f_tone, Q, gain, fs=FS):
|
||||||
|
w0 = fc*2*np.pi/fs
|
||||||
|
c, s = np.cos(w0), np.sin(w0)
|
||||||
|
p = (c*0.5)/Q
|
||||||
|
alpha = p*gain
|
||||||
|
alpha2 = p/gain
|
||||||
|
A = [alpha+1.0, s*(-0.5), 1.0-alpha]
|
||||||
|
B = [alpha2+1.0, s*(-0.5), 1.0-alpha2]
|
||||||
|
return h_mag(A, B, f_tone, fs)
|
||||||
|
|
||||||
|
def norm_db(d):
|
||||||
|
d = np.asarray(d, float)
|
||||||
|
return d - d[np.argmax(d)]
|
||||||
|
|
||||||
|
def fit(rows, pred_fn, f_tone=1000.0):
|
||||||
|
fc = np.array([r[0] for r in rows])
|
||||||
|
meas = np.array([r[1] for r in rows])
|
||||||
|
pred = np.array([pred_fn(f, f_tone) for f in fc])
|
||||||
|
pred_db = norm_db(-20*np.log10(np.maximum(pred, 1e-12)))
|
||||||
|
meas_db = norm_db(meas)
|
||||||
|
se = np.mean((pred_db - meas_db)**2)
|
||||||
|
rmse = np.sqrt(se)
|
||||||
|
return rmse, pred_db, meas_db, fc
|
||||||
|
|
||||||
|
def run(rows, tag):
|
||||||
|
print(f'=== {tag} ===')
|
||||||
|
best = []
|
||||||
|
cfa_fns = cfa_fn
|
||||||
|
# case1
|
||||||
|
for qname, q in [('Q0.707', 0.707), ('Q1.0', 1.0), ('Q0.99999', 0.9999978542327881)]:
|
||||||
|
for cfa_name in ['sin', 'sqrt', 'tan', 'asin', 'csc', 'cot', 'none']:
|
||||||
|
def make(cfa_name, q):
|
||||||
|
def f_(fc, ft):
|
||||||
|
return model_case1(fc, ft, q, cfa_fns(cfa_name))
|
||||||
|
return f_
|
||||||
|
rmse, p, m, fc = fit(rows, make(cfa_name, q))
|
||||||
|
best.append((rmse, f'case1 {qname} cfa={cfa_name}', p))
|
||||||
|
# case8
|
||||||
|
for qname, q in [('Q0.99999', 0.9999978542327881), ('Q1.0', 1.0), ('Q0.707', 0.707)]:
|
||||||
|
for gain in [10.0**(12.0/20.0), 1.0]:
|
||||||
|
def make(q, gain):
|
||||||
|
def f_(fc, ft):
|
||||||
|
return model_case8(fc, ft, q, gain)
|
||||||
|
return f_
|
||||||
|
rmse, p, m, fc = fit(rows, make(q, gain))
|
||||||
|
best.append((rmse, f'case8 {qname} gain={gain:.2f}', p))
|
||||||
|
best.sort()
|
||||||
|
print(f'{"model":45s} rmse')
|
||||||
|
for rmse, name, _ in best[:8]:
|
||||||
|
print(f'{name:45s} {rmse:.4f}')
|
||||||
|
print()
|
||||||
|
# best curve vs meas
|
||||||
|
rmse, name, p = best[0]
|
||||||
|
m = np.array([r[1] for r in rows])
|
||||||
|
print(f'best={name} rmse={rmse:.4f}')
|
||||||
|
for i, r in enumerate(rows):
|
||||||
|
print(f' fc={r[0]:8.1f} meas={m[i]:7.3f} model={p[i]:7.3f} dB')
|
||||||
|
return best
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
rows = measure_series('t1kq_only1',
|
||||||
|
'/home/m/soothe-bt/tone1kq.wav',
|
||||||
|
[800, 900, 950, 980, 1000, 1020, 1050, 1100, 1200], tag='t1kq_only1')
|
||||||
|
run(rows, 't1kq_only1')
|
||||||
+14
-3
@@ -334,6 +334,17 @@
|
|||||||
- **Матем. хелперы 0x181a14xxx — IAT-стабы** (`JMP [0x181bab3xx]`), цели 0x6fffff... вне дампа
|
- **Матем. хелперы 0x181a14xxx — IAT-стабы** (`JMP [0x181bab3xx]`), цели 0x6fffff... вне дампа
|
||||||
(грузит плагин). Семантика `FUN_181a14cfa` не подтверждена: кандидаты sin/sqrt/tan —
|
(грузит плагин). Семантика `FUN_181a14cfa` не подтверждена: кандидаты sin/sqrt/tan —
|
||||||
проверяется на харнессе против baseline (corr 0.99475/SNR 19.80/diff −0.065).
|
проверяется на харнессе против baseline (corr 0.99475/SNR 19.80/diff −0.065).
|
||||||
- **ОТКРЫТЫЙ ВОПРОС**: какой case фактически исполняется для band1 при XML mode=1
|
- **ОТВЕТ НА ВОПРОС (c): XML mode=1 реально исполняет case8 (mode=8/default), НЕ case1**.
|
||||||
(case1: Q=0.707 фикс vs case8: Q из +0x80c + gain) — нужен замер. Вопрос (c): factory default
|
Доказательство — серия `dual_b1q_*` (тон 500+2000, band1 на 500, sens=12, mode=1):
|
||||||
band mode=8 (0x18052f306), а XML mode=1 — где маппинг.
|
подавление @2000 (red@500≈10.2 насыщена и q-независима) монотонно убывает с q:
|
||||||
|
q=0.1→+5.00 dB, 0.3→+2.73, 0.5→+1.5, 0.7→+0.9, 1.0→+0.47, 1.5→+0.09, 2.0→−0.04,
|
||||||
|
3.0→−0.06. case1 (фикс Q=0.707) q НЕ читает → исключён. case8 (Q=+0x80c, fvar=0.6=sens/20):
|
||||||
|
delta(2000)=[4.94,1.9,0.85,0.46,0.24,0.11,0.06,0.03] rmse≈0.42, верный знак/направление
|
||||||
|
(завышен при q≥0.5 — модель шире факта). Свободный Q_eff(q): ratio≈0.7–0.95 при q≤1,
|
||||||
|
→ рост при q≥1.5 (Δ<0 — вне bell). Структура `FUN_180533ec0` подтверждена.
|
||||||
|
mode4-путь (Q=0.5 фикс, fvar=(q−0.707)/2+1) даёт ОТРИЦАТЕЛЬНЫЙ тренд (delta 0.57→−5.45) —
|
||||||
|
несовместим. Вывод: case8-структура (A=[1+α,−2s,1−α], B=[1+α2,−2s,1−α2], α=p·f, α2=p/f,
|
||||||
|
p=0.5·cos(w0)/Q, fvar≈sens/20 или sin(sens/20)=0.565) — модель формы полосы; fvar-различение
|
||||||
|
sin vs x — на харнессе (Q_eff ratio 0.96/0.70 vs 1.25/0.75 при q=0.1/1.0).
|
||||||
|
Несоответствие формы t1kq_only (D(800)=−0.92 при Q=0.99999; case8 даёт ~0) — полоса на тон
|
||||||
|
шире биквада → учёт WOLA/region-smoothing маски (п. B.4) + Q_eff-маппинг — следующий шаг.
|
||||||
|
|||||||
Reference in New Issue
Block a user