#pragma once #include #include #include #include "fn529fe0.hpp" struct DetectorBand { float fc; // band center freq (Hz) float q; // resonance Q float sens; // XML sens (dB); internal sens_stored = sens * 2.054 float level_scale = 1.0f; // calibration: level = am * res * level_scale }; // Live-captured BandConfig parameters from DSP snapshot (2026-08-20). // +0x180 (level LUT curve, FUN_180563a60): A = -24.0, B = +28.0, gamma = 1.0, flag = 0. // +0x188 (freq-range shaper, FUN_180563440): A = 16.0, B = 20000.0, gamma = 1.0, flag = 0. // These values are identical for both render_long.rpp and t1kq_only1_1000 configs. // The parametric LUT formula from FUN_180563a60 / FUN_180563440: // t = clamp((x - A) / (B - A), 0.0, 1.0); // val = A + (B - A) * t^gamma // With gamma=1: val = clamp(x, A, B) [linear interpolation between A and B]. // The x input is the mask-dependent dB-scaled value (mask * 8.6859 from 0x24c43e0). constexpr double CAP_A_LEVEL = -24.0; constexpr double CAP_B_LEVEL = 28.0; constexpr double CAP_GAMMA = 1.0; constexpr double CAP_A_FREQ = 16.0; constexpr double CAP_B_FREQ = 20000.0; constexpr double CAP_GAMMA_FREQ = 1.0; // Helper: parametric LUT evaluation (gamma=1 path, linear interpolation) inline double lut_parametric(double x, double A, double B, double gamma) { double t = (x - A) / (B - A); if (t < 0.0) t = 0.0; if (t > 1.0) t = 1.0; if (gamma == 1.0) { // linear: val = A + (B - A) * t = clamp(x, A, B) return A + (B - A) * t; } // power-law path (gamma != 1) double abs_t = std::abs(t); double sign_t = (t >= 0.0) ? 1.0 : -1.0; double pow_val = std::pow(std::max(abs_t, 1e-12), gamma); return A + (B - A) * 0.5 * (1.0 + sign_t * pow_val); } // FramedDetector — C++ transcription of the real soothe2 mask-apply chain // (FUN_180529fe0 mono path, 0x5408b8==0), bit-exact structure. // // Per band, per bin (exact decomp /tmp/consumers_out.txt:638-1111): // 1. scale: x = level * (fVar30/0x1a0) * 0x540870 * 0x54088c // 2. IIR1 leaky: y[i] = A1[i]*acc + B1[i]*x[i] (A1/B1 ramp attack, live tables) // 3. IIR2 leaky: same with A2/B2 (slow release) // 4. blend: b = freqaxis*(1-mix) + mix*0.8; mask = exp2(0.5*(mask-b)) // 5. combine: acc[band] = mask - b; mirror; += w_att*upper; += w_rel*lower; += mask // 6. warp: mask *= 0x540768[band]; mask *= warp (dual tilt) // 7. IIR3 leaky: twice with A3/B3 // 8. dry/wet: mask = mask*(fVar30*0x540888) + (1-fVar30) // final = min over bands. // PRNG (FUN_180529fe0 prologue :515-583): LCG state 0x2404e0 advances by round // offsets; fVar30 (scale coeff) = (int)(LUT[s+1]*LUT[s]+0.001), DAT_18262b5c8/ // b704/b700 == 1 (VA-linear dump; earlier 0.4552/0.6089/0.6070 was a bad offset). // At live state 112 this yields fVar30 == 1.0 deterministically over many frames. class FramedDetector { public: FramedDetector(size_t nfft, float sample_rate); ~FramedDetector(); void setParams(const std::vector& bands); void processFrame(const std::complex* spectrum, float* mask); // Cascade state access for per-band detector cascade std::vector& cascadeStates() { return cascade_states_; } const std::vector>>& twinRespComplex() const { return twin_resp_complex_; } std::vector>>& twinRespComplex() { return twin_resp_complex_; } private: size_t nfft_; float sample_rate_; double wsum_; int prng_state_ = 112; // 0x2404e0 (live snapshot value; advances per frame) std::vector bands_; std::vector> res_; // per band, per bin |2B/A| std::vector am_; // smoothed per-bin amplitude std::vector f6f8_; // shared 0x5406f8 blend buffer (IIR1 out) std::vector> track_; // per band, per bin accumulator 0x5407c8 // For cascade 529c60: per-band complex twin filter responses std::vector>> twin_resp_complex_; // Per-band cascade states std::vector cascade_states_; };