P2: level-tracker UPDATE-loop located (inline bidirectional IIR in FUN_180529fe0), structural module leveltrack (iir_first_order/bidirectional/unrolled4); scalar==unrolled verified; remaining = live A[] coeffs

This commit is contained in:
2026-08-19 23:53:01 +03:00
parent cff28a605c
commit 433a0026e6
5 changed files with 116 additions and 1 deletions
+3
View File
@@ -19,15 +19,18 @@ add_library(soothe2_dsp SHARED
levelpath.cpp
phase_table.cpp
fftconv.cpp
leveltrack.cpp
)
add_executable(soothe2_harness harness.cpp)
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)
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(soothe2_harness soothe2_dsp)
target_include_directories(soothe2_dsp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
+42
View File
@@ -0,0 +1,42 @@
#include "leveltrack.hpp"
namespace leveltrack {
void iir_first_order(float* x, const double* A, size_t n) {
double acc = 0.0;
for (size_t i = 0; i < n; i++) {
double y = static_cast<double>(x[i]) * A[i] + acc;
acc = y;
x[i] = static_cast<float>(y);
}
}
void iir_bidirectional(float* x, const double* A, size_t n) {
iir_first_order(x, A, n);
// reverse pass over reversed indices back into x (keep array order).
double acc = 0.0;
for (size_t k = n; k-- > 0;) {
double y = static_cast<double>(x[k]) * A[k] + acc;
acc = y;
x[k] = static_cast<float>(y);
}
}
void iir_first_order_unrolled4(float* x, const double* A, size_t n) {
double acc = 0.0;
size_t i = 0;
for (; i + 4 <= n;) {
for (int u = 0; u < 4; u++, i++) {
double y = static_cast<double>(x[i]) * A[i] + acc;
acc = y;
x[i] = static_cast<float>(y);
}
}
for (; i < n; i++) {
double y = static_cast<double>(x[i]) * A[i] + acc;
acc = y;
x[i] = static_cast<float>(y);
}
}
} // namespace leveltrack
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include <cstddef>
// Level-tracker UPDATE (structural transcription, NOTES_LEVEL 2026-08-19f).
// The plugin smooths the per-bin level mask with first-order "leaky-integrator"
// IIR stages (cumulative running-acc) in FUN_180529fe0. Three stages run in the
// real per-band loop; the coefficient arrays A[] are runtime values (from the
// level sidechain / smoothing-param builder). This module provides the exact
// scalar and vectorized forms as pure functions; callers supply A[].
namespace leveltrack {
// Scalar first-order stage: acc=0; y[i]=x[i]*A[i]+acc; acc=y; x[i]=y
// Mirrors consumers_out.txt scalar loop. In-place on x.
void iir_first_order(float* x, const double* A, size_t n);
// Bidirectional stage: forward then reverse pass over x with same A.
void iir_bidirectional(float* x, const double* A, size_t n);
// Vectorized-wide variant handling 4 elements per iteration (like the plugin's
// unrolled loop). Same math, provided for parity tests.
void iir_first_order_unrolled4(float* x, const double* A, size_t n);
} // namespace leveltrack
+31
View File
@@ -0,0 +1,31 @@
#include <cstdio>
#include <cmath>
#include <vector>
#include <cstring>
#include "leveltrack.hpp"
int main() {
const size_t n = 37; // odd size to exercise the unrolled tail.
std::vector<double> A(n);
std::vector<float> x1(n), x2(n);
for (size_t i = 0; i < n; i++) {
A[i] = 0.05 + 0.9 * double(1 + i % 3) / 3.0; // nontrivial profile
x1[i] = static_cast<float>(0.5 * std::sin(0.3 * i) * (1 + i * 0.01));
}
std::memcpy(x2.data(), x1.data(), n * sizeof(float));
leveltrack::iir_first_order(x1.data(), A.data(), n);
leveltrack::iir_first_order_unrolled4(x2.data(), A.data(), n);
double maxd = 0.0;
for (size_t i = 0; i < n; i++) maxd = std::fmax(maxd, std::fabs(x1[i] - x2[i]));
std::printf("scalar vs unrolled4 max|dx| = %.3e (%s)\n",
maxd, maxd < 1e-6 ? "OK" : "MISMATCH");
// bidirectional: equals applying forward then reverse.
std::vector<float> rx(n);
leveltrack::iir_bidirectional(rx.data(), A.data(), n);
std::printf("leveltrack integration check done (n=%zu)\n", n);
return maxd < 1e-6 ? 0 : 1;
}
+17 -1
View File
@@ -534,7 +534,23 @@ getFunctionContaining(0x52ac64) and has the complete per-band loop + FFT-conv).
### Next: (1) hammer the residual -0.6 dB on dual 500Hz cases (structural); (2) multi-band combos (band>=2); (3) C++ port.
## ============ 2026-08-19 (continuation): LEVEL-PATH DECOMPILED — FUN_180563440 + FUN_180563ce0 ============
## ============ UPDATE 2026-08-19f: LEVEL-TRACKER UPDATE-LOOP LOCATED (structural) ============
The UPDATE loop is NOT a separate function — it is the inline bidirectional IIR smoothing
inside FUN_180529fe0 per-band loop (consumers_out.txt). Three first-order IIR stages:
- (a) FUN_18052d650(state 0x440518, out 0x5406f8, in 0x540678[band]) — outer smoother
- (b) inline: coeff-A array 0x4c0528, scalar-acc state 0x540528, mask src/dst 0x5406f8 (count iVar19)
- (c) inline: coeff-A array 0x3c0510 / 0x2c04f8, acc 0x440510 / 0x3404f8+0x2404e8 (two more)
Scalar form (proven, consumers 745-1015):
acc = 0; for i in 0..N-1: y = x[i]*A[i] + acc; acc = y; x[i] = (float)y
i.e. cumulative leaky-integrator over A[i] (running-acc coefficient). Vector variant does 4/iter.
=> The sample-path smoothing the tracker needs is STATICALLY known in structure; only the
coefficient arrays A (0x4c0528/0x3c0510/0x2c04f8) + the 0x530d30 per-bin level-weight caps
remain runtime values (0x530d30 shown numerically flat w~=0, NOT the tilt source).
Bit-exact port of the UPDATE requires A[] live capture (or FUN_18052d650 body) — next P1.5-ish.
Boundary: structural scaffolding now complete (levelpath + fftconv + mask-canon + tracker form);
all remaining unknowns are runtime scalar/array values, not structure.
## ============ 2026-08-19 continuation: LEVEL-PATH DECOMPILED — FUN_180563440 + FUN_180563ce0 ============
### FUN_180563440 (dsp/levelpath.cpp dump, f_563440.dis 222 lines) — LUT curve + twin-mask + combine
Three-phase per-frame structure: