Initial commit: soothe2 RE workspace + roadmap
- Инфраструктура: RTTI-дампы (rtti_dsp/full.json), декомпиляции DSP-классов (decomp_*.txt), Ghidra-скрипты (Dump*.java, ImportRtti*.java, Diag.java), depthcurve/curve_fits LUT. - Поведенческая модель sim_v5.py + sim.py (RMSE ~0.3dB), утилиты (measure, tt_sweep, patchparam, sweep, harness_*, verify_sim). - summary.md + roadmap.md (контракт из manual M1-M12, топология пайплайна из OCR, фазы A-C). - pipeline_ocr.txt: OCR диаграммы Appendix A (mid/side ручка, trim/mix/bypass порядок).
This commit is contained in:
+22
@@ -0,0 +1,22 @@
|
|||||||
|
# venv и тяжёлые/бинарные артефакты — в репо НЕ пушим
|
||||||
|
*
|
||||||
|
!*.py
|
||||||
|
!*.txt
|
||||||
|
!*.json
|
||||||
|
!*.md
|
||||||
|
!*.npy
|
||||||
|
!*.npz
|
||||||
|
!*.java
|
||||||
|
!roadmap.md
|
||||||
|
!.gitignore
|
||||||
|
|
||||||
|
# тяжеловесы / чувствительные (исключены даже с include выше)
|
||||||
|
*.bin
|
||||||
|
regions/
|
||||||
|
regions_dep/
|
||||||
|
regions_render/
|
||||||
|
regions_rt/
|
||||||
|
regions_rt2/
|
||||||
|
soothe_rt.bin.regions/
|
||||||
|
*.wav
|
||||||
|
*.rpp
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import ghidra.app.script.GhidraScript;
|
||||||
|
import ghidra.program.model.listing.Function;
|
||||||
|
import ghidra.program.model.symbol.Symbol;
|
||||||
|
import ghidra.program.model.symbol.SymbolIterator;
|
||||||
|
import ghidra.program.model.symbol.SymbolType;
|
||||||
|
|
||||||
|
public class Diag extends GhidraScript {
|
||||||
|
@Override
|
||||||
|
public void run() throws Exception {
|
||||||
|
int nf = 0;
|
||||||
|
for (Function f : currentProgram.getFunctionManager().getFunctions(true)) {
|
||||||
|
nf++;
|
||||||
|
}
|
||||||
|
println("FUNCTION_COUNT " + nf);
|
||||||
|
|
||||||
|
String[] keys = {"Soothe2", "Spectral", "FilterGraph", "DigitalFilter",
|
||||||
|
"IIRFilter", "LowPass", "ModuleBase", "Decryptor",
|
||||||
|
"processBlock", "process"};
|
||||||
|
SymbolIterator si = currentProgram.getSymbolTable().getAllSymbols(true);
|
||||||
|
int shown = 0;
|
||||||
|
while (si.hasNext()) {
|
||||||
|
Symbol s = si.next();
|
||||||
|
String name = s.getName(true);
|
||||||
|
if (name == null) name = s.getName();
|
||||||
|
if (name == null) continue;
|
||||||
|
for (String k : keys) {
|
||||||
|
if (name.toLowerCase().contains(k.toLowerCase())) {
|
||||||
|
println("SYMB " + name + " " + s.getAddress() + " " + s.getSymbolType());
|
||||||
|
shown++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println("SYM_TOTAL " + shown);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import ghidra.app.script.GhidraScript;
|
||||||
|
import ghidra.app.decompiler.DecompInterface;
|
||||||
|
import ghidra.app.decompiler.DecompileResults;
|
||||||
|
import ghidra.program.model.listing.Function;
|
||||||
|
import ghidra.program.model.listing.FunctionManager;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
|
||||||
|
public class DumpCandidates extends GhidraScript {
|
||||||
|
@Override
|
||||||
|
public void run() throws Exception {
|
||||||
|
long[] addrs = {
|
||||||
|
0x180536f70L, 0x180537160L, 0x180536f60L, 0x180536670L,
|
||||||
|
0x180536660L, 0x180536cc0L, 0x180537720L, 0x180536cb0L,
|
||||||
|
0x180536b90L, 0x180537930L, 0x180536b80L, 0x180536c60L,
|
||||||
|
0x1813658a0L, 0x18052c7e0L, 0x18053846cL, 0x1852000000L,
|
||||||
|
0x18052c7e0L, 0x180538484L, 0x180536b60L, 0x1805379a0L,
|
||||||
|
0x180536b50L, 0x180536850L
|
||||||
|
};
|
||||||
|
DecompInterface di = new DecompInterface();
|
||||||
|
di.openProgram(currentProgram);
|
||||||
|
FunctionManager fm = currentProgram.getFunctionManager();
|
||||||
|
PrintWriter pw = new PrintWriter(new java.io.BufferedWriter(new FileWriter("/home/m/re-tools/decomp_candidates.txt", false)));
|
||||||
|
for (long a : addrs) {
|
||||||
|
Address addr = currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(a);
|
||||||
|
Function f = fm.getFunctionAt(addr);
|
||||||
|
if (f == null) { pw.println("=== 0x" + Long.toHexString(a) + " : NOT A FUNCTION ===\n"); continue; }
|
||||||
|
DecompileResults res = di.decompileFunction(f, 60, monitor);
|
||||||
|
pw.println("==================== 0x" + Long.toHexString(a) + " " + f.getName() + " size=" + f.getBody().getNumAddresses() + " refs=" + f.getSymbol().getReferenceCount() + " ====");
|
||||||
|
if (res != null) pw.println(res.getDecompiledFunction().getC());
|
||||||
|
pw.println();
|
||||||
|
}
|
||||||
|
pw.close();
|
||||||
|
di.dispose();
|
||||||
|
println("DUMP_DONE");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import ghidra.app.script.GhidraScript;
|
||||||
|
import ghidra.app.decompiler.DecompInterface;
|
||||||
|
import ghidra.app.decompiler.DecompileResults;
|
||||||
|
import ghidra.program.model.listing.Function;
|
||||||
|
import ghidra.program.model.listing.FunctionManager;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class DumpDsp extends GhidraScript {
|
||||||
|
@Override
|
||||||
|
public void run() throws Exception {
|
||||||
|
Map<String,long[]> vts = new LinkedHashMap<String,long[]>();
|
||||||
|
vts.put("SpectralProcessor", new long[]{0x1824abb90L, 64});
|
||||||
|
vts.put("Soothe2ModuleBase", new long[]{0x1824ac7a8L, 64});
|
||||||
|
vts.put("CloudyAudioProcessor", new long[]{0x1824ab7c0L, 64});
|
||||||
|
vts.put("AudioProcessingModule", new long[]{0x1824ac638L, 64});
|
||||||
|
vts.put("IIRFilterExtended", new long[]{0x1824ac5d8L, 64});
|
||||||
|
vts.put("Soothe2FilterGraph", new long[]{0x1824be810L, 64});
|
||||||
|
vts.put("FilterGraph_6_0x400", new long[]{0x1824be228L, 64});
|
||||||
|
vts.put("FilterGraphGrid", new long[]{0x1824be0a8L, 64});
|
||||||
|
DecompInterface di = new DecompInterface();
|
||||||
|
di.openProgram(currentProgram);
|
||||||
|
FunctionManager fm = currentProgram.getFunctionManager();
|
||||||
|
PrintWriter pw = new PrintWriter(new java.io.BufferedWriter(new FileWriter("/home/m/re-tools/decomp_dsp.txt", false)));
|
||||||
|
for (Map.Entry<String,long[]> e : vts.entrySet()) {
|
||||||
|
long base = e.getValue()[0]; int n = (int) e.getValue()[1];
|
||||||
|
pw.println("############ VTABLE " + e.getKey() + " 0x" + Long.toHexString(base));
|
||||||
|
pw.println("SLOTLIST:");
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
Address va = currentProgram.getAddressFactory().getDefaultAddressSpace().
|
||||||
|
getAddress(base + i * 8L);
|
||||||
|
long tgt = (long) currentProgram.getMemory().getLong(va);
|
||||||
|
if (tgt < 0x180000000L || tgt > 0x182a00000L) continue;
|
||||||
|
Address a = currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(tgt);
|
||||||
|
Function f = fm.getFunctionAt(a);
|
||||||
|
long sz = (f == null) ? 0 : f.getBody().getNumAddresses();
|
||||||
|
pw.println(" [" + i + "] 0x" + Long.toHexString(tgt) + " size=" + sz);
|
||||||
|
}
|
||||||
|
pw.println("CCODE:");
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
Address va = currentProgram.getAddressFactory().getDefaultAddressSpace().
|
||||||
|
getAddress(base + i * 8L);
|
||||||
|
long tgt = (long) currentProgram.getMemory().getLong(va);
|
||||||
|
if (tgt < 0x180000000L || tgt > 0x182a00000L) continue;
|
||||||
|
Address a = currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(tgt);
|
||||||
|
Function f = fm.getFunctionAt(a);
|
||||||
|
if (f == null) continue;
|
||||||
|
long sz = f.getBody().getNumAddresses();
|
||||||
|
if (sz < 60) continue;
|
||||||
|
DecompileResults res = di.decompileFunction(f, 90, monitor);
|
||||||
|
if (res != null && res.getDecompiledFunction() != null) {
|
||||||
|
pw.println(" --- [" + i + "] 0x" + Long.toHexString(tgt) + " size=" + sz + " ---");
|
||||||
|
pw.println(res.getDecompiledFunction().getC());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pw.close();
|
||||||
|
di.dispose();
|
||||||
|
println("DSP_DONE");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import ghidra.app.script.GhidraScript;
|
||||||
|
import ghidra.app.decompiler.DecompInterface;
|
||||||
|
import ghidra.app.decompiler.DecompileResults;
|
||||||
|
import ghidra.program.model.listing.Function;
|
||||||
|
import ghidra.program.model.listing.FunctionManager;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
|
||||||
|
public class DumpVtables extends GhidraScript {
|
||||||
|
@Override
|
||||||
|
public void run() throws Exception {
|
||||||
|
long[][] vts = {
|
||||||
|
{0x1824abb90L, 64}, // SpectralProcessor
|
||||||
|
{0x1824ac7a8L, 64}, // Soothe2ModuleBase
|
||||||
|
{0x1824ab7c0L, 64}, // CloudyAudioProcessor
|
||||||
|
};
|
||||||
|
DecompInterface di = new DecompInterface();
|
||||||
|
di.openProgram(currentProgram);
|
||||||
|
FunctionManager fm = currentProgram.getFunctionManager();
|
||||||
|
PrintWriter pw = new PrintWriter(new java.io.BufferedWriter(new FileWriter("/home/m/re-tools/decomp_vtables.txt", false)));
|
||||||
|
for (long[] vt : vts) {
|
||||||
|
long base = vt[0]; int n = (int) vt[1];
|
||||||
|
pw.println("############ VTABLE 0x" + Long.toHexString(base) + " (" + n + " slots) ############");
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
Address va = currentProgram.getAddressFactory().getDefaultAddressSpace().
|
||||||
|
getAddress(base + i * 8L);
|
||||||
|
long tgt = (long) currentProgram.getMemory().getLong(va);
|
||||||
|
if (tgt < 0x180000000L || tgt > 0x182a00000L) {
|
||||||
|
pw.println(" [" + i + "] 0x" + Long.toHexString(tgt) + " (invalid/enc)");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Address a = currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(tgt);
|
||||||
|
Function f = fm.getFunctionAt(a);
|
||||||
|
long sz = (f == null) ? 0 : f.getBody().getNumAddresses();
|
||||||
|
pw.println(" [" + i + "] 0x" + Long.toHexString(tgt) + " size=" + sz);
|
||||||
|
if (f != null && sz >= 40) {
|
||||||
|
DecompileResults res = di.decompileFunction(f, 60, monitor);
|
||||||
|
if (res != null && res.getDecompiledFunction() != null) {
|
||||||
|
pw.println(" --- C ---");
|
||||||
|
pw.println(res.getDecompiledFunction().getC());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pw.close();
|
||||||
|
di.dispose();
|
||||||
|
println("DUMP_VT_DONE");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import ghidra.app.script.GhidraScript;
|
||||||
|
import ghidra.program.model.symbol.SymbolTable;
|
||||||
|
import ghidra.program.model.symbol.SourceType;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.listing.Function;
|
||||||
|
import ghidra.program.model.listing.FunctionManager;
|
||||||
|
import ghidra.program.model.symbol.Namespace;
|
||||||
|
import ghidra.program.model.symbol.Symbol;
|
||||||
|
import ghidra.program.model.mem.MemoryBlock;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class ImportRtti extends GhidraScript {
|
||||||
|
@Override
|
||||||
|
public void run() throws Exception {
|
||||||
|
// map: vftable vendor address -> (class name, [func ptrs]) (decimal-addrs form from dump)
|
||||||
|
// encoded compactly: name|col|vf|func0,func1,... (up to 48)
|
||||||
|
String[] data = {
|
||||||
|
"Soothe2AudioProcessor|0x182506ca0|0x1824abf60|0x1813658a0,0x18052c7e0,0x18053846c,0x182504fa0,0x180536f70,0x180536f70,0x180537160,0x180536f60,0x18048db00,0x180481210,0x182505208,0x180536670,0x180536670,0x1804714b0,0x180536660,0x18048db00,0x180481210,0x1825068b0,0x180536cc0,0x180536cc0,0x180537720,0x180536cb0,0x18048db00,0x180481210,0x182504e40,0x180528ba0,0x1804714b0,0x1804714b0,0x1825067d0,0x180536b90,0x180536b90,0x180537930,0x180536b80,0x18048dc80,0x180481210,0x182505680,0x180536c60",
|
||||||
|
"$SpectralProcessor@M$07$01|0x1825033e0|0x1824abb90|0x18052d390,0x18052cfb0,0x180529550,0x1804714b0,0x180529610,0x18113285a,0x1805295c0,0x180529590,0x180536b60,0x180536b60,0x1805379a0,0x18250e798,0x1804d8db0,0x1804d8db0,0x18250e3f8,0x18052c7b0,0x1804d8db0,0x1804d8db0,0x1825074f0,0x1804714b0,0x18113285a,0x1804714b0,0x1825060e0,0x18113285a,0x18113285a,0x1804d8db0,0x18053fbf0,0x1804d8db0,0x1804d8db0",
|
||||||
|
"$Soothe2ModuleBase@M$01|0x1825038b0|0x1824ac7a8|0x18052fd40,0x18052cfb0,0x180529550,0x1804714b0,0x18052ce00,0x1804714b0,0x18052cb90,0x18052cbc0,0x180536b60,0x182507120,0x1805367a0,0x18052e540,0x18052ce00,0x18052cbe0,0x18048db00,0x180481210,0x18052ff40,0x180530060,0x18052cfb0",
|
||||||
|
"$AudioProcessingModule@M$01|0x182503180|0x1824ac638|0x18052d580,0x18052cfb0,0x18052cfa0,0x1804714b0,0x18113285a,0x18113285a,0x18052cbe0,0x180536b60,0x180536b60,0x1825071e8,0x18052cfa0,0x18052cfb0,0x18052cfb0",
|
||||||
|
"$Soothe2Module@M$01|0x182504e00|0x1824ac210|0x180535ed0,0x18052cfb0,0x180529550,0x1804714b0,0x18052b940,0x180529c60,0x18052bac0,0x18052b9d0,0x18052ba40,0x18052bad0,0x18052d3a0,0x18052b960,0x18052cb90,0x18052cbc0",
|
||||||
|
"$IIRFilterExtended@M$01|0x182503a58|0x1824ac5d8|0x1805346b0,0x18052cfb0,0x18052cfa0,0x18052fb50,0x18052fb30,0x182506a00,0x18052f820,0x18048db00,0x180481210,0x18052f7d0,0x18052f7c0,0x18052fc70,0x18052f950,0x18053fc00,0x18053fbc0,0x18053fa10",
|
||||||
|
"$DigitalFilter@M$0BA$01|0x182519b70|0x1824be7a0|0x18058d2b0,0x18052cfb0,0x18052cfa0,0x1804714b0,0x18113285a,0x18113285a,0x18058c850,0x18058c8b0,0x18058c7b0",
|
||||||
|
"$LowPassFilter@M$0BA$01|0x18251a141|0x1824be990|0x18058d230,0x18052cfb0,0x18052cfa0,0x18058c850,0x18058c8b0,0x18058c7b0",
|
||||||
|
"Soothe2FilterGraph|0x182518870|0x1824be810|0x180589260,0x18134bc00,0x18058b4d0,0x18058b380,0x18134bbd0,0x18134bbc0,0x18058b2d0,0x18058b290",
|
||||||
|
"$FilterGraph@M$05$0CAA@|0x1825189b8|0x1824be228|0x18058eb1c,0x18058b010,0x18251ab48,0x18058eab0,0x18160b000,0x1804714b0,0x18058ea70,0x18058ea90,0x18058dfa0,0x18058dfc0,0x18058df40,0x18058df60,0x18058dac0,0x18058daa0",
|
||||||
|
"Soothe2ModuleDecryptor|0x182504928|0x1824abe68|0x18052d520,0x1813e2a60,0x182505230,0x180536a50,0x180536a50,0x180537680,0x180536a50,0x180536a50,0x180537680",
|
||||||
|
"$ModuleDecryptor@VSoothe2_DynamicCryptoManager@eden@pace|0x1825043d8|0x1824ab228|0x18052d520,0x18113285a,0x182505008,0x180538490,0x18135c520,0x182506e60,0x180536900,0x180536b60",
|
||||||
|
};
|
||||||
|
|
||||||
|
SymbolTable st = currentProgram.getSymbolTable();
|
||||||
|
FunctionManager fm = currentProgram.getFunctionManager();
|
||||||
|
long base = 0x180000000L;
|
||||||
|
int lab = 0;
|
||||||
|
int fn = 0;
|
||||||
|
for (String line : data) {
|
||||||
|
String[] parts = line.split("\\|");
|
||||||
|
if (parts.length < 4) continue;
|
||||||
|
String cls = parts[0];
|
||||||
|
long col = Long.decode("0x" + parts[2]);
|
||||||
|
// skip vf slot - parts[2] is vftable
|
||||||
|
long vf = Long.decode("0x" + parts[2]);
|
||||||
|
String[] fptr = parts[3].split(",");
|
||||||
|
// namespace = class name
|
||||||
|
Namespace ns = st.getNamespace(cls, currentProgram.getGlobalNamespace());
|
||||||
|
if (ns == null) {
|
||||||
|
Address aCol = currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(col);
|
||||||
|
ns = st.createClass(null, cls, aCol, SourceType.USER_DEFINED);
|
||||||
|
lab++;
|
||||||
|
}
|
||||||
|
for (String fs : fptr) {
|
||||||
|
long f = Long.decode("0x" + fs);
|
||||||
|
Address addr = currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(f);
|
||||||
|
Function existing = fm.getFunctionAt(addr);
|
||||||
|
if (existing == null) {
|
||||||
|
fm.createFunction(null, addr, null, SourceType.USER_DEFINED);
|
||||||
|
fn++;
|
||||||
|
}
|
||||||
|
String nm = cls + "_v" + fptr.length + "";
|
||||||
|
Function fx = fm.getFunctionAt(addr);
|
||||||
|
if (fx != null && fx.getSymbol() != null
|
||||||
|
&& fx.getSymbol().getParentNamespace() == currentProgram.getGlobalNamespace()) {
|
||||||
|
String sname = "v_" + cls;
|
||||||
|
st.createLabel(addr, sname, ns, SourceType.USER_DEFINED);
|
||||||
|
lab++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println("IMPORT_RTTI_NAMESPACES " + lab);
|
||||||
|
println("IMPORT_RTTI_FUNCTIONS " + fn);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import ghidra.app.script.GhidraScript;
|
||||||
|
import ghidra.program.model.symbol.SymbolTable;
|
||||||
|
import ghidra.program.model.symbol.SourceType;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.listing.Function;
|
||||||
|
import ghidra.program.model.listing.FunctionManager;
|
||||||
|
import ghidra.program.model.symbol.Namespace;
|
||||||
|
import ghidra.program.model.symbol.Symbol;
|
||||||
|
|
||||||
|
public class ImportRtti2 extends GhidraScript {
|
||||||
|
@Override
|
||||||
|
public void run() throws Exception {
|
||||||
|
String[] data = {
|
||||||
|
".?AUSoothe2VertexData@@|0x18250ce80|0x1824b06c8|0x180561b00,0x180561370,0x180561450,0x180561440,0x18250c878,0x180570ea4,0x180568990,0x18250ce58,0x18056f890,0x18056f890,0x1804714b0,0x18056f880,0x18048db00,0x180481210,0x18250dcf8,0x180570e74,0x18133a110,0x18133a100,0x18133a0f0,0x18133a0e0,0x18250e010,0x18055e9a0,0x180471520,0x18250bd38,0x18056eb30,0x18056eb30,0x18056ef10,0x18056eb20,0x18048dc80,0x180481210,0x18250b6a0,0x180570f28,0x18055fda0,0x18250b9f8,0x180565b90,0x180570ee0,0x18250e268,0x18056f800,0x18056f800,0x18056f8b0,0x18056f7f0,0x18048db00,0x180481210,0x18250c600,0x180570e38,0x1813cc000,0x18250b978,0x18056f830|",
|
||||||
|
".?AV?$AudioProcessingModule@M$01@@|0x182504d80|0x1824ac638|0x18052d580,0x18052cfb0,0x18052cfa0,0x1804714b0,0x18113285a,0x1825071e8,0x180536aa0,0x180536aa0,0x180537b10,0x180536a90,0x18048db00,0x180481210,0x182505780,0x180536eb0,0x180536eb0,0x1805373c0,0x180536ea0,0x18048db00,0x180481210,0x182506568,0x1805365d0,0x1805365d0,0x180538110,0x1805365c0,0x18048dc80,0x180481210,0x1825056d8,0x18052fd40,0x18052cfb0|",
|
||||||
|
".?AV?$CloudyAudioProcessor@V?$Soothe2ModuleBase@M$01@@VSoothe2ModuleDecryptor@@VParameterStates@@M@@|0x182506e08|0x1824ab7c0|0x1805384a8,0x18052bd00,0x182507040,0x180536b60,0x180536b60,0x1805379a0,0x180536b50,0x18048dc80,0x180481210,0x182505588,0x180536850,0x180536850,0x180537e90,0x180536840,0x18048db00,0x180481210,0x182506590,0x180536f20,0x180536f20,0x180537290,0x180536f10,0x18048db00,0x180481210,0x182505910,0x180536610,0x180536610,0x1805380e0,0x180536600,0x1805365f0,0x180481210,0x182504cd0,0x180536d60,0x180536d60,0x1805375e0,0x180536d50,0x18048db00,0x180481210,0x182506f18,0x180536dc0,0x180536dc0,0x1805374e0,0x180536db0,0x18048db00,0x180481210,0x182506090,0x1805367f0,0x1805367f0|",
|
||||||
|
".?AV?$CloudyAudioProcessorEditor@VSoothe2AudioProcessor@@USoothe2VertexData@@@@|0x18250da78|0x1824b1528|0x180570e68,0x180565a70,0x18250cc08,0x180570f04,0x1813dc0d0,0x1813d8360,0x1813cf5a0,0x1813cde40,0x1813cde30,0x18250ce08,0x18056eb90,0x18056eb90,0x18056eea0,0x18056eb80,0x18048db00,0x180481210,0x18250bd10,0x1805603b0,0x18113285a,0x18250b590,0x18056ec20,0x18056ec20,0x18056eda0,0x18056ec10,0x18048db00,0x180481210,0x18250c018,0x18056e890,0x18056e890,0x18056f5b0,0x18056e880,0x18048dc80,0x180481210,0x18250df00,0x1804841c0,0x18055e1a0,0x18055e1c0,0x1811d2320,0x1811d2340,0x18055e1f0,0x18250bf08,0x180560030,0x18055fd70,0x18250c6a8,0x180564ca0,0x18134bc00,0x18134bbf0,0x18134bbe0|",
|
||||||
|
".?AV?$CloudyOpenGLLayer@USoothe2VertexData@@@@|0x18250bb00|0x1824b12b8|0x1805619b0,0x18113285a,0x18113285a,0x18113285a,0x18113285a,0x18113285a,0x18113285a,0x18113285a,0x18250b718,0x180570e80,0x180563430,0x18250d260,0x180570e50,0x1813d3b10,0x18250ca40,0x18056f710,0x18056f710,0x18056fb60,0x18056f700,0x18048db00,0x180481210,0x18250c4a8,0x1804834e0,0x180471520,0x18250bba0,0x18056f6e0,0x18056f6e0,0x18056fb90,0x18056f6d0,0x18048db00,0x180481210,0x18250d578,0x180570f1c,0x180565bd0,0x18250dc00,0x18056f740,0x18056f740,0x18056fb30,0x18056f730,0x18048db00,0x180481210,0x18250cc90,0x180570ed4,0x180568990,0x18250ba98,0x1804841c0,0x1811c9e40,0x1811c9e10|",
|
||||||
|
".?AV?$CloudyOpenGLRenderer@USoothe2VertexData@@@@|0x18250c270|0x1824b0678|0x180570e44,0x1805679e0,0x18250d238,0x18056e9b0,0x18056e9b0,0x18056f1e0,0x18056e9a0,0x18048db00,0x180481210,0x18250ce80,0x180561b00,0x180561370,0x180561450,0x180561440,0x18250c878,0x180570ea4,0x180568990,0x18250ce58,0x18056f890,0x18056f890,0x1804714b0,0x18056f880,0x18048db00,0x180481210,0x18250dcf8,0x180570e74,0x18133a110,0x18133a100,0x18133a0f0,0x18133a0e0,0x18250e010,0x18055e9a0,0x180471520,0x18250bd38,0x18056eb30,0x18056eb30,0x18056ef10,0x18056eb20,0x18048dc80,0x180481210,0x18250b6a0,0x180570f28,0x18055fda0,0x18250b9f8,0x180565b90,0x180570ee0,0x18250e268,0x18056f800|",
|
||||||
|
".?AV?$DigitalFilter@M$0BA@$01@@|0x18251a770|0x1824be7a0|0x18058d2b0,0x18052cfb0,0x18052cfa0,0x1804714b0,0x18113285a,0x18113285a,0x18251af28,0x18058e2d0,0x18058e2d0,0x18058e450,0x18058e2c0,0x18048db00,0x180481210,0x18251b090,0x180589260,0x18134bc00,0x18058b4d0,0x18058b380,0x18134bbd0,0x18134bbc0,0x18134bbb0,0x18134bba0,0x18134bb30,0x18134bad0,0x18134c900,0x18134c760,0x18134c750,0x18134c3a0,0x18134c2d0,0x18134c2c0,0x18134c270,0x1805689c0,0x18134bf00,0x18134be30,0x18134bde0,0x18134bcf0,0x18134bce0,0x18134bc50,0x18134bc20,0x18058ad80,0x18134bc10,0x18134bac0,0x18134bab0,0x18134ba90,0x18134ba80,0x18134ba70,0x18134ba60,0x18058af50|",
|
||||||
|
".?AV?$FilterGraph@M$05$0CAA@@@|0x18251ad88|0x1824be228|0x18058eb1c,0x18058b010,0x18251ab48,0x18058eab0,0x18160b000,0x1804714b0,0x18251a6c8,0x1805893c0,0x18134bc00,0x18134bbf0,0x18134bbe0,0x18134bbd0,0x18134bbc0,0x18134bbb0,0x18134bba0,0x18134bb30,0x18134bad0,0x18134c900,0x18134c760,0x18134c750,0x18134c3a0,0x18134c2d0,0x18134c2c0,0x18134c270,0x18134bf10,0x18134bf00,0x18134be30,0x18134bde0,0x18134bcf0,0x18134bce0,0x18134bc50,0x18134bc20,0x18134ade0,0x18134bc10,0x18134bac0,0x18134bab0,0x18134ba90,0x18134ba80,0x18134ba70,0x18134ba60,0x18160c410,0x18134ba50,0x18134adc0,0x18134ba40,0x18134ba30,0x18134ba20,0x18134ba10,0x18134b940|",
|
||||||
|
".?AV?$FilterGraphGrid@M@@|0x18251adb0|0x1824be0a8|0x18058c4b0,0x18134bc00,0x18134bbf0,0x18134bbe0,0x18134bbd0,0x18134bbc0,0x18134bbb0,0x18134bba0,0x18134bb30,0x18134bad0,0x18134c900,0x18134c760,0x18134c750,0x18134c3a0,0x18134c2d0,0x18134c2c0,0x18134c270,0x1805689c0,0x18134bf00,0x18134be30,0x18134bde0,0x18134bcf0,0x18134bce0,0x18134bc50,0x18134bc20,0x18134ade0,0x18134bc10,0x18134bac0,0x18134bab0,0x18134ba90,0x18134ba80,0x18134ba70,0x18134ba60,0x1805895c0,0x18134ba50,0x18134adc0,0x18134ba40,0x18134ba30,0x18134ba20,0x18134ba10,0x18134b940,0x18134b930,0x18134b870,0x180589f40,0x18251a470,0x18058eaf8,0x180568990,0x18251ad88|",
|
||||||
|
".?AV?$GainRangeScalingOptionComponent@VSoothe2GainRangeScalingOption@@@@|0x18250d088|0x1824b1e28|0x180570e5c,0x180564fc0,0x18250e158,0x18056f7d0,0x18056f7d0,0x18056f940,0x18056f7c0,0x18048db00,0x180481210,0x18250e628,0x180571090,0x18134bc00,0x18134bbf0,0x18134bbe0,0x18134bbd0,0x18134bbc0,0x18134bbb0,0x18134bba0,0x18134bb30,0x18134bad0,0x18134c900,0x18134c760,0x18134c750,0x18134c3a0,0x18134c2d0,0x18134c2c0,0x18134c270,0x18134bf10,0x18134bf00,0x18134be30,0x18134bde0,0x18134bcf0,0x18134bce0,0x18134bc50,0x18134bc20,0x18134ade0,0x18134bc10,0x18134bac0,0x18134bab0,0x18134ba90,0x18134ba80,0x18134ba70,0x18134ba60,0x18134add0,0x18134ba50,0x18134adc0,0x18134ba40,0x18134ba30|",
|
||||||
|
".?AV?$IIRFilterExtended@M$01@@|0x182505b18|0x1824ac5d8|0x1805346b0,0x18052cfb0,0x18052cfa0,0x18052fb50,0x18052fb30,0x182506a00,0x180538460,0x1804714b0,0x180529c50,0x182504d80,0x18052d580,0x18052cfb0,0x18052cfa0,0x1804714b0,0x18113285a,0x1825071e8,0x180536aa0,0x180536aa0,0x180537b10,0x180536a90,0x18048db00,0x180481210,0x182505780,0x180536eb0,0x180536eb0,0x1805373c0,0x180536ea0,0x18048db00,0x180481210,0x182506568,0x1805365d0,0x1805365d0,0x180538110,0x1805365c0,0x18048dc80,0x180481210|",
|
||||||
|
".?AV?$LowPassFilter@M$0BA@$01@@|0x18251a8e0|0x1824be990|0x18058d230,0x18052cfb0,0x18052cfa0,0x18058c850,0x18058c8b0,0x18058c7b0,0x18251ae98,0x18058e330,0x18058e330,0x18058e400,0x18058e320,0x18048db00,0x180481210,0x18251aab8,0x18058eb10,0x18058b500,0x18251ae70,0x1804840e0,0x180589190,0x1811efad0,0x1805891b0,0x1811ea380,0x18251a920,0x18058ea98,0x18058b500,0x18251a6f0,0x18058eaa4,0x180568990,0x18251a8b8,0x18058e2a0,0x18058e2a0,0x18058e520,0x18058e290,0x18048db00,0x180481210,0x18251aa40,0x18058ead4,0x18058b630,0x18251ad00,0x18058eae0,0x180568990,0x18251b440|",
|
||||||
|
".?AV?$ModuleDecryptor@VSoothe2_DynamicCryptoManager@eden@pace@@@@|0x182505fd8|0x1824ab228|0x18052d520,0x18113285a,0x182505008,0x180538490,0x18135c520,0x182506e60,0x1805368b0,0x1805368b0,0x180537d50,0x1805368a0,0x18048db00,0x180481210,0x182505108,0x1805292a0,0x18052c8f0,0x1812c1fb0,0x18052c4a0,0x1804714b0,0x1804714b0,0x1812c0b50,0x18052bfa0,0x1812c0b60,0x18135fd30,0x1804731c0,0x1804731c0,0x1812c0b40,0x181362010,0x1804731c0,0x1804731c0,0x1804731c0,0x1804731c0,0x1812c0ca0,0x1804727a0,0x1812c0df0,0x18135e010,0x180484870,0x1812c0cc0,0x18052c8d0|",
|
||||||
|
".?AV?$Soothe2Module@M$01@@|0x182506a78|0x1824ac210|0x180535ed0,0x18052cfb0,0x180529550,0x1804714b0,0x18052b940,0x180529c60,0x180529fe0,0x180529ef0,0x18052bbf0,0x1804714b0,0x1804714b0,0x1804714b0,0x18052bba0,0x18052bb80,0x18052bb60,0x18052bb40,0x18052bb20,0x18052bb00,0x18052bad0,0x18052baa0,0x18052ba50,0x18052ba40,0x18052ba30,0x18052ba20,0x18052b9b0,0x18052b990,0x18052ce80,0x18052b980,0x18052b960,0x1804731c0,0x18052b950,0x1804714b0,0x182505030,0x1805369d0,0x1805369d0,0x180537bd0,0x1805369c0,0x18048db00,0x180481210,0x1825070a8|",
|
||||||
|
".?AV?$Soothe2ModuleBase@M$01@@|0x1825056d8|0x1824ac7a8|0x18052fd40,0x18052cfb0,0x180529550,0x1804714b0,0x18052ce00,0x1804714b0,0x1804714b0,0x1804714b0,0x1804727a0,0x1804714b0,0x1804714b0,0x1804714b0,0x1804714b0,0x1804714b0,0x1804714b0,0x1804714b0,0x1804714b0,0x1804714b0,0x1804714b0,0x1804714b0,0x1804714b0,0x1804714b0,0x1804714b0,0x1804714b0,0x18052cec0,0x18052ce90,0x18052ce80,0x1804727a0,0x18052ce70,0x180484870,0x1804714b0,0x1804714b0,0x182507b08,0x180546620,0x1813afeb0,0x1813afef0,0x18136cc80,0x18136cc60,0x18136cc50,0x18136cc40,0x18136cc30,0x1813afd80,0x1813afe80,0x18136cbb0,0x1813afe40,0x1813afe70|",
|
||||||
|
".?AV?$SpectralProcessor@M$07$01@@|0x182504fe0|0x1824abb90|0x18052d390,0x18052cfb0,0x180529550,0x1804714b0,0x180529610,0x18113285a,0x1804714b0,0x1804714b0,0x1804727a0,0x1804714b0,0x1804714b0,0x1804714b0,0x182505548,0x18052d3d0,0x18052c8f0,0x1812c1fb0,0x18052c4a0,0x1804714b0,0x1804714b0,0x1812c0b50,0x18052bfa0,0x1812c0b60,0x18052bdf0,0x1804731c0,0x1804731c0,0x1812c0b40,0x18113285a,0x1804731c0,0x1804731c0,0x1804731c0,0x1804731c0,0x1812c0ca0,0x1804727a0,0x1812c0df0,0x18113285a,0x180484870,0x1812c0cc0,0x18052c8d0,0x18052c8c0,0x18052c880,0x18052c7f0,0x1804714b0|",
|
||||||
|
".?AV?$_Func_impl_no_alloc@V?$_Binder@U_Unforced@std@@P8?$CloudyAudioProcessor@V?$Soothe2ModuleBase@M$01@@VSoothe2ModuleDecryptor@@VParameterStates@@M@@EAAXXZPEAV3@@std@@X$$V@std@@|0x182505910|0x1824ab888|0x180536610,0x180536610,0x1805380e0,0x180536600,0x1805365f0,0x180481210,0x182504cd0,0x180536d60,0x180536d60,0x1805375e0,0x180536d50,0x18048db00,0x180481210,0x182506f18,0x180536dc0,0x180536dc0,0x1805374e0,0x180536db0,0x18048db00,0x180481210,0x182506090,0x1805367f0,0x1805367f0,0x180537f60,0x1805367e0,0x18048db00,0x180481210,0x182506948,0x1805366d0,0x1805366d0,0x180538010,0x1805366c0,0x18048db00,0x180481210,0x1825061b8,0x180536a30,0x180536a30,0x1805376a0,0x180536a20,0x18048db00,0x180481210|",
|
||||||
|
".?AVLines@?$FilterGraphGrid@M@@|0x18251af50|0x1824be5d8|0x18058e240,0x18134bc00,0x18134bbf0,0x18134bbe0,0x18134bbd0,0x18134bbc0,0x18134bbb0,0x18134bba0,0x18134bb30,0x18134bad0,0x18134c900,0x18134c760,0x18134c750,0x18134c3a0,0x18134c2d0,0x18134c2c0,0x18134c270,0x18134bf10,0x18134bf00,0x18134be30,0x18134bde0,0x18134bcf0,0x18134bce0,0x18134bc50,0x18134bc20,0x18058c900,0x18134bc10,0x18134bac0,0x18134bab0,0x18134ba90,0x18134ba80,0x18134ba70,0x18134ba60,0x18134add0,0x18134ba50,0x18134adc0,0x18134ba40,0x18134ba30,0x18134ba20,0x18134ba10,0x18134b940,0x18134b930,0x18134b870,0x18251a838,0x18058eaec,0x181a07140,0x18251a5b8,0x18058ea8c|",
|
||||||
|
".?AVListener@?$Soothe2ModuleBase@M$01@@|0x18250c588|0x1824b0fd0|0x180564360,0x18113285a,0x18250b740,0x180570e08,0x180565bd0,0x18250e180,0x18056ea10,0x18056ea10,0x18056f130,0x18056ea00,0x18048db00,0x180481210,0x18250cc30,0x180570ebc,0x180565f20,0x1804714b0,0x18250b4b8,0x18056e9e0,0x18056e9e0,0x18056f190,0x18056e9d0,0x18048db00,0x180481210,0x18250d408,0x180570eec,0x180568990,0x18250bfb0,0x18056eb60,0x18056eb60,0x18056eec0,0x18056eb50,0x18048db00,0x180481210,0x18250d060,0x180570df0,0x18055fda0,0x18250c538,0x180570ef8,0x18055fd70,0x18250bdf8,0x18056e980,0x18056e980,0x18056f230,0x18056e970,0x18048db00,0x180481210,0x18250e210,0x180570eb0|",
|
||||||
|
".?AVSoothe2AudioProcessor@@|0x182506ca0|0x1824abf60|0x1813658a0,0x18052c7e0,0x18053846c,0x182504fa0,0x180536f70,0x180536f70,0x180537160,0x180536f60,0x18048db00,0x180481210,0x182505208,0x180536670,0x180536670,0x1804714b0,0x180536660,0x18048db00,0x180481210,0x1825068b0,0x180536cc0,0x180536cc0,0x180537720,0x180536cb0,0x18048db00,0x180481210,0x182504e40,0x18113285a,0x18052c7e0,0x180538484,0x182506110,0x180528ba0,0x1804714b0,0x1804714b0,0x1825067d0,0x180536b90,0x180536b90,0x180537930,0x180536b80,0x18048dc80,0x180481210,0x182505680,0x180536c60,0x180536c60|",
|
||||||
|
".?AVSoothe2AudioProcessorEditor@@|0x18250e210|0x1824b1148|0x180570eb0,0x180565a70,0x18250d1a8,0x180570ec8,0x180562fd0,0x18250db38,0x180561240,0x181413fd0,0x18250ccb8,0x18056f690,0x18056f690,0x180537680,0x18056f680,0x18048db00,0x180481210,0x18250be20,0x18055e150,0x181639430,0x18055e070,0x181639320,0x1811d2340,0x18055e0a0,0x1816393e0,0x181639350,0x18250d4b0,0x18055e7e0,0x18055e5f0,0x18055e620,0x1811d2320,0x1811d2340,0x18055e650,0x18250dc28,0x18056ea70,0x18056ea70,0x18056f0a0,0x18056ea60,0x18048dc80,0x180481210,0x18250bbf0,0x18056f870,0x18056f870,0x1804714b0,0x18056f860,0x18048db00,0x180481210,0x18250bb00,0x1805619b0,0x18113285a|",
|
||||||
|
".?AVSoothe2DummyEqualizerWrapper@@|0x18250db38|0x1824b1178|0x180561240,0x181413fd0,0x18250ccb8,0x18056f690,0x18056f690,0x180537680,0x18056f680,0x18048db00,0x180481210,0x18250be20,0x18055e150,0x181639430,0x18055e070,0x181639320,0x1811d2340,0x18055e0a0,0x1816393e0,0x181639350,0x18250d4b0,0x18055e7e0,0x18055e5f0,0x18055e620,0x1811d2320,0x1811d2340,0x18055e650,0x18250dc28,0x18056ea70,0x18056ea70,0x18056f0a0,0x18056ea60,0x18048dc80,0x180481210,0x18250bbf0,0x18056f870,0x18056f870,0x1804714b0,0x18056f860,0x18048db00,0x180481210,0x18250bb00,0x1805619b0,0x18113285a,0x18113285a,0x18113285a,0x18113285a,0x18113285a,0x18113285a,0x18113285a|",
|
||||||
|
".?AVSoothe2FilterGraph@@|0x18251b090|0x1824be810|0x180589260,0x18134bc00,0x18058b4d0,0x18058b380,0x18134bbd0,0x18134bbc0,0x18134bbb0,0x18134bba0,0x18134bb30,0x18134bad0,0x18134c900,0x18134c760,0x18134c750,0x18134c3a0,0x18134c2d0,0x18134c2c0,0x18134c270,0x1805689c0,0x18134bf00,0x18134be30,0x18134bde0,0x18134bcf0,0x18134bce0,0x18134bc50,0x18134bc20,0x18058ad80,0x18134bc10,0x18134bac0,0x18134bab0,0x18134ba90,0x18134ba80,0x18134ba70,0x18134ba60,0x18058af50,0x18134ba50,0x18134adc0,0x18134ba40,0x18134ba30,0x18134ba20,0x18134ba10,0x18134b940,0x18134b930,0x18134b870,0x1804714b0,0x181a06ff0,0x1804714b0,0x181a07170,0x18251a8e0|",
|
||||||
|
".?AVSoothe2GainRangeScalingOption@@|0x18250be20|0x1824b11c8|0x18055e150,0x181639430,0x18055e070,0x181639320,0x1811d2340,0x18055e0a0,0x1816393e0,0x181639350,0x18250d4b0,0x18055e7e0,0x18055e5f0,0x18055e620,0x1811d2320,0x1811d2340,0x18055e650,0x18250dc28,0x18056ea70,0x18056ea70,0x18056f0a0,0x18056ea60,0x18048dc80,0x180481210,0x18250bbf0,0x18056f870,0x18056f870,0x1804714b0,0x18056f860,0x18048db00,0x180481210,0x18250bb00,0x1805619b0,0x18113285a,0x18113285a,0x18113285a,0x18113285a,0x18113285a,0x18113285a,0x18113285a,0x18250b718,0x180570e80,0x180563430,0x18250d260,0x180570e50,0x1813d3b10,0x18250ca40,0x18056f710,0x18056f710,0x18056fb60|",
|
||||||
|
".?AVSoothe2Grid@@|0x18250b450|0x1824b1ac8|0x1805649b0,0x1805644b0,0x1805644c0,0x1804714b0,0x1804731c0,0x1805615b0,0x1805644e0,0x180564530,0x18250e130,0x18113285a,0x180560370,0x18250bd78,0x18056f770,0x18056f770,0x18056fb00,0x18056f760,0x18048db00,0x180481210,0x18250c640,0x18055e9f0,0x1813489b0,0x1813489a0,0x181348990,0x181348980,0x181348970,0x181348960,0x181411fb0,0x181348940,0x18250ba20,0x18056ebf0,0x18056ebf0,0x18056ee00,0x18056ebe0,0x18048db00,0x180481210,0x18250bf58,0x18056e8c0,0x18056e8c0,0x18056f540,0x18056e8b0,0x18048dc80,0x180481210,0x18250d0d8,0x18056f660,0x18056f660,0x18056fd00,0x18056f650,0x18048db00|",
|
||||||
|
".?AVSoothe2Header@@|0x18251d498|0x1824c24f8|0x18059d1e0,0x18134bc00,0x18134bbf0,0x18134bbe0,0x18134bbd0,0x18134bbc0,0x18134bbb0,0x18134bba0,0x18134bb30,0x18134bad0,0x18134c900,0x18134c760,0x18134c750,0x18134c3a0,0x18134c2d0,0x18134c2c0,0x18134c270,0x1805689c0,0x18134bf00,0x18134be30,0x18134bde0,0x18134bcf0,0x18134bce0,0x18134bc50,0x18134bc20,0x18059cc40,0x18134bc10,0x18134bac0,0x18134bab0,0x18134ba90,0x18134ba80,0x18134ba70,0x18134ba60,0x18059cb60,0x18134ba50,0x18134adc0,0x18134ba40,0x18134ba30,0x18134ba20,0x18134ba10,0x18134b940,0x18134b930,0x18134b870,0x1804714b0,0x18251d388,0x18059e974,0x180483fd0,0x1804e4950|",
|
||||||
|
".?AVSoothe2ModuleDecryptor@@|0x182506528|0x1824abe68|0x18052d520,0x1813e2a60,0x182505230,0x180536a50,0x180536a50,0x180537680,0x180536a40,0x18048db00,0x180481210,0x182505f28,0x180536be0,0x180536be0,0x180537680,0x180536bd0,0x18048db00,0x180481210,0x182505d30,0x180536d90,0x180536d90,0x180537590,0x180536d80,0x18048db00,0x180481210,0x182506ca0,0x1813658a0,0x18052c7e0,0x18053846c,0x182504fa0,0x180536f70,0x180536f70,0x180537160,0x180536f60,0x18048db00,0x180481210,0x182505208,0x180536670,0x180536670,0x1804714b0,0x180536660,0x18048db00|",
|
||||||
|
".?AVSoothe2SectionHeader@@|0x18251bbe0|0x1824bf510|0x1805905e0,0x18134bc00,0x18134bbf0,0x18134bbe0,0x18134bbd0,0x18134bbc0,0x18134bbb0,0x18134bba0,0x18134bb30,0x18134bad0,0x18134c900,0x18134c760,0x18134c750,0x18134c3a0,0x18134c2d0,0x18134c2c0,0x18134c270,0x1805689c0,0x18134bf00,0x18134be30,0x18134bde0,0x18134bcf0,0x18134bce0,0x18134bc50,0x18134bc20,0x180590350,0x18134bc10,0x18134bac0,0x18134bab0,0x18134ba90,0x18134ba80,0x18134ba70,0x18134ba60,0x18058ff10,0x18134ba50,0x18134adc0,0x18134ba40,0x18134ba30,0x18134ba20,0x18134ba10,0x18134b940,0x18134b930,0x18134b870,0x1804714b0,0x18251b890,0x180591ee0,0x180591ee0,0x1805920b0|",
|
||||||
|
".?AVSoothe2XYSliderArea@@|0x18251b440|0x1824beb10|0x18058ebf0,0x18134bc00,0x18057f450,0x18057f4f0,0x18158a900,0x181585b70,0x1815810c0,0x18134bba0,0x181585a90,0x18134bad0,0x18134c900,0x18134c760,0x18134c750,0x18134c3a0,0x18134c2d0,0x18134c2c0,0x18134c270,0x18058ee90,0x18134bf00,0x18134be30,0x18134bde0,0x18134bcf0,0x18134bce0,0x18134bc50,0x18134bc20,0x18134ade0,0x18134bc10,0x18134bac0,0x18134bab0,0x18156b440,0x18134ba80,0x18134ba70,0x18134ba60,0x181612000,0x18134ba50,0x18134adc0,0x18134ba40,0x18134ba30,0x18134ba20,0x18134ba10,0x18134b940,0x18134b930,0x18134b870,0x1816139e0,0x1804714b0,0x18251b3c8,0x18058efac,0x180568990|",
|
||||||
|
".?AVSoothe2_DynamicCryptoManager@eden@pace@@|0x18250ed68|0x1824b2588|0x180575c40,0x18141b840,0x18141b5e0,0x18250eee8,0x180575990,0x182510fb8,0x18057c6f0,0x18057c6f0,0x1804714b0,0x18057c6e0,0x18048db00,0x180481210,0x18250f310,0x18057ed84,0x180568990,0x18250fb38,0x18057d4b0,0x18057d4b0,0x18057db80,0x18057d4a0,0x18048db00,0x180481210,0x182510490,0x18057bb00,0x18134bc00,0x181336710,0x1813366a0,0x1813365f0,0x1813363e0,0x1813364a0,0x18134bba0,0x18134bb30,0x18134bad0,0x18134c900,0x18134c760,0x1813362b0,0x18134c3a0,0x18134c2d0,0x18134c2c0,0x18134c270,0x180579330,0x18134bf00,0x18134be30,0x18134bde0,0x18134bcf0,0x181336f10,0x18134bc50,0x18134bc20|",
|
||||||
|
};
|
||||||
|
SymbolTable st = currentProgram.getSymbolTable();
|
||||||
|
FunctionManager fm = currentProgram.getFunctionManager();
|
||||||
|
int lab = 0, fn = 0;
|
||||||
|
for (String line : data) {
|
||||||
|
if (line.trim().isEmpty()) continue;
|
||||||
|
String[] parts = line.replace(",|", "|").split("\\|");
|
||||||
|
String cls = parts[0];
|
||||||
|
long col = Long.decode(parts[1]);
|
||||||
|
long vf = Long.decode(parts[2]);
|
||||||
|
String[] fptrRaw = parts[3].split(",");
|
||||||
|
Namespace ns = st.getNamespace(cls, currentProgram.getGlobalNamespace());
|
||||||
|
if (ns == null) {
|
||||||
|
ns = st.createClass(null, cls, SourceType.USER_DEFINED);
|
||||||
|
lab++;
|
||||||
|
}
|
||||||
|
for (String fs : fptrRaw) {
|
||||||
|
if (fs.isEmpty()) continue;
|
||||||
|
long f = Long.decode(fs);
|
||||||
|
Address addr = currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(f);
|
||||||
|
if (fm.getFunctionAt(addr) == null) {
|
||||||
|
try {
|
||||||
|
fm.createFunction(null, addr, null, SourceType.USER_DEFINED);
|
||||||
|
fn++;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// function may already exist as part of larger body; still label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
st.createLabel(addr, "vtbl_" + cls, ns, SourceType.USER_DEFINED);
|
||||||
|
lab++;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore slash/illegal names
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println("IMPORT_RTTI_NAMESPACES " + lab);
|
||||||
|
println("IMPORT_RTTI_FUNCTIONS " + fn);
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1,392 @@
|
|||||||
|
==================== 0x180536f70 vtbl_.?AVSoothe2AudioProcessor@@ size=22 refs=2 ====
|
||||||
|
|
||||||
|
undefined8 *
|
||||||
|
.?AVSoothe2AudioProcessor@@::vtbl___AVSoothe2AudioProcessor__(longlong param_1,undefined8 *param_2)
|
||||||
|
|
||||||
|
{
|
||||||
|
*param_2 = std::_Func_impl_no_alloc<<lambda_98f7f914cf7ea0a1749f276be9d3c260>,juce::String,float>
|
||||||
|
::vftable;
|
||||||
|
param_2[1] = *(undefined8 *)(param_1 + 8);
|
||||||
|
return param_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
==================== 0x180537160 vtbl_.?AVSoothe2AudioProcessor@@ size=122 refs=2 ====
|
||||||
|
|
||||||
|
undefined8
|
||||||
|
.?AVSoothe2AudioProcessor@@::vtbl___AVSoothe2AudioProcessor__
|
||||||
|
(longlong param_1,undefined8 param_2,undefined4 *param_3)
|
||||||
|
|
||||||
|
{
|
||||||
|
undefined4 uVar1;
|
||||||
|
undefined8 uVar2;
|
||||||
|
longlong lVar3;
|
||||||
|
|
||||||
|
uVar1 = *param_3;
|
||||||
|
uVar2 = FUN_18113e800(param_1,0xff547ddd,&DAT_1824aba30,0xb);
|
||||||
|
lVar3 = FUN_18119aa60(*(undefined8 *)(param_1 + 8),uVar2);
|
||||||
|
FUN_18141a8b0(param_2,uVar1,0.0 < *(float *)(lVar3 + 0x188));
|
||||||
|
return param_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
==================== 0x180536f60 vtbl_.?AVSoothe2AudioProcessor@@ size=8 refs=1 ====
|
||||||
|
|
||||||
|
TypeDescriptor * .?AVSoothe2AudioProcessor@@::vtbl___AVSoothe2AudioProcessor__(void)
|
||||||
|
|
||||||
|
{
|
||||||
|
return &<lambda_98f7f914cf7ea0a1749f276be9d3c260>::RTTI_Type_Descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
==================== 0x180536670 vtbl_.?AVSoothe2AudioProcessor@@ size=22 refs=2 ====
|
||||||
|
|
||||||
|
undefined8 *
|
||||||
|
.?AVSoothe2AudioProcessor@@::vtbl___AVSoothe2AudioProcessor__(longlong param_1,undefined8 *param_2)
|
||||||
|
|
||||||
|
{
|
||||||
|
*param_2 = std::_Func_impl_no_alloc<<lambda_ecbeee6575be61b12745f0f1713120e9>,void,float>::vftable
|
||||||
|
;
|
||||||
|
param_2[1] = *(undefined8 *)(param_1 + 8);
|
||||||
|
return param_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
==================== 0x180536660 vtbl_.?AVSoothe2AudioProcessor@@ size=8 refs=1 ====
|
||||||
|
|
||||||
|
TypeDescriptor * .?AVSoothe2AudioProcessor@@::vtbl___AVSoothe2AudioProcessor__(void)
|
||||||
|
|
||||||
|
{
|
||||||
|
return &<lambda_ecbeee6575be61b12745f0f1713120e9>::RTTI_Type_Descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
==================== 0x180536cc0 vtbl_.?AVSoothe2AudioProcessor@@ size=22 refs=2 ====
|
||||||
|
|
||||||
|
undefined8 *
|
||||||
|
.?AVSoothe2AudioProcessor@@::vtbl___AVSoothe2AudioProcessor__(longlong param_1,undefined8 *param_2)
|
||||||
|
|
||||||
|
{
|
||||||
|
*param_2 = std::_Func_impl_no_alloc<<lambda_de997d66c89a5ac0466663dd9df945b7>,void,float>::vftable
|
||||||
|
;
|
||||||
|
param_2[1] = *(undefined8 *)(param_1 + 8);
|
||||||
|
return param_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
==================== 0x180537720 vtbl_.?AVSoothe2AudioProcessor@@ size=24 refs=1 ====
|
||||||
|
|
||||||
|
void .?AVSoothe2AudioProcessor@@::vtbl___AVSoothe2AudioProcessor__
|
||||||
|
(longlong param_1,undefined4 *param_2)
|
||||||
|
|
||||||
|
{
|
||||||
|
/* WARNING: Could not recover jumptable at 0x000180537731. Too many branches */
|
||||||
|
/* WARNING: Treating indirect jump as call */
|
||||||
|
(**(code **)(*(longlong *)**(undefined8 **)(param_1 + 8) + 0xc0))
|
||||||
|
((longlong *)**(undefined8 **)(param_1 + 8),*param_2,0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
==================== 0x180536cb0 vtbl_.?AVSoothe2AudioProcessor@@ size=8 refs=1 ====
|
||||||
|
|
||||||
|
TypeDescriptor * .?AVSoothe2AudioProcessor@@::vtbl___AVSoothe2AudioProcessor__(void)
|
||||||
|
|
||||||
|
{
|
||||||
|
return &<lambda_de997d66c89a5ac0466663dd9df945b7>::RTTI_Type_Descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
==================== 0x180536b90 vtbl_.?AVSoothe2AudioProcessor@@ size=22 refs=2 ====
|
||||||
|
|
||||||
|
undefined8 *
|
||||||
|
.?AVSoothe2AudioProcessor@@::vtbl___AVSoothe2AudioProcessor__(longlong param_1,undefined8 *param_2)
|
||||||
|
|
||||||
|
{
|
||||||
|
undefined8 uVar1;
|
||||||
|
|
||||||
|
*param_2 = std::_Func_impl_no_alloc<<lambda_5a69e3fa1ed5b6db915c3c1c90fd9482>,void,float>::vftable
|
||||||
|
;
|
||||||
|
uVar1 = *(undefined8 *)(param_1 + 0x10);
|
||||||
|
param_2[1] = *(undefined8 *)(param_1 + 8);
|
||||||
|
param_2[2] = uVar1;
|
||||||
|
return param_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
==================== 0x180537930 vtbl_.?AVSoothe2AudioProcessor@@ size=104 refs=1 ====
|
||||||
|
|
||||||
|
void .?AVSoothe2AudioProcessor@@::vtbl___AVSoothe2AudioProcessor__
|
||||||
|
(longlong param_1,undefined4 *param_2)
|
||||||
|
|
||||||
|
{
|
||||||
|
uint uVar1;
|
||||||
|
longlong lVar2;
|
||||||
|
longlong lVar3;
|
||||||
|
longlong lVar4;
|
||||||
|
|
||||||
|
uVar1 = *(uint *)(param_1 + 8);
|
||||||
|
lVar2 = *(longlong *)(*(longlong *)(param_1 + 0x10) + 0x168);
|
||||||
|
lVar3 = lVar2 + 0x3d8;
|
||||||
|
if (uVar1 < *(uint *)(lVar2 + 0x444)) {
|
||||||
|
lVar4 = *(longlong *)(*(longlong *)(lVar2 + 0x438) + (longlong)(int)uVar1 * 8);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lVar4 = 0;
|
||||||
|
}
|
||||||
|
*(undefined4 *)(lVar4 + 0x808) = *param_2;
|
||||||
|
if ((int)uVar1 < 0) {
|
||||||
|
*(undefined4 *)(lVar2 + 0x240467) = 0x1010101;
|
||||||
|
*(undefined4 *)(lVar2 + 0x24046b) = 0x1010101;
|
||||||
|
FUN_18052fc30(lVar3);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*(undefined1 *)((longlong)(int)uVar1 + 0x24008f + lVar3) = 1;
|
||||||
|
FUN_18052fc30(lVar3);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
==================== 0x180536b80 vtbl_.?AVSoothe2AudioProcessor@@ size=8 refs=1 ====
|
||||||
|
|
||||||
|
TypeDescriptor * .?AVSoothe2AudioProcessor@@::vtbl___AVSoothe2AudioProcessor__(void)
|
||||||
|
|
||||||
|
{
|
||||||
|
return &<lambda_5a69e3fa1ed5b6db915c3c1c90fd9482>::RTTI_Type_Descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
==================== 0x180536c60 vtbl_.?AVSoothe2AudioProcessor@@ size=22 refs=2 ====
|
||||||
|
|
||||||
|
undefined8 *
|
||||||
|
.?AVSoothe2AudioProcessor@@::vtbl___AVSoothe2AudioProcessor__(longlong param_1,undefined8 *param_2)
|
||||||
|
|
||||||
|
{
|
||||||
|
*param_2 = std::_Func_impl_no_alloc<<lambda_4a5d3203691b7e65cdc50015b0597c78>,void,float>::vftable
|
||||||
|
;
|
||||||
|
param_2[1] = *(undefined8 *)(param_1 + 8);
|
||||||
|
return param_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
==================== 0x1813658a0 vtbl_.?AVSoothe2AudioProcessor@@ size=1221 refs=2 ====
|
||||||
|
|
||||||
|
/* WARNING: Function: _alloca_probe replaced with injection: alloca_probe */
|
||||||
|
/* WARNING: Removing unreachable block (ram,0x000181365a33) */
|
||||||
|
/* WARNING: Removing unreachable block (ram,0x000181365a1a) */
|
||||||
|
/* WARNING: Removing unreachable block (ram,0x0001813659cb) */
|
||||||
|
/* WARNING: Removing unreachable block (ram,0x000181365a49) */
|
||||||
|
|
||||||
|
void .?AVSoothe2AudioProcessor@@::vtbl___AVSoothe2AudioProcessor__(longlong param_1)
|
||||||
|
|
||||||
|
{
|
||||||
|
undefined8 uVar1;
|
||||||
|
ulonglong uVar2;
|
||||||
|
uint uVar3;
|
||||||
|
int iVar4;
|
||||||
|
undefined8 uVar5;
|
||||||
|
undefined8 uVar6;
|
||||||
|
undefined8 in_R11;
|
||||||
|
int unaff_retaddr;
|
||||||
|
undefined1 auStack_3ae8 [112];
|
||||||
|
longlong local_3a78;
|
||||||
|
longlong local_3a60;
|
||||||
|
longlong local_3a00;
|
||||||
|
longlong local_39e8;
|
||||||
|
undefined4 local_3980;
|
||||||
|
uint local_397c;
|
||||||
|
undefined4 local_3978;
|
||||||
|
undefined8 local_3288;
|
||||||
|
undefined1 local_568 [24];
|
||||||
|
undefined1 local_550 [64];
|
||||||
|
undefined1 local_510 [64];
|
||||||
|
undefined1 local_4d0 [72];
|
||||||
|
undefined1 local_488 [24];
|
||||||
|
undefined1 local_470 [64];
|
||||||
|
undefined1 local_430 [64];
|
||||||
|
undefined1 local_3f0 [832];
|
||||||
|
ulonglong local_b0;
|
||||||
|
|
||||||
|
local_3288 = 0xfffffffffffffffe;
|
||||||
|
local_b0 = DAT_182615970 ^ (ulonglong)auStack_3ae8;
|
||||||
|
local_3980 = 0xf40;
|
||||||
|
uVar5 = 0;
|
||||||
|
uVar6 = 0x23d7;
|
||||||
|
uVar3 = (uint)&stack0x00000000 & 0x33f;
|
||||||
|
iVar4 = (uVar3 + 2) * (uVar3 + 1) * uVar3;
|
||||||
|
local_397c = 0x2877U - (((iVar4 >> 1) - (iVar4 >> 3)) + iVar4 * 0x55555555 >> 0x1e) ^ 0x23d7;
|
||||||
|
local_3a00 = param_1;
|
||||||
|
uVar1 = FUN_180529410(local_568,DAT_1824c442c,DAT_1824c45e0,DAT_1824c3ea4);
|
||||||
|
local_3a60 = param_1 + 0x5c0;
|
||||||
|
FUN_1805291a0(local_3a60,uVar1);
|
||||||
|
FUN_180473170(local_4d0);
|
||||||
|
FUN_180473170(local_510);
|
||||||
|
FUN_180473170(local_550);
|
||||||
|
uVar2 = FUN_180487470(local_488,DAT_1824c4724,DAT_1824c4408);
|
||||||
|
local_3a78 = param_1 + 0x698;
|
||||||
|
FUN_1805291a0(local_3a78,uVar2);
|
||||||
|
FUN_180473170(local_3f0);
|
||||||
|
FUN_180473170(local_430);
|
||||||
|
FUN_180473170(local_470);
|
||||||
|
do {
|
||||||
|
local_3978 = 0x26d2ef6;
|
||||||
|
uVar3 = (unaff_retaddr + 1) * unaff_retaddr & 0x80000001;
|
||||||
|
if ((int)uVar3 < 0) {
|
||||||
|
uVar3 = (uVar3 - 1 | 0xfffffffe) + 1;
|
||||||
|
}
|
||||||
|
} while (uVar3 != 0);
|
||||||
|
local_39e8 = param_1 + 0x30;
|
||||||
|
FUN_1813c975b(0,((~(~uVar2 + 1) + 1 ^ 0xffffffffffffffff) + 1 ^ 0xffffffffffffffff) + 1,uVar5,
|
||||||
|
uVar6,&LAB_1813cf09f,&DAT_181365dbb,0x905206f8,in_R11,0x7bf54441);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
==================== 0x18052c7e0 vtbl_.?AVSoothe2AudioProcessor@@ size=12 refs=2 ====
|
||||||
|
|
||||||
|
void .?AVSoothe2AudioProcessor@@::vtbl___AVSoothe2AudioProcessor__(longlong param_1)
|
||||||
|
|
||||||
|
{
|
||||||
|
FUN_18115fa70(param_1 + 0x2b8);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
==================== 0x18053846c vtbl_.?AVSoothe2AudioProcessor@@ size=12 refs=1 ====
|
||||||
|
|
||||||
|
void .?AVSoothe2AudioProcessor@@::vtbl___AVSoothe2AudioProcessor__(longlong param_1)
|
||||||
|
|
||||||
|
{
|
||||||
|
.?AV?$ModuleDecryptor@VSoothe2_DynamicCryptoManager@eden@pace@@@@::
|
||||||
|
vtbl___AV__ModuleDecryptor_VSoothe2_DynamicCryptoManager_eden_pace____(param_1 + -0x148);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
=== 0x1852000000 : NOT A FUNCTION ===
|
||||||
|
|
||||||
|
==================== 0x18052c7e0 vtbl_.?AVSoothe2AudioProcessor@@ size=12 refs=2 ====
|
||||||
|
|
||||||
|
void .?AVSoothe2AudioProcessor@@::vtbl___AVSoothe2AudioProcessor__(longlong param_1)
|
||||||
|
|
||||||
|
{
|
||||||
|
FUN_18115fa70(param_1 + 0x2b8);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
==================== 0x180538484 vtbl_.?AVSoothe2AudioProcessor@@ size=12 refs=1 ====
|
||||||
|
|
||||||
|
void .?AVSoothe2AudioProcessor@@::vtbl___AVSoothe2AudioProcessor__(longlong param_1)
|
||||||
|
|
||||||
|
{
|
||||||
|
.?AV?$SpectralProcessor@M$07$01@@::vtbl___AV__SpectralProcessor_M_07_01__(param_1 + -0x148);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
==================== 0x180536b60 vtbl_.?AV?$CloudyAudioProcessor@V?$Soothe2ModuleBase@M$01@@VSoothe2ModuleDecryptor@@VParameterStates@@M@@ size=22 refs=2 ====
|
||||||
|
|
||||||
|
undefined8 *
|
||||||
|
.?AV?$CloudyAudioProcessor@V?$Soothe2ModuleBase@M$01@@VSoothe2ModuleDecryptor@@VParameterStates@@M@@
|
||||||
|
::
|
||||||
|
vtbl___AV__CloudyAudioProcessor_V__Soothe2ModuleBase_M_01__VSoothe2ModuleDecryptor__VParameterStates__M__
|
||||||
|
(longlong param_1,undefined8 *param_2)
|
||||||
|
|
||||||
|
{
|
||||||
|
undefined8 uVar1;
|
||||||
|
|
||||||
|
*param_2 = std::_Func_impl_no_alloc<<lambda_31112e0b8380e26aebb588356a79bbf7>,void,float>::vftable
|
||||||
|
;
|
||||||
|
uVar1 = *(undefined8 *)(param_1 + 0x10);
|
||||||
|
param_2[1] = *(undefined8 *)(param_1 + 8);
|
||||||
|
param_2[2] = uVar1;
|
||||||
|
return param_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
==================== 0x1805379a0 vtbl_.?AV?$CloudyAudioProcessor@V?$Soothe2ModuleBase@M$01@@VSoothe2ModuleDecryptor@@VParameterStates@@M@@ size=104 refs=1 ====
|
||||||
|
|
||||||
|
void .?AV?$CloudyAudioProcessor@V?$Soothe2ModuleBase@M$01@@VSoothe2ModuleDecryptor@@VParameterStates@@M@@
|
||||||
|
::
|
||||||
|
vtbl___AV__CloudyAudioProcessor_V__Soothe2ModuleBase_M_01__VSoothe2ModuleDecryptor__VParameterStates__M__
|
||||||
|
(longlong param_1,undefined4 *param_2)
|
||||||
|
|
||||||
|
{
|
||||||
|
uint uVar1;
|
||||||
|
longlong lVar2;
|
||||||
|
longlong lVar3;
|
||||||
|
longlong lVar4;
|
||||||
|
|
||||||
|
uVar1 = *(uint *)(param_1 + 8);
|
||||||
|
lVar2 = *(longlong *)(*(longlong *)(param_1 + 0x10) + 0x168);
|
||||||
|
lVar3 = lVar2 + 0x3d8;
|
||||||
|
if (uVar1 < *(uint *)(lVar2 + 0x444)) {
|
||||||
|
lVar4 = *(longlong *)(*(longlong *)(lVar2 + 0x438) + (longlong)(int)uVar1 * 8);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lVar4 = 0;
|
||||||
|
}
|
||||||
|
*(undefined4 *)(lVar4 + 0x80c) = *param_2;
|
||||||
|
if ((int)uVar1 < 0) {
|
||||||
|
*(undefined4 *)(lVar2 + 0x240467) = 0x1010101;
|
||||||
|
*(undefined4 *)(lVar2 + 0x24046b) = 0x1010101;
|
||||||
|
FUN_18052fc30(lVar3);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*(undefined1 *)((longlong)(int)uVar1 + 0x24008f + lVar3) = 1;
|
||||||
|
FUN_18052fc30(lVar3);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
==================== 0x180536b50 vtbl_.?AV?$CloudyAudioProcessor@V?$Soothe2ModuleBase@M$01@@VSoothe2ModuleDecryptor@@VParameterStates@@M@@ size=8 refs=1 ====
|
||||||
|
|
||||||
|
TypeDescriptor *
|
||||||
|
.?AV?$CloudyAudioProcessor@V?$Soothe2ModuleBase@M$01@@VSoothe2ModuleDecryptor@@VParameterStates@@M@@
|
||||||
|
::
|
||||||
|
vtbl___AV__CloudyAudioProcessor_V__Soothe2ModuleBase_M_01__VSoothe2ModuleDecryptor__VParameterStates__M__
|
||||||
|
(void)
|
||||||
|
|
||||||
|
{
|
||||||
|
return &<lambda_31112e0b8380e26aebb588356a79bbf7>::RTTI_Type_Descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
==================== 0x180536850 vtbl_.?AV?$CloudyAudioProcessor@V?$Soothe2ModuleBase@M$01@@VSoothe2ModuleDecryptor@@VParameterStates@@M@@ size=22 refs=2 ====
|
||||||
|
|
||||||
|
undefined8 *
|
||||||
|
.?AV?$CloudyAudioProcessor@V?$Soothe2ModuleBase@M$01@@VSoothe2ModuleDecryptor@@VParameterStates@@M@@
|
||||||
|
::
|
||||||
|
vtbl___AV__CloudyAudioProcessor_V__Soothe2ModuleBase_M_01__VSoothe2ModuleDecryptor__VParameterStates__M__
|
||||||
|
(longlong param_1,undefined8 *param_2)
|
||||||
|
|
||||||
|
{
|
||||||
|
*param_2 = std::_Func_impl_no_alloc<<lambda_9e6d435da2f4f474e078479ad72b1896>,void,float>::vftable
|
||||||
|
;
|
||||||
|
param_2[1] = *(undefined8 *)(param_1 + 8);
|
||||||
|
return param_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+6634
File diff suppressed because it is too large
Load Diff
+2116
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -0,0 +1,70 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import frida, sys, struct, time
|
||||||
|
|
||||||
|
TARGET = "yabridge-host"
|
||||||
|
OUT = "/home/m/re-tools/soothe_mem.bin"
|
||||||
|
|
||||||
|
js = r"""
|
||||||
|
const name = "soothe2_x64.vst3";
|
||||||
|
let mod = null;
|
||||||
|
for (const m of Process.enumerateModules()) {
|
||||||
|
if (m.name.toLowerCase().includes(name)) { mod = m; break; }
|
||||||
|
}
|
||||||
|
if (!mod) {
|
||||||
|
for (const m of Process.enumerateModules()) {
|
||||||
|
if (m.path.toLowerCase().includes("soothe")) { mod = m; break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!mod) { send({err: "soothe module not found"}); }
|
||||||
|
else {
|
||||||
|
send({base: mod.base.toString(), size: mod.size, path: mod.path, name: mod.name});
|
||||||
|
const buf = new ArrayBuffer(mod.size);
|
||||||
|
try {
|
||||||
|
const r = Memory.readByteArray(mod.base, mod.size);
|
||||||
|
buf = r;
|
||||||
|
send({ok: true, size: mod.size}, buf ? buf : new ArrayBuffer(0));
|
||||||
|
} catch (e) { send({err: "readByteArray: " + e}); }
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def main():
|
||||||
|
dev = frida.get_local_device()
|
||||||
|
procs = dev.enumerate_processes()
|
||||||
|
target = None
|
||||||
|
for p in procs:
|
||||||
|
if TARGET in p.name and ("yabridge" in p.name or "wine" in p.name):
|
||||||
|
target = p
|
||||||
|
if target is None:
|
||||||
|
print("target process not found:")
|
||||||
|
for p in procs:
|
||||||
|
if "wine" in p.name or "yabridge" in p.name or "reaper" in p.name:
|
||||||
|
print(" ", p.pid, p.name)
|
||||||
|
sys.exit(1)
|
||||||
|
print("attaching to", target.pid, target.name)
|
||||||
|
session = dev.attach(target.pid)
|
||||||
|
done = {}
|
||||||
|
def on_message(msg, data):
|
||||||
|
if msg["type"] == "send":
|
||||||
|
payload = msg["payload"]
|
||||||
|
if isinstance(payload, dict) and "err" in payload:
|
||||||
|
print("ERR:", payload["err"])
|
||||||
|
done["err"] = payload
|
||||||
|
return
|
||||||
|
if payload.get("ok"):
|
||||||
|
with open(OUT, "wb") as f:
|
||||||
|
f.write(data)
|
||||||
|
print("DUMPED", len(data), "bytes to", OUT)
|
||||||
|
done["dumped"] = len(data)
|
||||||
|
else:
|
||||||
|
print("module:", payload)
|
||||||
|
elif msg["type"] == "error":
|
||||||
|
print("JS ERR:", msg.get("description"))
|
||||||
|
script = session.create_script(js)
|
||||||
|
script.on("message", on_message)
|
||||||
|
script.load()
|
||||||
|
for _ in range(60):
|
||||||
|
if done: break
|
||||||
|
time.sleep(0.2)
|
||||||
|
session.detach()
|
||||||
|
|
||||||
|
main()
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Fit parametric curves (v2): amount_total = A(depth)*S(sens)*H(sharp).
|
||||||
|
S normalized so S(12)=1, H(10)=1. A→1 as depth→large.
|
||||||
|
Saturation form: f(x)=1-exp(-(x/k)^m).
|
||||||
|
"""
|
||||||
|
import numpy as np
|
||||||
|
from scipy.optimize import curve_fit
|
||||||
|
|
||||||
|
def sat(x, k, m):
|
||||||
|
return 1 - np.exp(-(x / k) ** m)
|
||||||
|
|
||||||
|
def to_amount(red):
|
||||||
|
return 1 - 10 ** (-red / 20.0)
|
||||||
|
|
||||||
|
def fit(name, x, y, p0):
|
||||||
|
popt, _ = curve_fit(sat, x, y, p0=p0, maxfev=50000)
|
||||||
|
pred = sat(x, *popt)
|
||||||
|
print(f"{name}: k={popt[0]:.4f} m={popt[1]:.3f} RMSE={np.sqrt(np.mean((pred-y)**2)):.5f}")
|
||||||
|
for xi, yi, pi in zip(x, y, pred):
|
||||||
|
print(f" x={xi:6.2f} meas={yi:.4f} pred={pi:.4f}")
|
||||||
|
return popt
|
||||||
|
|
||||||
|
A_def = to_amount(7.70)
|
||||||
|
|
||||||
|
# A: depth sweep at S=H=1 (sens12 sharp10)
|
||||||
|
x = np.array([0.5, 0.864, 1.0, 2.0, 3.0, 5.0, 10.0, 20.0], float)
|
||||||
|
y = np.array([to_amount(d) for d in [7.14, 7.70, 7.91, 9.56, 11.29, 14.89, 24.25, 39.41]])
|
||||||
|
ka = fit('A(depth)', x, y, [3.0, 0.8])
|
||||||
|
|
||||||
|
# S: sens at depth0.864 (S saturates -> S(12)=1). amount = A_def * S
|
||||||
|
# S has a floor at sens=0 (still 2.89dB): S(x) = s0 + (1-s0)*sat(x)
|
||||||
|
def S(x, s0, k, m):
|
||||||
|
return s0 + (1 - s0) * sat(x, k, m)
|
||||||
|
x = np.array([0.0, 6.0, 12.0, 24.0, 48.0], float)
|
||||||
|
y = np.array([to_amount(d) for d in [2.89, 5.14, 7.70, 7.70, 7.70]]) / A_def
|
||||||
|
popt, _ = curve_fit(S, x, y, p0=[0.4, 3.0, 3.0], maxfev=50000)
|
||||||
|
pred = S(x, *popt)
|
||||||
|
print(f"S(sens): s0={popt[0]:.4f} k={popt[1]:.4f} m={popt[2]:.3f} RMSE={np.sqrt(np.mean((pred-y)**2)):.5f}")
|
||||||
|
for xi, yi, pi in zip(x, y, pred):
|
||||||
|
print(f" x={xi:6.2f} meas={yi:.4f} pred={pi:.4f}")
|
||||||
|
ks = popt
|
||||||
|
|
||||||
|
# H: sharp at depth0.864 -> H(10)=1
|
||||||
|
x = np.array([1.0, 3.0, 5.0, 10.0, 20.0], float)
|
||||||
|
y = np.array([to_amount(d) for d in [1.11, 3.71, 5.90, 7.70, 7.70]]) / A_def
|
||||||
|
kh = fit('H(sharp)/norm', x, y, [3.0, 1.5])
|
||||||
|
|
||||||
|
print("\ncross-check amount_total = A*S*H at default:")
|
||||||
|
print(f" A(0.864)*S(12)*H(10) = {sat(0.864,*ka):.4f}*1*1 -> red {(-20*np.log10(1-sat(0.864,*ka))):.2f} dB (meas 7.70)")
|
||||||
|
print(f" depth=10 sens=0: A(10)*S(0) = {sat(10,*ka):.4f}*{sat(0,*ks):.4f} = {sat(10,*ka)*sat(0,*ks):.4f} -> {( -20*np.log10(1-sat(10,*ka)*sat(0,*ks))):.2f} dB")
|
||||||
|
np.savez('/home/m/re-tools/curve_fits.npz',
|
||||||
|
k_depth=ka, k_sens=ks, k_sharp=kh, A_def=A_def)
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
from ghidra.program.model.listing import CodeUnit
|
||||||
|
from ghidra.program.model.symbol import SymbolTable, SymbolType, SourceType
|
||||||
|
from ghidra.program.model.data import DataUtilities
|
||||||
|
|
||||||
|
program = getCurrentProgram()
|
||||||
|
listing = program.getListing()
|
||||||
|
symtab = program.getSymbolTable()
|
||||||
|
|
||||||
|
def p(*a):
|
||||||
|
out = ",".join(str(x) for x in a)
|
||||||
|
print(out)
|
||||||
|
|
||||||
|
# 1. functions count
|
||||||
|
funcs = listing.getFunctions(True)
|
||||||
|
n = 0
|
||||||
|
for f in funcs:
|
||||||
|
n += 1
|
||||||
|
p("FUNCTION_COUNT", n)
|
||||||
|
|
||||||
|
# 2. symbols matching DSP keywords
|
||||||
|
import re
|
||||||
|
keys = ["Soothe2", "Spectral", "FilterGraph", "DigitalFilter", "IIRFilter", "LowPass", "ModuleBase", "Decryptor", "processBlock", "ProcessData"]
|
||||||
|
syms = symtab.getAllSymbols(True)
|
||||||
|
shown = set()
|
||||||
|
for s in syms:
|
||||||
|
name = s.getName(True) or s.getName()
|
||||||
|
for k in keys:
|
||||||
|
if k.lower() in name.lower():
|
||||||
|
key = (name, str(s.getAddress()), s.getSymbolType().toString())
|
||||||
|
if key not in shown:
|
||||||
|
shown.add(key)
|
||||||
|
p("SYMB", name, s.getAddress(), s.getSymbolType().toString())
|
||||||
|
break
|
||||||
|
p("SYM_TOTAL", len(shown))
|
||||||
|
|
||||||
|
# 3. exports
|
||||||
|
for s in symtab.getExternalSymbols():
|
||||||
|
pass
|
||||||
|
|
||||||
|
# external functions imports (VST exports / DLL imports of interest)
|
||||||
|
for f in listing.getFunctions(True):
|
||||||
|
sym = f.getSymbol()
|
||||||
|
if sym is not None and sym.isExternalEntryPoint():
|
||||||
|
p("EXP", f.getName(), f.getEntryPoint())
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os, glob, sys, time, subprocess
|
||||||
|
|
||||||
|
REAPER = "/usr/bin/reaper"
|
||||||
|
RPP = "/home/m/soothe-bt/dep0b_rt.rpp"
|
||||||
|
OUTDIR = "/home/m/re-tools/regions_dep"
|
||||||
|
|
||||||
|
os.makedirs(OUTDIR, exist_ok=True)
|
||||||
|
|
||||||
|
def find_soothe2_pid():
|
||||||
|
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' not in cmd and 'wine' not in cmd:
|
||||||
|
continue
|
||||||
|
maps = open(f'/proc/{pid}/maps').read()
|
||||||
|
if 'soothe2' in maps:
|
||||||
|
st = open(p + '/stat','rb').read().decode('utf8','replace')
|
||||||
|
state = st.split(')')[-1].split()[0]
|
||||||
|
return pid, state
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
def init_ok(pid, base=0x180000000):
|
||||||
|
try:
|
||||||
|
maps = open(f'/proc/{pid}/maps').read()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
seen = 0
|
||||||
|
for line in maps.splitlines():
|
||||||
|
parts = line.split()
|
||||||
|
if len(parts) < 6: continue
|
||||||
|
lo = int(parts[0].split('-')[0],16)
|
||||||
|
if 'r' in parts[1] and base <= lo < base+0x7000000:
|
||||||
|
seen += 1
|
||||||
|
return seen >= 8
|
||||||
|
|
||||||
|
def dump_module(pid, base=0x180000000, extent=0x7000000):
|
||||||
|
maps = open(f'/proc/{pid}/maps').read()
|
||||||
|
readable = []
|
||||||
|
for line in maps.splitlines():
|
||||||
|
parts = line.split()
|
||||||
|
if len(parts) < 6: continue
|
||||||
|
lo, hi = (int(x,16) for x in parts[0].split('-'))
|
||||||
|
if 'r' in parts[1] and (base <= lo < base+extent):
|
||||||
|
readable.append((lo, hi, parts[1]))
|
||||||
|
print(f' pid {pid}: {len(readable)} regions')
|
||||||
|
mem = os.open(f'/proc/{pid}/mem', os.O_RDONLY)
|
||||||
|
total = 0
|
||||||
|
for lo, hi, perms in readable:
|
||||||
|
try:
|
||||||
|
data = os.pread(mem, hi-lo, lo)
|
||||||
|
total += len(data)
|
||||||
|
fn = os.path.join(OUTDIR, f'pid{pid}_{lo:08x}_{hi:08x}.bin')
|
||||||
|
with open(fn,'wb') as f: f.write(data)
|
||||||
|
except Exception as e:
|
||||||
|
print(' fail', lo, e)
|
||||||
|
os.close(mem)
|
||||||
|
print(f' dumped total {total} bytes in {time.time()-T0:.1f}s')
|
||||||
|
|
||||||
|
T0 = time.time()
|
||||||
|
print("spawning reaper (realtime render)...")
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
[REAPER, "-nosplash", "-renderproject", RPP],
|
||||||
|
stdout=open('/tmp/harness_dep.log','w'), stderr=subprocess.STDOUT,
|
||||||
|
env={**os.environ})
|
||||||
|
print("reaper pid", proc.pid)
|
||||||
|
|
||||||
|
deadline = time.time() + 200
|
||||||
|
dumped = False
|
||||||
|
while time.time() < deadline and proc.poll() is None:
|
||||||
|
pid, state = find_soothe2_pid()
|
||||||
|
if pid and init_ok(pid):
|
||||||
|
print(f' found pid {pid} state {state}, dumping...')
|
||||||
|
try:
|
||||||
|
dump_module(pid)
|
||||||
|
except Exception as e:
|
||||||
|
print(' dump error:', e)
|
||||||
|
dumped = True
|
||||||
|
break
|
||||||
|
time.sleep(0.05)
|
||||||
|
|
||||||
|
if not dumped:
|
||||||
|
print("no live soothe2 captured")
|
||||||
|
print("waiting for render to finish...")
|
||||||
|
try:
|
||||||
|
proc.wait(timeout=240)
|
||||||
|
except Exception:
|
||||||
|
proc.kill()
|
||||||
|
print(f"done in {time.time()-T0:.0f}s, exit {proc.returncode}")
|
||||||
+114
@@ -0,0 +1,114 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os, glob, sys, time, subprocess
|
||||||
|
|
||||||
|
REAPER = "/usr/bin/reaper"
|
||||||
|
RPP = "/home/m/0.RPP"
|
||||||
|
OUTDIR = "/home/m/re-tools/regions"
|
||||||
|
|
||||||
|
os.makedirs(OUTDIR, exist_ok=True)
|
||||||
|
|
||||||
|
def all_procs():
|
||||||
|
out = {}
|
||||||
|
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')
|
||||||
|
st = open(p + '/stat','rb').read().decode('utf8','replace')
|
||||||
|
ppid = int(st.split(')')[-1].split()[3]) # rough: parse later
|
||||||
|
state = st.split(')')[-1].split()[0]
|
||||||
|
out[pid] = (ppid, state, cmd.strip())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return out
|
||||||
|
|
||||||
|
def wait_full(pid, base=0x180000000, extent=0x7000000, tmo=40):
|
||||||
|
start = time.time()
|
||||||
|
while time.time() - start < tmo:
|
||||||
|
try:
|
||||||
|
maps = open(f'/proc/{pid}/maps').read()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
seen = 0
|
||||||
|
for line in maps.splitlines():
|
||||||
|
parts = line.split()
|
||||||
|
if len(parts) < 6: continue
|
||||||
|
lo, hi = (int(x,16) for x in parts[0].split('-'))
|
||||||
|
if 'r' in parts[1] and base <= lo < base+extent:
|
||||||
|
seen += 1
|
||||||
|
if lo <= 0x184000000 < hi:
|
||||||
|
return True
|
||||||
|
if seen < 8:
|
||||||
|
time.sleep(1)
|
||||||
|
continue
|
||||||
|
# big enough set of readable regions present; dump what exists
|
||||||
|
return True
|
||||||
|
|
||||||
|
def dump_module(pid, base=0x180000000, extent=0x7000000):
|
||||||
|
maps_path = f'/proc/{pid}/maps'
|
||||||
|
maps = open(maps_path).read()
|
||||||
|
readable = []
|
||||||
|
for line in maps.splitlines():
|
||||||
|
parts = line.split()
|
||||||
|
if len(parts) < 6: continue
|
||||||
|
addr, perms, off, dev, ino, *rest = parts
|
||||||
|
lo, hi = (int(x,16) for x in addr.split('-'))
|
||||||
|
if 'r' in perms and (base <= lo < base+extent):
|
||||||
|
readable.append((lo, hi, perms))
|
||||||
|
print(f' pid {pid}: {len(readable)} readable regions in [{base:#x},{base+extent:#x})')
|
||||||
|
if not readable:
|
||||||
|
print(' LISTING MAPS around base:')
|
||||||
|
for line in maps.splitlines():
|
||||||
|
parts = line.split()
|
||||||
|
if len(parts) < 6: continue
|
||||||
|
addr, perms, *rest = parts
|
||||||
|
lo = int(addr.split('-')[0],16)
|
||||||
|
if base-0x100000 <= lo <= base+0x1000000:
|
||||||
|
print(' ', line)
|
||||||
|
mem = os.open(f'/proc/{pid}/mem', os.O_RDONLY)
|
||||||
|
total = 0
|
||||||
|
for lo, hi, perms in readable:
|
||||||
|
size = hi - lo
|
||||||
|
try:
|
||||||
|
data = os.pread(mem, size, lo)
|
||||||
|
total += len(data)
|
||||||
|
fn = os.path.join(OUTDIR, f'pid{pid}_{lo:08x}_{hi:08x}_{perms.replace("-","")}.bin')
|
||||||
|
with open(fn,'wb') as f: f.write(data)
|
||||||
|
print(f' dumped {lo:#x}-{hi:#x} {len(data):#x} bytes {perms}')
|
||||||
|
except Exception as e:
|
||||||
|
print(f' {lo:#x}-{hi:#x} {perms} FAIL {e}')
|
||||||
|
os.close(mem)
|
||||||
|
print(f' pid {pid} total {total} bytes')
|
||||||
|
|
||||||
|
print("spawning reaper...")
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
[REAPER, "-nosplash", RPP],
|
||||||
|
stdout=open('/tmp/harness.log','w'), stderr=subprocess.STDOUT,
|
||||||
|
env={**os.environ})
|
||||||
|
print("reaper pid", proc.pid)
|
||||||
|
|
||||||
|
deadline = time.time() + 120
|
||||||
|
dumped = False
|
||||||
|
while time.time() < deadline and proc.poll() is None:
|
||||||
|
time.sleep(1)
|
||||||
|
procs = all_procs()
|
||||||
|
for pid,(ppid,state,cmd) in list(procs.items()):
|
||||||
|
if state != 'S': continue
|
||||||
|
if 'yabridge' in cmd or 'wine' in cmd:
|
||||||
|
with open(f'/proc/{pid}/maps') as f:
|
||||||
|
if 'soothe2' in f.read():
|
||||||
|
ok = wait_full(pid)
|
||||||
|
print(f' wait_full(pid {pid}): {ok}')
|
||||||
|
if not ok:
|
||||||
|
continue
|
||||||
|
dump_module(pid)
|
||||||
|
dumped = True
|
||||||
|
time.sleep(2)
|
||||||
|
if dumped:
|
||||||
|
break
|
||||||
|
if not dumped:
|
||||||
|
print("no live soothe2 process observed")
|
||||||
|
print("killing reaper")
|
||||||
|
proc.terminate()
|
||||||
|
time.sleep(1)
|
||||||
|
if proc.poll() is None:
|
||||||
|
proc.kill()
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os, glob, sys, time, subprocess
|
||||||
|
|
||||||
|
REAPER = "/usr/bin/reaper"
|
||||||
|
RPP = "/home/m/soothe-bt/render_long.rpp"
|
||||||
|
OUTDIR = "/home/m/re-tools/regions_render"
|
||||||
|
|
||||||
|
os.makedirs(OUTDIR, exist_ok=True)
|
||||||
|
|
||||||
|
def all_procs():
|
||||||
|
out = {}
|
||||||
|
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')
|
||||||
|
st = open(p + '/stat','rb').read().decode('utf8','replace')
|
||||||
|
ppid = int(st.split(')')[-1].split()[3]) # rough: parse later
|
||||||
|
state = st.split(')')[-1].split()[0]
|
||||||
|
out[pid] = (ppid, state, cmd.strip())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return out
|
||||||
|
|
||||||
|
def wait_full(pid, base=0x180000000, extent=0x7000000, tmo=40):
|
||||||
|
start = time.time()
|
||||||
|
while time.time() - start < tmo:
|
||||||
|
try:
|
||||||
|
maps = open(f'/proc/{pid}/maps').read()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
seen = 0
|
||||||
|
for line in maps.splitlines():
|
||||||
|
parts = line.split()
|
||||||
|
if len(parts) < 6: continue
|
||||||
|
lo, hi = (int(x,16) for x in parts[0].split('-'))
|
||||||
|
if 'r' in parts[1] and base <= lo < base+extent:
|
||||||
|
seen += 1
|
||||||
|
if lo <= 0x184000000 < hi:
|
||||||
|
return True
|
||||||
|
if seen < 8:
|
||||||
|
time.sleep(1)
|
||||||
|
continue
|
||||||
|
# big enough set of readable regions present; dump what exists
|
||||||
|
return True
|
||||||
|
|
||||||
|
def dump_module(pid, base=0x180000000, extent=0x7000000):
|
||||||
|
maps_path = f'/proc/{pid}/maps'
|
||||||
|
maps = open(maps_path).read()
|
||||||
|
readable = []
|
||||||
|
for line in maps.splitlines():
|
||||||
|
parts = line.split()
|
||||||
|
if len(parts) < 6: continue
|
||||||
|
addr, perms, off, dev, ino, *rest = parts
|
||||||
|
lo, hi = (int(x,16) for x in addr.split('-'))
|
||||||
|
if 'r' in perms and (base <= lo < base+extent):
|
||||||
|
readable.append((lo, hi, perms))
|
||||||
|
print(f' pid {pid}: {len(readable)} readable regions in [{base:#x},{base+extent:#x})')
|
||||||
|
if not readable:
|
||||||
|
print(' LISTING MAPS around base:')
|
||||||
|
for line in maps.splitlines():
|
||||||
|
parts = line.split()
|
||||||
|
if len(parts) < 6: continue
|
||||||
|
addr, perms, *rest = parts
|
||||||
|
lo = int(addr.split('-')[0],16)
|
||||||
|
if base-0x100000 <= lo <= base+0x1000000:
|
||||||
|
print(' ', line)
|
||||||
|
mem = os.open(f'/proc/{pid}/mem', os.O_RDONLY)
|
||||||
|
total = 0
|
||||||
|
for lo, hi, perms in readable:
|
||||||
|
size = hi - lo
|
||||||
|
try:
|
||||||
|
data = os.pread(mem, size, lo)
|
||||||
|
total += len(data)
|
||||||
|
fn = os.path.join(OUTDIR, f'pid{pid}_{lo:08x}_{hi:08x}_{perms.replace("-","")}.bin')
|
||||||
|
with open(fn,'wb') as f: f.write(data)
|
||||||
|
print(f' dumped {lo:#x}-{hi:#x} {len(data):#x} bytes {perms}')
|
||||||
|
except Exception as e:
|
||||||
|
print(f' {lo:#x}-{hi:#x} {perms} FAIL {e}')
|
||||||
|
os.close(mem)
|
||||||
|
print(f' pid {pid} total {total} bytes')
|
||||||
|
|
||||||
|
print("spawning reaper...")
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
[REAPER, "-nosplash", "-renderproject", RPP],
|
||||||
|
stdout=open('/tmp/harness.log','w'), stderr=subprocess.STDOUT,
|
||||||
|
env={**os.environ})
|
||||||
|
print("reaper pid", proc.pid)
|
||||||
|
|
||||||
|
deadline = time.time() + 120
|
||||||
|
dumped = False
|
||||||
|
while time.time() < deadline and proc.poll() is None:
|
||||||
|
time.sleep(1)
|
||||||
|
procs = all_procs()
|
||||||
|
for pid,(ppid,state,cmd) in list(procs.items()):
|
||||||
|
if state != 'S': continue
|
||||||
|
if 'yabridge' in cmd or 'wine' in cmd:
|
||||||
|
with open(f'/proc/{pid}/maps') as f:
|
||||||
|
if 'soothe2' in f.read():
|
||||||
|
ok = wait_full(pid)
|
||||||
|
print(f' wait_full(pid {pid}): {ok}')
|
||||||
|
if not ok:
|
||||||
|
continue
|
||||||
|
dump_module(pid)
|
||||||
|
dumped = True
|
||||||
|
time.sleep(2)
|
||||||
|
if dumped:
|
||||||
|
break
|
||||||
|
if not dumped:
|
||||||
|
print("no live soothe2 process observed")
|
||||||
|
print("killing reaper")
|
||||||
|
proc.terminate()
|
||||||
|
time.sleep(1)
|
||||||
|
if proc.poll() is None:
|
||||||
|
proc.kill()
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os, glob, sys, time, subprocess
|
||||||
|
|
||||||
|
REAPER = "/usr/bin/reaper"
|
||||||
|
RPP = "/home/m/soothe-bt/render_rt.rpp"
|
||||||
|
OUTDIR = "/home/m/re-tools/regions_rt"
|
||||||
|
|
||||||
|
os.makedirs(OUTDIR, exist_ok=True)
|
||||||
|
|
||||||
|
def find_soothe2_pid():
|
||||||
|
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' not in cmd and 'wine' not in cmd:
|
||||||
|
continue
|
||||||
|
maps = open(f'/proc/{pid}/maps').read()
|
||||||
|
if 'soothe2' in maps:
|
||||||
|
st = open(p + '/stat','rb').read().decode('utf8','replace')
|
||||||
|
state = st.split(')')[-1].split()[0]
|
||||||
|
return pid, state
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
def init_ok(pid, base=0x180000000):
|
||||||
|
try:
|
||||||
|
maps = open(f'/proc/{pid}/maps').read()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
seen = 0
|
||||||
|
for line in maps.splitlines():
|
||||||
|
parts = line.split()
|
||||||
|
if len(parts) < 6: continue
|
||||||
|
lo = int(parts[0].split('-')[0],16)
|
||||||
|
if 'r' in parts[1] and base <= lo < base+0x7000000:
|
||||||
|
seen += 1
|
||||||
|
return seen >= 8
|
||||||
|
|
||||||
|
def dump_module(pid, base=0x180000000, extent=0x7000000):
|
||||||
|
maps = open(f'/proc/{pid}/maps').read()
|
||||||
|
readable = []
|
||||||
|
for line in maps.splitlines():
|
||||||
|
parts = line.split()
|
||||||
|
if len(parts) < 6: continue
|
||||||
|
lo, hi = (int(x,16) for x in parts[0].split('-'))
|
||||||
|
if 'r' in parts[1] and (base <= lo < base+extent):
|
||||||
|
readable.append((lo, hi, parts[1]))
|
||||||
|
print(f' pid {pid}: {len(readable)} regions')
|
||||||
|
mem = os.open(f'/proc/{pid}/mem', os.O_RDONLY)
|
||||||
|
total = 0
|
||||||
|
for lo, hi, perms in readable:
|
||||||
|
try:
|
||||||
|
data = os.pread(mem, hi-lo, lo)
|
||||||
|
total += len(data)
|
||||||
|
fn = os.path.join(OUTDIR, f'pid{pid}_{lo:08x}_{hi:08x}.bin')
|
||||||
|
with open(fn,'wb') as f: f.write(data)
|
||||||
|
except Exception as e:
|
||||||
|
print(' fail', lo, e)
|
||||||
|
os.close(mem)
|
||||||
|
print(f' dumped total {total} bytes in {time.time()-T0:.1f}s')
|
||||||
|
|
||||||
|
T0 = time.time()
|
||||||
|
print("spawning reaper (realtime render)...")
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
[REAPER, "-nosplash", "-renderproject", RPP],
|
||||||
|
stdout=open('/tmp/harness_fast.log','w'), stderr=subprocess.STDOUT,
|
||||||
|
env={**os.environ})
|
||||||
|
print("reaper pid", proc.pid)
|
||||||
|
|
||||||
|
deadline = time.time() + 200
|
||||||
|
dumped = False
|
||||||
|
while time.time() < deadline and proc.poll() is None:
|
||||||
|
pid, state = find_soothe2_pid()
|
||||||
|
if pid and init_ok(pid):
|
||||||
|
print(f' found pid {pid} state {state}, dumping...')
|
||||||
|
try:
|
||||||
|
dump_module(pid)
|
||||||
|
except Exception as e:
|
||||||
|
print(' dump error:', e)
|
||||||
|
dumped = True
|
||||||
|
break
|
||||||
|
time.sleep(0.05)
|
||||||
|
|
||||||
|
if not dumped:
|
||||||
|
print("no live soothe2 captured")
|
||||||
|
print("waiting for render to finish...")
|
||||||
|
try:
|
||||||
|
proc.wait(timeout=240)
|
||||||
|
except Exception:
|
||||||
|
proc.kill()
|
||||||
|
print(f"done in {time.time()-T0:.0f}s, exit {proc.returncode}")
|
||||||
+114
@@ -0,0 +1,114 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Burst analyzer for soothe2 renders.
|
||||||
|
Reads a 24-bit stereo wav (fx) and dry, computes bin magnitude at fc with a
|
||||||
|
10ms sliding window, then tracks NOTCH DEPTH over time:
|
||||||
|
reduction(t) = -20*log10(g_fx(t)/g_dry(t)) (>=0 when suppressed)
|
||||||
|
and fits:
|
||||||
|
- settled depth (mean over plateau window)
|
||||||
|
- attack tau (fit reduction: A*(1-exp(-(t-t0)/tau)) on burst onset)
|
||||||
|
- release tau (fit reduction: A*exp(-(t-b1)/tau) after burst end)
|
||||||
|
Works best when a weak probe tone at fc is present during the whole file so
|
||||||
|
the notch remains observable after the burst (see synth_burstp.py).
|
||||||
|
Usage: measure.py <fx.wav> <dry.wav> --fc 500 [--burst 0.5 1.5] [--name X]
|
||||||
|
"""
|
||||||
|
import argparse, os
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
def read_wav(path):
|
||||||
|
import wave
|
||||||
|
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, :]
|
||||||
|
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)
|
||||||
|
|
||||||
|
def mag_at(x, fc, sr, win=0.010):
|
||||||
|
w = int(sr * win)
|
||||||
|
n = x.size // w
|
||||||
|
if n == 0: return np.array([])
|
||||||
|
xw = x[:n * w].reshape(n, w)
|
||||||
|
X = np.fft.rfft(xw, axis=1)
|
||||||
|
k = int(round(fc * w / sr))
|
||||||
|
return 2.0 * np.abs(X[:, k]) / w
|
||||||
|
|
||||||
|
def fit_tau(t, red, t0, A, lo, hi, t1=None, invert=False, floor=None):
|
||||||
|
"""fit red(t) = A*(1-exp(-(t-t0)/tau)) [invert=False] or A*exp(-(t-t0)/tau) [invert].
|
||||||
|
Window [t0, t1]; grid search tau on [lo,hi] by r2. Returns (tau, r2)."""
|
||||||
|
if t1 is None:
|
||||||
|
t1 = t0 + 1.5
|
||||||
|
m = (t >= t0) & (t <= t1)
|
||||||
|
tt, rr = t[m], red[m]
|
||||||
|
if floor is not None:
|
||||||
|
rr = rr - floor
|
||||||
|
if rr.size < 4: return (float('nan'), 0.0)
|
||||||
|
best, bestr2 = None, -1e9
|
||||||
|
for tau in np.geomspace(lo, hi, 200):
|
||||||
|
if invert:
|
||||||
|
pred = A * np.exp(-(tt - t0) / tau)
|
||||||
|
else:
|
||||||
|
pred = A * (1 - np.exp(-(tt - t0) / tau))
|
||||||
|
ss = 1 - np.sum((rr - pred) ** 2) / np.sum((rr - rr.mean()) ** 2)
|
||||||
|
if ss > bestr2:
|
||||||
|
bestr2, best = ss, tau
|
||||||
|
return (best, bestr2)
|
||||||
|
|
||||||
|
def profile(fx_path, dry_path, freqs, win0=1.6, win1=3.0, sr=44100, win=0.010):
|
||||||
|
"""Notch transfer profile via probe tones: red(f) in window. Returns dict f->dB."""
|
||||||
|
fx = read_wav(fx_path); dry = read_wav(dry_path)
|
||||||
|
n = min(fx.size, dry.size)
|
||||||
|
fx, dry = fx[:n], dry[:n]
|
||||||
|
out = {}
|
||||||
|
for f in freqs:
|
||||||
|
mf = mag_at(fx, f, sr, win); md = mag_at(dry, f, sr, win)
|
||||||
|
tw = (np.arange(min(mf.size, md.size)) + 0.5) * win
|
||||||
|
m = (tw >= win0) & (tw <= win1)
|
||||||
|
if m.sum() == 0:
|
||||||
|
out[f] = float('nan'); continue
|
||||||
|
out[f] = -20 * np.log10(
|
||||||
|
np.maximum(mf[m].mean(), 1e-9) / np.maximum(md[m].mean(), 1e-9))
|
||||||
|
return out
|
||||||
|
|
||||||
|
def measure(fx_path, dry_path, fc=500.0, burst=(0.5, 1.5), sr=44100, win=0.010):
|
||||||
|
fx = read_wav(fx_path); dry = read_wav(dry_path)
|
||||||
|
n = min(fx.size, dry.size)
|
||||||
|
fx, dry = fx[:n], dry[:n]
|
||||||
|
mf = mag_at(fx, fc, sr, win); md = mag_at(dry, fc, sr, win)
|
||||||
|
tw = (np.arange(mf.size) + 0.5) * win
|
||||||
|
nmin = min(mf.size, md.size)
|
||||||
|
tw, mf, md = tw[:nmin], mf[:nmin], md[:nmin]
|
||||||
|
guard = 1e-9
|
||||||
|
red = -20 * np.log10(np.maximum(mf, guard) / np.maximum(md, guard))
|
||||||
|
|
||||||
|
b0, b1 = burst
|
||||||
|
pl0, pl1 = b1 - 0.30, b1 - 0.01
|
||||||
|
pm = (tw >= pl0) & (tw <= pl1)
|
||||||
|
settled = red[pm].mean() if pm.sum() else float('nan')
|
||||||
|
|
||||||
|
# attack: relative rise of reduction from burst start; A=settled
|
||||||
|
t0a = b0
|
||||||
|
ta, r2a = fit_tau(tw, red, t0a, max(settled, 0.001), 0.001, 1.0, t1=b1 - 0.03)
|
||||||
|
# release: decay (invert) after burst end; A=settled, toward a floor
|
||||||
|
# floor = mean of red in the last stable probe window (well after burst)
|
||||||
|
tail = (tw >= b1 + 0.35) & (tw <= b1 + 0.85)
|
||||||
|
floor = red[tail].mean() if tail.sum() else 0.0
|
||||||
|
tr, r2r = fit_tau(tw, red, b1, max(settled - floor, 0.001), 0.001, 5.0,
|
||||||
|
t1=b1 + 0.35, invert=True, floor=floor)
|
||||||
|
|
||||||
|
return dict(depth_db=settled, attack_tau=ta, attack_r2=r2a,
|
||||||
|
release_tau=tr, release_r2=r2r)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument('fx'); ap.add_argument('dry')
|
||||||
|
ap.add_argument('--fc', type=float, default=500.0)
|
||||||
|
ap.add_argument('--burst', nargs=2, type=float, default=[0.5, 1.5])
|
||||||
|
ap.add_argument('--name')
|
||||||
|
a = ap.parse_args()
|
||||||
|
r = measure(a.fx, a.dry, a.fc, tuple(a.burst))
|
||||||
|
nm = a.name or os.path.basename(a.fx)
|
||||||
|
print(f"{nm}: depth={r['depth_db']:7.2f}dB att_tau={r['attack_tau']*1000:7.2f}ms "
|
||||||
|
f"(r2={r['attack_r2']:.3f}) rel_tau={r['release_tau']*1000:7.2f}ms "
|
||||||
|
f"(r2={r['release_r2']:.3f})")
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import re, sys
|
||||||
|
def patch_tt(template, src_wav, out_rpp, out_wav, dur=6.0):
|
||||||
|
t = open(template).read()
|
||||||
|
t = t.replace('/home/m/soothe-bt/testtone.wav', src_wav)
|
||||||
|
ns = int(44100*dur)
|
||||||
|
t = re.sub(r'LENGTH 6\.000000 264600 264600 1', f'LENGTH {dur:.6f} {ns} {ns} 1', t)
|
||||||
|
t = re.sub(r'NAME ".*?"', 'NAME "' + out_wav.split("/")[-1] + '"', t)
|
||||||
|
t = t.replace('RENDER_FILE "/home/m/soothe-bt/tt_ref.wav"', f'RENDER_FILE "{out_wav}"')
|
||||||
|
open(out_rpp, 'w').write(t)
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys, os, struct
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
SR = 44100
|
||||||
|
|
||||||
|
def read_wav(path):
|
||||||
|
d = open(path, 'rb').read()
|
||||||
|
i = 12
|
||||||
|
ch = bps = None
|
||||||
|
data_off = data_sz = None
|
||||||
|
while i + 8 <= len(d):
|
||||||
|
cid = d[i:i+4]; sz = struct.unpack('<I', d[i+4:i+8])[0]
|
||||||
|
if cid == b'fmt ':
|
||||||
|
ch = struct.unpack('<H', d[i+8:i+10])[0]
|
||||||
|
bps = struct.unpack('<H', d[i+16:i+18])[0]
|
||||||
|
if cid == b'data':
|
||||||
|
data_off = i + 8; data_sz = sz
|
||||||
|
i += 8 + sz + (sz & 1)
|
||||||
|
raw = d[data_off:data_off+data_sz]
|
||||||
|
if bps == 32:
|
||||||
|
a = np.frombuffer(raw, dtype='<f4')
|
||||||
|
return a.reshape(-1, ch)[:, 0], ch
|
||||||
|
if bps == 24:
|
||||||
|
b = np.frombuffer(raw, dtype=np.uint8).reshape(-1, ch*3)
|
||||||
|
v = b[:, 0] | (b[:,1].astype(np.int32) << 8) | (b[:,2].astype(np.int32) << 16)
|
||||||
|
v = (v ^ (1 << 23)) - (1 << 23)
|
||||||
|
return v / 8388608.0, ch
|
||||||
|
a = np.frombuffer(raw, dtype='<i2')
|
||||||
|
return a.reshape(-1, ch)[:, 0] / 32768.0, ch
|
||||||
|
|
||||||
|
def load(path):
|
||||||
|
return read_wav(path)[0]
|
||||||
|
|
||||||
|
def band_db(x, fl, fh, sr=SR):
|
||||||
|
# band-integrated power between fl..fh via Goertzel on windowed segments
|
||||||
|
n = len(x)
|
||||||
|
seg = 4096
|
||||||
|
hop = 2048
|
||||||
|
total = 0.0
|
||||||
|
frames = 0
|
||||||
|
for t in range(0, n-seg, hop):
|
||||||
|
fr = x[t:t+seg]
|
||||||
|
w = np.hanning(seg)
|
||||||
|
X = np.fft.rfft(fr*w)
|
||||||
|
frq = np.fft.rfftfreq(seg, 1/sr)
|
||||||
|
m = (frq >= fl) & (frq <= fh)
|
||||||
|
total += float(np.sum(np.abs(X[m])**2))
|
||||||
|
frames += 1
|
||||||
|
if frames == 0: return -300.0
|
||||||
|
return 10*np.log10(total/frames + 1e-30)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# probe band around the carrier at flexible freqs
|
||||||
|
center = float(sys.argv[1])
|
||||||
|
outs = sys.argv[2:]
|
||||||
|
half_w = float(os.environ.get('PROBE_BW', 800.0))
|
||||||
|
widths = []
|
||||||
|
step = float(os.environ.get('PROBE_STEP', 25.0))
|
||||||
|
f = center - half_w
|
||||||
|
while f <= center + half_w:
|
||||||
|
widths.append(f)
|
||||||
|
f += step
|
||||||
|
datas = {p: load(p) for p in outs}
|
||||||
|
colw = 11
|
||||||
|
print(f"{'freq':>6}" + ''.join(f" {os.path.basename(p)[:colw]:>{colw}}" for p in outs))
|
||||||
|
prevbase = None
|
||||||
|
for fl in widths:
|
||||||
|
fh = fl + step
|
||||||
|
row = f"{fl + step/2:6.0f}"
|
||||||
|
base = None
|
||||||
|
for p in outs:
|
||||||
|
d = band_db(datas[p], fl, fh)
|
||||||
|
if base is None: base = d
|
||||||
|
row += f" {d:>{colw}.1f}"
|
||||||
|
print(row)
|
||||||
|
# also print relative-to-first file per bin is tricky; print a second table relative to first col
|
||||||
|
print("--- relative dB to first file ---")
|
||||||
|
for fl in widths:
|
||||||
|
fh = fl + step
|
||||||
|
row = f"{fl + step/2:6.0f}"
|
||||||
|
first = None
|
||||||
|
for p in outs:
|
||||||
|
d = band_db(datas[p], fl, fh)
|
||||||
|
if first is None: first = d
|
||||||
|
row += f" {d - first:>+{colw}.1f}"
|
||||||
|
print(row)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Patch a soothe2 PARAM value inside an RPP's base64 state block.
|
||||||
|
Usage: patchparam.py <in.rpp> <out.rpp> param=value [param=value...]
|
||||||
|
Value formatting: %16 params (depth, band freq/q/sens/balance, offset etc)
|
||||||
|
need %.16f; short params (selectivity, mix, mode, oversample, resolution,
|
||||||
|
attack, band on/balance?) use 'X.0'. Append '!' to force %.16f, e.g.
|
||||||
|
depth=0.0! to guarantee full precision.
|
||||||
|
"""
|
||||||
|
import re, sys, base64
|
||||||
|
|
||||||
|
def fmt(val: float, force16: bool):
|
||||||
|
if force16:
|
||||||
|
return '%.16f' % val
|
||||||
|
s = repr(val)
|
||||||
|
if '.' not in s:
|
||||||
|
s += '.0'
|
||||||
|
if '.' in s and float(s) == int(float(s)) and len(s.rstrip('0').rstrip('.')) <= 4:
|
||||||
|
return s.rstrip('0').rstrip('.') if '.' in s else s
|
||||||
|
return '%.16f' % val
|
||||||
|
|
||||||
|
def main():
|
||||||
|
src, out = sys.argv[1], sys.argv[2]
|
||||||
|
params = {}
|
||||||
|
for a in sys.argv[3:]:
|
||||||
|
if a in ('-f16',):
|
||||||
|
continue
|
||||||
|
k, v, f16 = a.split('=', 1)[0], a.split('=', 1)[1], '!' in a
|
||||||
|
params[k] = (float(v.rstrip('!')), f16)
|
||||||
|
s = open(src, encoding='utf8', errors='replace').read()
|
||||||
|
i = s.find('PpeX')
|
||||||
|
if i < 0:
|
||||||
|
i = s.find('VkMy')
|
||||||
|
if i < 0:
|
||||||
|
sys.exit('no state block found')
|
||||||
|
j = s.find('>', i)
|
||||||
|
if j < 0:
|
||||||
|
sys.exit('malformed state block')
|
||||||
|
block = s[i:j]
|
||||||
|
raw = bytearray(base64.b64decode(block))
|
||||||
|
txt = raw.decode('latin-1')
|
||||||
|
n = 0
|
||||||
|
for pm in re.finditer(r'(<PARAM id="([^"]*)" value=")[^"]*(")/>', txt):
|
||||||
|
did = pm.group(2)
|
||||||
|
if did in params:
|
||||||
|
val, f16 = params[did]
|
||||||
|
txt = txt[:pm.start()] + pm.group(1) + fmt(val, f16) + pm.group(3) + '/>' + txt[pm.end():]
|
||||||
|
n += 1
|
||||||
|
if n != len(params):
|
||||||
|
print(f'warning: patched {n}/{len(params)} params')
|
||||||
|
raw = txt.encode('latin-1')
|
||||||
|
enc = base64.b64encode(raw).decode('ascii')
|
||||||
|
line_start = s.rfind('\n', 0, i) + 1
|
||||||
|
indent = s[line_start:i]
|
||||||
|
wrapped = '\n'.join(indent + enc[i:i+128] for i in range(0, len(enc), 128))
|
||||||
|
jline = s.rfind('\n', 0, j) + 1
|
||||||
|
gt_indent = s[jline:j]
|
||||||
|
s = s[:line_start] + wrapped + '\n' + gt_indent + s[j:]
|
||||||
|
open(out, 'w', encoding='utf8').write(s)
|
||||||
|
print(f'{out}: patched {n} params')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
depth
|
||||||
|
|
||||||
|
sharpness
|
||||||
|
|
||||||
|
/
|
||||||
|
|
||||||
|
\
|
||||||
|
|
||||||
|
selectivity
|
||||||
|
|
||||||
|
visualisation
|
||||||
|
|
||||||
|
\.
|
||||||
|
|
||||||
|
(GUI)
|
||||||
|
|
||||||
|
f
|
||||||
|
|
||||||
|
\
|
||||||
|
|
||||||
|
N
|
||||||
|
|
||||||
|
y,
|
||||||
|
|
||||||
|
[ per-band balances }controls—> EQ curves
|
||||||
|
|
||||||
|
N
|
||||||
|
|
||||||
|
N
|
||||||
|
|
||||||
|
controls
|
||||||
|
|
||||||
|
channel link
|
||||||
|
|
||||||
|
f
|
||||||
|
|
||||||
|
\
|
||||||
|
|
||||||
|
\
|
||||||
|
|
||||||
|
\
|
||||||
|
|
||||||
|
[
|
||||||
|
|
||||||
|
\
|
||||||
|
|
||||||
|
(
|
||||||
|
|
||||||
|
\
|
||||||
|
|
||||||
|
f
|
||||||
|
|
||||||
|
\
|
||||||
|
|
||||||
|
/
|
||||||
|
|
||||||
|
\
|
||||||
|
|
||||||
|
[
|
||||||
|
|
||||||
|
/
|
||||||
|
|
||||||
|
\
|
||||||
|
|
||||||
|
.
|
||||||
|
|
||||||
|
f
|
||||||
|
|
||||||
|
\
|
||||||
|
|
||||||
|
—>
|
||||||
|
|
||||||
|
—>
|
||||||
|
|
||||||
|
—»
|
||||||
|
|
||||||
|
—>
|
||||||
|
|
||||||
|
global
|
||||||
|
|
||||||
|
—>
|
||||||
|
|
||||||
|
—>
|
||||||
|
|
||||||
|
sidechain
|
||||||
|
|
||||||
|
ms encode?
|
||||||
|
|
||||||
|
sidechain
|
||||||
|
|
||||||
|
stereo
|
||||||
|
|
||||||
|
apply
|
||||||
|
|
||||||
|
trim
|
||||||
|
|
||||||
|
inputs
|
||||||
|
|
||||||
|
enabled?
|
||||||
|
|
||||||
|
input trim
|
||||||
|
|
||||||
|
X
|
||||||
|
|
||||||
|
analysis
|
||||||
|
|
||||||
|
balance
|
||||||
|
|
||||||
|
processing
|
||||||
|
|
||||||
|
—>
|
||||||
|
|
||||||
|
—>
|
||||||
|
|
||||||
|
—>
|
||||||
|
|
||||||
|
—
|
||||||
|
|
||||||
|
— >
|
||||||
|
|
||||||
|
—
|
||||||
|
|
||||||
|
-
|
||||||
|
|
||||||
|
J
|
||||||
|
|
||||||
|
.
|
||||||
|
|
||||||
|
/
|
||||||
|
|
||||||
|
\
|
||||||
|
|
||||||
|
/
|
||||||
|
|
||||||
|
\-
|
||||||
|
|
||||||
|
/
|
||||||
|
|
||||||
|
\
|
||||||
|
|
||||||
|
y
|
||||||
|
|
||||||
|
\-
|
||||||
|
|
||||||
|
/
|
||||||
|
|
||||||
|
Ny
|
||||||
|
|
||||||
|
/
|
||||||
|
|
||||||
|
N
|
||||||
|
|
||||||
|
Yy
|
||||||
|
|
||||||
|
A
|
||||||
|
|
||||||
|
A
|
||||||
|
|
||||||
|
\
|
||||||
|
|
||||||
|
/
|
||||||
|
|
||||||
|
\
|
||||||
|
|
||||||
|
/
|
||||||
|
|
||||||
|
\
|
||||||
|
|
||||||
|
/
|
||||||
|
|
||||||
|
\
|
||||||
|
|
||||||
|
s
|
||||||
|
|
||||||
|
f
|
||||||
|
|
||||||
|
\
|
||||||
|
|
||||||
|
f
|
||||||
|
|
||||||
|
\
|
||||||
|
|
||||||
|
<
|
||||||
|
|
||||||
|
>
|
||||||
|
|
||||||
|
>
|
||||||
|
|
||||||
|
—>
|
||||||
|
|
||||||
|
>
|
||||||
|
|
||||||
|
ms encode?
|
||||||
|
|
||||||
|
mix
|
||||||
|
|
||||||
|
ms decode?
|
||||||
|
|
||||||
|
main inputs
|
||||||
|
|
||||||
|
bypass?
|
||||||
|
|
||||||
|
output
|
||||||
|
|
||||||
|
>
|
||||||
|
|
||||||
|
—>
|
||||||
|
|
||||||
|
\
|
||||||
|
|
||||||
|
y
|
||||||
|
|
||||||
|
\.
|
||||||
|
|
||||||
|
/
|
||||||
|
|
||||||
|
.
|
||||||
|
|
||||||
|
y
|
||||||
|
|
||||||
|
.
|
||||||
|
|
||||||
|
y
|
||||||
|
|
||||||
|
\
|
||||||
|
|
||||||
|
y
|
||||||
|
|
||||||
|
—, J"—)
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys, os, struct
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
SR = 44100
|
||||||
|
TONES = [110, 220, 440, 880, 1760, 3520, 7040, 14080]
|
||||||
|
|
||||||
|
def read_wav(path):
|
||||||
|
d = open(path, 'rb').read()
|
||||||
|
i = 12
|
||||||
|
ch = bps = None
|
||||||
|
data_off = data_sz = None
|
||||||
|
while i + 8 <= len(d):
|
||||||
|
cid = d[i:i+4]; sz = struct.unpack('<I', d[i+4:i+8])[0]
|
||||||
|
if cid == b'fmt ':
|
||||||
|
ch = struct.unpack('<H', d[i+8:i+10])[0]
|
||||||
|
bps = struct.unpack('<H', d[i+16:i+18])[0]
|
||||||
|
if cid == b'data':
|
||||||
|
data_off = i + 8; data_sz = sz
|
||||||
|
i += 8 + sz + (sz & 1)
|
||||||
|
raw = d[data_off:data_off+data_sz]
|
||||||
|
if bps == 32:
|
||||||
|
a = np.frombuffer(raw, dtype='<f4')
|
||||||
|
return a.reshape(-1, ch)[:, 0], ch
|
||||||
|
if bps == 24:
|
||||||
|
b = np.frombuffer(raw, dtype=np.uint8).reshape(-1, ch*3)
|
||||||
|
v = b[:, 0] | (b[:,1].astype(np.int32) << 8) | (b[:,2].astype(np.int32) << 16)
|
||||||
|
v = (v ^ (1 << 23)) - (1 << 23)
|
||||||
|
return v / 8388608.0, ch
|
||||||
|
a = np.frombuffer(raw, dtype='<i2')
|
||||||
|
return a.reshape(-1, ch)[:, 0] / 32768.0, ch
|
||||||
|
|
||||||
|
def tone_db(x, f, sr=SR):
|
||||||
|
n = len(x)
|
||||||
|
w = np.hanning(n*2-1) if False else None
|
||||||
|
# use Goertzel over entire file
|
||||||
|
w0 = 2*np.pi*f/sr
|
||||||
|
c = 2*np.cos(w0)
|
||||||
|
s0=s1=s2=0.0
|
||||||
|
for v in x:
|
||||||
|
s0 = v + c*s1 - s2
|
||||||
|
s2 = s1; s1 = s0
|
||||||
|
p = s1*s1 + s2*s2 - c*s1*s2
|
||||||
|
return 10*np.log10(p + 1e-30) - 20*np.log10(n) # normalize by length
|
||||||
|
|
||||||
|
def main():
|
||||||
|
outs = sys.argv[1:]
|
||||||
|
colw = 12
|
||||||
|
n0 = None
|
||||||
|
datas = {}
|
||||||
|
for p in outs:
|
||||||
|
y, ch = read_wav(p)
|
||||||
|
datas[p] = y
|
||||||
|
if n0 is None or len(y) > n0: n0 = len(y)
|
||||||
|
print(f"{'Hz':>5}" + ''.join(f" {os.path.basename(p)[:colw]:>{colw}}" for p in outs))
|
||||||
|
for f in TONES:
|
||||||
|
row = f"{f:5d}"
|
||||||
|
base = None
|
||||||
|
for p in outs:
|
||||||
|
d = tone_db(datas[p], f)
|
||||||
|
if base is None: base = d
|
||||||
|
row += f" {d:>{colw}.1f}"
|
||||||
|
print(row, f" (rel ref {outs[0]})")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os, glob, sys, re, time
|
||||||
|
|
||||||
|
def find_procs():
|
||||||
|
out = []
|
||||||
|
for p in glob.glob('/proc/[0-9]*'):
|
||||||
|
try:
|
||||||
|
cmd = open(p + '/cmdline','rb').read().replace(b'\0',b' ').decode('utf8','replace')
|
||||||
|
st = open(p + '/stat','rb').read().decode('utf8','replace')
|
||||||
|
state = st.split(')')[-1].split()[0]
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if 'yabridge' in cmd or 'wine' in cmd:
|
||||||
|
out.append((int(os.path.basename(p)), state, cmd.strip()))
|
||||||
|
return out
|
||||||
|
|
||||||
|
def dump_module(pid):
|
||||||
|
maps = open(f'/proc/{pid}/maps').read()
|
||||||
|
regs = []
|
||||||
|
for line in maps.splitlines():
|
||||||
|
parts = line.split()
|
||||||
|
if len(parts) < 6: continue
|
||||||
|
addr, perms, off, dev, ino, *rest = parts
|
||||||
|
if 'soothe2' in ' '.join(rest) and 'r' in perms:
|
||||||
|
lo, hi = (int(x,16) for x in addr.split('-'))
|
||||||
|
regs.append((lo, hi, perms, ' '.join(rest)))
|
||||||
|
if not regs:
|
||||||
|
print(f' pid {pid}: no soothe2 maps')
|
||||||
|
return
|
||||||
|
print(f' pid {pid}: {len(regs)} sobering readable regions')
|
||||||
|
total = 0
|
||||||
|
mem = os.open(f'/proc/{pid}/mem', os.O_RDONLY)
|
||||||
|
try:
|
||||||
|
for lo, hi, perms, path in regs:
|
||||||
|
size = hi - lo
|
||||||
|
try:
|
||||||
|
data = os.pread(mem, size, lo)
|
||||||
|
except Exception as e:
|
||||||
|
print(f' region {lo:#x}-{hi:#x} ({perms}) READ FAIL: {e} [{path}]')
|
||||||
|
continue
|
||||||
|
fn = f'/home/m/re-tools/regions/pid{pid}_{lo:08x}_{hi:08x}_{perms.replace("-","")}.bin'
|
||||||
|
with open(fn,'wb') as f: f.write(data)
|
||||||
|
total += len(data)
|
||||||
|
print(f' dumped {lo:#x}-{hi:#x} size={len(data):#x} rw={perms} -> {fn.split("/")[-1]}')
|
||||||
|
finally:
|
||||||
|
os.close(mem)
|
||||||
|
print(f' total {total} bytes')
|
||||||
|
|
||||||
|
for pid, state, cmd in find_procs():
|
||||||
|
print(f'{pid} [{state}] {cmd}')
|
||||||
|
if state == 'S':
|
||||||
|
dump_module(pid)
|
||||||
+94
@@ -0,0 +1,94 @@
|
|||||||
|
# Roadmap: bit-exact реверс DSP-ядра oeksound soothe2 (v1.1.2)
|
||||||
|
|
||||||
|
## Цель
|
||||||
|
Воспроизвести DSP-путь soothe2 в виде **собираемого standalone C++** с **побайтово идентичным**
|
||||||
|
выходом (bit-exact) на верификационных свипах, с пониманием логики и подписанными функциями.
|
||||||
|
|
||||||
|
## Результат
|
||||||
|
`re-tools/dsp/` — `*.cpp/*.h` (классы DSP, подписаны), `CMakeLists.txt`, `harness.cpp`
|
||||||
|
(WAV16 → рендер → WAV24), `dsp_notes.md` (карта функция↔смысл, константы, LUT),
|
||||||
|
`verify_bit_exact.py` (прогон всех свипов, побайтовое сравнение).
|
||||||
|
|
||||||
|
## Задел (уже есть)
|
||||||
|
- Ghidra-проект `ghidra-proj/soothe2.rep` + скрипты `Dump*.java`/`ImportRtti*.java`/`Diag.java`.
|
||||||
|
- RTTI иерархия DSP: `SpectralProcessor<float,7,1>`, `Soothe2ModuleBase<float,1>`,
|
||||||
|
`Soothe2Module<float,1>`, `FilterGraph<float,6,0x400>`, `FilterGraphGrid<float>`,
|
||||||
|
`DigitalFilter<float,0xBA,1>`, `IIRFilterExtended<float,1>`, `AudioProcessingModule<float,1>`
|
||||||
|
(`rtti_dsp.json`, `rtti_full.json`).
|
||||||
|
- Декомпиляция vtable-слотов DSP-классов (`decomp_dsp.txt`, `decomp_vtables.txt`,
|
||||||
|
`decomp_candidates.txt`) — 6634 строки C, 127 неразрешённых `FUN_*`, ~30 констант `DAT_1824c*`.
|
||||||
|
- Поведенческая модель `sim_v5.py` (RMSE ≈ 0.3dB): base_red, W(f)=6.02·min(1,sens/12)·H,
|
||||||
|
H=1/√(1+(Qeff·A)²), Qeff=1.54·q^1.33, sat(level), Q_notch=qn·(1−g)+1, per-frame env, WOLA-маска.
|
||||||
|
- LUT: depthcurve (207 float @0x1826170e8, `0.302+0.698·sin(π/2·x)^0.94`, r²=0.99999).
|
||||||
|
- Рендер-свипы в `/home/m/soothe-bt/` (506 wav + 609 rpp) — эталон для bit-exact.
|
||||||
|
|
||||||
|
## Контракт из manual (`soothe2_ManualFAQ.pdf`, v1.0.0)
|
||||||
|
|
||||||
|
| # | Факт | Следствие для DSP |
|
||||||
|
|---|---|---|
|
||||||
|
| M1 | Soft/Hard: выбор режима меняет всё; depth 3.0 в soft ≠ hard (стр.7). Soft менее level-dependent | mode = глобальный препроцессор детектора (перемасштаб глубины/масштаба) |
|
||||||
|
| M2 | Depth — референтный dB (−18..18), реальная глубина до 60dB (стр.8) | вход depthcurve-LUT; clip 60dB = `red.clip(0,60)` в sim |
|
||||||
|
| M3 | Sharpness: выше = глубже и уже (стр.8) | Q_notch растёт с sharp; подтверждает `Q≈qn·(1−g)` |
|
||||||
|
| M4 | Selectivity: выше = только сильные резонансы; 0 = «everything must go» (стр.9) | selectivity = отбор/порог пиков, число нотчей НЕ ограничивает |
|
||||||
|
| M5 | Attack/Release: референтные константы, **реальные времена частотно-зависимы**, атака быстрее на ВЧ (стр.11) | Env НЕ один one-pole; нужен частотный масштаб времени — замер τ(f) + код |
|
||||||
|
| M6 | Oversample = «расчёт reduction-фильтра в более высоком спектральном разрешении» (стр.11) | это интерполяция детекторной сетки → фильтра, НЕ up/downsample аудио |
|
||||||
|
| M7 | Resolution = «частота обновления детекции и фильтра» (стр.11), eco/high/ultra | период пересчёта коэффициентов фильтра |
|
||||||
|
| M8 | Stereo: link 100% = сумма каналов для анализа + общий фикс; 0% = dual mono; balance глоб+per-band; L/R vs M/S | детектор на sum; коррекция симметрична/раздельна |
|
||||||
|
| M9 | Trim: «регулирует ТОЛЬКО wet»; Delta = x−wet; Mix 0..100% (стр.14) | порядок: detect→notch→trim→mix/delta/bypass |
|
||||||
|
| M10 | Банды: 2 cut + 4 general; general: peak/shelf/reject/tilt (стр.17-19) | `FilterGraph<float,6,0x400>`, `DigitalFilter` мультитипный |
|
||||||
|
| M11 | Кривая = «inverse EQ / side-chain EQ», сумма band-кривых = белая кривая (стр.17) | `red(f)=base+ΣW_i·H_i` подтверждён |
|
||||||
|
| M12 | Sidechain: детект по второму входу; Input trim — скрытый предусилитель анализа (стр.26) | analysis имеет отдельный вход/гейн — граф с 2 входами |
|
||||||
|
|
||||||
|
## Топология пайплайна (Приложение A, стр.30 — OCR `pipeline_ocr.txt`)
|
||||||
|
|
||||||
|
```
|
||||||
|
main inputs ──┐
|
||||||
|
sidechain ────┤ (sidechain enabled?)
|
||||||
|
▼
|
||||||
|
input trim ──► analysis (детекция; depth/sharpness/selectivity; EQ curves из per-band balances)
|
||||||
|
│
|
||||||
|
stereo apply + balance (channel link + global balance)
|
||||||
|
│
|
||||||
|
ms encode? ──► processing (нотч-синтез в M/S) ──► ms decode?
|
||||||
|
│
|
||||||
|
mix ──► bypass? ──► output
|
||||||
|
```
|
||||||
|
- **mid/side ручка**: обработка в M/S-области (`ms encode?`/`ms decode?`) — стерео-путь подтверждён диаграммой.
|
||||||
|
- GUI/visualisation (depth/sharpness/selectivity, EQ curves, per-band balances) — побочная ветвь, в DSP-ядро не входит (кроме параметров).
|
||||||
|
|
||||||
|
## Фазы
|
||||||
|
|
||||||
|
### Фаза 0 — Снятие диаграммы пайплайна (сделано)
|
||||||
|
0.1. OCR стр.30 (`tesseract`, PNG в `/tmp/opencode/manual/p30_s4.png`) → `pipeline_ocr.txt`. ✅
|
||||||
|
|
||||||
|
### Фаза A — Полный статический RE (Ghidra headless)
|
||||||
|
- A.1. Декомпилировать все 127 `FUN_*` из vtable-слотов 8 DSP-классов → полный `processBlock`-путь.
|
||||||
|
- A.2. Извлечь float-константы `DAT_1824c*` + все LUT-таблицы в `.data` (поиск массивов float).
|
||||||
|
- A.3. **Идентифицировать FFT** (twiddle/бит-реверс/радикс) и окно (формула `sin²` vs LUT) — ключ bit-exact.
|
||||||
|
- A.4. Найти частотно-зависимые attack/release (M5): функции масштабирования времени по частоте.
|
||||||
|
- A.5. Найти oversample/resolution-путь (M6/M7): интерполяция сетки, период обновления.
|
||||||
|
- A.6. Сопоставить топологию с диаграммой (0.1) и stereo/link/balance (M8).
|
||||||
|
|
||||||
|
### Фаза B — Реконструкция C++ (bit-exact)
|
||||||
|
- B.1. Классы 1:1: `Soothe2FilterGraph::processBlock`, `FilterGraphGrid`, `DigitalFilter`,
|
||||||
|
`SpectralProcessor` (STFT), `AudioProcessingModule`, `IIRFilterExtended`, env-модуль с
|
||||||
|
частотным масштабом (M5), интерполятор oversample (M6), периодичность resolution (M7).
|
||||||
|
- B.2. Точный порядок FP-операций float32, без перестановок; буферный фрейминг независимо от блока хоста.
|
||||||
|
- B.3. Имена по смыслу + подписи, сверяемые с моделью sim_v5 (base_red, W, Qeff, sat, Q_notch).
|
||||||
|
- B.4. Модули по manual: mode (M1), input trim/sidechain (M12), trim/delta/mix/bypass-порядок (M9),
|
||||||
|
stereo/link/balance + ms encode/decode (M8).
|
||||||
|
|
||||||
|
### Фаза C — Сборка и bit-exact-верификация
|
||||||
|
- C.1. `libsoothe2_dsp.so` + CLI-харнесс (WAV16→рендер→WAV24, как Reaper).
|
||||||
|
- C.2. Критерий: каждый свип (`trk_*`, `wf_*`, `qmap`, `sens`, `lvl`, `neut`, `al`, `psh`,
|
||||||
|
`dual`, `pair`, `chirp`, `burst`, `tj`) — **побайтовая идентичность** int24 PCM с рендерами.
|
||||||
|
- C.3. Итеративный цикл diff → локализация блока → фикс → повтор.
|
||||||
|
|
||||||
|
## Риски
|
||||||
|
- **FFT bit-exact**: если FFTW — нужна та же сборка; свой radix-2 — воспроизводим напрямую (A.3).
|
||||||
|
- **Хост-зависимость**: уточнить точный размер буфера/фрейма (рендеры при разных RENDER_RANGE должны давать одинаковые байты).
|
||||||
|
- **Stereo**: все текущие свипы mono; для бит-exact графа M8 нужны стерео-рендеры (link/balance/ms).
|
||||||
|
|
||||||
|
## Открытые вопросы
|
||||||
|
1. Окна/oversample детали — из декомпиляции (A.6) или требуется доп. замеры.
|
||||||
|
2. Стерео-верификация (M8) — приоритет mono-путь или сразу стерео-граф.
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import subprocess, os, glob, sys, time
|
||||||
|
|
||||||
|
RPP = sys.argv[1] if len(sys.argv) > 1 else "/home/m/soothe-bt/render_rt.rpp"
|
||||||
|
OUT = sys.argv[2] if len(sys.argv) > 2 else "/home/m/re-tools/rt.bin"
|
||||||
|
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
['reaper', '-nosplash', '-renderproject', RPP],
|
||||||
|
stdout=open('/tmp/rtdump.log', 'w'), stderr=subprocess.STDOUT)
|
||||||
|
print('reaper', proc.pid, flush=True)
|
||||||
|
|
||||||
|
base = 0x180000000
|
||||||
|
extent = 0x7000000
|
||||||
|
host = None
|
||||||
|
deadline = time.time() + 90
|
||||||
|
while time.time() < deadline and proc.poll() is None:
|
||||||
|
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:
|
||||||
|
host = pid
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if host:
|
||||||
|
break
|
||||||
|
time.sleep(0.2)
|
||||||
|
print('host', host, flush=True)
|
||||||
|
if not host:
|
||||||
|
proc.kill()
|
||||||
|
sys.exit('no host')
|
||||||
|
|
||||||
|
readable = []
|
||||||
|
maps = open(f'/proc/{host}/maps').read()
|
||||||
|
for line in maps.splitlines():
|
||||||
|
parts = line.split()
|
||||||
|
lo, hi = (int(x, 16) for x in parts[0].split('-'))
|
||||||
|
if 'r' in parts[1] and base <= lo < base + extent:
|
||||||
|
readable.append((lo, hi, parts[1]))
|
||||||
|
print('regions:', len(readable), flush=True)
|
||||||
|
|
||||||
|
mem = os.open(f'/proc/{host}/mem', os.O_RDONLY)
|
||||||
|
outdir = OUT + '.regions'
|
||||||
|
os.makedirs(outdir, exist_ok=True)
|
||||||
|
with open(OUT, 'wb') as out:
|
||||||
|
total = 0
|
||||||
|
for lo, hi, _ in readable:
|
||||||
|
try:
|
||||||
|
d = os.pread(mem, hi - lo, lo)
|
||||||
|
out.write(d)
|
||||||
|
total += len(d)
|
||||||
|
with open(os.path.join(outdir, f'{lo:08x}_{hi:08x}.bin'), 'wb') as rf:
|
||||||
|
rf.write(d)
|
||||||
|
except Exception as e:
|
||||||
|
print('fail', hex(lo), e, flush=True)
|
||||||
|
os.close(mem)
|
||||||
|
print('dumped', total, flush=True)
|
||||||
|
proc.kill()
|
||||||
+2210
File diff suppressed because it is too large
Load Diff
+4062
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,63 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Batch soothes2 RPP render runner with hang protection.
|
||||||
|
Usage: run_sweep.py <grid.json> [--src burst500.wav] [--dur 2.0] [--outdir ...]
|
||||||
|
grid.json:
|
||||||
|
{ "name": "verif",
|
||||||
|
"base": "/home/m/soothe-bt/render_rt.rpp",
|
||||||
|
"data": [ {"rpp":"d05", "wav":"d05.wav", "params":{"depth":"0.5!"}}, ... ] }
|
||||||
|
Each data row renders base template with src substituted and params applied,
|
||||||
|
then runs reaper -renderproject with a per-render timeout and kills on hang.
|
||||||
|
"""
|
||||||
|
import json, sys, os, time, subprocess, signal, argparse
|
||||||
|
sys.path.insert(0, '/home/m/re-tools')
|
||||||
|
from mkbase import patch_tt
|
||||||
|
from tt_sweep import TPL as _x # noqa
|
||||||
|
|
||||||
|
REAPER = '/usr/bin/reaper'
|
||||||
|
SRC = '/home/m/soothe-bt/burst500p.wav'
|
||||||
|
DUR = 2.0
|
||||||
|
OUTDIR = '/home/m/soothe-bt'
|
||||||
|
|
||||||
|
def render(rpp, timeout=150):
|
||||||
|
proc = subprocess.Popen([REAPER, '-nosplash', '-renderproject', rpp],
|
||||||
|
stdout=open('/tmp/render.log','w'), stderr=subprocess.STDOUT)
|
||||||
|
try:
|
||||||
|
return proc.wait(timeout=timeout)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
for sig in (signal.SIGTERM, signal.SIGKILL):
|
||||||
|
try: proc.send_signal(sig); proc.wait(timeout=5)
|
||||||
|
except Exception: pass
|
||||||
|
print(f' !! HANG killed {rpp}')
|
||||||
|
return 'hang'
|
||||||
|
|
||||||
|
def pkill_sos():
|
||||||
|
os.system("pkill -9 -x reaper 2>/dev/null; pkill -9 -f '[y]abridge' 2>/dev/null")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument('grid')
|
||||||
|
ap.add_argument('--src', default=SRC)
|
||||||
|
ap.add_argument('--dur', type=float, default=DUR)
|
||||||
|
ap.add_argument('--outdir', default=OUTDIR)
|
||||||
|
a = ap.parse_args()
|
||||||
|
g = json.load(open(a.grid))
|
||||||
|
pkill_sos(); time.sleep(1)
|
||||||
|
miss = 0
|
||||||
|
for row in g['data']:
|
||||||
|
rpp = os.path.join(a.outdir, row['rpp'] + '.rpp')
|
||||||
|
wav = os.path.join(a.outdir, row['wav'])
|
||||||
|
if not os.path.exists(wav):
|
||||||
|
patch_tt(g['base'], a.src, rpp, wav, dur=a.dur)
|
||||||
|
# now patch params on top
|
||||||
|
os.system(f"python3 /home/m/re-tools/tt_sweep.py {rpp} {rpp}.tmp {wav} "
|
||||||
|
+ ' '.join(f'{k}={v.rstrip("!")}' for k, v in row['params'].items()))
|
||||||
|
os.rename(rpp + '.tmp', rpp)
|
||||||
|
rc = render(rpp)
|
||||||
|
print(f"{row['rpp']}: render rc={rc}")
|
||||||
|
miss += 1 if os.path.exists(wav) else 0
|
||||||
|
else:
|
||||||
|
print(f"{row['rpp']}: already exists")
|
||||||
|
print(f"done. rendered {miss} fresh.")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Verification sweep: generate RPPs from burst500p_b1 template, render, measure.
|
||||||
|
Cleanup-friendly: skips already-rendered wavs.
|
||||||
|
"""
|
||||||
|
import os, re, sys, time, subprocess, signal
|
||||||
|
sys.path.insert(0, '/home/m/re-tools')
|
||||||
|
|
||||||
|
TPL = '/home/m/soothe-bt/burst500p_b1.rpp'
|
||||||
|
DIR = '/home/m/soothe-bt'
|
||||||
|
REAPER = '/usr/bin/reaper'
|
||||||
|
|
||||||
|
def render(rpp, wav, timeout=150):
|
||||||
|
proc = subprocess.Popen([REAPER, '-nosplash', '-renderproject', rpp],
|
||||||
|
stdout=open('/tmp/verif_render.log','w'), stderr=subprocess.STDOUT)
|
||||||
|
try:
|
||||||
|
proc.wait(timeout=timeout)
|
||||||
|
return 'ok' if os.path.exists(wav) else 'no-file'
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
for s in (signal.SIGTERM, signal.SIGKILL):
|
||||||
|
try: proc.send_signal(s); proc.wait(timeout=5)
|
||||||
|
except Exception: pass
|
||||||
|
return 'hang'
|
||||||
|
|
||||||
|
os.system("pkill -9 -x reaper 2>/dev/null; pkill -9 -f '[y]abridge' 2>/dev/null; sleep 1")
|
||||||
|
|
||||||
|
def gen_rpp(name, params):
|
||||||
|
rpp = os.path.join(DIR, name + '.rpp')
|
||||||
|
wav = os.path.join(DIR, name + '.wav')
|
||||||
|
args = [f'{k}={v}' for k, v in params.items()]
|
||||||
|
if os.path.exists(wav):
|
||||||
|
print(f'{name}: cached')
|
||||||
|
return wav
|
||||||
|
os.system(f"python3 /home/m/re-tools/tt_sweep.py {TPL} {rpp} {wav} " + ' '.join(args))
|
||||||
|
rc = render(rpp, wav)
|
||||||
|
print(f'{name}: {rc}')
|
||||||
|
return wav
|
||||||
|
|
||||||
|
DRY = '/home/m/soothe-bt/burst500p_byp.wav'
|
||||||
|
if not os.path.exists(DRY):
|
||||||
|
os.system("cp /home/m/soothe-bt/burst500p_byp.rpp /home/m/soothe-bt/zz_byp.rpp")
|
||||||
|
# render dry baseline
|
||||||
|
proc = subprocess.Popen([REAPER, '-nosplash', '-renderproject', '/home/m/soothe-bt/burst500p_byp.rpp'],
|
||||||
|
stdout=open('/tmp/verif_byp.log','w'), stderr=subprocess.STDOUT)
|
||||||
|
try:
|
||||||
|
proc.wait(timeout=150)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
proc.kill()
|
||||||
|
print('dry baseline', os.path.exists(DRY))
|
||||||
|
|
||||||
|
sweeps = {
|
||||||
|
'depth': {'0.5':'0.5','1':'1.0','2':'2.0','3':'3.0','5':'5.0','10':'10.0'},
|
||||||
|
'attack': {'a5':'5.0','a25':'25.0','a100':'100.0','a250':'250.0','a500':'500.0'},
|
||||||
|
'release': {'r10':'10.0','r100':'100.0','r500':'500.0','r1000':'1000.0'},
|
||||||
|
'sharp': {'sh1':'1.0','sh3':'3.0','sh5':'5.0','sh20':'20.0'},
|
||||||
|
'mode': {'m0':'0.0','m2':'2.0'},
|
||||||
|
'sens': {'sn0':'0.0','sn6':'6.0','sn24':'24.0','sn48':'48.0'},
|
||||||
|
}
|
||||||
|
for group, vals in sweeps.items():
|
||||||
|
for name, val in vals.items():
|
||||||
|
gen_rpp(f'vp_{group}_{name}', {group: val})
|
||||||
|
|
||||||
|
print('\nall done; run measure.py on each vp_*.wav vs dry')
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Render remaining verification sweeps serially with hang protection.
|
||||||
|
Waits for reaper process EXIT (not just wav file appearing)."""
|
||||||
|
import os, sys, time, subprocess, signal
|
||||||
|
|
||||||
|
TPL = '/home/m/soothe-bt/burst500q_b1.rpp'
|
||||||
|
DIR = '/home/m/soothe-bt'
|
||||||
|
REAPER = '/usr/bin/reaper'
|
||||||
|
|
||||||
|
def render(rpp, timeout=180):
|
||||||
|
proc = subprocess.Popen([REAPER, '-nosplash', '-renderproject', rpp],
|
||||||
|
stdout=open('/tmp/verif2.log','w'), stderr=subprocess.STDOUT)
|
||||||
|
try:
|
||||||
|
proc.wait(timeout=timeout)
|
||||||
|
return proc.returncode
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
for s in (signal.SIGTERM, signal.SIGKILL):
|
||||||
|
try: proc.send_signal(s); proc.wait(timeout=5)
|
||||||
|
except Exception: pass
|
||||||
|
return 'hang'
|
||||||
|
|
||||||
|
os.system("pkill -9 -x reaper 2>/dev/null; pkill -9 -f '[y]abridge' 2>/dev/null; sleep 1")
|
||||||
|
|
||||||
|
def gen(name, params, depth_base='0.8639736175537109'):
|
||||||
|
rpp = os.path.join(DIR, name + '.rpp')
|
||||||
|
wav = os.path.join(DIR, name + '.wav')
|
||||||
|
if os.path.exists(wav) and os.path.getsize(wav) == 1588290:
|
||||||
|
print(f'{name}: cached')
|
||||||
|
return
|
||||||
|
args = ' '.join(f'{k}={v}' for k, v in params.items())
|
||||||
|
cmd = ['python3', '/home/m/re-tools/tt_sweep.py', TPL, rpp, wav]
|
||||||
|
for k, v in params.items():
|
||||||
|
cmd.append(f'{k}={v}')
|
||||||
|
subprocess.run(cmd, capture_output=True)
|
||||||
|
rc = render(rpp)
|
||||||
|
size = os.path.getsize(wav) if os.path.exists(wav) else 0
|
||||||
|
print(f'{name}: rc={rc} size={size}')
|
||||||
|
# avoid "already exists" false positives on truncated files
|
||||||
|
if size != 1588290 and os.path.exists(wav):
|
||||||
|
os.remove(wav)
|
||||||
|
|
||||||
|
sweeps = {
|
||||||
|
'release': {'r10':'10.0','r100':'100.0','r500':'500.0','r1000':'1000.0'},
|
||||||
|
'sharpness': {'sh1':'1.0','sh3':'3.0','sh5':'5.0','sh20':'20.0'},
|
||||||
|
'sens': {'sn0':'0.0','sn6':'6.0','sn24':'24.0','sn48':'48.0'},
|
||||||
|
'mode_d5': {'m0':'0.0','m2':'2.0'},
|
||||||
|
}
|
||||||
|
for group, vals in sweeps.items():
|
||||||
|
for name, val in vals.items():
|
||||||
|
params = {group.replace('_d5',''): val}
|
||||||
|
if group == 'sens':
|
||||||
|
params = {'band1 sens': val}
|
||||||
|
if group == 'mode_d5':
|
||||||
|
params = {'mode': val, 'depth': '5.0'}
|
||||||
|
gen(f'vpq_{group}_{name}', params)
|
||||||
|
print('done')
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""soothe2 поведенческий симулятор.
|
||||||
|
Модель: out = x − amount(t)·bp(x; fc, Q)
|
||||||
|
bp — 2nd-order bandpass, пик gain 1.0 на fc; Q из selectivity.
|
||||||
|
amount(t) = amt_total·env(t), env — one-pole со скоростью attack (в рост) / release (в спад).
|
||||||
|
env управляется энергией полосы (детектор): bp-loudness > порога → целимся в amt, иначе в 0.
|
||||||
|
Кривые из fit_curves.py / измерения.
|
||||||
|
"""
|
||||||
|
import numpy as np
|
||||||
|
import wave
|
||||||
|
|
||||||
|
def _lut_sat(xs, ys, x):
|
||||||
|
return np.interp(x, xs, ys, left=ys[0], right=ys[-1])
|
||||||
|
|
||||||
|
# --- измеренные кривые как LUT (максимальная точность) ---
|
||||||
|
_DEPTH_LUT = (np.array([0.5, 0.864, 1.0, 2.0, 3.0, 5.0, 10.0, 20.0]),
|
||||||
|
np.array([0.5605, 0.5879, 0.5977, 0.6673, 0.7274, 0.8199, 0.9387, 0.9893]))
|
||||||
|
_SENS_LUT = (np.array([0.0, 6.0, 12.0, 24.0, 48.0]),
|
||||||
|
np.array([0.4814, 0.7597, 1.0, 1.0, 1.0]))
|
||||||
|
_SHARP_LUT = (np.array([1.0, 3.0, 5.0, 10.0, 20.0]),
|
||||||
|
np.array([0.2041, 0.5913, 0.8386, 1.0, 1.0]))
|
||||||
|
|
||||||
|
def amount_total(depth=0.864, sens=12.0, sharp=10.0, mode=1):
|
||||||
|
A = _lut_sat(*_DEPTH_LUT, depth)
|
||||||
|
S = _lut_sat(*_SENS_LUT, sens)
|
||||||
|
H = _lut_sat(*_SHARP_LUT, sharp)
|
||||||
|
M = 0.745 if mode == 0 else 1.0
|
||||||
|
return A * S * H * M
|
||||||
|
|
||||||
|
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)
|
||||||
|
elif sw == 2:
|
||||||
|
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)
|
||||||
|
else:
|
||||||
|
v = ch[:, 0].astype(np.float64)
|
||||||
|
return (v - 128) / 128.0
|
||||||
|
|
||||||
|
def write_wav(path, x, sr=44100):
|
||||||
|
x = np.clip(x, -1, 1)
|
||||||
|
frames = (x.astype(np.float64) * (1 << 23)).astype(np.int64)
|
||||||
|
out = np.empty((frames.size, 1, 3), dtype=np.uint8)
|
||||||
|
out[:, 0, 0] = (frames & 0xFF).astype(np.uint8)
|
||||||
|
out[:, 0, 1] = ((frames >> 8) & 0xFF).astype(np.uint8)
|
||||||
|
out[:, 0, 2] = ((frames >> 16) & 0xFF).astype(np.uint8)
|
||||||
|
w = wave.open(path, 'wb')
|
||||||
|
w.setnchannels(1); w.setsampwidth(3); w.setframerate(sr)
|
||||||
|
w.writeframes(np.ascontiguousarray(out).tobytes()); w.close()
|
||||||
|
|
||||||
|
def tau_attack(attack):
|
||||||
|
return 0.020 * np.exp(attack / 1.955) # a=0→20ms, a=5→258ms, a=10→3.3s
|
||||||
|
|
||||||
|
def tau_release(release):
|
||||||
|
# измеренные tau (экспоненциальный фит спада к floor 0.2dB)
|
||||||
|
xs = np.array([0.0, 1.0, 2.0, 5.0, 10.0, 100.0])
|
||||||
|
ys = np.array([0.027, 0.08, 0.11, 0.14, 15.0, 15.0])
|
||||||
|
return float(np.interp(release, xs, ys))
|
||||||
|
|
||||||
|
def q_from_sel(sel):
|
||||||
|
# лучший фит ширины (probes-зонд): sel1→3.5, sel8→6.0 (2-порядка полоса)
|
||||||
|
return 3.0 + 0.36 * min(sel, 10.0)
|
||||||
|
|
||||||
|
# --- bandpass biquad: peak gain = 1.0 на fc (RBJ normalized) ---
|
||||||
|
def bp_coeffs(fc, q, sr):
|
||||||
|
w0 = 2 * np.pi * fc / sr
|
||||||
|
alpha = np.sin(w0) / (2 * q)
|
||||||
|
b = np.array([alpha, 0.0, -alpha]) / alpha # [1, 0, -1]
|
||||||
|
a = np.array([1 + alpha, -2 * np.cos(w0), 1 - alpha])
|
||||||
|
an = a / a[0] # normalize denominator: a[0]=1
|
||||||
|
# численное сканирование → норм на пик=1 (fn scale)
|
||||||
|
w = np.linspace(w0 * 0.4, w0 * 1.6, 8000)
|
||||||
|
H = np.abs(b[0] / a[0] * (1 - np.exp(-2j * w)) /
|
||||||
|
(1 + an[1] * np.exp(-1j * w) + an[2] * np.exp(-2j * w)))
|
||||||
|
G = H.max()
|
||||||
|
bn = b / a[0] / G
|
||||||
|
return bn, an
|
||||||
|
|
||||||
|
def apply_bp(x, fc, q, sr):
|
||||||
|
b, a = bp_coeffs(fc, q, sr)
|
||||||
|
b0, b1, b2 = b
|
||||||
|
a1, a2 = a[1], a[2]
|
||||||
|
y = np.zeros_like(x)
|
||||||
|
z1 = z2 = 0.0
|
||||||
|
for i in range(x.size):
|
||||||
|
v = x[i]
|
||||||
|
out = b0 * v + z1
|
||||||
|
z1 = b1 * v - a1 * out + z2
|
||||||
|
z2 = b2 * v - a2 * out
|
||||||
|
y[i] = out
|
||||||
|
return y
|
||||||
|
|
||||||
|
def simulate(x, sr=44100, fc=500.0, depth=0.864, sens=12.0, sharp=10.0,
|
||||||
|
sel=10.0, mode=1, attack=0.0, release=0.0, mix=100.0, thr=0.010):
|
||||||
|
amt = amount_total(depth, sens, sharp, mode)
|
||||||
|
q = q_from_sel(sel)
|
||||||
|
ta, tr = tau_attack(attack), tau_release(release)
|
||||||
|
a_alpha = 1 - np.exp(-1.0 / (ta * sr))
|
||||||
|
r_alpha = 1 - np.exp(-1.0 / (tr * sr))
|
||||||
|
bp = apply_bp(x, fc, q, sr)
|
||||||
|
# детектор: мгновенная амплитуда полосы → smoothed loudness
|
||||||
|
if len(x) > 0:
|
||||||
|
loud = np.abs(bp)
|
||||||
|
N = int(0.005 * sr)
|
||||||
|
win = np.ones(N) / N
|
||||||
|
loud = np.convolve(loud, win, mode='same')
|
||||||
|
env = 0.0
|
||||||
|
out = np.zeros_like(x)
|
||||||
|
lp = 0.0
|
||||||
|
for i in range(x.size):
|
||||||
|
tar = amt if loud[i] > thr else 0.0
|
||||||
|
env += (a_alpha if tar > env else r_alpha) * (tar - env)
|
||||||
|
out[i] = x[i] - env * bp[i]
|
||||||
|
if mix < 100:
|
||||||
|
out = (100 - mix) / 100.0 * x + mix / 100.0 * out
|
||||||
|
return out
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""soothe2 поведенческий симулятор v5 — STFT-детектор (архитектура фазы 3-6).
|
||||||
|
|
||||||
|
Модель (верифицировано рендерами фазы 4-6):
|
||||||
|
red(f) = base_red(level_f) + 6.02 * min(1, |sens_i|/12) * H(f; fc_i, Qeff_i) * sat(level)
|
||||||
|
H(f; fc, Qeff) = 1/sqrt(1 + (Qeff*A)^2), A = f/fc - fc/f
|
||||||
|
Qeff = 1.54 * q^1.33 (измерено: q=1->1.54, 2->3.32, 3->5.64, 6->16.8)
|
||||||
|
sat(level) = 1/(1+exp(-0.117*(level_db - (-32.99)))) (boost насыщается к 6.02 на высоком уровне)
|
||||||
|
base_red(level): 2.22/3.36/4.85/5.71/6.65/7.65/9.80 @ -30/-24/-18/-15/-12/-9/-3 dBFS (neut, depth0.864)
|
||||||
|
|
||||||
|
Синтез нотча: per detected peak - спектральная маска на кадре FFT:
|
||||||
|
X[k] *= 1 - g_peak * bell(k; f_peak, Q_notch), g_peak = 1 - 10^(-red(f_peak)/20)
|
||||||
|
Q_notch = 1.15 * sharpness (измерено psh_{1,3,5,10}: Q ~11/6/3/1.2)
|
||||||
|
|
||||||
|
Детектор: окно 2048/hop 512 Hann; |X| на кадре -> лок. луднесс -> red(f) -> пики
|
||||||
|
(лок. макс. выше порога; selectivity = min-peak-spacing + кол-во). Env attack/release на g_peak.
|
||||||
|
"""
|
||||||
|
import numpy as np
|
||||||
|
import wave
|
||||||
|
|
||||||
|
SR = 44100
|
||||||
|
|
||||||
|
# ---- измеренные LUT ----
|
||||||
|
_NLEVEL_DB = np.array([-30.02, -24.02, -18.02, -15.02, -12.02, -9.02, -3.05])
|
||||||
|
_NLEVEL_RED = np.array([2.22, 3.36, 4.85, 5.71, 6.65, 7.65, 9.80])
|
||||||
|
_DEPTH_MULT = {0.5: 0.56, 0.864: 1.0, 1.0: 1.03, 2.0: 1.24, 3.0: 1.36,
|
||||||
|
5.0: 1.51, 10.0: 1.58, 20.0: 1.60} # base_red * mult(depth) (грубо, калибровать)
|
||||||
|
_DEPTH_XS = np.array([0.5, 0.864, 1.0, 2.0, 3.0, 5.0, 10.0, 20.0])
|
||||||
|
_DEPTH_YS = np.array([0.56, 1.0, 1.03, 1.24, 1.36, 1.51, 1.58, 1.60])
|
||||||
|
|
||||||
|
def base_red(level_db, depth=0.864):
|
||||||
|
b = np.interp(level_db, _NLEVEL_DB, _NLEVEL_RED)
|
||||||
|
return b * float(np.interp(depth, _DEPTH_XS, _DEPTH_YS))
|
||||||
|
|
||||||
|
def sat_level(level_db):
|
||||||
|
return 1.0 / (1 + np.exp(-0.117 * (level_db - (-32.99))))
|
||||||
|
|
||||||
|
def qeff(q):
|
||||||
|
return 1.54 * q ** 1.33
|
||||||
|
|
||||||
|
def bell_h(f, fc, q):
|
||||||
|
a = f / fc - fc / f
|
||||||
|
qe = qeff(q)
|
||||||
|
return 1.0 / np.sqrt(1 + (qe * a) ** 2)
|
||||||
|
|
||||||
|
def eq_weight_db(freqs, bands, sens_global=0.0):
|
||||||
|
"""Сумма весов по включённым полосам (on=1). bands: list of dict(freq,q,sens,on)."""
|
||||||
|
w = np.zeros_like(freqs, dtype=float)
|
||||||
|
for b in bands:
|
||||||
|
if not b.get('on', 0):
|
||||||
|
continue
|
||||||
|
s = b.get('sens', 0.0)
|
||||||
|
if abs(s) < 1e-9:
|
||||||
|
continue
|
||||||
|
coeff = 6.02 * min(1.0, abs(s) / 12.0)
|
||||||
|
h = bell_h(freqs, b.get('freq', 500.0), b.get('q', 1.0))
|
||||||
|
w = w + coeff * h
|
||||||
|
return w
|
||||||
|
|
||||||
|
_QSHARP = (np.array([1.0, 3.0, 5.0, 10.0]), np.array([2.0, 6.0, 10.0, 12.0]))
|
||||||
|
|
||||||
|
def q_notch_from_sharp(sharp):
|
||||||
|
"""Базовый Q нотча от sharpness (умеренный драйв)."""
|
||||||
|
sh = max(float(sharp), 0.05)
|
||||||
|
return float(np.interp(sh, _QSHARP[0], _QSHARP[1], left=2.0, right=12.0))
|
||||||
|
|
||||||
|
def notch_bell(freqs, f_peak, qn):
|
||||||
|
"""Колокол нотча (gain-форма), peak 1 на f_peak, асимметричный BP-форм."""
|
||||||
|
a = freqs / f_peak - f_peak / freqs
|
||||||
|
return 1.0 / np.sqrt(1 + (qn * a) ** 2)
|
||||||
|
|
||||||
|
def stft_frames(x, nfft=2048, hop=512):
|
||||||
|
w = np.hanning(nfft)
|
||||||
|
n = len(x)
|
||||||
|
nf = max(0, (n - nfft) // hop + 1)
|
||||||
|
fr = np.fft.rfftfreq(nfft, 1 / SR)
|
||||||
|
return fr, w, nf, hop
|
||||||
|
|
||||||
|
def detect_peaks(red, freqs, spacing_hz, min_red_db=3.0, max_peaks=16, floor_db=1.0):
|
||||||
|
"""Локальные максимумы red(f) (нормир. к 0..max) выше min_red_db + min spacing."""
|
||||||
|
if red.size == 0:
|
||||||
|
return []
|
||||||
|
r = red.copy()
|
||||||
|
# локальные максимумы
|
||||||
|
d = np.diff(np.sign(np.diff(r)))
|
||||||
|
idx = np.where(d < 0)[0] + 1
|
||||||
|
out = []
|
||||||
|
for i in idx:
|
||||||
|
f = freqs[i]
|
||||||
|
if r[i] < min_red_db:
|
||||||
|
continue
|
||||||
|
# spacing
|
||||||
|
if any(abs(f - o) < spacing_hz for o in out):
|
||||||
|
continue
|
||||||
|
out.append(f)
|
||||||
|
if len(out) >= max_peaks:
|
||||||
|
break
|
||||||
|
return out
|
||||||
|
|
||||||
|
def lp_smooth(x, alpha):
|
||||||
|
y = np.empty_like(x)
|
||||||
|
s = 0.0
|
||||||
|
for i in range(x.size):
|
||||||
|
s += alpha * (x[i] - s)
|
||||||
|
y[i] = s
|
||||||
|
return y
|
||||||
|
|
||||||
|
def simulate(x, sr=SR, bands=None, depth=0.864, sharp=10.0, sel=10.0,
|
||||||
|
attack=0.0, release=0.0, mix=100.0, nfft=2048, hop=512,
|
||||||
|
thr_db=6.0, level_ref=1.0):
|
||||||
|
"""bands: [{'on':1,'freq':500,'q':1,'sens':12}, ...] дефолт = factory trk."""
|
||||||
|
if bands is None:
|
||||||
|
bands = [dict(on=1, freq=500.0, q=1.0, sens=12.0)]
|
||||||
|
x = np.asarray(x, dtype=np.float64)
|
||||||
|
fr, w, nf, H = stft_frames(x, nfft, hop)
|
||||||
|
W = eq_weight_db(fr, bands)
|
||||||
|
# loudness per bin: |X| на кадре -> dBFS относительно level_ref
|
||||||
|
out = np.zeros(len(x) + nfft)
|
||||||
|
win_cnt = np.zeros(len(x) + nfft)
|
||||||
|
# параметры
|
||||||
|
from sim_v5 import q_notch_from_sharp as qn_map
|
||||||
|
qn = qn_map(sharp)
|
||||||
|
# Q растёт с sharpness, сужается при глубоком нотче (g->1): Q_eff = qn*(1-g)+1
|
||||||
|
# (умеренный драйв Q=12 при sharp10; глубокий нотч сильного тона Q~4.5)
|
||||||
|
spacing = max(80.0, nfft / sr * 2.0) # ~ bin-разрешение*2 по умолчанию
|
||||||
|
# selectivity -> spacing & порог (sel больше = меньше ложных пиков)
|
||||||
|
spacing = 6.02 * sel * (nfft / sr) if False else max(80.0, 90.0 * sel)
|
||||||
|
minred = thr_db * (1.0) + (10 - sel) * 0.0
|
||||||
|
# level_db на кадре = 20log10( |X| / (level_ref * nfft/2) ) - 3dB (peak-bin -> rms)
|
||||||
|
norm = level_ref * nfft / 4.0
|
||||||
|
# per-frame smoothing alpha (кадр = hop сэмплов)
|
||||||
|
ta = max(0.020 * np.exp(attack / 1.955), 1e-9)
|
||||||
|
tr = max(float(np.interp(release, [0, 1, 2, 5, 10, 100],
|
||||||
|
[0.027, 0.08, 0.11, 0.14, 15, 15])), 1e-9)
|
||||||
|
a_alpha = 1 - np.exp(-hop / (ta * sr))
|
||||||
|
r_alpha = 1 - np.exp(-hop / (tr * sr))
|
||||||
|
# state per peak: current gain; мы будем сглаживать маску целиком (nfft/2+1)
|
||||||
|
mask_prev = np.ones(fr.size)
|
||||||
|
mask_cur = np.ones(fr.size)
|
||||||
|
for fi in range(nf):
|
||||||
|
s0 = fi * hop
|
||||||
|
seg = x[s0:s0 + nfft]
|
||||||
|
seg = seg * w
|
||||||
|
X = np.fft.rfft(seg)
|
||||||
|
mag = np.abs(X)
|
||||||
|
# per-bin уровень: |X|/(level_ref*nfft/4) -> dBFS (peak-bin ~ амплитуда тона)
|
||||||
|
lvl_bin = 20 * np.log10(mag / norm + 1e-12)
|
||||||
|
# кадровый уровень для sat: rms кадра
|
||||||
|
lvl = np.sqrt(np.mean(x[s0:s0 + nfft] ** 2) + 1e-12)
|
||||||
|
level_db = 20 * np.log10(lvl / level_ref + 1e-12)
|
||||||
|
# red(f): база от per-bin уровня + EQ-вес с sat(кадровый уровень)
|
||||||
|
red = base_red(lvl_bin, depth) + W * sat_level(level_db)
|
||||||
|
red = np.clip(red, 0, 60)
|
||||||
|
# пики детектим по lvl_bin (тон выделяется над шумовым дном на любом уровне)
|
||||||
|
thr = float(np.max(lvl_bin)) - 40.0
|
||||||
|
peaks = detect_peaks(lvl_bin, fr, spacing, thr)
|
||||||
|
if not peaks:
|
||||||
|
im = int(np.argmax(lvl_bin))
|
||||||
|
if lvl_bin[im] > thr and red[im] > 1.0:
|
||||||
|
peaks = [float(fr[im])]
|
||||||
|
factor = np.ones(fr.size)
|
||||||
|
for f_peak in peaks:
|
||||||
|
rr = float(np.interp(f_peak, fr, red))
|
||||||
|
g = 1 - 10 ** (-rr / 20.0)
|
||||||
|
qeff = max(qn * (1 - g) + 1.0, 0.5)
|
||||||
|
bell = notch_bell(fr, f_peak, qeff)
|
||||||
|
factor = factor * (1 - g * bell)
|
||||||
|
factor = np.clip(factor, 1e-6, 1.0)
|
||||||
|
# env smoothing (целевая маска -> сглаженная)
|
||||||
|
alpha = a_alpha if (factor < mask_prev).mean() > 0.5 else r_alpha
|
||||||
|
mask = mask_prev + alpha * (factor - mask_prev)
|
||||||
|
mask_prev = mask
|
||||||
|
Y = X * mask
|
||||||
|
y = np.fft.irfft(Y, nfft)
|
||||||
|
out[s0:s0 + nfft] += y * w
|
||||||
|
win_cnt[s0:s0 + nfft] += w * w
|
||||||
|
out = out[:len(x)] / np.maximum(win_cnt[:len(x)], 1e-9)
|
||||||
|
if mix < 100:
|
||||||
|
out = (100 - mix) / 100.0 * x + mix / 100.0 * out
|
||||||
|
return out
|
||||||
|
|
||||||
|
# ---- wav IO (как в sim.py) ----
|
||||||
|
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)
|
||||||
|
elif sw == 2:
|
||||||
|
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)
|
||||||
|
v = ch[:, 0].astype(np.float64)
|
||||||
|
return (v - 128) / 128.0
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
# test: tone1k через factory trk (band1@500 sens12)
|
||||||
|
base = '/home/m/soothe-bt'
|
||||||
|
x = read_wav(os.path.join(base, 'tone1k.wav'))
|
||||||
|
y = simulate(x, bands=[dict(on=1, freq=500.0, q=1.0, sens=12.0)])
|
||||||
|
# ред на 1k
|
||||||
|
sr = SR
|
||||||
|
i0, i1 = int(1.5 * sr), int(2.0 * sr)
|
||||||
|
red = -20 * np.log10(np.sqrt(np.mean(y[i0:i1] ** 2)) / np.sqrt(np.mean(x[i0:i1] ** 2)))
|
||||||
|
print("sim_v5 tone1k factory(b1@500,s12): red = %.2f dB (real trk_b1_500 = 11.91)" % red)
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys, os, struct
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
SR = 44100
|
||||||
|
|
||||||
|
def read_wav(path):
|
||||||
|
d = open(path, 'rb').read()
|
||||||
|
i = 12
|
||||||
|
ch = bps = None
|
||||||
|
data_off = data_sz = None
|
||||||
|
while i + 8 <= len(d):
|
||||||
|
cid = d[i:i+4]; sz = struct.unpack('<I', d[i+4:i+8])[0]
|
||||||
|
if cid == b'fmt ':
|
||||||
|
ch = struct.unpack('<H', d[i+8:i+10])[0]
|
||||||
|
bps = struct.unpack('<H', d[i+16:i+18])[0]
|
||||||
|
if cid == b'data':
|
||||||
|
data_off = i + 8; data_sz = sz
|
||||||
|
i += 8 + sz + (sz & 1)
|
||||||
|
raw = d[data_off:data_off+data_sz]
|
||||||
|
if bps == 32:
|
||||||
|
a = np.frombuffer(raw, dtype='<f4')
|
||||||
|
e = (1, 2)[ch-1:]
|
||||||
|
return a.reshape(-1, ch)[:, 0], ch
|
||||||
|
if bps == 24:
|
||||||
|
b = np.frombuffer(raw, dtype=np.uint8).reshape(-1, ch*3)
|
||||||
|
v = b[:, 0] | (b[:,1].astype(np.int32) << 8) | (b[:,2].astype(np.int32) << 16)
|
||||||
|
v = (v ^ (1 << 23)) - (1 << 23)
|
||||||
|
return v / 8388608.0, ch
|
||||||
|
a = np.frombuffer(raw, dtype='<i2')
|
||||||
|
return a.reshape(-1, ch)[:, 0] / 32768.0, ch
|
||||||
|
|
||||||
|
def db_spec_mono(x, nfft=16384, hop=2048, sr=SR):
|
||||||
|
# averaged magnitude spectrum in dB
|
||||||
|
w = np.hanning(nfft)
|
||||||
|
frames = [x[t:t+nfft] for t in range(0, len(x)-nfft, hop)]
|
||||||
|
if not frames: return None
|
||||||
|
X = np.stack([np.fft.rfft(f*w) for f in frames])
|
||||||
|
return 20*np.log10(np.abs(X).mean(0) + 1e-12), np.fft.rfftfreq(nfft, 1/sr)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
inp = sys.argv[1]
|
||||||
|
outs = sys.argv[2:]
|
||||||
|
x, ch = read_wav(inp)
|
||||||
|
bin, fr = db_spec_mono(x)
|
||||||
|
NFFT = 16384
|
||||||
|
# collect peaks (sines) of test tone from input
|
||||||
|
masks = {}
|
||||||
|
peakset = set()
|
||||||
|
for f in range(1, len(fr)):
|
||||||
|
if bin[f] > -120 and bin[f] == np.max(bin[max(0,f-8):f+9]):
|
||||||
|
peakset.add(f)
|
||||||
|
print("peaks in input (Hz):", sorted(round(fr[f]) for f in peakset if fr[f] < 18000))
|
||||||
|
colw = 22
|
||||||
|
hdr = f"{'Hz':>6} {'IN dB':>7}" + ''.join(f" {os.path.basename(p)[:colw]:>{colw}}" for p in outs)
|
||||||
|
print(hdr)
|
||||||
|
for f in sorted(peakset):
|
||||||
|
if fr[f] < 18000:
|
||||||
|
row = f"{fr[f]:6.0f} {bin[f]:7.1f}"
|
||||||
|
refbase = None
|
||||||
|
for p in outs:
|
||||||
|
y, _ = read_wav(p)
|
||||||
|
b, f2 = db_spec_mono(y)
|
||||||
|
# nearest bin to fr[f]
|
||||||
|
idx = int(round(fr[f]*(len(f2)-1)/f2[-1]))
|
||||||
|
idx = int(np.argmin(np.abs(f2 - fr[f])))
|
||||||
|
if p == outs[0]:
|
||||||
|
refbase = b[idx]
|
||||||
|
row += f" {b[idx]:{colw}.2f}"
|
||||||
|
print(row)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
+73
@@ -0,0 +1,73 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import struct, os, sys, math
|
||||||
|
|
||||||
|
SR = 44100
|
||||||
|
|
||||||
|
def read24(path):
|
||||||
|
d = open(path, 'rb').read()
|
||||||
|
i = 12
|
||||||
|
ch = bps = None
|
||||||
|
data_off = data_sz = None
|
||||||
|
while i + 8 <= len(d):
|
||||||
|
cid = d[i:i+4]; sz = struct.unpack('<I', d[i+4:i+8])[0]
|
||||||
|
if cid == b'fmt ':
|
||||||
|
ch = struct.unpack('<H', d[i+8:i+10])[0]
|
||||||
|
if cid == b'data':
|
||||||
|
data_off = i + 8; data_sz = sz
|
||||||
|
i += 8 + sz + (sz & 1)
|
||||||
|
raw = d[data_off:data_off+data_sz]
|
||||||
|
L = []
|
||||||
|
for k in range(data_sz // (ch*3)):
|
||||||
|
v = int.from_bytes(raw[k*ch*3:k*ch*3+3], 'little', signed=True)
|
||||||
|
L.append(v / 8388608.0)
|
||||||
|
return L, ch
|
||||||
|
|
||||||
|
def spectral_profile(samples):
|
||||||
|
# Welch-ish: split into 128-sample frames, hann window, count per-bin RMS
|
||||||
|
n = len(samples)
|
||||||
|
win = [0.5 - 0.5*math.cos(2*math.pi*k/(128)) for k in range(128)]
|
||||||
|
bins = [0.0]*65
|
||||||
|
frames = 0
|
||||||
|
for s in range(0, n-128, 64):
|
||||||
|
fr = [samples[s+k]*win[k] for k in range(128)]
|
||||||
|
re = [0.0]*65; im = [0.0]*65
|
||||||
|
for k in range(128):
|
||||||
|
f_c = fr[k]
|
||||||
|
for b in range(65):
|
||||||
|
ang = 2*math.pi*b*k/128
|
||||||
|
re[b] += f_c*math.cos(ang)
|
||||||
|
im[b] += f_c*math.sin(ang)
|
||||||
|
for b in range(65):
|
||||||
|
bins[b] += re[b]*re[b] + im[b]*im[b]
|
||||||
|
frames += 1
|
||||||
|
return [math.sqrt(b/frames) for b in bins]
|
||||||
|
|
||||||
|
def profile_db(prof):
|
||||||
|
return [20*math.log10(p+1e-12) for p in prof]
|
||||||
|
|
||||||
|
def main(inp, outs):
|
||||||
|
Li, ch = read24(inp)
|
||||||
|
pi = spectral_profile(Li)
|
||||||
|
pids = {}
|
||||||
|
for _, path in outs:
|
||||||
|
L, _ = read24(path)
|
||||||
|
pids[path] = spectral_profile(L)
|
||||||
|
# clamp frames for profile in case lengths differ
|
||||||
|
return pi, pids
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
inp = sys.argv[1]
|
||||||
|
outs = [(os.path.basename(p).split('_')[1] if False else p, p) for p in sys.argv[2:]]
|
||||||
|
pi, pids = main(inp, outs)
|
||||||
|
freqs = [SR*k/256 for k in range(65)]
|
||||||
|
print(f"{'freq':>7} {'IN':>8}" + ''.join(f" {os.path.basename(p):>18}" for _,p in outs))
|
||||||
|
print("LOG2-ish band graph")
|
||||||
|
# print compact dB difference
|
||||||
|
dbin = profile_db(pi)
|
||||||
|
for b in range(2, 64, 2):
|
||||||
|
row = f"{freqs[b]:7.0f} {dbin[b]:8.1f}"
|
||||||
|
for _, p in outs:
|
||||||
|
pp = pids[p]
|
||||||
|
d = 20*math.log10(pp[b]+1e-12) - dbin[b]
|
||||||
|
row += f" {d:18.2f}"
|
||||||
|
print(row)
|
||||||
+146
@@ -0,0 +1,146 @@
|
|||||||
|
## Objective
|
||||||
|
- Реверс-DSP oeksound soothe2 v1.1.2 VST3 → математическая модель (STFT, детектор резонансов, нотич-синтез). Текущая фаза — поведенческий параметр-свип через рендер Reaper RPP + статический декомпил (без декрипты бинарника пока).
|
||||||
|
|
||||||
|
## Key Facts
|
||||||
|
- Пользователь русскоязычный; sudo нет. Объект: `~/.wine/.../soothe2_x64.vst3` (PE32+, base 0x180000000), yabridge 5.1.1, Reaper 7.78.
|
||||||
|
- Frida невозможна (Seccomp). Дамп памяти — `pread /proc/pid/mem` от родителя. pkill-ловушки: paths без `harneb`/`reaper` → `pkill -9 -x reaper`, `pkill -9 -f '[y]abridge'`.
|
||||||
|
- Рендер-проект: `RENDER_RANGE 1 0 0 0 1000`, ITEM обязательно `POSITION 0`. Offline-рендер мгновенный → для живого дампа realtime (`RENDER_1X 1`) + `testtone.wav`.
|
||||||
|
- VST-state = 92-байт JUCE-заголовок + base64 XML `<SOOTHE2STATE>` + 54 `<PARAM id=... value=.../>`; внутри — UI-state без DSP.
|
||||||
|
|
||||||
|
## Breakthrough (исправлено в этой сессии)
|
||||||
|
- **Формат-адаптивность PARAM**: плагин применяет `<PARAM value>` ТОЛЬКО если строка сериализована в том же формате, что оригинал:
|
||||||
|
- full-precision (depth, band freq/q...) → `%.16f` (напр. `"0.9000000000000000"`). Короткие строки → схлопывание (fallback).
|
||||||
|
- short-формат `"X.0"` (selectivity, mix, mode, oversample, resolution) → ЛОМАЕТСЯ от `%.16f`.
|
||||||
|
- `sweep.py`/`tt_sweep.py` адаптивны: >4 десятичных в оригинале → `%.16f`, 1-4 → то же число знаков, иначе — как есть.
|
||||||
|
|
||||||
|
## Синтез модели (поведенческая, v4 — реализована в sim.py, RMSE на settled ≤0.05dB по всем свипам)
|
||||||
|
```
|
||||||
|
PARADИГМА (реализована): out(t) = x(t) − amount_b(t)·bp_b(t; fc, Q) [субтрактивный нотч, не микш-параллельный]
|
||||||
|
amount(total) = A(depth)·S(sens)·H(sharp)·M(mode) — LUT из измерений (пересечение, не аналит. фит)
|
||||||
|
A(depth): 0.5/0.864/1/2/3/5/10/20 → 0.560/0.588/0.598/0.667/0.727/0.820/0.939/0.989
|
||||||
|
S(sens): 0/6/12/24 → 0.48/0.76/1.0/1.0 (floor при sens=0!)
|
||||||
|
H(sharp): 1/3/5/10 → 0.20/0.59/0.84/1.0
|
||||||
|
M(mode): 0 → 0.745 (mode0 слабее), 1.. → 1.0
|
||||||
|
Q(sel): = 3.0+0.36·sel (2nd-order bandpass; реал. даёт более крутые борта — приближ.)
|
||||||
|
env(t): one-pole, α_attack=1−e^(−1/(T_a·fs)), α_release=1−e^(−1/(T_r·fs))
|
||||||
|
T_a = 0.02·e^(a/1.955) (0→20мс, 5→258мс, 10→3.3c); T_r LUT из спадов (0→27мс, 5→140мс, 10→15c)
|
||||||
|
детектор: лауднеss полосы |bp| smoothed 5мс > thr 0.01 → цель amt, иначе 0 (порог между probe 0.0005 и burst 0.2)
|
||||||
|
выход: x_out = mix/100·… — линейный кроссфейд: (100−mix)/100·x + mix/100·fully_reduced
|
||||||
|
(mix=100 = полная обработка = ref; mix=0 → сухой; проверено на s_corr_mix: монот. линейно)
|
||||||
|
```
|
||||||
|
Верификация (settled, RMSE по reduction dB на burst-окне):
|
||||||
|
| параметр | реал | sim | RMSE | | параметр | реал | sim | RMSE |
|
||||||
|
|---|---|---|--|---|---|---|---|---|
|
||||||
|
| дефолт | 7.70 | 7.70 | 0.002 | sharp=1 | 1.11 | 1.11 | 0.002 |
|
||||||
|
| depth=2 | 9.56 | 9.56 | 0.001 | sharp=3 | 3.72 | 3.71 | 0.006 |
|
||||||
|
| depth=5 | 14.90 | 14.89 | 0.008 | attack=5 | 5.68 | 5.90 | 0.360 |
|
||||||
|
| depth=10 | 24.27 | 24.25 | 0.018 | mode=0 | 5.03 | 5.00 | 0.023 |
|
||||||
|
| depth=20 | 39.43 | 39.40 | 0.054 | mode=2 | 7.70 | 7.70 | 0.002 |
|
||||||
|
| sens=0 | 2.89 | 2.89 | 0.001 | sens=24 | 7.70 | 7.70 | 0.002 |
|
||||||
|
Q-профиль (probes-зонд): sim sel1 500→680@540→0.62@700 vs real 8.03→4.54→1.13; 2nd-order не покрывает
|
||||||
|
крутые борта реала (реал резче ~×3): допустимо как 1-я итерация, RMSE макс по профилю ~1.6dB.
|
||||||
|
Release-трассы: r0/r1/r2/r5 совпадают (sim против real 1.6s:3.5/4.3, 1.7s:1.5/1.9, 1.9s:0.3/0.3).
|
||||||
|
|
||||||
|
## Q/width-зонд (probes500/probes500b, drive 500Hz amp0.2 + справки 0.02)
|
||||||
|
- Методика: длинный single-FFT на окне 1.7..2.9s (разрешение ~0.7Hz), справки на 500,505,510,520,540,
|
||||||
|
555,575,600,700 (b-set) / 500,505,515,530,560,640,780 (a-set). Драйв-тон держит детект-клок,
|
||||||
|
справки зондируют передаточную характеристику в установившемся режиме.
|
||||||
|
- Данные (a-set) red@freq: sel1: 500:8.0 510:7.9 520:7.7 540:4.5 555:2.7 575:1.6 600:0.7 (плавный хвост)
|
||||||
|
sel8: 500:8.7 510:9.1 520:6.6 540:1.3 555:0.1 575:0.0 600:0.0 (резкий срез) — Q уверенно растёт с sel.
|
||||||
|
- Центраئة нотча на 500..510 чуть выше 500 (fs на 505..510) — трекинг слегка субадио.
|
||||||
|
- Значения на дип-тонах 530/560/640 в b-наборе — из мусора (нет справки) и исключены; a-набор чистый.
|
||||||
|
- ВАЖНО: dry-файлы дважды перезаписывались провальными dry-рендерами (RENDER_FILE не патчился по
|
||||||
|
`.wav` напр. burst500L_byp → overwritten probes). ФИКС: патчить RENDER_FILE явно в sed до рендера.
|
||||||
|
- data: оба svипа предсказуемо монотонны; таблица в summary выше.
|
||||||
|
|
||||||
|
## Верифицированные данные (все свипы с корректным форматом)
|
||||||
|
- **depth (mir)**: монотонно distinct (vs-ref 496k→441k→...→347k@0.85→384k@1.0) — работает.
|
||||||
|
- **selectivity**: 0/1/2/5 distinct (476k/466k/458k/461k vs ref); 10==20==50==100 (сатурация ≥10).
|
||||||
|
- **oversample**: 0==ref; 1 distinct; 2==3==4 (сатурация на 2).
|
||||||
|
- **resolution**: 0 distinct; 1==ref; 2 distinct (все distinct); 3==4.
|
||||||
|
- **mix**: 0/25/50/75/100 всё distinct монотонно; 100==ref.
|
||||||
|
- **mode**: 0 distinct; 1==2==3==4==ref.
|
||||||
|
- **band1 freq**: tt_b1f_678 (дефолт banda freq) == tt_ref бит-в-бит; свип 100..8000 отрендерен.
|
||||||
|
|
||||||
|
## Прочие находки
|
||||||
|
- 60-сек рендер p60_ref/p60_dep0/p60_dep1 подтвердил collapse класса при коротких строках.
|
||||||
|
- rtdump.py живые дампы (регионы по VA, RA/WA) — DSP-кластер .data меняется между глубинами; раньше не совпали по layout (артефакт noise).
|
||||||
|
|
||||||
|
## Work State
|
||||||
|
### Completed
|
||||||
|
- Свип-инфраструктура: sweep.py + tt_sweep.py (адаптив формата), рендер, сверочный анализ (spectrum.py медленный, нужно numpy).
|
||||||
|
- Тест-тон: testtone.wav (2s, multi-tone 110Hz–14kHz с AM) для изоляции резонансов; tt_ref==tt_678 (0 diff).
|
||||||
|
- Базовые свипы по всем основным параметрам.
|
||||||
|
|
||||||
|
### Active
|
||||||
|
- **Извлечена статическая depth-кривая**: LUT 207 float @0x1826170e8..0x182617420, форма `0.302 + 0.698·sin(π/2·x)^0.94` (r²=0.99999), saved `depthcurve.npy`. ИДЕНТИЧНА у ref/dep0/dep1 → вшита в бинарь (build-time), не меняется от параметра = внутренний «depth→amount» маппинг.
|
||||||
|
- **Адаптивная динамика (burst-тест, band1@500Hz, q≈1, depth 0.864)**: burst500.wav (2s: 0.5s тишины → 1.0s 500Hz burst amp 0.2 → 0.5s тишины, + 1000Hz amp 0.05 фон). `burst500_b1` (fx) vs `burst500_byp` (dry):
|
||||||
|
- ATTACK: плавное включение нотч-ослабления, τ≈**18 мс** (фит на экспоненту A·(1−e^−t/τ), stable 17.6/17.7/18.8/18.8), установка −7.7dB бай-в-бай за ~100мс.
|
||||||
|
- RELEASE: хвост после снятия бурста спадает за τ≈**5 мс** (после 1.5s fx-энергия 24→0 за ~30мс) — быстрый трелинг, симметрия фола не классическая (вероятно фильтр-транзиент, не медленный rel).
|
||||||
|
- Глубина ослабления НЕ зависит от «порога» — это фикс. нотч на частоте band, amount задаётся curve(depth).
|
||||||
|
- Рендерится через те же tt-файлы; РЕЗУЛЬТАТЫ в wav — 24-bit (`sampwidth=3`), читать через 24-bit decode (или любой np.frombuffer под sw), не 16-bit!
|
||||||
|
- **τ-инвариантность** (burst-тест, band1@500Hz, win 10ms, Goertzel, фит к A(1−e^−t/τ)):
|
||||||
|
- depth 0→0.86→1: A=6.39→7.70→7.91 dB; τ=18.3→16.8→16.5 ms (τ почти const) → depth контролирует amount, НЕ скорость.
|
||||||
|
- selectivity: τ РАСТЁТ монотонно 12.3→16.8 ms при sel 0→10 (12.3/12.4/12.6/12.9/13.0/13.4/14.3/16.8) → Q/selectivity = time-const антреккинга.
|
||||||
|
- depth-amount таблица (settled, band1@500): sel 0..6 монотонно −7.79→−9.08 dB, затем dip sel8=−8.79, sel10=−7.71 (non-monotonic у высоких sel, нюанс трекера).
|
||||||
|
- «650Hz notch» в раннем FFT-анализе — АРТЕФАКТ деления на near-zero dry; реальные числа = ratio dry/fx только там, где dry>5% пика (pure 500Hz tone → окно вокруг 500).
|
||||||
|
- **secret свипа: `patchparam.py`** обязателен для формат-сохранения: state-блок в RPP = base64, wrapped по 128-симв/строка с 2-sp indent и НОВОЙ строкой перед '>'. Ключевое: глубина пишется ТОЛЬКО `%.16f` (короткая `0.0` → collapse!), поэтому CLI `depth=0.0!` форсит полную точность.
|
||||||
|
- Float-поиск по живому дампу: depth 301 hits (множество копий по bands), selectivity/sharpness → общий бакет (10.015, 9.997), mix → 4 копии 100.0, band freqs (678.76, 8242.67) → 0 точных (хранятся иначе, напр. Hz→binidx или через npf).
|
||||||
|
- Живой diff dep0 vs dep1 в .data (0x182622130, 0x182622350, 0x1826223c0, 0x18262aca8, 0x182672fe4) — флоаты-состояние меняются; но регион содержит много мусора/nan (это .data буферы рендера, не коэфф).
|
||||||
|
|
||||||
|
### Провалы/уроки
|
||||||
|
- ASLR между отдельными rtdump-запусками сдвигает раскладку регионов → прямого VA-сравнения НЕТ (dep0b 10 регионов, dep1b 9). Только те адреса, что реально в 0x18260a000-0x18267c000 (совпадают) сравнимы.
|
||||||
|
|
||||||
|
## Relevant Files
|
||||||
|
- **`/home/m/re-tools/sim.py`** — поведенческий симулятор (LUT amount + 2nd-order bp Q(sel) + env attack/release + mix/mode). `simulate(x, fc, depth, sens, sharp, sel, mode, attack, release, mix)`. RMSE settled ≤0.05dB (см. таблицу выше).
|
||||||
|
- **`/home/m/re-tools/verify_sim.py`** — RMSE-сверка симулятора против рендеров (mag@500 трасса). Использование: `verify_sim.py <fx.wav> --params depth=5 attack=5`.
|
||||||
|
- **`/home/m/re-tools/fit_curves.py`** — фит LUT (остаётся как аналитический эквивалент; LUT в sim.py приоритетнее).
|
||||||
|
- probes500/probes500b_wav+byp — зонды ширины нотча; prb_*/prb2_* — рендеры.
|
||||||
|
- `synth_multi.py` (AM-комб 200–3000Hz резонансный), `burst500.wav` + `burst500_b1.rpp/.wav` + `burst500_byp.rpp/.wav` (burst-атак/релиз band1@500Hz), `burst_dep0/dep1/sel0/sel2/sel5.rpp/.wav` (τ-sweep), `comb.wav` + `comb_b1_*.rpp/wav`, `comb_dep_{0..1}`, `depthcurve.npy`. **Рендеры Reaper = 24-bit** — читать с `sampwidth=3`, не 16-bit!
|
||||||
|
- `/home/m/re-tools/patchparam.py` — патчер PARAM в base64 state с формат-сохранением (128-wrap, `depth=...!` → %.16f).
|
||||||
|
- `/home/m/re-tools/spec.py`, `probe.py`, `notch.py`, `synth.py`, `mkbase.py`, `tt_sweep.py`.
|
||||||
|
- `/home/m/soothe-bt/tone1kq.wav` (чистый 1k, -18dBFS), `dual.wav` (500+2000Hz), `resonant.wav` (500Hz-резонанс).
|
||||||
|
- `/home/m/soothe-bt/t1kq_*.rpp/wav`, `res_only1_*.rpp/wav`, `dual_b1_*.rpp/wav`.
|
||||||
|
- `/home/m/re-tools/sweep.py` — адаптивный генератор RPP (tpl фиксирован, для смены файла использовать mkbase + tt_sweep).
|
||||||
|
- `/home/m/soothe-bt/tt_base.rpp` (template: testtone + дефолтные парамы), `tt_ref.wav`.
|
||||||
|
- `/home/m/soothe-bt/s_corr_{sel,os,res,mix,mode}*.wav` — верифицированные свипы на resonant.wav.
|
||||||
|
- `/home/m/soothe-bt/render_v5.rpp`, `s_ref2.wav` (бит-в-бит оригинал).
|
||||||
|
## Фаза 3: Трекинг подтверждён (aug 16) — ARCHITECTURE REVISION
|
||||||
|
- **Вход-зависимость**: ред@1k растёт с уровнем сигнала (band1=1000, тон 1k):
|
||||||
|
`-27→-21→-15→-12→-9→-6→-3 dBFS` ⇒ `5.80→7.75→9.91→11.05→12.21→13.40→14.60 dB`. amount растёт с уровнем детектируемого резонанса (не бинарный порог).
|
||||||
|
- **Трекинг нотча** (fresh renders `trk_b1_*`, вход tone1k 1k, band1∈{500,1000,2000}, длинная FFT): нотч ВСЕГДА на fc=тон=1k, band-частота НЕ двигает позицию режектора:
|
||||||
|
- band=500 → 11.91dB, band=1000 → 15.82dB, band=2000 → 11.91dB, band1 off → 9.80dB, sens=0 → 9.80dB.
|
||||||
|
- Единственный вклад band-EQ — ВЕС глубины детекции в области (совпадение band~тон даёт +4dB; off/sens0 −2dB).
|
||||||
|
- **Мульти-пики**: dual (500+2000), оба нотча активны одновременно при sel=1..20 (~10dB оба) → детектор находит НЕСКОЛЬКО резонансов; selectivity НЕ регулирует число нотчей на явных тонах.
|
||||||
|
- **Пользователь (иерархия)**: детектор оценивает каждую частоту спектра; band-EQ формирует его ВХОД (усиление/ослабление детекции по областям, у band2 sens может быть −12); selectivity = отбор пиков; sharpness = форма нотча; depth = глобальная глубина.
|
||||||
|
- **Reaper-ловушка**: tt_sweep с ОТНОСИТЕЛЬНЫМ out_wav создаёт ПОДДИРЕКТОРИЮ → всегда абсолютный путь в RENDER_FILE.
|
||||||
|
- Файлы: `trk_base.rpp`, `trk_b1_{500,1000,2000,eqoff,sens0}.rpp/.wav`, `sel_base.rpp`, `sel_{1,3,10,20}.rpp/.wav`, `lvl_{03..27}db.wav` + `lvl_t_*.rpp/.wav`.
|
||||||
|
### Фаза 3b: amount зависИТ от уровня сигнала (не просто порог)
|
||||||
|
- lvl_sweep (тон 1k, band1@1000, depth=0.864): ред@1k = 5.80 / 7.75 / 9.91 / 11.05 / 12.21 / 13.40 / 14.60 для −27..−3 dBFS.
|
||||||
|
- При depth=10: 21.67 / 24.15 / 26.63 / 27.86 / 29.09 / 30.33 / 31.56. Наклон d(ред)/d(level) ≈ 0.37–0.41 dB/dB обоих depth → amount ∝ уровень^p (монотонно с насыщением), НЕ бинарный порог. depth — масштаб, не floor.
|
||||||
|
- Обе кривые монотонны и близки по форме (отношение ~2.2 при громком, ~3.7 при тихом → форма зависит от depth слабо).
|
||||||
|
### Фаза 4 (aug 16): КАРТА EQ-ВЕСОВ ДЕТЕКТОРА
|
||||||
|
- **`on` полосы = enable детекторного веса**; off-полоса её sens НЕ отдаёт в детекцию. Подтверждения:
|
||||||
|
- eqT_b1_b2/b3/b4/b2b3b4 и trk_b1_500 (sens off-полос любая) ВСЕ = 11.91dB (только от b1 sens12); eq_off_sensfac (все off, factory sens) = 9.80dB = нейтраль.
|
||||||
|
- eq1778_b2s_12 (b2 on=0) = 11.81, b1on12_b2s12 (b2 on=1) = 17.60 — ЕДИНСТВЕННАЯ разница band2 on.
|
||||||
|
- Баг в раннем clear_bands сбрасывал b1 sens→0 (eq_b1_on был sens0=нейтраль 9.80; правильный net_b1on12 sens12 = 11.91).
|
||||||
|
- **S(sens) при совмещённом весе (band1@1k, тон 1k)**: sens 0/6/9/12/18/24 → boost над полом 0.00/3.16/4.65/6.02/6.02/6.02 dB.
|
||||||
|
boost = 6.02·min(1, sens/12), насыщение +6dB при sens≥12 (не монотонно из-за сатурации детектора, не бага).
|
||||||
|
- **W(f) — колокол EQ-веса** (тон 1k, одна полоса on sens12, варьирую band freq):
|
||||||
|
- band1 (q≈1): 500→11.91(+2.11), 600→12.80(+3.00), 750→14.28(+4.48), 850→15.19(+5.39), 950→15.75(+5.95), 1000→15.82(+6.02), 1050→15.76, 1200→15.05(+5.25), 1500→13.44(+3.64), 2000→11.91(+2.11), 2500→11.16(+1.36). Симметричный широкий колокол (первый октав хвост +2.1dB, не спадает к 0).
|
||||||
|
- band2 (q≈4.5): 600→10.15(+0.35), 750→10.78(+0.98), 850→11.97(+2.17), 1000→15.80(+6.00), 1200→11.68(+1.88), 1500→10.34(+0.54) — РЕЗКИЙ колокол. **Q полосы управляет шириной детекторного веса** (q=4.5 на −1 октаве теряет ~полностью, q=1 теряет только ~66%).
|
||||||
|
- **Позиция нотча**: band freq не двигает режектор (всегда на резонансе тона); EQ-веса модулируют ТОЛЬКО глубину.
|
||||||
|
- Новый модельный блок: `red(f_tone) = floor(level) + Σ_on boost_i`, `boost_i = 6.02·min(1,|sens_i|/12)·H_q(f_tone; fc_i, Q_i)`.
|
||||||
|
- Файлы: `wf_{600,750,...,2500}.wav` (band1 q-sweep), `ws_{6,9,18,24}.wav` (aligned sens), `b2q_{600..1500}.wav` (band2 q=4.5 sweep), `b1on12_b2{on12,s12}.wav`.
|
||||||
|
### Фаза 5 (aug 16): STFT-параметры детектора (Stage 1)
|
||||||
|
- **Хоп/задержка**: tone-jump зонд tj.wav (1k→1.5k в t=2.0s) → нотч пересаживается на новый резонанс за ~1 кадр ≈ 18мс (hop ≤ 512 сэмплов). Трекинг чирпа 400→2000Hz в реальном времени, dt≈0.03→0.05s (шум оценки FFT-бина, значимой задержки нет).
|
||||||
|
- **Разрешение по частоте**: пары 1000+1040 → сливаются в один широкий нотч; 1000+1100 разделяются (центры ≈996/1104, но провал между ними всё ещё глубокий); 1000+1200 → два чётких отдельных нотча с полным восстановлением на 1100. → окно FFT ≈ 1024–2048 (бины 21–43Hz), multiple-нотч подтверждён.
|
||||||
|
- **Энерготрекинг (fx@f по окну 2048, sync-Goertzel)**: до прыжка fx@1000 ≈ −24dB (ред ~14dB), после — fx@1500 ≈ −23.8dB. Двойной бурст (gap 10..100ms) → нотч на том же тоне не переоткрывается между бурстами (ред остаётся ~4dB floor) — bурст probe (осб) указывает на фиксированный window-центр, не пер-открытие.
|
||||||
|
- Вывод для v5: STFT с окном ~2048, хоп ~512–1024; window function Hann; детектор по |X| per-bin → локальные пики (selectivity); EQ-веса W(f) как множитель sensitivity per-bin; per-пик нотч (bandstop Q=sharpness).
|
||||||
|
- Файлы: `chirp_log_400_2000.wav`, `chirp_trk.rpp/.wav`, `pair_{1040,1100,1200}.wav` + `pair_trk_{40,100,200}`, `dburst_{10,20,50,100}.wav` + `dburst_trk_*`, `tj.wav` + `tj_trk.rpp/.wav`.
|
||||||
|
### Фаза 6 (aug 16): КОЛОКОЛ EQ-ВЕСА + SHARPNESS + level-floor (Stage 2 фит)
|
||||||
|
- **H(f) — колокол**: `H(f) = 1/sqrt(1 + (Qeff·A)²)`, `A = f/fc − fc/f`. Qeff = **1.54·q^1.33** (q=1→1.54, 2→3.32, 3→5.64, 6→16.8). Воспроизводит кривую band1 (wf_*) с ошибкой ≤0.15dB по всему диапазону 500..2500.
|
||||||
|
- **Sharpness → Q нотча**: psh_{1,3,5,10} (probes500, drive 500·0.2, band1@500): центр 505Hz red 1.3/4.3/6.8/8.7dB; ширина Q_notch≈1.15·sharp (sh10→~11, sh5→~6, sh3→~3, sh1→~1.2). Центр-глубина НЕ постоянна — растёт с sharp (1.3→8.7), т.е. sharp масштабирует amount И Q.
|
||||||
|
- **level-floor (нейтраль)**: `neut_{24..3}` (все полосы off/sens0, тон 1k): red = 2.22/3.36/4.85/5.71/6.65/7.65 dB при −24/−18/−12/−9/−6/−3 dBFS rms. Монотонно, наклон ~0.31 dB/dB. `al_{*}` (b1@1000 sens12): 5.80/7.75/9.91/11.04/12.21/13.39 → boost = 3.59/4.39/5.06/5.33/5.56/5.74, **насыщается к ~6dB** с уровнем (не аддитивен в dB!).
|
||||||
|
- Модель amount: `red(f) = curv(level·G_eq(f))` с насыщением (не линейный сдвиг в dB). depth масштабирует (фаза 3b). Для sim: 2D LUT level×W.
|
||||||
|
- Файлы: `lvl_tone_lv{24,18,12,9,6,3}.wav`, `neut_{*}.rpp/.wav`, `al_{*}.rpp/.wav`, `psh_{1,3,5,10}.rpp/.wav`.
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import base64, re, sys, os
|
||||||
|
|
||||||
|
# Reads render_v5.rpp, changes plugin PARAM values, writes a new RPP targeting an output name.
|
||||||
|
|
||||||
|
TPL = "/home/m/soothe-bt/render_v5.rpp"
|
||||||
|
|
||||||
|
def load_state(rpp=TPL):
|
||||||
|
txt = open(rpp).read().split('\n')
|
||||||
|
vi = [i for i,l in enumerate(txt) if 'VST "VST3: soothe2' in l][0]
|
||||||
|
blob = []
|
||||||
|
end = vi
|
||||||
|
for i in range(vi+1, vi+40):
|
||||||
|
s = txt[i].strip()
|
||||||
|
if s == '>' or not s:
|
||||||
|
end = i
|
||||||
|
break
|
||||||
|
blob.append(s)
|
||||||
|
out = b''
|
||||||
|
for l in blob:
|
||||||
|
out += base64.b64decode(l + '=' * ((-len(l)) % 4))
|
||||||
|
return txt, vi, end, blob, out
|
||||||
|
|
||||||
|
def save_state(txt, vi, end, blob, out, binary_len, rpp_out):
|
||||||
|
# re-encode: binary_len bytes header + rest is ascii xml
|
||||||
|
header = out[:binary_len]
|
||||||
|
xml = out[binary_len:]
|
||||||
|
new = base64.b64encode(header + xml).decode()
|
||||||
|
lines = [new[i:i+128] for i in range(0, len(new), 128)]
|
||||||
|
newblk = [' ' + l for l in lines]
|
||||||
|
txt2 = txt[:vi+1] + newblk + [txt[end]]
|
||||||
|
open(rpp_out, 'w').write('\n'.join(txt2))
|
||||||
|
|
||||||
|
def set_params(rpp, out_rpp, out_wav, params):
|
||||||
|
txt, vi, end, blob, out = load_state(rpp)
|
||||||
|
# preserve everything: prefix before '<?xml', the xml, and the binary tail
|
||||||
|
xml_idx = out.find(b'<?xml')
|
||||||
|
if xml_idx < 0:
|
||||||
|
raise ValueError("no XML in state")
|
||||||
|
header = out[:xml_idx]
|
||||||
|
xml_bytes = out[xml_idx:-1] # last byte is padding
|
||||||
|
xml = xml_bytes.decode('utf8', 'replace')
|
||||||
|
for pid, val in params.items():
|
||||||
|
m = re.search(rf'<PARAM id="{re.escape(pid)}" value="([^"]*)"', xml)
|
||||||
|
if not m:
|
||||||
|
raise ValueError(f"param not found: {pid}")
|
||||||
|
# IMPORTANT: plugin honors the value only if serialized in the same
|
||||||
|
# float format the original state used. depth uses full precision;
|
||||||
|
# selectivity/mix/mode/oversample store "X.0" short form.
|
||||||
|
# Try to keep the original format's decimal count.
|
||||||
|
orig = m.group(1)
|
||||||
|
if '.' in orig and len(orig.split('.')[1]) > 4:
|
||||||
|
# full-precision param (e.g. depth) -> needs 16 decimals
|
||||||
|
full = f"{float(val):.16f}"
|
||||||
|
elif '.' in orig:
|
||||||
|
# short-form param (e.g. selectivity "10.0") -> keep decimal count
|
||||||
|
full = f"{float(val):.{len(orig.split('.')[1])}f}"
|
||||||
|
else:
|
||||||
|
full = val
|
||||||
|
xml = xml[:m.start()] + f'<PARAM id="{pid}" value="{full}"' + xml[m.end():]
|
||||||
|
if out_rpp:
|
||||||
|
newb = base64.b64encode(header + xml.encode('utf8') + out[-1:]).decode()
|
||||||
|
lines = [newb[i:i+128] for i in range(0, len(newb), 128)]
|
||||||
|
txt2 = txt[:vi+1] + [' ' + l for l in lines]
|
||||||
|
if end < len(txt) - 1:
|
||||||
|
txt2 = txt2 + txt[end:]
|
||||||
|
txt2 = [l if not l.strip().startswith('RENDER_FILE')
|
||||||
|
else f' RENDER_FILE "{out_wav}"' for l in txt2]
|
||||||
|
open(out_rpp, 'w').write('\n'.join(txt2))
|
||||||
|
return txt
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# usage: sweep.py <rpp_out> <out_wav> <id=val> [id=val ...]
|
||||||
|
rpp_out = sys.argv[1]
|
||||||
|
out_wav = sys.argv[2]
|
||||||
|
params = dict(a.split('=', 1) for a in sys.argv[3:])
|
||||||
|
set_params(TPL, rpp_out, out_wav, params)
|
||||||
|
print("wrote", rpp_out, params)
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
650 matches in 1 files:
|
||||||
|
|
||||||
|
52:INFO Diag.java> FUNCTION_COUNT 38538 (GhidraScript)
|
||||||
|
53:INFO Diag.java> SYMB KERNEL32.DLL::TerminateProcess EXTERNAL:00000024 Label ...
|
||||||
|
54:INFO Diag.java> SYMB KERNEL32.DLL::ExitProcess EXTERNAL:00000043 Function (G...
|
||||||
|
55:INFO Diag.java> SYMB KERNEL32.DLL::GetCurrentProcess EXTERNAL:00000095 Funct...
|
||||||
|
56:INFO Diag.java> SYMB KERNEL32.DLL::IsProcessorFeaturePresent EXTERNAL:000000...
|
||||||
|
57:INFO Diag.java> SYMB KERNEL32.DLL::GetProcessHeap EXTERNAL:000000a8 Function...
|
||||||
|
58:INFO Diag.java> SYMB KERNEL32.DLL::GetCurrentProcessId EXTERNAL:000000a9 Fun...
|
||||||
|
59:INFO Diag.java> SYMB USER32.DLL::GetWindowThreadProcessId EXTERNAL:000000f8 ...
|
||||||
|
60:INFO Diag.java> SYMB USER32.DLL::GetProcessWindowStation EXTERNAL:00000106 F...
|
||||||
|
61:INFO Diag.java> SYMB KERNEL32.DLL::IsProcessorFeaturePresent 181132908 Funct...
|
||||||
|
62:INFO Diag.java> SYMB PTR_TerminateProcess_181baa1c0 181baa1c0 Label (GhidraS...
|
||||||
|
63:INFO Diag.java> SYMB PTR_ExitProcess_181baa2b8 181baa2b8 Label (GhidraScript)
|
||||||
|
64:INFO Diag.java> SYMB PTR_GetCurrentProcess_181baa548 181baa548 Label (Ghidra...
|
||||||
|
65:INFO Diag.java> SYMB PTR_IsProcessorFeaturePresent_181baa5b0 181baa5b0 Label...
|
||||||
|
66:INFO Diag.java> SYMB PTR_GetProcessHeap_181baa5e0 181baa5e0 Label (GhidraScr...
|
||||||
|
67:INFO Diag.java> SYMB PTR_GetCurrentProcessId_181baa5e8 181baa5e8 Label (Ghid...
|
||||||
|
68:INFO Diag.java> SYMB PTR_GetWindowThreadProcessId_181baaec8 181baaec8 Label ...
|
||||||
|
69:INFO Diag.java> SYMB s_Error_processing_the_command_lin_181bb56b0 181bb56b0 ...
|
||||||
|
70:INFO Diag.java> SYMB s_[%d]_HASH_ByID_ProcessBuffer_err_181bb7db8 181bb7db8 ...
|
||||||
|
71:INFO Diag.java> SYMB s_Cannot_process_the_request_becau_181bc3610 181bc3610 ...
|
||||||
|
72:INFO Diag.java> SYMB s_FailedToStartChildProcess_181bc9480 181bc9480 Label (...
|
||||||
|
73:INFO Diag.java> SYMB s_Could_not_start_a_child_process._181bc94a0 181bc94a0 ...
|
||||||
|
74:INFO Diag.java> SYMB s_ChildProcessWaitTimeout_181bc94c8 181bc94c8 Label (Gh...
|
||||||
|
75:INFO Diag.java> SYMB s_ChildProcessFailed_181bc9508 181bc9508 Label (GhidraS...
|
||||||
|
76:INFO Diag.java> SYMB s_Child_process_failed._181bc9520 181bc9520 Label (Ghid...
|
||||||
|
+625 more in /home/m/re-tools/diag2.log [see remaining: tail -n +26 ~/.local/share/rtk/tee/1786831385_grep_0__home_m_re_tools_diag2_log.log]
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import wave, array, math, sys
|
||||||
|
|
||||||
|
SR = 44100
|
||||||
|
|
||||||
|
def synth(path, freqs, dur=6.0, amp=1.0, gain_dbs=None, sr=SR):
|
||||||
|
# amp = target peak amplitude (1.0 = full scale)
|
||||||
|
n = int(sr*dur)
|
||||||
|
out = [0.0]*n
|
||||||
|
if gain_dbs is None:
|
||||||
|
gain_dbs = [-20*math.log10(len(freqs))*0 + 0]*len(freqs) # equal
|
||||||
|
for k, f in enumerate(freqs):
|
||||||
|
g = 1.0/len(freqs) if len(gain_dbs) != len(freqs) else 10**(gain_dbs[k]/20)
|
||||||
|
for i in range(n):
|
||||||
|
t = i/sr
|
||||||
|
out[i] += g*math.sin(2*math.pi*f*t)
|
||||||
|
peak = max(abs(v) for v in out) or 1.0
|
||||||
|
sc = amp/peak
|
||||||
|
a = array.array('h')
|
||||||
|
for i, v in enumerate(out):
|
||||||
|
t = i/sr
|
||||||
|
fade = min(1.0, t/0.03, (dur-t)/0.05)
|
||||||
|
a.append(max(-32767, min(32767, int(v*sc*fade*32767))))
|
||||||
|
w = wave.open(path, 'wb')
|
||||||
|
w.setnchannels(1); w.setsampwidth(2); w.setframerate(sr)
|
||||||
|
w.writeframes(a.tobytes())
|
||||||
|
w.close()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# usage: synth.py <freq> [<amp level dBFS or amplitude>] [<path>]
|
||||||
|
import os
|
||||||
|
freq = float(sys.argv[1])
|
||||||
|
amp = 1.0
|
||||||
|
path = '/home/m/soothe-bt/tone.wav'
|
||||||
|
for a in sys.argv[2:]:
|
||||||
|
try:
|
||||||
|
amp = float(a)
|
||||||
|
except ValueError:
|
||||||
|
path = a
|
||||||
|
synth(path, [freq], amp=amp)
|
||||||
|
print("wrote", path, freq, "Hz amp", amp)
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import wave, array, math, sys
|
||||||
|
|
||||||
|
SR = 44100
|
||||||
|
|
||||||
|
def synth_multi(path, freqs, dur=6.0, amp=0.5, am=0.3, sr=SR):
|
||||||
|
n = int(sr*dur)
|
||||||
|
out = [0.0]*n
|
||||||
|
for f in freqs:
|
||||||
|
for i in range(n):
|
||||||
|
t = i/sr
|
||||||
|
# each tone: carrier + gentle AM (makes it "resonant" for the detector)
|
||||||
|
out[i] += math.sin(2*math.pi*f*t)*(1.0 + am*math.sin(2*math.pi*(f/8.0)*t))
|
||||||
|
peak = max(abs(v) for v in out) or 1.0
|
||||||
|
sc = amp/peak
|
||||||
|
a = array.array('h')
|
||||||
|
for i, v in enumerate(out):
|
||||||
|
t = i/sr
|
||||||
|
fade = min(1.0, t/0.05, (dur-t)/0.1)
|
||||||
|
a.append(max(-32767, min(32767, int(v*sc*32767*fade))))
|
||||||
|
w = wave.open(path, 'wb')
|
||||||
|
w.setnchannels(1); w.setsampwidth(2); w.setframerate(sr)
|
||||||
|
w.writeframes(a.tobytes())
|
||||||
|
w.close()
|
||||||
|
print("wrote", path, "tones", len(freqs), "amp", amp)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
freqs = [int(f) for f in sys.argv[2].split(',')]
|
||||||
|
synth_multi(sys.argv[1], freqs, dur=float(sys.argv[3]) if len(sys.argv)>3 else 6.0)
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, '/home/m/re-tools')
|
||||||
|
import re, base64
|
||||||
|
from sweep import load_state
|
||||||
|
|
||||||
|
TPL = sys.argv[1]
|
||||||
|
rpp_out = sys.argv[2]
|
||||||
|
out_wav = sys.argv[3]
|
||||||
|
params = dict(a.split('=', 1) for a in sys.argv[4:])
|
||||||
|
|
||||||
|
txt, vi, end, blob, out = load_state(TPL)
|
||||||
|
xml_idx = out.find(b'<?xml')
|
||||||
|
header = out[:xml_idx]
|
||||||
|
xml = out[xml_idx:-1].decode('utf8', 'replace')
|
||||||
|
for pid, val in params.items():
|
||||||
|
m = re.search(rf'<PARAM id="{re.escape(pid)}" value="([^"]*)"', xml)
|
||||||
|
if not m:
|
||||||
|
raise ValueError(f"param not found: {pid}")
|
||||||
|
orig = m.group(1)
|
||||||
|
if '.' in orig and len(orig.split('.')[1]) > 4:
|
||||||
|
full = f"{float(val):.16f}"
|
||||||
|
elif '.' in orig:
|
||||||
|
full = f"{float(val):.{len(orig.split('.')[1])}f}"
|
||||||
|
else:
|
||||||
|
full = val
|
||||||
|
xml = xml[:m.start()] + f'<PARAM id="{pid}" value="{full}"' + xml[m.end():]
|
||||||
|
newb = base64.b64encode(header + xml.encode('utf8') + out[-1:]).decode()
|
||||||
|
lines = [newb[i:i+128] for i in range(0, len(newb), 128)]
|
||||||
|
txt2 = txt[:vi+1] + [' ' + l for l in lines]
|
||||||
|
if end < len(txt) - 1:
|
||||||
|
txt2 = txt2 + txt[end:]
|
||||||
|
txt2 = [l if not l.strip().startswith('RENDER_FILE')
|
||||||
|
else f' RENDER_FILE "{out_wav}"' for l in txt2]
|
||||||
|
open(rpp_out, 'w').write('\n'.join(txt2))
|
||||||
|
print("wrote", rpp_out, params)
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Верификация симулятора: сравнение трассы 500Hz mag (sim vs реальный рендер).
|
||||||
|
Использует тот же metric, что measure.py (rfft 10ms окно, mag@500).
|
||||||
|
RMSE по оси reduction (dB) на окне burst; выводит attack-установку, settled, RMSE.
|
||||||
|
"""
|
||||||
|
import argparse, sys, os
|
||||||
|
sys.path.insert(0, '/home/m/re-tools')
|
||||||
|
import numpy as np
|
||||||
|
import sim
|
||||||
|
from measure import mag_at
|
||||||
|
|
||||||
|
def trace(path):
|
||||||
|
return mag_at(sim.read_wav(path), 500, 44100)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument('fx_wav', help='реальный светлый рендер (burst500L_b1.wav)')
|
||||||
|
ap.add_argument('--dry', default='/home/m/soothe-bt/burst500L_byp.wav')
|
||||||
|
ap.add_argument('--inp', default='/home/m/soothe-bt/burst500L.wav')
|
||||||
|
ap.add_argument('--burst', nargs=2, type=float, default=[0.5, 1.5])
|
||||||
|
ap.add_argument('--params', default='', help='depth=0.864 sens=12 sharp=10 sel=10 mode=1 attack=0 release=0')
|
||||||
|
a = ap.parse_args()
|
||||||
|
|
||||||
|
p = dict(depth=0.864, sens=12.0, sharp=10.0, sel=10.0, mode=1, attack=0.0, release=0.0)
|
||||||
|
for kv in a.params.split():
|
||||||
|
k, v = kv.split('=')
|
||||||
|
p[k] = float(v)
|
||||||
|
|
||||||
|
dry = sim.read_wav(a.dry)
|
||||||
|
x = sim.read_wav(a.inp)
|
||||||
|
y = sim.simulate(x, fc=500, **{k: p.get(k, d) for k, d in
|
||||||
|
dict(depth=0.864, sens=12, sharp=10, sel=10, mode=1,
|
||||||
|
attack=0, release=0, mix=100).items()})
|
||||||
|
real = trace(a.fx_wav)
|
||||||
|
mdl = mag_at(y, 500, 44100)
|
||||||
|
tw = np.arange(min(real.size, mdl.size)) * 0.010 + 0.005
|
||||||
|
red_real = -20 * np.log10(np.maximum(real[:tw.size], 1e-9) / np.maximum(mag_at(dry, 500, 44100)[:tw.size], 1e-9))
|
||||||
|
red_mdl = -20 * np.log10(np.maximum(mdl[:tw.size], 1e-9) / np.maximum(mag_at(dry, 500, 44100)[:tw.size], 1e-9))
|
||||||
|
b0, b1 = a.burst
|
||||||
|
seg = (tw >= b0 + 0.15) & (tw <= b1 - 0.05)
|
||||||
|
rmse = np.sqrt(np.mean((red_mdl[seg] - red_real[seg]) ** 2))
|
||||||
|
print(f"params {a.params or '(default)'}")
|
||||||
|
print(f" settled real={red_real[seg].mean():7.2f}dB sim={red_mdl[seg].mean():7.2f}dB")
|
||||||
|
print(f" RMSE(reduction dB, burst steady) = {rmse:.3f}")
|
||||||
|
for t in [b0 + 0.02, b0 + 0.05, b0 + 0.1, b0 + 0.2, b0 + 0.5]:
|
||||||
|
i = int(t / 0.010)
|
||||||
|
print(f" t={t:.2f}s real={red_real[i]:6.2f} sim={red_mdl[i]:6.2f}")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user