P0: fix build (levelpath in CMake, FLOOR_LIN, exact LUT curve formulas), robust RPP param decoder (607 rpp ok), preserve f_52b570/f529fe0 disasms
This commit is contained in:
@@ -16,6 +16,7 @@ add_library(soothe2_dsp SHARED
|
|||||||
detect.cpp
|
detect.cpp
|
||||||
twin.cpp
|
twin.cpp
|
||||||
freqpath.cpp
|
freqpath.cpp
|
||||||
|
levelpath.cpp
|
||||||
phase_table.cpp
|
phase_table.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+35
-56
@@ -24,7 +24,7 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
// Constants extracted from binary
|
// Constants extracted from binary
|
||||||
static constexpr float SCALE = 0.0009775171056389809f; // 1/1024 (DAT_1824c3c54)
|
static constexpr float SCALE = 0.0009775171056389809f; // 1/1023 (DAT_1824c3c54, verified 2026-08-19)
|
||||||
static constexpr float ONE = 1.0f; // DAT_1824c3ea4
|
static constexpr float ONE = 1.0f; // DAT_1824c3ea4
|
||||||
static constexpr float TWO = 2.0f; // DAT_1824c41e0
|
static constexpr float TWO = 2.0f; // DAT_1824c41e0
|
||||||
static constexpr float NEG1 = -1.0f; // DAT_1824c4680
|
static constexpr float NEG1 = -1.0f; // DAT_1824c4680
|
||||||
@@ -33,7 +33,7 @@ static constexpr float ZERO = 0.0f; // DAT_1824c4140
|
|||||||
static constexpr float DEPTH_SCALE = 4.0f; // DAT_1824c4334
|
static constexpr float DEPTH_SCALE = 4.0f; // DAT_1824c4334
|
||||||
static constexpr float DB_CONV = 8.68588924407959f; // 20/ln(10) (DAT_1824c43e0)
|
static constexpr float DB_CONV = 8.68588924407959f; // 20/ln(10) (DAT_1824c43e0)
|
||||||
static constexpr float FLOOR_DB = -6.907755374908447f; // ln(0.001) (DAT_1824c4704)
|
static constexpr float FLOOR_DB = -6.907755374908447f; // ln(0.001) (DAT_1824c4704)
|
||||||
static constexpr float FLOOR LIN = 0.001f; // exp(FLOOR_DB)
|
static constexpr float FLOOR_LIN = 0.001f; // exp(FLOOR_DB)
|
||||||
|
|
||||||
// PRNG state offsets from param_1
|
// PRNG state offsets from param_1
|
||||||
static constexpr int PRNG_STATE = 0x2404e0;
|
static constexpr int PRNG_STATE = 0x2404e0;
|
||||||
@@ -51,68 +51,47 @@ struct BandConfig {
|
|||||||
void* callback; // +0x50: vtable callback (if non-null, use callback)
|
void* callback; // +0x50: vtable callback (if non-null, use callback)
|
||||||
};
|
};
|
||||||
|
|
||||||
// LUT evaluation for a single bin
|
// Structural LUT curve (f_563440.dis, exact transcription 2026-08-19)
|
||||||
// x is in [0, 1] range
|
// x in [0,1], gamma == band->threshold (offset +0x0c), A=+0x00, B=+0x04
|
||||||
static float eval_lut_bin(float x, const BandConfig* band) {
|
static float eval_lut_bin(float x, const BandConfig* band) {
|
||||||
// Path 1: callback exists → use vtable
|
float gamma = band->threshold;
|
||||||
if (band->callback != nullptr) {
|
float result;
|
||||||
// TODO: transcribe callback vtable call
|
if (band->flag == 0) {
|
||||||
return x;
|
// Linear path (0x563595): t = x^(1/γ) if γ!=1 && x>0; val = A + (B-A)*t
|
||||||
}
|
float t = x;
|
||||||
|
if (gamma != ONE && x > ZERO) {
|
||||||
// Path 2: power-law (flag != 0 and threshold != 1.0)
|
double d = static_cast<double>(fabsf(x));
|
||||||
if (band->flag != 0 && band->threshold != ONE) {
|
d = std::log(d) / static_cast<double>(gamma);
|
||||||
float C = band->threshold;
|
t = static_cast<float>(std::exp(d));
|
||||||
// x = 2*x - 1 (center at zero: [-1, 1])
|
|
||||||
float centered = TWO * x - ONE;
|
|
||||||
if (C == ONE || centered == ZERO) {
|
|
||||||
// fall through to linear
|
|
||||||
} else {
|
|
||||||
// sign(x) * 10^(log10(|x|) / C)
|
|
||||||
float sign = (centered < ZERO) ? NEG1 : ONE;
|
|
||||||
// absolute value: |x|
|
|
||||||
float abs_x = fabsf(centered);
|
|
||||||
// if abs_x > 0: result = sign * exp(log(|x|) * (1/C))
|
|
||||||
if (abs_x > ZERO) {
|
|
||||||
float log_val = log10f(abs_x);
|
|
||||||
float result = powf(10.0f, log_val / C);
|
|
||||||
centered = sign * result;
|
|
||||||
}
|
|
||||||
// fall through to linear with transformed x
|
|
||||||
x = centered * HALF + HALF; // remap back to [0,1]
|
|
||||||
}
|
}
|
||||||
|
result = band->A + (band->B - band->A) * t;
|
||||||
|
} else {
|
||||||
|
// Power-law path (0x5635cd): t = 2x-1; if γ!=1 && t!=0: t = sign(t)·|t|^(1/γ)
|
||||||
|
// val = A + (B-A)·0.5·(1+t)
|
||||||
|
float t = TWO * x - ONE;
|
||||||
|
if (gamma != ONE && t != ZERO) {
|
||||||
|
float sign = (t < ZERO) ? NEG1 : ONE;
|
||||||
|
double d = static_cast<double>(fabsf(t));
|
||||||
|
d = std::log(d) / static_cast<double>(gamma);
|
||||||
|
t = static_cast<float>(std::exp(d)) * sign;
|
||||||
|
}
|
||||||
|
result = band->A + (band->B - band->A) * HALF * (ONE + t);
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
// Path 3: linear interpolation (always applied after transform)
|
|
||||||
float slope = band->B - band->A;
|
|
||||||
return slope * x + band->A;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FUN_180563440: LUT curve evaluation for 1024 bins
|
// FUN_180563440: LUT curve evaluation for 0x400 bins
|
||||||
// r13 = context pointer (param_1)
|
// r13 = context pointer (param_1). Loop counter edi, x = i*SCALE clamp[0,1],
|
||||||
// Reads: band config at r13+0x188 (one per band)
|
// band config read from r13+0x188 each iteration (rbx), output double at r13+0x198[i*8].
|
||||||
// Writes: output at r13+0x198 (1024 doubles, stride 8)
|
void lut_curve_eval(void* ctx) {
|
||||||
void lut_curve_eval(void* ctx, int bin_start, int bin_end) {
|
|
||||||
auto* base = static_cast<uint8_t*>(ctx);
|
auto* base = static_cast<uint8_t*>(ctx);
|
||||||
int band_count = *reinterpret_cast<int*>(base + 0x540868);
|
|
||||||
if (band_count <= 0) {
|
|
||||||
// Initialize with default 0x800 bins
|
|
||||||
band_count = 0x800; // 2048? or 1024?
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output pointer: r13+0x198
|
|
||||||
double* output = reinterpret_cast<double*>(base + 0x198);
|
double* output = reinterpret_cast<double*>(base + 0x198);
|
||||||
|
BandConfig* band = reinterpret_cast<BandConfig*>(base + 0x188);
|
||||||
// Evaluate LUT curve for each bin (0x400 = 1024 iterations)
|
for (int bin = 0; bin < 0x400; bin++) { // cmp $0x400 jl
|
||||||
for (int bin = 0; bin < 0x400; bin++) {
|
|
||||||
float x = static_cast<float>(bin) * SCALE;
|
float x = static_cast<float>(bin) * SCALE;
|
||||||
x = fminf(fmaxf(x, ZERO), ONE); // clamp to [0, 1]
|
x = fminf(x, ONE);
|
||||||
|
if (x < ZERO) x = ZERO;
|
||||||
BandConfig* band = reinterpret_cast<BandConfig*>(base + 0x188);
|
output[bin] = static_cast<double>(eval_lut_bin(x, band));
|
||||||
float result = eval_lut_bin(x, band);
|
|
||||||
|
|
||||||
// Store as double-precision (line 196: cvtss2sd + movsd [rsi])
|
|
||||||
output[bin] = static_cast<double>(result);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+63
-32
@@ -1,41 +1,72 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import base64, re, sys, html
|
"""Extract soothe2 plugin params from an .rpp as a JSON dict.
|
||||||
|
|
||||||
|
Reaper stores the VST3 state as base64: line1 after <VST> is a short binary
|
||||||
|
header, following lines (128-char chunks) are the outer b64 blob which wraps
|
||||||
|
<SOOTHE2STATE><PARAM .../>...</SOOTHE2STATE> at byte offset 16.
|
||||||
|
"""
|
||||||
|
import base64, re, html, sys
|
||||||
|
|
||||||
|
def b64dec(s):
|
||||||
|
s = re.sub(r'[^A-Za-z0-9+/=]', '', s)
|
||||||
|
best = b''
|
||||||
|
for pad in range(4):
|
||||||
|
s2 = s + '=' * pad
|
||||||
|
try:
|
||||||
|
d = base64.b64decode(s2, validate=False)
|
||||||
|
if len(d) > len(best):
|
||||||
|
best = d
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
# if nothing cleanly decodes, brute-force by trimming tail bad chars
|
||||||
|
if not best:
|
||||||
|
for cut in range(1, min(8, len(s))):
|
||||||
|
try:
|
||||||
|
d = base64.b64decode(s[:-cut], validate=False)
|
||||||
|
if len(d) > len(best):
|
||||||
|
best = d
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
return best
|
||||||
|
|
||||||
def rpp_params(path):
|
def rpp_params(path):
|
||||||
txt = open(path, encoding='utf8', errors='ignore').read()
|
lines = open(path, encoding='utf8', errors='ignore').read().split('\n')
|
||||||
lines = txt.split('\n')
|
|
||||||
for i, l in enumerate(lines):
|
for i, l in enumerate(lines):
|
||||||
if 'soothe2_x64.vst3' in l:
|
if 'soothe2_x64.vst3' in l:
|
||||||
break
|
break
|
||||||
b = []
|
chunks = []
|
||||||
for l in lines[i + 1:]:
|
for l in lines[i + 1:]:
|
||||||
if l.strip() == '}':
|
s = l.strip()
|
||||||
|
if s == '}':
|
||||||
break
|
break
|
||||||
b.append(l.strip())
|
if s:
|
||||||
raw = re.sub(r'[^A-Za-z0-9+/=]', '', ''.join(b))
|
chunks.append(s)
|
||||||
raw = raw.rstrip('=')
|
# Some RPPs wrap the param blob with the short binary-header line as the
|
||||||
# drop chars until b64 len divisible by 4 (the leading binary header causes odd)
|
# first entry, others start directly with the payload line. Try both joins.
|
||||||
while (len(raw) % 4) != 0:
|
for text in (''.join(chunks), ''.join(chunks[1:])):
|
||||||
raw = raw[:-1]
|
outer = b64dec(text)
|
||||||
padded = raw + '=' * ((-len(raw)) % 4)
|
p = outer.find(b'<?xml')
|
||||||
dec = base64.b64decode(padded, validate=False)
|
if p < 0:
|
||||||
p = dec.find(b'<?xml')
|
continue
|
||||||
xml = dec[p:].decode('utf8', 'ignore')
|
xml = html.unescape(outer[p:].decode('utf8', 'ignore'))
|
||||||
xml = html.unescape(xml)
|
params = dict(re.findall(r'<PARAM id="([^"]+)" value="([^"]+)"', xml))
|
||||||
params = dict(re.findall(r'<PARAM id="([^"]+)" value="([^"]+)"', xml))
|
proc = re.search(r'processorStateData="([^"]+)"', xml)
|
||||||
# also decode processorStateData inner
|
if proc:
|
||||||
proc = re.search(r'processorStateData="([^"]+)"', xml)
|
inner = b64dec(re.sub(r'\s', '', proc.group(1)))
|
||||||
if proc:
|
params['processorStateData'] = inner.decode('utf8', 'ignore')
|
||||||
inner = proc.group(1)
|
if params:
|
||||||
try:
|
return params
|
||||||
inner_dec = base64.b64decode(re.sub(r'\s', '', inner))
|
return {}
|
||||||
print(' [processorStateData inner]', inner_dec.decode('utf8', 'ignore')[:400])
|
|
||||||
except Exception as e:
|
|
||||||
print(' proc inner err', e)
|
|
||||||
return params
|
|
||||||
|
|
||||||
for f in sys.argv[1:]:
|
def params_json(path):
|
||||||
p = rpp_params(f)
|
p = rpp_params(path)
|
||||||
print('===', f.split('/')[-1])
|
p.pop('processorStateData', None)
|
||||||
for k in sorted(p):
|
return p
|
||||||
print(f' {k:24s} = {p[k]}')
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import json
|
||||||
|
for f in sys.argv[1:]:
|
||||||
|
p = params_json(f)
|
||||||
|
print('===', f.split('/')[-1])
|
||||||
|
for k in sorted(p):
|
||||||
|
print(f' {k:22s} = {p[k]}')
|
||||||
Reference in New Issue
Block a user