Files
soothe2-re/dsp/levelpath.cpp
T

220 lines
9.3 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// levelpath.cpp — transcription of FUN_180563440 (LUT curve + band combine)
// and FUN_18056e3e0 (twin-mask factory)
//
// Extracted from: handoff/nls_dasm/f_563440.dis (222 lines)
// Constants from: soothe_mem.bin at ImageBase 0x180000000
//
// Key addresses:
// 0x540000+0x2198 = r13+0x2198 = output accumulator (1024 doubles, stride 0x2000)
// 0x540000+0x188 = band config struct (A, B, C, flag, callback)
// 0x5408b0 = LUT coefficient table (PRNG state)
// 0x540868 = band count (max 6)
// 0x540870 = level weight (float)
// 0x540874 = level-dependent weight (float)
// 0x54087c = band weight (float)
// 0x54088c = sharpness weight (float)
// 0x540658 = window table (2048 floats, live-captured)
// 0x540698 = freq-axis (2049 floats, live-captured)
// 0x5406a8 = warp table (2049 floats)
// 0x5406b8 = warp exponent (float, = A_FIT)
#include <cmath>
#include <algorithm>
#include <cstring>
#include <cstdint>
// Constants extracted from binary
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
static constexpr float HALF = 0.5f; // DAT_1824c3d8c
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 double TWO_PI = 6.283185307179586; // DAT_1824c4248 (2π, twin-mask factory)
static constexpr float SCALE_1024 = 0.0009765625f; // 1/1024 (DAT_1824c3c50, band LUT apply)
static constexpr float CONST_5 = 5.0f; // DAT_1824c4230 (AudioProcessingModule ctor)
// PRNG state offsets from param_1
static constexpr int PRNG_STATE = 0x2404e0;
static constexpr int PRNG_LUT = 0x5408b0;
// Band config struct layout (offsets from band_base = param_1 + 0x188)
struct BandConfig {
float A; // +0x00: start value
float B; // +0x04: end value
float _pad[2];
float threshold; // +0x0c: threshold (compared to 1.0)
uint8_t flag; // +0x10: 0=linear, 1=power-law
uint8_t _pad2[3];
float _pad3[15];
void* callback; // +0x50: vtable callback (if non-null, use callback)
};
// 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) {
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
// decomp: fVar16 = expf(logf(x)/gamma) (FLOAT log/exp)
float t = x;
if (gamma != ONE && x > ZERO) {
t = expf(logf(x) / gamma);
}
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); decomp: sign·expf(logf(|t|)/gamma)
float t = TWO * x - ONE;
if (gamma != ONE && t != ZERO) {
float sign = (t < ZERO) ? NEG1 : ONE;
t = expf(logf(fabsf(t)) / gamma) * sign;
}
result = band->A + (band->B - band->A) * HALF * (ONE + t);
}
return result;
}
// 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<uint8_t*>(ctx);
double* output = reinterpret_cast<double*>(base + 0x198);
BandConfig* band = reinterpret_cast<BandConfig*>(base + 0x188);
for (int bin = 0; bin < 0x400; bin++) { // cmp $0x400 jl
float x = static_cast<float>(bin) * SCALE;
x = fminf(x, ONE);
if (x < ZERO) x = ZERO;
output[bin] = static_cast<double>(eval_lut_bin(x, band));
}
}
// FUN_18056e3e0: twin-mask factory
// DECODED (decomp_funs2.txt:7988 + f_56e3e0.dis): fills the 0x400-bin mask
// with a SINGLE scalar s = 2π / (count·SR), where
// count = [ctx+0x240080] (int), SR = [ctx+0x24] (float, internal SR).
// NOT a per-bin twin resonance — a constant fill (the "twin" shape enters
// elsewhere via the LUT curve FUN_180563440). Output mask stride 0x2000/band.
void twin_mask_factory(void* ctx, int band_idx, int n_bins) {
auto* base = static_cast<uint8_t*>(ctx);
int count = *reinterpret_cast<int*>(base + 0x240080);
float sr = *reinterpret_cast<float*>(base + 0x24);
double s = TWO_PI / (static_cast<double>(count) * static_cast<double>(sr));
float* mask = reinterpret_cast<float*>(base + 0x4198 + band_idx * 0x2000);
for (int i = 0; i < 0x400; i++) {
mask[i] = static_cast<float>(s);
}
}
// FUN_180563a60: band LUT apply (level -> gain). DECODED (decomp_funs2.txt:8975 + f_563a60.dis).
// For each of 6 bands and 0x400 bins:
// level_dB = 20·log10(mask[band][bin]) (logf · 8.6859)
// level_axis[bin] = bin·(1/1024) (SCALE_1024)
// t = clamp((dB A)/(B A), 0, 1) (BandConfig ctx+0x180: A,B,gamma,flag)
// if gamma == 1.0: val = t
// elif flag == 0 (linear): val = t^gamma (powf, NOT 1/gamma)
// else (power-law): val = 0.5·(1 + sign(2t1)·|2t1|^gamma)
// level_axis[bin+1] = val (pairs level, gain)
// NOTE: this is the INVERSE curve of FUN_180563440 (which uses x^(1/γ)).
void band_lut_apply(void* ctx) {
auto* base = static_cast<uint8_t*>(ctx);
float* bandcfg = *reinterpret_cast<float**>(base + 0x180);
float A = bandcfg[0];
float B = bandcfg[1];
float gamma = bandcfg[3];
float flag = bandcfg[4];
double* mask = reinterpret_cast<double*>(base + 0x4198);
for (int band = 0; band < 6; band++) {
double* m = mask + band * 0x400;
float* level_gain = *reinterpret_cast<float**>(base + 0xe0 + band * 0x18);
for (int bin = 0; bin < 0x400; bin++) {
float db = logf(static_cast<float>(m[bin])) * DB_CONV;
level_gain[bin * 2] = static_cast<float>(bin) * SCALE_1024;
float t = (db - A) / (B - A);
t = std::max(ZERO, std::min(ONE, t));
float val = t;
if (gamma != ONE) {
if (flag == ZERO) {
val = powf(t, gamma);
} else {
float u = TWO * t - ONE;
float sgn = (u < ZERO) ? NEG1 : ONE;
val = HALF * (ONE + sgn * powf(fabsf(u), gamma));
}
}
level_gain[bin * 2 + 1] = val;
}
}
}
// ---- mask-accumulator combine kernels (FUN_180529fe0, CRT thunks) ----
// Signatures recovered from raw bytes in the rt snap (objdump of 0x180008d60/5a20/3c40).
//
// 0x8d60 combine3: out[i] = a[i] - b[i] (vsubpd, 3 pointers; dst is the 3rd arg)
// In the per-band loop: 0x5406f8[i] = 0x540678[i] - 0x5407c8[i]
void combine_sub(double* out, const double* a, const double* b, int n) {
for (int i = 0; i < n; i++) out[i] = a[i] - b[i];
}
// 0x5a20: dst[i] += src[i] (double; kernel 0x18001a5a0)
void acc_add(double* dst, const double* src, int n) {
for (int i = 0; i < n; i++) dst[i] += src[i];
}
// 0x3c40: dst[i] += a[i] * b[i] (double; vfmadd213pd)
void acc_fma(double* dst, const double* a, const double* b, int n) {
for (int i = 0; i < n; i++) dst[i] += a[i] * b[i];
}
// FUN_180529fe0: coefficient setup (from decomp_funs.txt)
void coefficient_setup(void* ctx, int band_idx, int param3, int param4) {
auto* base = static_cast<uint8_t*>(ctx);
// Lock (atomic flag at 0x2404dc)
uint32_t* lock = reinterpret_cast<uint32_t*>(base + 0x2404dc);
// LOCK(); *lock |= 1; UNLOCK(); // simplified
// PRNG state update (LCG)
int32_t state = *reinterpret_cast<int32_t*>(base + PRNG_STATE);
state = (state + 0x3cdca) & 0x7fffffff;
*reinterpret_cast<int32_t*>(base + PRNG_STATE) = state;
// Load LUT coefficients
float* lut_table = reinterpret_cast<float*>(base + PRNG_LUT);
float coeff0 = lut_table[state];
float coeff1 = lut_table[state + 1];
// Generate 6 coefficient pairs
// Each pair: (coeff_i * scale + offset) * global_scale
float acc = ZERO;
for (int i = 0; i < 3; i++) {
state = (state + 0x140236 + i * 0x10d56) & 0x7fffffff;
float a = lut_table[state];
float b = lut_table[state + 1];
acc += a * b;
}
// Normalize
float normalized = acc / static_cast<float>(param3);
// Apply depth scaling: powf(normalized, depth)
float depth = *reinterpret_cast<float*>(base + 0x2c);
float depthScaled = powf(normalized, depth);
// Store result
*reinterpret_cast<float*>(base + 0x54088c) = depthScaled;
// Apply sharpness weight
float sharpness = *reinterpret_cast<float*>(base + 0x540870);
depthScaled *= sharpness;
// Invert: gain = 1 - mask
*reinterpret_cast<float*>(base + 0x54088c) = ONE - depthScaled;
}