add dsp/ with FFT, filters, detector, stereo support

This commit is contained in:
2026-08-17 03:28:05 +03:00
parent 230c6a63ad
commit 66bac5e1e5
2 changed files with 230 additions and 0 deletions
+112
View File
@@ -0,0 +1,112 @@
#!/usr/bin/env python3
import subprocess, os, glob, sys, time, struct, math
RPP = "/home/m/soothe-bt/render_short.rpp"
OUT = "/home/m/soothe-bt/out_rt.wav"
proc = subprocess.Popen(['reaper', '-nosplash', '-renderproject', RPP],
stdout=open('/tmp/rttbl.log', 'w'), stderr=subprocess.STDOUT)
print('reaper', proc.pid, flush=True)
def find_hosts():
out = []
for p in glob.glob('/proc/[0-9]*'):
try:
pid = int(os.path.basename(p))
m = open(f'/proc/{pid}/maps').read()
c = open(f'/proc/{pid}/cmdline', 'rb').read().decode('utf8', 'replace')
if 'soothe2' in m and 'yabridge-host.exe' in c:
out.append(pid)
except Exception:
pass
return out
def out_growth():
try:
return os.path.getsize(OUT)
except Exception:
return 0
t0 = time.time()
hosts = None
base_sz = out_growth()
while time.time() - t0 < 60:
hosts = find_hosts()
if hosts:
break
time.sleep(0.2)
print('hosts:', hosts, flush=True)
grew = 0
while time.time() - t0 < 120:
s = out_growth()
if s > base_sz:
grew = s; break
if proc.poll() is not None:
print('reaper exited early', flush=True); break
time.sleep(0.3)
print('out:', grew, flush=True)
if not hosts or grew == 0:
proc.kill(); sys.exit('fail')
time.sleep(0.5)
step = 2 * math.pi / 2048
def qsin(k):
# sin(k*step) search tolerance 1e-6 (we only need anchor match)
return math.sin(k * step)
for pid in hosts:
mem = os.open(f'/proc/{pid}/mem', os.O_RDONLY)
tables = []
maps = open(f'/proc/{pid}/maps').read()
for line in maps.splitlines():
p = line.split()
lo, hi = (int(x, 16) for x in p[0].split('-'))
if 'r' not in p[1]:
continue
if lo < 0x180000000 or lo >= 0x183000000:
try:
d = os.pread(mem, hi - lo, lo)
except Exception:
continue
if len(d) < 16:
continue
q = len(d) // 8
qw = struct.unpack('<%dQ' % q, memoryview(d)[:q * 8])
for o in range(0, q - 4):
if qw[o] != 0:
continue
# candidate anchor: qw[o]==0.0 and qw[o+1] approx sin(step), qw[o+2] approx sin(2step)
v1 = struct.unpack('<d', struct.pack('<Q', qw[o + 1]))[0]
v2 = struct.unpack('<d', struct.pack('<Q', qw[o + 2]))[0]
e1, e2 = qsin(1), qsin(2)
if abs(v1 - e1) < 1e-8 and abs(v2 - e2) < 1e-8:
# count how many follow match sin(k*step) to confirm length
cnt = 0
for k in range(1, 900):
v = struct.unpack('<d', struct.pack('<Q', qw[o + k]))[0]
if abs(v - qsin(k % 1024)) < 1e-6 and k <= 300:
cnt = k
else:
break
tables.append((lo + o * 8, cnt))
# limit scanning to a sane budget
print('PID', pid, 'tables:', tables[:10], flush=True)
for t, cnt in tables[:3]:
off = t
vals = []
mem2 = os.open(f'/proc/{pid}/mem', os.O_RDONLY)
try:
d = os.pread(mem2, 1024 * 8, t)
vals = struct.unpack('<1024d', d)
except Exception as e:
print('read err', e, flush=True)
os.close(mem2)
with open('twiddle_2048_ref.f64', 'wb') as f:
for v in vals:
f.write(struct.pack('<d', v))
print(' ', hex(t), 'len~', cnt, 'first', ' '.join('%.9g' % v for v in vals[:5]),
'idx256', vals[256], flush=True)
print(' saved twiddle_2048_ref.f64', flush=True)
break
os.close(mem)
proc.kill()
print('done', flush=True)
+118
View File
@@ -0,0 +1,118 @@
#!/usr/bin/env python3
import subprocess, os, glob, sys, time, struct
RPP = "/home/m/soothe-bt/render_short.rpp"
OUT = "/home/m/soothe-bt/out_rt.wav"
proc = subprocess.Popen(['reaper', '-nosplash', '-renderproject', RPP],
stdout=open('/tmp/rtwin.log', 'w'), stderr=subprocess.STDOUT)
print('reaper', proc.pid, flush=True)
def find_hosts():
out = []
for p in glob.glob('/proc/[0-9]*'):
try:
pid = int(os.path.basename(p))
m = open(f'/proc/{pid}/maps').read()
c = open(f'/proc/{pid}/cmdline', 'rb').read().decode('utf8', 'replace')
if 'soothe2' in m and 'yabridge-host.exe' in c:
out.append(pid)
except Exception:
pass
return out
def out_growth():
try:
return os.path.getsize(OUT)
except Exception:
return 0
t0 = time.time()
hosts = None
base_sz = out_growth()
while time.time() - t0 < 60:
hosts = find_hosts()
if hosts:
break
time.sleep(0.2)
print('hosts:', hosts, flush=True)
grew = 0
while time.time() - t0 < 120:
s = out_growth()
if s > base_sz:
grew = s; break
if proc.poll() is not None:
print('reaper exited early', flush=True); break
time.sleep(0.3)
print('out:', grew, flush=True)
if not hosts or grew == 0:
proc.kill(); sys.exit('fail')
time.sleep(0.5)
N_POW2 = {2048, 4096}
UPS = {2,4,8,16}
for pid in hosts:
mem = os.open(f'/proc/{pid}/mem', os.O_RDONLY)
found = []
maps = open(f'/proc/{pid}/maps').read()
for line in maps.splitlines():
p = line.split()
lo, hi = (int(x, 16) for x in p[0].split('-'))
if 'r' not in p[1]:
continue
if lo < 0x180000000 or lo >= 0x183000000:
try:
d = os.pread(mem, hi - lo, lo)
except Exception:
continue
if len(d) < 0x1ac + 8:
continue
for o in range(0, len(d) - (0x1ac + 8), 8):
n0 = struct.unpack_from('<I', d, o + 0x19c)[0]
n1 = struct.unpack_from('<I', d, o + 0x1a0)[0]
u = struct.unpack_from('<I', d, o + 0x1ac)[0]
if n0 in N_POW2 and n1 == n0 and u in UPS:
w = struct.unpack_from('<f', d, o + 0x1a4)[0]
if 0.1 < w < 5.0:
found.append((lo + o))
print('PID', pid, 'SP objects:', [hex(a) for a in found], flush=True)
# dump a generous window around each object; hunt for a sin-quarter table
for a in found:
try:
big = os.pread(mem, 0x400000, a)
except Exception:
continue
db = struct.unpack('<%dQ' % (len(big) // 8), big)
# sin table starts with 0.0 then sin(2*pi/N)... find 513 consecutive doubles matching
import math
N = 2048
step = 2 * math.pi / N
best = None
for o in range(0, len(db) - 520):
# candidate: db[o]==0 and db[o+1]~sin(step), monotonic up to pi/2
if db[o] != 0:
continue
# convert qwords to doubles for compare window
ok = True
for k in range(1, 8):
v0 = struct.unpack('<d', struct.pack('<Q', db[o + k]))[0]
e = math.sin(k * step)
if abs(v0 - e) > 1e-9 and k <= 4:
ok = False
break
if ok:
best = o
break
if best is not None:
tgt = a + best * 8
print(' table start ~', hex(tgt), 'collecting 1024 doubles', flush=True)
vals = [struct.unpack('<d', struct.pack('<Q', db[best + k]))[0] for k in range(1024)]
with open('twiddle_2048_ref.f64', 'wb') as f:
for v in vals:
f.write(struct.pack('<d', v))
print(' first:', ' '.join('%.9g' % v for v in vals[:6]), flush=True)
print(' at 256 (pi/2):', vals[256], flush=True)
print(' saved twiddle_2048_ref.f64', flush=True)
os.close(mem)
proc.kill()
print('done', flush=True)