From 58164f29529a8e930e2a624681fce312f9d11bec Mon Sep 17 00:00:00 2001 From: Matiq Date: Wed, 19 Aug 2026 21:54:26 +0300 Subject: [PATCH] P0: fix build (levelpath in CMake, FLOOR_LIN, exact LUT curve formulas), robust RPP param decoder (607 rpp ok), preserve f_52b570/f529fe0 disasms --- dsp/CMakeLists.txt | 1 + dsp/levelpath.cpp | 91 +++++++++++++++----------------------- handoff/rpp_allparams.py | 95 ++++++++++++++++++++++++++-------------- 3 files changed, 99 insertions(+), 88 deletions(-) diff --git a/dsp/CMakeLists.txt b/dsp/CMakeLists.txt index f0cce89..1d465e9 100644 --- a/dsp/CMakeLists.txt +++ b/dsp/CMakeLists.txt @@ -16,6 +16,7 @@ add_library(soothe2_dsp SHARED detect.cpp twin.cpp freqpath.cpp + levelpath.cpp phase_table.cpp ) diff --git a/dsp/levelpath.cpp b/dsp/levelpath.cpp index 66b9694..6a16da1 100644 --- a/dsp/levelpath.cpp +++ b/dsp/levelpath.cpp @@ -24,7 +24,7 @@ #include // 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 TWO = 2.0f; // DAT_1824c41e0 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 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 LIN = 0.001f; // exp(FLOOR_DB) +static constexpr float FLOOR_LIN = 0.001f; // exp(FLOOR_DB) // PRNG state offsets from param_1 static constexpr int PRNG_STATE = 0x2404e0; @@ -51,68 +51,47 @@ struct BandConfig { void* callback; // +0x50: vtable callback (if non-null, use callback) }; -// LUT evaluation for a single bin -// x is in [0, 1] range +// Structural LUT curve (f_563440.dis, exact transcription 2026-08-19) +// x in [0,1], gamma == band->threshold (offset +0x0c), A=+0x00, B=+0x04 static float eval_lut_bin(float x, const BandConfig* band) { - // Path 1: callback exists → use vtable - if (band->callback != nullptr) { - // TODO: transcribe callback vtable call - return x; - } - - // Path 2: power-law (flag != 0 and threshold != 1.0) - if (band->flag != 0 && band->threshold != ONE) { - float C = band->threshold; - // 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] + float gamma = band->threshold; + float result; + if (band->flag == 0) { + // Linear path (0x563595): t = x^(1/γ) if γ!=1 && x>0; val = A + (B-A)*t + float t = x; + if (gamma != ONE && x > ZERO) { + double d = static_cast(fabsf(x)); + d = std::log(d) / static_cast(gamma); + t = static_cast(std::exp(d)); } + 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(fabsf(t)); + d = std::log(d) / static_cast(gamma); + t = static_cast(std::exp(d)) * sign; + } + result = band->A + (band->B - band->A) * HALF * (ONE + t); } - - // Path 3: linear interpolation (always applied after transform) - float slope = band->B - band->A; - return slope * x + band->A; + return result; } -// FUN_180563440: LUT curve evaluation for 1024 bins -// r13 = context pointer (param_1) -// Reads: band config at r13+0x188 (one per band) -// Writes: output at r13+0x198 (1024 doubles, stride 8) -void lut_curve_eval(void* ctx, int bin_start, int bin_end) { +// FUN_180563440: LUT curve evaluation for 0x400 bins +// r13 = context pointer (param_1). Loop counter edi, x = i*SCALE clamp[0,1], +// band config read from r13+0x188 each iteration (rbx), output double at r13+0x198[i*8]. +void lut_curve_eval(void* ctx) { auto* base = static_cast(ctx); - int band_count = *reinterpret_cast(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(base + 0x198); - - // Evaluate LUT curve for each bin (0x400 = 1024 iterations) - for (int bin = 0; bin < 0x400; bin++) { + BandConfig* band = reinterpret_cast(base + 0x188); + for (int bin = 0; bin < 0x400; bin++) { // cmp $0x400 jl float x = static_cast(bin) * SCALE; - x = fminf(fmaxf(x, ZERO), ONE); // clamp to [0, 1] - - BandConfig* band = reinterpret_cast(base + 0x188); - float result = eval_lut_bin(x, band); - - // Store as double-precision (line 196: cvtss2sd + movsd [rsi]) - output[bin] = static_cast(result); + x = fminf(x, ONE); + if (x < ZERO) x = ZERO; + output[bin] = static_cast(eval_lut_bin(x, band)); } } diff --git a/handoff/rpp_allparams.py b/handoff/rpp_allparams.py index 5700c8e..746a09c 100644 --- a/handoff/rpp_allparams.py +++ b/handoff/rpp_allparams.py @@ -1,41 +1,72 @@ #!/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 is a short binary +header, following lines (128-char chunks) are the outer b64 blob which wraps +... 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): - txt = open(path, encoding='utf8', errors='ignore').read() - lines = txt.split('\n') + lines = open(path, encoding='utf8', errors='ignore').read().split('\n') for i, l in enumerate(lines): if 'soothe2_x64.vst3' in l: break - b = [] + chunks = [] for l in lines[i + 1:]: - if l.strip() == '}': + s = l.strip() + if s == '}': break - b.append(l.strip()) - raw = re.sub(r'[^A-Za-z0-9+/=]', '', ''.join(b)) - raw = raw.rstrip('=') - # drop chars until b64 len divisible by 4 (the leading binary header causes odd) - while (len(raw) % 4) != 0: - raw = raw[:-1] - padded = raw + '=' * ((-len(raw)) % 4) - dec = base64.b64decode(padded, validate=False) - p = dec.find(b'