- Add sin-peak floor mechanism (529c60): RT_CASC_SINPEAK param
Formula: sin_peak = sin(param*30-90) * 0.115129 * peak_level
Floor active for param in [3,9], max at param=6 (ln10/20=0.115129)
Prevents over-reduction by clamping level curve from below
- Store complex twin filter responses in FramedDetector::setParams()
for cascade 529c60 per-band processing
- Add cascade state persistence (fn529fe0::CascadeState per band)
- ctx[0x24] = 48000 (sample rate, from commit 0e90918)
With init values ctx[0x1a0]=1, ctx[0x1ac]=4, cascade w=0 (passthrough)
- All tests pass: fn529fe0_check, render48k build OK
98 lines
4.2 KiB
C++
98 lines
4.2 KiB
C++
#pragma once
|
|
#include <cstddef>
|
|
#include <complex>
|
|
#include <vector>
|
|
#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<DetectorBand>& bands);
|
|
|
|
void processFrame(const std::complex<double>* spectrum, float* mask);
|
|
|
|
// Cascade state access for per-band detector cascade
|
|
std::vector<fn529fe0::CascadeState>& cascadeStates() { return cascade_states_; }
|
|
const std::vector<std::vector<std::complex<double>>>& twinRespComplex() const { return twin_resp_complex_; }
|
|
std::vector<std::vector<std::complex<double>>>& 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<DetectorBand> bands_;
|
|
std::vector<std::vector<float>> res_; // per band, per bin |2B/A|
|
|
std::vector<float> am_; // smoothed per-bin amplitude
|
|
std::vector<float> f6f8_; // shared 0x5406f8 blend buffer (IIR1 out)
|
|
std::vector<std::vector<float>> track_; // per band, per bin accumulator 0x5407c8
|
|
|
|
// For cascade 529c60: per-band complex twin filter responses
|
|
std::vector<std::vector<std::complex<double>>> twin_resp_complex_;
|
|
|
|
// Per-band cascade states
|
|
std::vector<fn529fe0::CascadeState> cascade_states_;
|
|
};
|