P2.5: decode twin-mask factory (FUN_18056e3e0 = constant fill 2π/(count·SR)) + band LUT apply (FUN_180563a60: level->gain t^γ/power-law); implement in levelpath.cpp, levelpath_check ALL OK

This commit is contained in:
2026-08-20 01:23:22 +03:00
parent a49e35dc25
commit d3db772121
5 changed files with 142 additions and 24 deletions
+2
View File
@@ -27,10 +27,12 @@ add_executable(twin_check twin_check.cpp)
add_executable(tables_check tables_check.cpp) add_executable(tables_check tables_check.cpp)
add_executable(fftconv_check fftconv_check.cpp) add_executable(fftconv_check fftconv_check.cpp)
add_executable(leveltrack_check leveltrack_check.cpp) add_executable(leveltrack_check leveltrack_check.cpp)
add_executable(levelpath_check levelpath_check.cpp)
target_link_libraries(twin_check soothe2_dsp) target_link_libraries(twin_check soothe2_dsp)
target_link_libraries(tables_check soothe2_dsp) target_link_libraries(tables_check soothe2_dsp)
target_link_libraries(fftconv_check soothe2_dsp) target_link_libraries(fftconv_check soothe2_dsp)
target_link_libraries(leveltrack_check soothe2_dsp) target_link_libraries(leveltrack_check soothe2_dsp)
target_link_libraries(levelpath_check soothe2_dsp)
target_link_libraries(soothe2_harness soothe2_dsp) target_link_libraries(soothe2_harness soothe2_dsp)
target_include_directories(soothe2_dsp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(soothe2_dsp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
+51 -24
View File
@@ -34,6 +34,9 @@ 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)
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 // PRNG state offsets from param_1
static constexpr int PRNG_STATE = 0x2404e0; static constexpr int PRNG_STATE = 0x2404e0;
@@ -96,35 +99,59 @@ void lut_curve_eval(void* ctx) {
} }
// FUN_18056e3e0: twin-mask factory // FUN_18056e3e0: twin-mask factory
// Creates per-band mask by applying twin resonance to the LUT curve // DECODED (decomp_funs2.txt:7988 + f_56e3e0.dis): fills the 0x400-bin mask
// band_count = number of bands (max 6) // with a SINGLE scalar s = 2π / (count·SR), where
// N = 1024 (FFT size for LUT evaluation) // count = [ctx+0x240080] (int), SR = [ctx+0x24] (float, internal SR).
// Output stride: 0x2000 (8192 bytes = 1024 doubles) // 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) { void twin_mask_factory(void* ctx, int band_idx, int n_bins) {
auto* base = static_cast<uint8_t*>(ctx); auto* base = static_cast<uint8_t*>(ctx);
// Calls twin evaluation for each bin int count = *reinterpret_cast<int*>(base + 0x240080);
// TODO: transcribe the full loop from disassembly float sr = *reinterpret_cast<float*>(base + 0x24);
// The factory applies the band's resonance shape to the LUT curve 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 combine // FUN_180563a60: band LUT apply (level -> gain). DECODED (decomp_funs2.txt:8975 + f_563a60.dis).
// Combines 6 band masks into final per-bin gain // For each of 6 bands and 0x400 bins:
// Stereo: max 2 channels, output stride per band = 0x2000 // level_dB = 20·log10(mask[band][bin]) (logf · 8.6859)
// Pattern: gain = 1.0 - sum(band_masks) // level_axis[bin] = bin·(1/1024) (SCALE_1024)
void band_combine(void* ctx, int n_channels, int n_bins) { // 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); auto* base = static_cast<uint8_t*>(ctx);
int band_count = *reinterpret_cast<int*>(base + 0x540868); float* bandcfg = *reinterpret_cast<float**>(base + 0x180);
if (band_count > 6) band_count = 6; float A = bandcfg[0];
float B = bandcfg[1];
// Output accumulator at r13+0x2198 float gamma = bandcfg[3];
// Each band's mask is at r13+0x2198 + band_idx * 0x2000 float flag = bandcfg[4];
double* mask = reinterpret_cast<double*>(base + 0x4198);
for (int ch = 0; ch < n_channels; ch++) { for (int band = 0; band < 6; band++) {
// For each bin: sum all band contributions double* m = mask + band * 0x400;
// Then invert: gain = 1.0 - sum float* level_gain = *reinterpret_cast<float**>(base + 0xe0 + band * 0x18);
double* acc = reinterpret_cast<double*>(base + 0x2198 + ch * 0x2000); for (int bin = 0; bin < 0x400; bin++) {
for (int bin = 0; bin < n_bins; bin++) { float db = logf(static_cast<float>(m[bin])) * DB_CONV;
acc[bin] = ONE - acc[bin]; 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;
} }
} }
} }
+13
View File
@@ -0,0 +1,13 @@
#pragma once
// Level-path transcriptions (decomp_funs2.txt + f_*.dis).
// All offsets are relative to the level-path object (NOT the DSP ctx).
// FUN_180563440: LUT curve build (x = i/1023, linear x^(1/gamma), power-law).
void lut_curve_eval(void* ctx);
// FUN_18056e3e0: twin-mask factory = constant fill 2π/(count·SR) over 0x400 bins.
void twin_mask_factory(void* ctx, int band_idx, int n_bins);
// FUN_180563a60: band LUT apply (level -> gain) t^gamma / power-law, 6 bands.
void band_lut_apply(void* ctx);
+53
View File
@@ -0,0 +1,53 @@
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdint>
#include <vector>
#include "levelpath.hpp"
int main() {
bool ok = true;
auto chk = [&](const char* n, double got, double exp, double tol) {
bool p = std::fabs(got - exp) < tol;
ok = ok && p;
std::printf(" %-28s got=%.6f exp=%.6f (%s)\n", n, got, exp, p ? "ok" : "MISMATCH");
};
// ---- twin_mask_factory (FUN_18056e3e0): fill 2π/(count·SR) ----
{
std::vector<uint8_t> b(0x241000, 0);
*reinterpret_cast<int*>(b.data() + 0x240080) = 1024;
*reinterpret_cast<float*>(b.data() + 0x24) = 48000.0f;
twin_mask_factory(b.data(), 0, 1024);
float expect = static_cast<float>(6.283185307179586 / (1024.0 * 48000.0));
float* mask = reinterpret_cast<float*>(b.data() + 0x4198);
chk("twin_mask_factory fill", mask[0], expect, 1e-9);
chk("twin_mask_factory uniform", mask[511], expect, 1e-9);
}
// ---- band_lut_apply (FUN_180563a60): t^gamma linear curve ----
{
std::vector<uint8_t> b(0x12000, 0);
float bandcfg[8] = {0.0f, 1.0f, 0.0f, 2.0f, 0.0f, 0, 0, 0}; // A=0,B=1,gamma=2,flag=0
*reinterpret_cast<float**>(b.data() + 0x180) = bandcfg;
std::vector<float> level_gain(0x800 * 6, 0.0f);
for (int band = 0; band < 6; band++) {
*reinterpret_cast<float**>(b.data() + 0xe0 + band * 0x18) = level_gain.data() + band * 0x800;
}
double* mask = reinterpret_cast<double*>(b.data() + 0x4198);
// mask[0] = 1.0 -> dB=0 -> t=(0-0)/(1-0)=0 -> val=0^2=0
// mask[1] = 0.3162277 (=-10dB) -> t=(-10-0)/1=-10 -> clamp 0 -> 0
// mask[2] = 1.0 -> 0
for (int i = 0; i < 0x400 * 6; i++) mask[i] = 1.0;
// one bin at dB = +0.5 (mask = 10^(0.5/20) = 1.059254) -> t = 0.5 -> 0.5^2 = 0.25
mask[0] = 1.0592537251772883; // +0.5 dB
band_lut_apply(b.data());
chk("band_lut_apply level_axis[0]", level_gain[0], 0.0f, 1e-6); // bin 0 * 1/1024
chk("band_lut_apply gain[0] t^gamma", level_gain[1], 0.25f, 1e-3); // 0.5^2
// bin at index 512: level = 512/1024 = 0.5
chk("band_lut_apply level_axis[512]", level_gain[1024], 0.5f, 1e-6);
}
std::printf(ok ? "ALL OK\n" : "FAILURES\n");
return ok ? 0 : 1;
}
+23
View File
@@ -160,6 +160,29 @@
Остаток 0.7 dB (Q=0.1) = LUT-колено 0.574 -> нужен FFT-уровень (0x535a70 + окно 0x540658 + freq-axis Остаток 0.7 dB (Q=0.1) = LUT-колено 0.574 -> нужен FFT-уровень (0x535a70 + окно 0x540658 + freq-axis
0x540698), см. SESSION_HANDOFF §5-6. 0x540698), см. SESSION_HANDOFF §5-6.
## ============ UPDATE 2026-08-20g: P2.5 twin-mask factory + band LUT apply DECODED ============
Source: decomp_funs2.txt (FUN_18056e3e0:7988, FUN_180563a60:8975, FUN_180563440:7697) +
f_56e3e0.dis + f_563a60.dis. Implemented in dsp/levelpath.cpp (levelpath_check ALL OK).
### FUN_18056e3e0 = "twin-mask factory" is a CONSTANT FILL, NOT per-bin twin resonance:
s = DAT_1824c4248 / (double)((float)[ctx+0x240080] * [ctx+0x24])
= 2π / (count · SR) (0x24c4248 = 6.283185307 = 2π)
fills 0x400 bins of the mask (stride 0x2000/band) with s. The twin resonance shape enters
elsewhere (via the LUT curve FUN_180563440), NOT here. (corrects NOTES:547 "twin resonance".)
### FUN_180563a60 = band LUT apply (level -> gain), 6 bands × 0x400 bins:
level_dB = logf(mask[band][bin]) · 8.6859 (20·log10)
axis[bin] = bin · (1/1024) (0x24c3c50 = 0.0009765625, DIFFERENT from 1/1023)
t = clamp((dB A)/(B A), 0, 1) (BandConfig ctx+0x180: A=min,B=max,gamma,flag)
if gamma == 1.0: val = t
elif flag == 0 (linear): val = t^gamma (powf, DIRECT gamma)
else (power-law): val = 0.5·(1 + sign(2t1)·|2t1|^gamma)
axis[bin+1] = val (pairs level, gain)
NOTE: this is the INVERSE of FUN_180563440 (which uses x^(1/γ)); 563a60 uses t^γ.
### Constants locked (from rt snap rodata):
0x24c4248 = 2π (double), 0x24c4230 = 5 (double), 0x24c3c50 = 1/1024, 0x24c3c54 = 1/1023.
## ============ UPDATE 2026-08-19e: FUN_180529fe0 FULL DECOMP CONFIRMS MASK-CANON ============ ## ============ UPDATE 2026-08-19e: FUN_180529fe0 FULL DECOMP CONFIRMS MASK-CANON ============
Source: /tmp/consumers_out.txt:471-1277 (full Ghidra decomp). Validates + completes the Source: /tmp/consumers_out.txt:471-1277 (full Ghidra decomp). Validates + completes the
"CONFIRMED per-band pipeline" section below (lines 237-253). Notes: "CONFIRMED per-band pipeline" section below (lines 237-253). Notes: