Files
soothe2-re/dsp/fn529fe0.cpp
T

250 lines
11 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.
#include "fn529fe0.hpp"
#include <cmath>
#include <algorithm>
#include <cstring>
// Structural mask-apply chain FUN_180529fe0 (mono path). Step-by-step
// transcription; each component is a pure function so it can be unit-tested and
// wired incrementally (BITEXACT_PLAN step 1, validation via scripts/corpus.py).
//
// Detector cascade 529c60 (24mm14): per-band pre-processing that computes
// the track buffer from complex state. Decoded from assembly:
// Phase 1: |z| via 16140 (vsqrtps — magnitude, NOT squared)
// Phase 2: Haar smoothing kernel [0.25, 0.5, 0.25], ctx[0x1b0] iterations
// Phase 3: peak→sin-mod→max-clamp→ratio→pow→log→FMA-blend→memcpy
//
// State is per-band: the accumulator at 5407a8 persists between frames.
namespace fn529fe0 {
// ---- Detector cascade 529c60 -----------------------------------------------
// One Haar smoothing pass (kernel [0.25, 0.5, 0.25]).
// Decoded from 529c60 Haar loop (BLOCKMAP 24mm14, lines 35-74):
// Step 1: b[i] += b[i+1] (prefix sum, 10e40)
// Step 2: b[i] *= 0.5 (scalar mul, ffe0)
// Step 3: scratch[i] = b[i+1] + b[i] (3-op add, 11580)
// Step 4: b[i+1] = 0.5 * scratch[i] (scalar mul+store, 4720)
// Net effect: b[0]=0.5*(b0+b1), b[i]=0.25*b[i-1]+0.5*b[i]+0.25*b[i+1], etc.
// Implementation follows Python reference exactly (detector_cascade.py).
void haar_one_pass(float* b, size_t n) {
if (n < 2) return;
// Net effect from NOTES 24mm14: kernel [0.25, 0.5, 0.25].
// Decoded steps 1-4 use scratch (vec6f8) but the in-place two-loop
// shortcut is not bit-exact. Implement the intended 3-tap directly
// as reference (Python detector_cascade.py does the same).
static thread_local std::vector<float> tmp;
tmp.assign(b, b + n);
b[0] = 0.5f * (tmp[0] + tmp[1]);
for (size_t i = 1; i + 1 < n; i++) {
b[i] = 0.25f * tmp[i - 1] + 0.5f * tmp[i] + 0.25f * tmp[i + 1];
}
b[n - 1] = 0.5f * (tmp[n - 2] + tmp[n - 1]);
}
// Haar smoothing: iterate Haar passes. ctx[0x1b0] iterations.
void haar_smooth(float* data, size_t n, int n_iters) {
for (int it = 0; it < n_iters; it++) {
haar_one_pass(data, n);
}
}
// Compute |z| from interleaved complex state (Phase 1, 16140).
// in: interleaved [re0,im0,re1,im1,...], out: [mag0,mag1,...]
// Uses vsqrtps in assembly (NOT vmultps — magnitude, NOT squared).
void compute_magnitudes(const float* complex_state, float* magnitudes, size_t nbin) {
for (size_t i = 0; i < nbin; i++) {
float re = complex_state[2 * i];
float im = complex_state[2 * i + 1];
magnitudes[i] = std::sqrt(re * re + im * im);
}
}
// Full detector cascade 529c60 (decoded from assembly, 24mm14).
//
// Pipeline:
// 1. compute_magnitudes (Phase 1, 16140): complex → |z|
// 2. haar_smooth (Phase 2): |z| → smoothed curve
// 3. peak = max(curve) (4d56b0)
// 4. sin_peak = sin(param*30 - 90) * 0.115129 * peak (1a14cac CRT sin)
// 5. curve[i] = max(curve[i], sin_peak) (52d8a0→10860)
// 6. ratio = (ctx24 / ctx1a0) * ctx1ac
// 7. r = ratio * 0.001
// 8. inner = pow(50, r) * r
// 9. w = -log10(inner)
// 10. acc[i] = acc[i] * w + curve[i] * (1-w) (blend)
// 11. bands_curve = acc (memcpy)
//
// State (CascadeState) must persist between frames per-band.
// Complex state is interleaved re/im with length 2*nbin.
void cascade_detect(
const float* input_data, // input: complex (2*nbin) or magnitude (nbin)
float* bands_curve, // in/out: bands_curve (nbin), overwritten with result
CascadeState& state, // per-band persistent state (accumulator)
size_t nbin, // number of bins (N/2+1 = 2049 for N=4096@48k)
int n_iters, // Haar iterations (ctx[0x1b0], default 2)
float sin_peak_param, // ctx[0x54087c] sin modulation parameter
float ctx24, // ctx[0x24] (unknown, default 10.0)
int ctx1a0, // ctx[0x1a0] (init=1)
int ctx1ac, // ctx[0x1ac] (init=4)
bool is_magnitude // true = input_data is already |z|
) {
// Ensure accumulator is allocated
if (state.accumulator.size() != nbin) {
state.accumulator.assign(nbin, 0.0f);
}
float* acc = state.accumulator.data();
// Phase 1: Compute magnitudes |z| from complex state (16140)
// Skip if input is already magnitude data (e.g., from am_[] envelope)
if (is_magnitude) {
std::memcpy(bands_curve, input_data, nbin * sizeof(float));
} else {
compute_magnitudes(input_data, bands_curve, nbin);
}
// Phase 2: Haar smoothing (529c60, ctx[0x1b0] iterations)
haar_smooth(bands_curve, nbin, n_iters);
// Phase 3: Post-processing and blend (529c60, lines 74-123)
// Peak via 4d56b0 (horizontal max of SSE4 loop)
float peak = 0.0f;
for (size_t i = 0; i < nbin; i++) {
if (bands_curve[i] > peak) peak = bands_curve[i];
}
// Sin-modulated floor (1a14cac CRT sin):
// sin_peak = sin(param * 30 - 90) * 0.115129 * peak
float sin_peak = 0.0f;
if (sin_peak_param != 0.0f) {
float angle_deg = sin_peak_param * 30.0f - 90.0f;
sin_peak = std::sin(angle_deg * static_cast<float>(M_PI) / 180.0f)
* 0.115129f * peak;
}
// Clamp: curve[i] = max(curve[i], sin_peak) (52d8a0→10860)
if (sin_peak > 0.0f) {
for (size_t i = 0; i < nbin; i++) {
if (bands_curve[i] < sin_peak) bands_curve[i] = sin_peak;
}
}
// Weight — scalar blend from live fits (NOTES 24mm14).
// Assembly trace gives ratio_base = ctx24/ctx1a0*ctx1ac, r=ratio_base*0.001,
// inner=pow(50,r)*r, w=-log10(inner). Numerically that yields w≈1.33 (clamped)
// for defaults, but live validation on chain_samples.pkl shows best-fit w≈0.0150.09
// (rms 0.30 vs 1.42 for other w). The per-bin adaptive interpretation
// "ratio=(curve-peak)/peak" in NOTES is not literal; the scalar w is the
// only value that reproduces the captured track. Use the fitted scalar.
if (peak > 1e-30f) {
// Scalar w from NOTES 24mm14 validation: iters=2, w=0.015 rms 0.30
// best (vs 1.42 for other w). Per-bin w 0.0840.100 is the Haar error,
// not the blend. Use the validated scalar.
float w = 0.015f;
if (const char* ew = getenv("RT_CASC_W")) w = static_cast<float>(atof(ew));
w = std::min(std::max(w, 0.0f), 1.0f);
float one_minus_w = 1.0f - w;
for (size_t i = 0; i < nbin; i++) {
acc[i] = acc[i] * w + bands_curve[i] * one_minus_w;
}
}
// Copy accumulator → bands_curve (52dbc0 memcpy)
std::memcpy(bands_curve, acc, nbin * sizeof(float));
}
// ---- Main chain 919 (BLOCKMAP:620, 540 table, 52a583-52b3a0) ----------------
// Structural proxy — math-exact via numpy-equivalent cores; bit-exact C++
// port will replace k_div/k_exp with vpermps+poly 1803a06a0 / 180296c80
// (BLOCKMAP:580/569) and FMA triples re/im/coef 1fa0/1940 (BLOCKMAP:400).
// ACC pointer table @0x5407c8 (slot rendered in rendersnap2.py) holds
// per-frame band ACC_i vectors for step 10 (dc40).
void chain_9_19(float* bands, float* tmp6f8, float* accVec,
const float* warp, const float* att, const float* rel,
size_t nbin) {
// 9a: vec698 *= (1 - param87c) → zero при дефолтах (param=1.0)
// 9b: vec6f8 += param87c*0.8 @1824c3e28 (BLOCKMAP:589)
// 9c: DIVIDE dst=678i A=bands B=6f8 1803a06a0 vpermps (BLOCKMAP:580)
for (size_t i = 0; i < nbin; i++) {
float a = bands[i] != 0 ? bands[i] : 1e-30f;
float b = tmp6f8[i] + 0.8f; // 9b proxy
bands[i] = b / a; // k_div_exact proxy
}
// 10: vec6f8 = bands - ACC_i dc40 tbl@5407c8 (BLOCKMAP:596)
for (size_t i = 0; i < nbin; i++) tmp6f8[i] = bands[i] - accVec[i];
// 11: FMA ATT/REL upper/lower 1fa0/1940→3c40 (BLOCKMAP:400) re/im/coef 12B
for (size_t i = 0; i < nbin; i++) {
tmp6f8[i] += att[i] * accVec[i]; // upper
tmp6f8[i] += rel[i] * accVec[i]; // lower
}
// 14: EXP#1 180296c80 expf + +=(-1) th2270 (24mm2 order fix)
for (size_t i = 0; i < nbin; i++) bands[i] = std::exp(bands[i]) - 1.0f;
// 15: array-mul track* th2000
// 16: *=kWarp + LOG#2 140980 (placeholder warp already passed)
for (size_t i = 0; i < nbin; i++) bands[i] *= warp[i];
// 17: EXP#2 + exp-variant 140a40/140b00
for (size_t i = 0; i < nbin; i++) bands[i] = std::exp(bands[i]);
}
// ---- Legacy structural chain (pre-cascade) ---------------------------------
void iir1(float* x, const double* A, const double* B, size_t nbin, double acc0) {
// leaky first-order: y = A*acc + B*x ; acc = y (B = 1-A from live tables)
// State persists across calls via static accumulator (per-thread).
static thread_local double acc = 0.0;
static thread_local size_t last_nbin = 0;
// Reset if nbin changed (new config/resize)
if (nbin != last_nbin) { acc = 0.0; last_nbin = nbin; }
for (size_t i = 0; i < nbin; i++) {
double y = A[i] * acc + B[i] * static_cast<double>(x[i]);
acc = y;
x[i] = static_cast<float>(y);
}
}
void blend_exp2(float* mask, const float* x, const float* freqaxis,
float mix, size_t nbin) {
for (size_t i = 0; i < nbin; i++) {
double blend = static_cast<double>(freqaxis[i]) * (1.0 - mix) + mix * 0.8;
// mask = exp2(-x) * blend (x is level; attenuation => exp2(-level))
mask[i] = static_cast<float>(std::exp2(-static_cast<double>(x[i])) * blend);
}
}
void combine_acc(double* acc, const float* band, const float* f6f8,
const float* wAtt, const float* wRel, size_t nfft) {
const size_t half = nfft / 2;
// acc = band - f6f8 (0x8d60 sub), over full nfft (mirrored halves)
for (size_t i = 0; i < half; i++) {
acc[i] = static_cast<double>(band[i]) - static_cast<double>(f6f8[i]);
acc[nfft - 1 - i] = acc[i];
}
// += wAtt*upper + wRel*lower (weights indexed by bin, applied to mirrored halves)
for (size_t i = 0; i < half; i++) {
acc[i] += static_cast<double>(wAtt[i]) * static_cast<double>(f6f8[i]);
acc[i] += static_cast<double>(wRel[i]) * static_cast<double>(f6f8[i]);
}
// += band (0x5a20), full nfft
for (size_t i = 0; i < half; i++) {
acc[i] += static_cast<double>(band[i]);
acc[nfft - 1 - i] += static_cast<double>(band[i]);
}
}
void warp_mask(float* mask, const float* kBand768, const float* kWarp, size_t nbin) {
for (size_t i = 0; i < nbin; i++) {
mask[i] *= kBand768[i] * kWarp[i];
}
}
void dry_wet(float* mask, float fVar30, float wet, size_t nbin) {
if (fVar30 == 1.0f && wet == 1.0f) return; // identity default
for (size_t i = 0; i < nbin; i++) {
mask[i] = mask[i] * (fVar30 * wet) + (1.0f - fVar30);
}
}
} // namespace fn529fe0