85 lines
3.3 KiB
Python
85 lines
3.3 KiB
Python
#!/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')
|