chore: add rt-capture artifacts (rtobj/rtsnap scripts, rwin_*.npy tables, dsp grep dumps, rpp param decoders)
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import base64, re
|
||||
def decode(path):
|
||||
txt=open(path).read()
|
||||
lines=txt.split('\n')
|
||||
for i,l in enumerate(lines):
|
||||
if 'soothe2_x64.vst3' in l:
|
||||
b=[]
|
||||
j=i+1
|
||||
while j<len(lines):
|
||||
t=lines[j].strip()
|
||||
if t=='}': break
|
||||
b.append(t); j+=1
|
||||
raw=re.sub(r'\s','',''.join(b))
|
||||
raw=raw.rstrip('=')
|
||||
raw+='='*((-len(raw))%4)
|
||||
dec=base64.b64decode(raw)
|
||||
# dec is nested: outer base64 blob contains an inner chunked-xml whose first child is another base64 or actual xml
|
||||
# find '<PARAM ...' anywhere after utf8 decode of inner
|
||||
# Look for actual XML PARAM tags embedded (they are inside processorStateData attribute - base64 too)
|
||||
# Simplest: find 'band1 mode' bytes
|
||||
for pidname in [b'band1 mode', b'band1 q', b'band1 sens', b'band1 freq', b'band1 on']:
|
||||
i2=dec.find(pidname)
|
||||
if i2>=0:
|
||||
seg=dec[i2-60:i2+80]
|
||||
m=re.search(rb'<PARAM id="([^"]+)" value="([^"]+)"', seg)
|
||||
if m:
|
||||
print(f" {m.group(1).decode():16s} = {m.group(2).decode()}")
|
||||
return
|
||||
for f in ['/home/m/soothe-bt/t1kq_b1f_800.rpp','/home/m/soothe-bt/dual_b1q_0.1.rpp','/home/m/soothe-bt/dual_b1q_10.0.rpp','/home/m/soothe-bt/dual_b1q_1.0.rpp','/home/m/soothe-bt/t1k_b1f_1100.rpp']:
|
||||
print("=== ",f.split('/')[-1]); decode(f)
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
import base64, re, sys
|
||||
|
||||
def full_decode(path):
|
||||
txt = open(path).read()
|
||||
lines = txt.split('\n')
|
||||
for i, l in enumerate(lines):
|
||||
if 'soothe2_x64.vst3' in l:
|
||||
b = []
|
||||
j = i + 1
|
||||
while j < len(lines):
|
||||
t = lines[j].strip()
|
||||
if t == '}':
|
||||
break
|
||||
b.append(t)
|
||||
j += 1
|
||||
raw = re.sub(r'\s', '', ''.join(b))
|
||||
raw = raw.rstrip('=')
|
||||
raw += '=' * ((-len(raw)) % 4)
|
||||
dec = base64.b64decode(raw)
|
||||
# print first bytes overview
|
||||
print('decoded blob len', len(dec))
|
||||
# Look for inner base64 chunked xml (soothe2 vst uses nested b64)
|
||||
# search for PARAM tags in raw decoded bytes
|
||||
tags = re.findall(rb'<PARAM id="([^"]+)" value="([^"]+)"', dec)
|
||||
if tags:
|
||||
for pid, val in tags:
|
||||
print(f' {pid.decode():16s} = {val.decode()}')
|
||||
else:
|
||||
# try inner b64
|
||||
inner = re.sub(rb'[^A-Za-z0-9+/=]', b'', dec)
|
||||
inner = inner.rstrip(b'=')
|
||||
inner += b'=' * ((-len(inner)) % 4)
|
||||
try:
|
||||
dec2 = base64.b64decode(inner)
|
||||
tags = re.findall(rb'<PARAM id="([^"]+)" value="([^"]+)"', dec2)
|
||||
for pid, val in tags:
|
||||
print(f' {pid.decode():16s} = {val.decode()}')
|
||||
except Exception as e:
|
||||
print(' inner b64 fail:', e)
|
||||
return
|
||||
print('no soothe2_x64.vst3 found')
|
||||
|
||||
for f in sys.argv[1:]:
|
||||
print('===', f.split('/')[-1])
|
||||
full_decode(f)
|
||||
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Render-parity harness (Phase 5 step 5, dB stage).
|
||||
|
||||
Measures the steady-state per-tone reduction directly on the reference wavs in
|
||||
/home/m/soothe-bt/ and compares with the B.12 bridge model (g*LUT + w*warp^a).
|
||||
|
||||
Model: C(f) = g*LUT(log10(L0/res_band(f,fc,Q))) + w*warp(f)^a -> red = -20*log10(1-C).
|
||||
LUT is the frozen PCHIP (B.11 knots). g=1.221, w=0.358, a=3.143 (model_fir.py canonical).
|
||||
|
||||
Reference renders: 24-bit WAV, input sources 16-bit mono. Tone amplitude estimated by
|
||||
Goertzel at the exact tone frequency over a late steady window (3.0-3.75 s region).
|
||||
"""
|
||||
import wave
|
||||
import numpy as np
|
||||
from scipy.interpolate import PchipInterpolator
|
||||
|
||||
BT = '/home/m/soothe-bt/'
|
||||
FS = 44100.0
|
||||
GAIN = 4.132 # 10^(sens_dB/40) with sens_dB=24.65
|
||||
QM = [0.1, 0.2, 0.3, 0.5, 0.7, 1.0, 1.5, 2.0, 3.0, 5.0, 10.0]
|
||||
FCS = [800., 900., 950., 1000., 1050., 1100., 1200.]
|
||||
L0D = 10 ** (-7.142 / 20) # dual input tone level
|
||||
L0Q = 10 ** (-18.063 / 20) # t1kq input tone level
|
||||
L0T = 1.0 # t1k input tone level (0 dBFS)
|
||||
|
||||
# frozen LUT knots (B.11)
|
||||
LX = np.array([-0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.574, 0.61, 0.75, 1.0])
|
||||
LY = np.array([0.4402, 0.4552, 0.4813, 0.5072, 0.5329, 0.5332, 0.5645, 0.6471, 0.6562, 0.6670])
|
||||
LUT = PchipInterpolator(LX, LY)
|
||||
|
||||
|
||||
def load(p):
|
||||
w = wave.open(p, 'rb')
|
||||
n, ch, sr, sw = w.getnframes(), w.getnchannels(), w.getframerate(), w.getsampwidth()
|
||||
b = w.readframes(n)
|
||||
N = n * ch
|
||||
if sw == 2:
|
||||
x = np.frombuffer(b, dtype='<i2').astype(np.float64) / 32768.0
|
||||
else:
|
||||
raw = np.frombuffer(b, dtype=np.uint8).reshape(N, 3)
|
||||
x = (raw[:, 0].astype(np.int64) | (raw[:, 1].astype(np.int64) << 8)
|
||||
| (raw[:, 2].astype(np.int64) << 16))
|
||||
x = np.where(x >= 0x800000, x - 0x1000000, x).astype(np.float64) / 8388607.0
|
||||
return x.reshape(-1, ch)
|
||||
|
||||
|
||||
def tone_amp(p, f):
|
||||
a = load(p)
|
||||
end = min(len(a), int(3.5 * FS))
|
||||
seg = a[:end][-int(0.75 * FS):]
|
||||
x = np.mean(seg, axis=1)
|
||||
n = len(x)
|
||||
w = 2 * np.pi * f / FS
|
||||
cw = 2 * np.cos(w)
|
||||
s0 = s1 = s2 = 0.0
|
||||
for v in x:
|
||||
s2 = s1
|
||||
s1 = s0
|
||||
s0 = v + cw * s1 - s2
|
||||
return np.sqrt(abs(s0 * s0 + s1 * s1 - 2 * cw * s0 * s1)) / n
|
||||
|
||||
|
||||
def red(src, out, f):
|
||||
return 20 * np.log10(tone_amp(src, f) / tone_amp(out, f))
|
||||
|
||||
|
||||
def res_band(f, fc, Q):
|
||||
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]
|
||||
ww = 2 * np.pi * f / FS
|
||||
z = np.exp(-1j * ww)
|
||||
return abs(2 * (B[0] + B[1] * z + B[2] * z * z) / (A[0] + A[1] * z + A[2] * z * z))
|
||||
|
||||
|
||||
def warp(f):
|
||||
x = f / 2000.
|
||||
return 0.87 * 7.942 * x / (7.942 + x)
|
||||
|
||||
|
||||
def pred(f, fc, Q, L0):
|
||||
xv = np.log10(L0 / res_band(f, fc, Q))
|
||||
C = 1.221 * float(LUT(float(np.clip(xv, -1, 1.5)))) + 0.358 * warp(f) ** 3.143
|
||||
return -20 * np.log10(1 - min(C, 0.999))
|
||||
|
||||
|
||||
def main():
|
||||
meas, pr = [], []
|
||||
for q in QM:
|
||||
for f in (500, 2000):
|
||||
meas.append(red(BT + 'dual.wav', BT + f'dual_b1q_{q}.wav', f))
|
||||
pr.append(pred(f, 500, q, L0D))
|
||||
for fc in FCS:
|
||||
meas.append(red(BT + 'tone1kq.wav', BT + f't1kq_b1f_{int(fc)}.wav', 1000))
|
||||
pr.append(pred(1000, fc, 0.9999978, L0Q))
|
||||
for fc in FCS:
|
||||
meas.append(red(BT + 'tone1k.wav', BT + f't1k_b1f_{int(fc)}.wav', 1000))
|
||||
pr.append(pred(1000, fc, 0.9999978, L0T))
|
||||
meas = np.array(meas)
|
||||
pr = np.array(pr)
|
||||
print('RENDER-PARITY vs /home/m/soothe-bt (36 pts, real wavs), B.12 (g=1.221,w=0.358,a=3.143):')
|
||||
print(' TOTAL rmse = %.4f dB' % np.sqrt(np.mean((pr - meas) ** 2)))
|
||||
for name, sl in [('dual500', slice(0, 22, 2)), ('dual2000', slice(1, 22, 2)),
|
||||
('t1kq', slice(22, 29)), ('t1k', slice(29, 36))]:
|
||||
print(' %-9s rmse=%.4f' % (name, np.sqrt(np.mean((pr[sl] - meas[sl]) ** 2))))
|
||||
print(' dual2000 resid:', ' '.join('%+.2f' % x for x in (pr[1:22:2] - meas[1:22:2])))
|
||||
print(' t1kq resid: ', ' '.join('%+.2f' % x for x in (pr[22:29] - meas[22:29])))
|
||||
print(' t1k resid: ', ' '.join('%+.2f' % x for x in (pr[29:] - meas[29:])))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
import base64, re, sys, html
|
||||
|
||||
def rpp_params(path):
|
||||
txt = open(path, encoding='utf8', errors='ignore').read()
|
||||
lines = txt.split('\n')
|
||||
for i, l in enumerate(lines):
|
||||
if 'soothe2_x64.vst3' in l:
|
||||
break
|
||||
b = []
|
||||
for l in lines[i + 1:]:
|
||||
if l.strip() == '}':
|
||||
break
|
||||
b.append(l.strip())
|
||||
raw = re.sub(r'[^A-Za-z0-9+/=]', '', ''.join(b))
|
||||
raw = raw.rstrip('=')
|
||||
# drop chars until b64 len divisible by 4 (the leading binary header causes odd)
|
||||
while (len(raw) % 4) != 0:
|
||||
raw = raw[:-1]
|
||||
padded = raw + '=' * ((-len(raw)) % 4)
|
||||
dec = base64.b64decode(padded, validate=False)
|
||||
p = dec.find(b'<?xml')
|
||||
xml = dec[p:].decode('utf8', 'ignore')
|
||||
xml = html.unescape(xml)
|
||||
params = dict(re.findall(r'<PARAM id="([^"]+)" value="([^"]+)"', xml))
|
||||
# also decode processorStateData inner
|
||||
proc = re.search(r'processorStateData="([^"]+)"', xml)
|
||||
if proc:
|
||||
inner = proc.group(1)
|
||||
try:
|
||||
inner_dec = base64.b64decode(re.sub(r'\s', '', inner))
|
||||
print(' [processorStateData inner]', inner_dec.decode('utf8', 'ignore')[:400])
|
||||
except Exception as e:
|
||||
print(' proc inner err', e)
|
||||
return params
|
||||
|
||||
for f in sys.argv[1:]:
|
||||
p = rpp_params(f)
|
||||
print('===', f.split('/')[-1])
|
||||
for k in sorted(p):
|
||||
print(f' {k:24s} = {p[k]}')
|
||||
Binary file not shown.
Reference in New Issue
Block a user