Files
soothe2-re/dsp/levelpath.cpp
T

179 lines
7.1 KiB
C++
Raw 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)
// 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
float t = x;
if (gamma != ONE && x > ZERO) {
double d = static_cast<double>(fabsf(x));
d = std::log(d) / static_cast<double>(gamma);
t = static_cast<float>(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<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;
}
// 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
// Creates per-band mask by applying twin resonance to the LUT curve
// band_count = number of bands (max 6)
// N = 1024 (FFT size for LUT evaluation)
// Output stride: 0x2000 (8192 bytes = 1024 doubles)
void twin_mask_factory(void* ctx, int band_idx, int n_bins) {
auto* base = static_cast<uint8_t*>(ctx);
// Calls twin evaluation for each bin
// TODO: transcribe the full loop from disassembly
// The factory applies the band's resonance shape to the LUT curve
}
// FUN_180563a60: band combine
// Combines 6 band masks into final per-bin gain
// Stereo: max 2 channels, output stride per band = 0x2000
// Pattern: gain = 1.0 - sum(band_masks)
void band_combine(void* ctx, int n_channels, int n_bins) {
auto* base = static_cast<uint8_t*>(ctx);
int band_count = *reinterpret_cast<int*>(base + 0x540868);
if (band_count > 6) band_count = 6;
// Output accumulator at r13+0x2198
// Each band's mask is at r13+0x2198 + band_idx * 0x2000
for (int ch = 0; ch < n_channels; ch++) {
// For each bin: sum all band contributions
// Then invert: gain = 1.0 - sum
double* acc = reinterpret_cast<double*>(base + 0x2198 + ch * 0x2000);
for (int bin = 0; bin < n_bins; bin++) {
acc[bin] = ONE - acc[bin];
}
}
}
// FUN_180529fe0: coefficient setup (from decomp_funs.txt)
// Generates per-band coefficients via PRNG, applies depth scaling
// This is the vtable method for Soothe2Module
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;
}