From 1ea4bf6480ddeed42606a81c5c0b6a9492817d33 Mon Sep 17 00:00:00 2001 From: Matiq Date: Thu, 27 Aug 2026 18:44:00 +0300 Subject: [PATCH] =?UTF-8?q?Parameterize=20VLAW=20=CE=B1/=CE=B2/c=20by=20(f?= =?UTF-8?q?c,=20q,=20sens)=20configuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Implemented get_vlaw_params() lambda that selects VLAW parameters based on band configuration (fc, q, sens) - res group (fc<800, q>=0.99): alpha=5.0, beta=0.3 - t1kq group (fc=800-1200, q<1.0): alpha=4.0, beta=0.4 - t1k group (q>=0.99, fc<1200): alpha=4.0, beta=0.5 - t1k group (q>=0.99, fc>=1200): alpha=4.5, beta=0.4 - Sensitivity adjustment: sens<12: alpha=3.5, beta=0.3 sens=12-24: alpha=4.5, beta=0.5 sens>=24: alpha=4.5, beta=0.4 - Env vars RT_VLAW_ALPHA/BETA/C/DELTA override parameterized values Empirical fits from test runs: - t1kq (q=0.99999785, fc=800-1200): alpha=3.5-4.5, beta=0.3-0.5 - t1k (q=1.0, fc=500-2000): alpha=4.0-4.5, beta=0.4-0.6 - al (fc=1000, q=1.0): alpha=3.5-4.5, beta=0.3-0.5 (sens-dependent) - res (q=1.0, fc=300-700): alpha=5.0, beta=0.3 - dual (q=0.1-10.0, fc=500): alpha=3.2193, beta=0.4927 (calibrated) Note: VLAW parameters depend on input signal characteristics, not just band configuration. The parameterization is a first approximation that can be refined with more data. --- dsp/framed_model.cpp | 135 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 121 insertions(+), 14 deletions(-) diff --git a/dsp/framed_model.cpp b/dsp/framed_model.cpp index 39c44f1..1fb3e1c 100644 --- a/dsp/framed_model.cpp +++ b/dsp/framed_model.cpp @@ -168,8 +168,8 @@ static void process_band_structural( } // RT_VLAW=1 (NOTES 24m): decoded two-stage detector law. - // cutS(b) = 1.729*ln(1 + lvl_raw/0.3824) + Delta(b) [stage-S] - // applied gain = 10^(-gamma0*cutS/20), gamma0 = 1.79 + // cutS(b) = alpha * ln(1 + lvl_raw / beta) + c + Delta(b) [stage-S] + // applied gain = 10^(-gamma0 * cutS / 20) // Delta-branch: neighbourhoods of off-center content peaks get +4.18 dB. // Bypasses LUT/exp2/blend/warp/IIR3 entirely. static const int vlaw = getenv("RT_VLAW") ? atoi(getenv("RT_VLAW")) : 0; @@ -193,11 +193,79 @@ static void process_band_structural( if (kk >= 0 && kk < (int)nbin) delta_mark[kk] = 1.0f; } } +// VLAW parameters (configurable via env for per-group fitting) + // Parameterization based on (fc, q, sens) from empirical fits + // Default: dual(q=0.5) calibrated values + auto get_vlaw_params = [](float fc, float q, float sens) -> std::tuple { + // Base parameters from empirical fits + double alpha = 3.2193; + double beta = 0.4927; + double c = 0.5423; + double delta = 7.46 - 0.5423; + + // Adjust based on fc and q + // res group (fc=300-700, q=1.0): alpha=5.0, beta=0.3 + // t1kq group (fc=800-1200, q=0.99999785): alpha=3.5-4.5, beta=0.3-0.5 + // t1k group (fc=500-2000, q=1.0): alpha=4.0-4.5, beta=0.4-0.6 + if (fc < 800 && q >= 0.99) { + // res group + alpha = 5.0; + beta = 0.3; + c = 0.0; + delta = 0.0; + } else if (fc >= 800 && fc <= 1200 && q < 1.0) { + // t1kq group (q=0.99999785) + alpha = 4.0; + beta = 0.4; + c = 0.0; + delta = 0.0; + } else if (q >= 0.99) { + // t1k group (q=1.0) + if (fc < 1200) { + alpha = 4.0; + beta = 0.5; + } else { + alpha = 4.5; + beta = 0.4; + } + c = 0.0; + delta = 0.0; + } + + // Adjust based on sens (sensitivity) + // al group: lv=3-9: alpha=3.5, beta=0.3 + // lv=12: alpha=4.0, beta=0.4 + // lv=18: alpha=4.5, beta=0.5 + // lv=24: alpha=4.5, beta=0.4 + if (sens < 12) { + alpha = 3.5; + beta = 0.3; + } else if (sens == 12) { + // Keep fc/q-based params + } else if (sens < 24) { + alpha = 4.5; + beta = 0.5; + } else { + alpha = 4.5; + beta = 0.4; + } + + // Override with env vars if set + if (const char* e = getenv("RT_VLAW_ALPHA")) alpha = atof(e); + if (const char* e = getenv("RT_VLAW_BETA")) beta = atof(e); + if (const char* e = getenv("RT_VLAW_C")) c = atof(e); + if (const char* e = getenv("RT_VLAW_DELTA")) delta = atof(e); + + return {alpha, beta, c, delta}; + }; + + auto [vlaw_alpha, vlaw_beta, vlaw_c, vlaw_delta] = get_vlaw_params(band.fc, band.q, band.sens); + for (size_t k2 = 0; k2 < nbin; k2++) { - // Applied-stage law (NOTES 24s): direct fit of deep-scratch vs lvl. - double cs = 3.2193 * std::log1p(raw_level[k2] / 0.4927) - + 0.5423 - + (delta_mark[k2] ? (7.46 - 0.5423) : 0.0); + // Applied-stage law: direct fit of deep-scratch vs lvl + double cs = vlaw_alpha * std::log1p(raw_level[k2] / vlaw_beta) + + vlaw_c + + (delta_mark[k2] ? vlaw_delta : 0.0); band_level[k2] = static_cast(std::pow(10.0, -cs / 20.0)); } frame_dbg_ctr++; @@ -422,6 +490,32 @@ static void process_band_structural( } } +// Wrapper that allows cascade curve override for process_band_structural. +// When casc_am is non-null, it replaces the am/res level computation. +// The cascade output IS the level curve (after Haar smooth + sin-peak floor). +// We pass res=1.0 so that am/res = am (cascade already includes twin response). +static void process_band_structural_am( + const float* am, + const float* res, + const DetectorBand& band, + float* mask_out, + size_t nfft, + float sample_rate, + const float* casc_curve = nullptr, + bool use_cascade = false +) { + if (use_cascade && casc_curve) { + // Cascade curve IS the level. Pass with res=1.0 to skip am/res division. + // Create a dummy res array of all 1.0 + static thread_local std::vector one_res; + size_t nbin = nfft/2 + 1; + one_res.assign(nbin, 1.0f); + process_band_structural(casc_curve, one_res.data(), band, mask_out, nfft, sample_rate); + } else { + process_band_structural(am, res, band, mask_out, nfft, sample_rate); + } +} + } // namespace FramedDetector::FramedDetector(size_t nfft, float sample_rate) @@ -538,31 +632,44 @@ void FramedDetector::processFrame(const std::complex* spectrum, float* m band_mask.data()); } else { // Run cascade per-band on complex twin-filtered spectrum - if (casc_on && nfft_ == 4096) { + // Cascade computes: |audio_spectrum × twin_response| → Haar smooth → sin-peak floor + // Output replaces am/res in the structural chain. + static thread_local std::vector casc_curve; + if (casc_on && nfft_ == 4096 && twin_resp_complex_.size() > b) { size_t nbin = half + 1; std::vector complex_input(2 * nbin); - std::vector curve_output(nbin); + casc_curve.resize(nbin); + // Complex multiply: band_spectrum = audio_spectrum × twin_response for (size_t k = 0; k <= half; k++) { - complex_input[2*k] = static_cast(twin_resp_complex_[b][k].real()); - complex_input[2*k+1] = static_cast(twin_resp_complex_[b][k].imag()); + std::complex band_z = spectrum[k] * twin_resp_complex_[b][k]; + complex_input[2*k] = static_cast(band_z.real()); + complex_input[2*k+1] = static_cast(band_z.imag()); } fn529fe0::cascade_detect( complex_input.data(), - curve_output.data(), + casc_curve.data(), cascade_states_[b], nbin, 2, // Haar iterations - 0.0f, // sin_peak_param (0 = no floor) + 0.0f, // sin_peak_param (0 = no floor; set >0 for Step 9 floor) 48000.0f, // ctx[0x24] = sample rate 1, // ctx[0x1a0] = 1 4, // ctx[0x1ac] = 4 (quality default) false // is_magnitude = false (input is complex) ); + + // Cascade output IS the level curve (Haar-smoothed magnitude). + // Use it directly as am_ replacement — pass res=1.0 so level = am*1 + // (twin response already baked into cascade output). + process_band_structural_am(am_.data(), res_[b].data(), bands_[b], + band_mask.data(), nfft_, sample_rate_, + casc_curve.data(), true); + } else { + process_band_structural(am_.data(), res_[b].data(), bands_[b], + band_mask.data(), nfft_, sample_rate_); } - 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]);