cascade integration: sin-peak floor, complex twin resp storage, per-band cascade

- 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
This commit is contained in:
2026-08-27 03:12:56 +03:00
parent b6e7fdc289
commit 4d78785f0b
2 changed files with 78 additions and 2 deletions
+66 -2
View File
@@ -134,6 +134,32 @@ static void process_band_structural(
for (size_t k = 0; k < nbin; k++) if (lvl_in[k] > cap) lvl_in[k] = cap;
}
// Cascade sin-peak floor (529c60): the -20.72 dB floor mechanism.
// From assembly: sin_peak = sin(param * 30 - 90) * (ln10/20) * peak
// where ln10/20 = 0.115129 (constant at 0x1824c3cd4).
// This prevents over-reduction by clamping the level curve.
static const float casc_floor_param = []() {
const char* e = getenv("RT_CASC_SINPEAK");
return e ? (float)atof(e) : 0.0f;
}();
if (casc_floor_param != 0.0f) {
// Find peak of level curve
float peak_lvl = 0.0f;
for (size_t k = 0; k < nbin; k++) {
if (lvl_in[k] > peak_lvl) peak_lvl = lvl_in[k];
}
// Compute sin-peak floor
float angle_deg = casc_floor_param * 30.0f - 90.0f;
float sin_peak = std::sin(angle_deg * static_cast<float>(M_PI) / 180.0f)
* 0.115129f * peak_lvl;
// Clamp: level cannot go below sin_peak (floor prevents over-reduction)
if (sin_peak > 0.0f) {
for (size_t k = 0; k < nbin; k++) {
if (lvl_in[k] < sin_peak) lvl_in[k] = sin_peak;
}
}
}
// Save raw level BEFORE LUT transform (for RT_FIRPOWER)
std::vector<float> raw_level(nbin);
for (size_t k = 0; k < nbin; k++) {
@@ -410,6 +436,8 @@ void FramedDetector::setParams(const std::vector<DetectorBand>& bands) {
size_t half = nfft_ / 2;
res_.clear();
track_.clear();
twin_resp_complex_.clear();
cascade_states_.clear();
// RT_DUMPRESPATH=<file> (NOTES 22t): static twin-response spectra per band,
// binary {int32 band, int32 nbin, float res[nbin]} records (append).
@@ -434,6 +462,13 @@ void FramedDetector::setParams(const std::vector<DetectorBand>& bands) {
r[k] = std::sqrt(out[k].re * out[k].re + out[k].im * out[k].im);
r[k] = std::max(r[k], 1e-12f);
}
// Store complex response for cascade 529c60
std::vector<std::complex<double>> complex_resp(half + 1);
for (size_t k = 0; k <= half; k++) {
complex_resp[k] = std::complex<double>(out[k].re, out[k].im);
}
twin_resp_complex_.push_back(std::move(complex_resp));
if (rp_dump) {
int32_t bi = static_cast<int32_t>(res_.size());
int32_t nb = static_cast<int32_t>(r.size());
@@ -445,6 +480,7 @@ void FramedDetector::setParams(const std::vector<DetectorBand>& bands) {
}
if (rp_dump) fclose(rp_dump);
track_.assign(bands_.size(), std::vector<float>(half + 1, 1.0f));
cascade_states_.assign(bands_.size(), fn529fe0::CascadeState());
}
void FramedDetector::processFrame(const std::complex<double>* spectrum, float* mask) {
@@ -481,6 +517,10 @@ void FramedDetector::processFrame(const std::complex<double>* spectrum, float* m
}
}
// Detector cascade 529c60: per-band pre-processor on complex twin-filtered
// spectrum. Computes magnitudes, Haar-smooths, applies sin-peak floor.
static const int casc_on = getenv("RT_CASC") ? atoi(getenv("RT_CASC")) : 0;
for (size_t k = 0; k <= half; k++) mask[k] = 1.0f;
if (is_internal_grid(nfft_, sample_rate_)) {
@@ -497,8 +537,32 @@ void FramedDetector::processFrame(const std::complex<double>* spectrum, float* m
sample_rate_, sf, fparams,
band_mask.data());
} else {
process_band_structural(am_.data(), res_[b].data(), bands_[b],
band_mask.data(), nfft_, sample_rate_);
// Run cascade per-band on complex twin-filtered spectrum
if (casc_on && nfft_ == 4096) {
size_t nbin = half + 1;
std::vector<float> complex_input(2 * nbin);
std::vector<float> curve_output(nbin);
for (size_t k = 0; k <= half; k++) {
complex_input[2*k] = static_cast<float>(twin_resp_complex_[b][k].real());
complex_input[2*k+1] = static_cast<float>(twin_resp_complex_[b][k].imag());
}
fn529fe0::cascade_detect(
complex_input.data(),
curve_output.data(),
cascade_states_[b],
nbin,
2, // Haar iterations
0.0f, // sin_peak_param (0 = no floor)
48000.0f, // ctx[0x24] = sample rate
1, // ctx[0x1a0] = 1
4, // ctx[0x1ac] = 4 (quality default)
false // is_magnitude = false (input is complex)
);
}
process_band_structural(am_.data(), res_[b].data(), bands_[b],
band_mask.data(), nfft_, sample_rate_);
}
for (size_t k = 0; k <= half; k++) {
mask[k] = std::min(band_mask[k], mask[k]);
+12
View File
@@ -2,6 +2,7 @@
#include <cstddef>
#include <complex>
#include <vector>
#include "fn529fe0.hpp"
struct DetectorBand {
float fc; // band center freq (Hz)
@@ -71,6 +72,11 @@ public:
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_;
@@ -82,4 +88,10 @@ private:
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_;
};