Parameterize VLAW α/β/c by (fc, q, sens) configuration
- 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.
This commit is contained in:
+121
-14
@@ -168,8 +168,8 @@ static void process_band_structural(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RT_VLAW=1 (NOTES 24m): decoded two-stage detector law.
|
// RT_VLAW=1 (NOTES 24m): decoded two-stage detector law.
|
||||||
// cutS(b) = 1.729*ln(1 + lvl_raw/0.3824) + Delta(b) [stage-S]
|
// cutS(b) = alpha * ln(1 + lvl_raw / beta) + c + Delta(b) [stage-S]
|
||||||
// applied gain = 10^(-gamma0*cutS/20), gamma0 = 1.79
|
// applied gain = 10^(-gamma0 * cutS / 20)
|
||||||
// Delta-branch: neighbourhoods of off-center content peaks get +4.18 dB.
|
// Delta-branch: neighbourhoods of off-center content peaks get +4.18 dB.
|
||||||
// Bypasses LUT/exp2/blend/warp/IIR3 entirely.
|
// Bypasses LUT/exp2/blend/warp/IIR3 entirely.
|
||||||
static const int vlaw = getenv("RT_VLAW") ? atoi(getenv("RT_VLAW")) : 0;
|
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;
|
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<double, double, double, double> {
|
||||||
|
// 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++) {
|
for (size_t k2 = 0; k2 < nbin; k2++) {
|
||||||
// Applied-stage law (NOTES 24s): direct fit of deep-scratch vs lvl.
|
// Applied-stage law: direct fit of deep-scratch vs lvl
|
||||||
double cs = 3.2193 * std::log1p(raw_level[k2] / 0.4927)
|
double cs = vlaw_alpha * std::log1p(raw_level[k2] / vlaw_beta)
|
||||||
+ 0.5423
|
+ vlaw_c
|
||||||
+ (delta_mark[k2] ? (7.46 - 0.5423) : 0.0);
|
+ (delta_mark[k2] ? vlaw_delta : 0.0);
|
||||||
band_level[k2] = static_cast<float>(std::pow(10.0, -cs / 20.0));
|
band_level[k2] = static_cast<float>(std::pow(10.0, -cs / 20.0));
|
||||||
}
|
}
|
||||||
frame_dbg_ctr++;
|
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<float> 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
|
} // namespace
|
||||||
|
|
||||||
FramedDetector::FramedDetector(size_t nfft, float sample_rate)
|
FramedDetector::FramedDetector(size_t nfft, float sample_rate)
|
||||||
@@ -538,31 +632,44 @@ void FramedDetector::processFrame(const std::complex<double>* spectrum, float* m
|
|||||||
band_mask.data());
|
band_mask.data());
|
||||||
} else {
|
} else {
|
||||||
// Run cascade per-band on complex twin-filtered spectrum
|
// 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<float> casc_curve;
|
||||||
|
if (casc_on && nfft_ == 4096 && twin_resp_complex_.size() > b) {
|
||||||
size_t nbin = half + 1;
|
size_t nbin = half + 1;
|
||||||
std::vector<float> complex_input(2 * nbin);
|
std::vector<float> complex_input(2 * nbin);
|
||||||
std::vector<float> curve_output(nbin);
|
casc_curve.resize(nbin);
|
||||||
|
|
||||||
|
// Complex multiply: band_spectrum = audio_spectrum × twin_response
|
||||||
for (size_t k = 0; k <= half; k++) {
|
for (size_t k = 0; k <= half; k++) {
|
||||||
complex_input[2*k] = static_cast<float>(twin_resp_complex_[b][k].real());
|
std::complex<double> band_z = spectrum[k] * twin_resp_complex_[b][k];
|
||||||
complex_input[2*k+1] = static_cast<float>(twin_resp_complex_[b][k].imag());
|
complex_input[2*k] = static_cast<float>(band_z.real());
|
||||||
|
complex_input[2*k+1] = static_cast<float>(band_z.imag());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn529fe0::cascade_detect(
|
fn529fe0::cascade_detect(
|
||||||
complex_input.data(),
|
complex_input.data(),
|
||||||
curve_output.data(),
|
casc_curve.data(),
|
||||||
cascade_states_[b],
|
cascade_states_[b],
|
||||||
nbin,
|
nbin,
|
||||||
2, // Haar iterations
|
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
|
48000.0f, // ctx[0x24] = sample rate
|
||||||
1, // ctx[0x1a0] = 1
|
1, // ctx[0x1a0] = 1
|
||||||
4, // ctx[0x1ac] = 4 (quality default)
|
4, // ctx[0x1ac] = 4 (quality default)
|
||||||
false // is_magnitude = false (input is complex)
|
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++) {
|
for (size_t k = 0; k <= half; k++) {
|
||||||
mask[k] = std::min(band_mask[k], mask[k]);
|
mask[k] = std::min(band_mask[k], mask[k]);
|
||||||
|
|||||||
Reference in New Issue
Block a user