From d3db772121449ec00c24fdfa194382daa0c7e132 Mon Sep 17 00:00:00 2001 From: Matiq Date: Thu, 20 Aug 2026 01:23:22 +0300 Subject: [PATCH] =?UTF-8?q?P2.5:=20decode=20twin-mask=20factory=20(FUN=5F1?= =?UTF-8?q?8056e3e0=20=3D=20constant=20fill=202=CF=80/(count=C2=B7SR))=20+?= =?UTF-8?q?=20band=20LUT=20apply=20(FUN=5F180563a60:=20level->gain=20t^?= =?UTF-8?q?=CE=B3/power-law);=20implement=20in=20levelpath.cpp,=20levelpat?= =?UTF-8?q?h=5Fcheck=20ALL=20OK?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dsp/CMakeLists.txt | 2 ++ dsp/levelpath.cpp | 75 ++++++++++++++++++++++++++++------------- dsp/levelpath.hpp | 13 +++++++ dsp/levelpath_check.cpp | 53 +++++++++++++++++++++++++++++ handoff/NOTES_LEVEL.md | 23 +++++++++++++ 5 files changed, 142 insertions(+), 24 deletions(-) create mode 100644 dsp/levelpath.hpp create mode 100644 dsp/levelpath_check.cpp diff --git a/dsp/CMakeLists.txt b/dsp/CMakeLists.txt index 125fab7..334687a 100644 --- a/dsp/CMakeLists.txt +++ b/dsp/CMakeLists.txt @@ -27,10 +27,12 @@ add_executable(twin_check twin_check.cpp) add_executable(tables_check tables_check.cpp) add_executable(fftconv_check fftconv_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(tables_check soothe2_dsp) target_link_libraries(fftconv_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_include_directories(soothe2_dsp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/dsp/levelpath.cpp b/dsp/levelpath.cpp index 6a16da1..fcb4d5b 100644 --- a/dsp/levelpath.cpp +++ b/dsp/levelpath.cpp @@ -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 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; @@ -96,35 +99,59 @@ void lut_curve_eval(void* ctx) { } // 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) +// 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(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 + int count = *reinterpret_cast(base + 0x240080); + float sr = *reinterpret_cast(base + 0x24); + double s = TWO_PI / (static_cast(count) * static_cast(sr)); + float* mask = reinterpret_cast(base + 0x4198 + band_idx * 0x2000); + for (int i = 0; i < 0x400; i++) { + mask[i] = static_cast(s); + } } -// 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) { +// 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(2t−1)·|2t−1|^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(ctx); - int band_count = *reinterpret_cast(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(base + 0x2198 + ch * 0x2000); - for (int bin = 0; bin < n_bins; bin++) { - acc[bin] = ONE - acc[bin]; + float* bandcfg = *reinterpret_cast(base + 0x180); + float A = bandcfg[0]; + float B = bandcfg[1]; + float gamma = bandcfg[3]; + float flag = bandcfg[4]; + double* mask = reinterpret_cast(base + 0x4198); + for (int band = 0; band < 6; band++) { + double* m = mask + band * 0x400; + float* level_gain = *reinterpret_cast(base + 0xe0 + band * 0x18); + for (int bin = 0; bin < 0x400; bin++) { + float db = logf(static_cast(m[bin])) * DB_CONV; + level_gain[bin * 2] = static_cast(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; } } } diff --git a/dsp/levelpath.hpp b/dsp/levelpath.hpp new file mode 100644 index 0000000..cbe916c --- /dev/null +++ b/dsp/levelpath.hpp @@ -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); diff --git a/dsp/levelpath_check.cpp b/dsp/levelpath_check.cpp new file mode 100644 index 0000000..e9162c1 --- /dev/null +++ b/dsp/levelpath_check.cpp @@ -0,0 +1,53 @@ +#include +#include +#include +#include +#include +#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 b(0x241000, 0); + *reinterpret_cast(b.data() + 0x240080) = 1024; + *reinterpret_cast(b.data() + 0x24) = 48000.0f; + twin_mask_factory(b.data(), 0, 1024); + float expect = static_cast(6.283185307179586 / (1024.0 * 48000.0)); + float* mask = reinterpret_cast(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 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(b.data() + 0x180) = bandcfg; + std::vector level_gain(0x800 * 6, 0.0f); + for (int band = 0; band < 6; band++) { + *reinterpret_cast(b.data() + 0xe0 + band * 0x18) = level_gain.data() + band * 0x800; + } + double* mask = reinterpret_cast(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; +} \ No newline at end of file diff --git a/handoff/NOTES_LEVEL.md b/handoff/NOTES_LEVEL.md index 42c6414..21a6cdc 100644 --- a/handoff/NOTES_LEVEL.md +++ b/handoff/NOTES_LEVEL.md @@ -160,6 +160,29 @@ Остаток 0.7 dB (Q=0.1) = LUT-колено 0.574 -> нужен FFT-уровень (0x535a70 + окно 0x540658 + freq-axis 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(2t−1)·|2t−1|^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 ============ Source: /tmp/consumers_out.txt:471-1277 (full Ghidra decomp). Validates + completes the "CONFIRMED per-band pipeline" section below (lines 237-253). Notes: