From 230c6a63ad5cd12bcbd4e8fa7638c540a4e30a94 Mon Sep 17 00:00:00 2001 From: Matiq Date: Mon, 17 Aug 2026 00:47:52 +0300 Subject: [PATCH] re: LIVE N0@44100 = 2048, oversample 4 (two SP instances agree); angle-ramp+Cody-Waite twiddle path confirmed for main FFT; rtone/rtver scripts --- roadmap.md | 19 ++++++++++ rtall.py | 67 +++++++++++++++++++++++++++++++++++ rtdeep.py | 94 +++++++++++++++++++++++++++++++++++++++++++++++++ rtdeep2.py | 76 ++++++++++++++++++++++++++++++++++++++++ rtdump2.py | 92 ++++++++++++++++++++++++++++++++++++++++++++++++ rtone.py | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++ rtscan.py | 74 +++++++++++++++++++++++++++++++++++++++ rtsig.py | 63 +++++++++++++++++++++++++++++++++ rtver.py | 88 ++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 673 insertions(+) create mode 100644 rtall.py create mode 100644 rtdeep.py create mode 100644 rtdeep2.py create mode 100644 rtdump2.py create mode 100644 rtone.py create mode 100644 rtscan.py create mode 100644 rtsig.py create mode 100644 rtver.py diff --git a/roadmap.md b/roadmap.md index d83adf6..7893556 100644 --- a/roadmap.md +++ b/roadmap.md @@ -192,3 +192,22 @@ 182617828/850/878 ALL currently point to 0x18014ec20 (same fn). - CONSEQUENCE: for 44100 => N=N0; if N0<=1024 the twiddle = static sin table (reproducible directly); if N0>1024 must replicate Cody-Waite sin/cos of FUN_18014ec20 bit-exactly. N0 (+0x19c) still unfound statically. + +### N0 RESOLVED - LIVE DUMP @44100 (2026-08-17) — KEY +- Soothe2 render_short.rpp (44100 Hz) + /proc/pid/mem scan of yabridge-host heap + (out file growth confirmed processing; targeted field-signature scan: + qword-aligned, +0x19c == +0x1a0 == pow2 in [256..65536], +0x1ac in {2,4,8,16}). +- Two independent live SP instances (fork L/R, vptr 0x1824ac210 / 0x1824ac7a8, + vtbl-slot families 0x18052xxx = SpectralProcessor) BOTH report: + +0x19c N0 = 2048, +0x1a0 = 2048 (=N0, since sr==44100 => 2^floor(log2(1))=1) + +0x1a4 winscale = 0.49999988 (Hann 0.5) / 0.66666639 (2/3 window) + +0x1ac oversample = 4 +- N0=2048 > 1024 => twiddle goes the ANGLE-RAMP branch of FUN_180039b00 + (buf[k]=k*(2π/N) then sin/cos via thunk 1801de760 / thunk 1801e3f20), + and cplx-stage loader FUN_18014ec20 re-derives re/im (Dekker-split + Cody-Waite). +- Implication for reimpl: for 44.1k we MUST reproduce Cody-Waite sin/cos + (1/π=0.31830988618379069, magic 6755399441055744, π_hi/π_lo reduction, + poly -1/3!..-1/15!), NOT the static 1024-table path. Soothing does NOT use the + static sin-table for its main FFT at 44.1k. +- Method: rtone.py (single render, wait for out growth, scan), rtver.py (field verify). + Old soothe_rt.bin is module-only range => heap objects absent (why vptr-scan found 0). diff --git a/rtall.py b/rtall.py new file mode 100644 index 0000000..7ff1ec7 --- /dev/null +++ b/rtall.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +import subprocess, os, glob, sys, time, struct + +RPP = "/home/m/soothe-bt/render_long.rpp" +proc = subprocess.Popen( + ['reaper', '-nosplash', '-renderproject', RPP], + stdout=open('/tmp/rtall.log', 'w'), stderr=subprocess.STDOUT) +print('reaper', proc.pid, flush=True) + +start, end = 0x1824abb00, 0x1824abd00 # vtbl region +SLOTS = list(range(start, end, 8)) + + +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 scan_all(pid): + mem = os.open(f'/proc/{pid}/mem', os.O_RDONLY) + resc = {s: [] for s in SLOTS} + 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 + try: + d = os.pread(mem, hi - lo, lo) + except Exception: + continue + for s in SLOTS: + t = struct.pack(' 1 else "/home/m/soothe-bt/render_long.rpp" +START = len(sys.argv) > 2 and sys.argv[2] == "start" +if START: + proc = subprocess.Popen( + ['reaper', '-nosplash', '-renderproject', RPP], + stdout=open('/tmp/rtfind.log', 'w'), stderr=subprocess.STDOUT) + print('reaper', proc.pid, flush=True) + +# The SpectralProcessor vtable slot containing ctor 0x180529610 +CTOR = 0x180529610 +# candidate vptrs: slots near it +VCANDS = [0x1824abb80, 0x1824abb88, 0x1824abb90, 0x1824abb98, 0x1824abba0, 0x1824abba8, 0x1824abbb0] + +def all_mapped(pid): + out = [] + 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' in p[1]: + out.append((lo, hi)) + return out + +def scan(pid, vptr, tgt): + mem = os.open(f'/proc/{pid}/mem', os.O_RDONLY) + hits = [] + for lo, hi in all_mapped(pid): + try: + d = os.pread(mem, hi - lo, lo) + except Exception: + continue + i = 0 + while True: + i = d.find(tgt, i) + if i < 0: + break + hits.append(lo + i) + i += 1 + os.close(mem) + return hits + +def filter_candidates(pid, hit_addrs): + # for each candidate object addr (addr = where vptr stored => object base), read fields + out = [] + mem = os.open(f'/proc/{pid}/mem', os.O_RDONLY) + for a in hit_addrs: + try: + d = os.pread(mem, 0x600, a) + except Exception: + continue + f4 = struct.unpack('<%dI' % (len(d) // 4), d) + # look for N at +0x19c and oversample at +0x1ac + n = f4[0x19c // 4] if (0x19c // 4) < len(f4) else 0 + u = f4[0x1ac // 4] if (0x1ac // 4) < len(f4) else 0 + w = struct.unpack('= 0x1a4 + 8 else 0 + ok = (n in (256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536) and + u in (2, 4, 8, 16)) + if w != 0 and (n > 0 or ok): + out.append((a, n, u, w, ok)) + os.close(mem) + return out + +hosts = {} +t0 = time.time() +seen = set() +while time.time() - t0 < 90: + for p in glob.glob('/proc/[0-9]*'): + try: + pid = int(os.path.basename(p)) + if pid in seen: + continue + maps = open(f'/proc/{pid}/maps').read() + if 'soothe2' not in maps or 'yabridge-host.exe' not in open(f'/proc/{pid}/cmdline', 'rb').read().decode('utf8', 'replace'): + continue + except Exception: + continue + seen.add(pid) + print('host', pid, flush=True) + # scan whole maps for each candidate vptr + for v in VCANDS: + h = scan(pid, v, struct.pack(' 1 else "/home/m/soothe-bt/render_rt.rpp" +KEEP = len(sys.argv) > 2 and sys.argv[2] == "keep" + +VPTR = 0x1824abb90 # SpectralProcessor vtbl first entry addr (static). verify at runtime too +CTOR = 0x180529610 + +proc = subprocess.Popen( + ['reaper', '-nosplash', '-renderproject', RPP], + stdout=open('/tmp/rtdump2.log', 'w'), stderr=subprocess.STDOUT) +print('reaper', proc.pid, flush=True) + +host = None +deadline = time.time() + 90 +while time.time() < deadline and not KEEP: + found = [] + for p in glob.glob('/proc/[0-9]*'): + try: + pid = int(os.path.basename(p)) + cmd = open(p + '/cmdline', 'rb').read().replace(b'\0', b' ').decode('utf8', 'replace') + if 'yabridge-host' not in cmd: + continue + maps = open(f'/proc/{pid}/maps').read() + if 'soothe2' in maps: + found.append(pid) + except Exception: + pass + if found: + host = found[0] + break + time.sleep(0.2) +print('host', host, flush=True) +if not host: + if not KEEP: + proc.kill() + sys.exit('no host') + +# gather ALL readable regions +readable = [] +maps = open(f'/proc/{host}/maps').read() +tot = 0 +for line in maps.splitlines(): + parts = line.split() + lo, hi = (int(x, 16) for x in parts[0].split('-')) + if 'r' in parts[1]: + readable.append((lo, hi, parts[1])) + tot += hi - lo +print('readable regions:', len(readable), 'total MB:', tot / 1e6, flush=True) + +mem = os.open(f'/proc/{host}/mem', os.O_RDONLY) +pat = {v: struct.pack(' base_sz: + grew = s + break + # if reaper exited, stop trying + if proc.poll() is not None: + print('reaper exited, last out size', s, flush=True) + break + time.sleep(0.3) +print('out grew to', grew, flush=True) +if not hosts or grew == 0: + proc.kill() + sys.exit('no live render') + +time.sleep(0.5) + +N_POW2 = {2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536} +UPS = {2,4,8,16} + +def scan(pid): + 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(' 1 else "/home/m/soothe-bt/render_long.rpp" + +VCAND = list(range(0x1824abb60, 0x1824abc20, 8)) # wide vtable range +CTOR = 0x180529610 + +proc = subprocess.Popen( + ['reaper', '-nosplash', '-renderproject', RPP], + stdout=open('/tmp/rtov.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)) + maps = open(f'/proc/{pid}/maps').read() + if 'soothe2' in maps and 'yabridge-host' in open(f'/proc/{pid}/cmdline','rb').read().decode('utf8','replace'): + out.append(pid) + except Exception: + pass + return out + + +def scan(pid): + mem = os.open(f'/proc/{pid}/mem', os.O_RDONLY) + found = [] + pats = {v: struct.pack(' 0: + print('HITS', host if 'host' in dir() else h, len(found), flush=True) + with open('/tmp/inst2.txt', 'w') as fo: + for hh, va, a in found: + fo.write(f'{hh} {va:#x} {a:#x}\n') + if not hosts: + proc.kill() + sys.exit(0) + time.sleep(0.3) +print('none', flush=True) +proc.kill() \ No newline at end of file diff --git a/rtsig.py b/rtsig.py new file mode 100644 index 0000000..717339d --- /dev/null +++ b/rtsig.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +import subprocess, os, glob, sys, time, struct + +RPP = "/home/m/soothe-bt/render_short.rpp" +proc = subprocess.Popen(['reaper', '-nosplash', '-renderproject', RPP], + stdout=open('/tmp/rtsig.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 + +N_POW2 = {2:1,4:1,8:1,16:1,32:1,64:1,128:1,256:1,512:1,1024:1,2048:1,4096:1,8192:1,16384:1,32768:1,65536:1} + +def scan(pid): + 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 + try: + d = os.pread(mem, hi - lo, lo) + except Exception: + continue + if lo < 0x180000000 or lo >= 0x183000000: + # heap/stack chunk. scan qword-aligned for N signature + for o in range(0, len(d) - (0x1ac + 8), 8): + n0 = struct.unpack_from(' 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) + +for pid in hosts: + mem = os.open(f'/proc/{pid}/mem', os.O_RDONLY) + for a in CAND: + try: + d = os.pread(mem, 0x300, a) + except Exception as e: + print(pid, hex(a), 'err', e, flush=True); continue + # ?possibly the object base is NOT a; our signature matched offset at +0x19c. + # Re-scan around a for the exact int field to find true object base. + # fields: N at base+0x19c, N at +0x1a0, float win at +0x1a4, int ups at +0x1ac + # find qword-aligned true base by searching +0x1ac==4 in window + f4 = struct.unpack('<%dI' % (len(d) // 4), d) + bases = [] + for o in range(0, len(f4) - (0x1ac // 4 + 1)): + if f4[o + 0x1ac // 4] == 4 and f4[o + 0x19c // 4] in (2048, 4096) \ + and f4[o + 0x1a0 // 4] == f4[o + 0x19c // 4]: + bases.append(o * 4) + print(pid, hex(a), 'true-base candidates:', [hex(a + b) for b in bases[:6]], flush=True) + for b in bases[:1]: + o = b + vptr = struct.unpack_from('