From 1072dfba6559e1d716c3b83fd34e2b06354a52b7 Mon Sep 17 00:00:00 2001 From: Armin Date: Fri, 3 Jul 2026 23:00:42 +0200 Subject: [PATCH] fix noise floor detection --- tcd.c | 51 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/tcd.c b/tcd.c index 9b3666c..d851142 100644 --- a/tcd.c +++ b/tcd.c @@ -170,7 +170,8 @@ static int detect_cutoff(const Analyzer *a, double threshold_db, double sensitivity, double *out_cutoff, double *out_steepness, double *out_noise_db, double *out_roughness, - double *out_band_ratio) + double *out_band_ratio, + double *out_extended_cutoff) { int n = a->fft_size / 2; int sr = a->sample_rate; @@ -221,6 +222,21 @@ static int detect_cutoff(const Analyzer *a, double threshold_db, double noise_floor = (noise_count > 0) ? (noise_sum / noise_count) : 1e-12; *out_noise_db = 20.0 * log10(noise_floor / peak); + /* Compute extended cutoff at noise floor + 6 dB margin. + Catches high-frequency content below the user's threshold but + above the noise floor, proving the file isn't a low-bitrate + re-encode even when high frequencies are very quiet. + Limit to at most 30 dB more sensitive than user threshold. */ + double ext_db = *out_noise_db + 6.0; + double min_ext_db = threshold_db - 30.0; + if (ext_db < min_ext_db) ext_db = min_ext_db; + double ext_peak = peak * pow(10.0, ext_db / 20.0); + double ext_cutoff = 0; + for (int i = n - 1; i >= 0; i--) { + if (mag[i] >= ext_peak) { ext_cutoff = (double)i * sr / a->fft_size; break; } + } + *out_extended_cutoff = ext_cutoff; + double roughness = 0.0; double cutoff_idx = cutoff_hz * a->fft_size / sr; int lo = (int)(cutoff_idx * 0.60); @@ -494,7 +510,8 @@ static void freq_label_row(int chart_w, double nyquist) static void render_visual_spectrum( const Analyzer *a, - double cutoff_hz, double steepness, double noise_db, + double cutoff_hz, double effective_cutoff_hz, + double steepness, double noise_db, double roughness, double band_ratio, int is_transcode, int is_native_lossy, int upscaled, double peak_db, double threshold_db, @@ -671,8 +688,10 @@ static void render_visual_spectrum( draw_hline(chart_w + label_w); double cutoff_ratio = cutoff_hz / nyquist; + double eff_cutoff_ratio = effective_cutoff_hz / nyquist; double confidence = compute_confidence( - cutoff_ratio, steepness, roughness, band_ratio, is_native_lossy); + is_native_lossy ? eff_cutoff_ratio : cutoff_ratio, + steepness, roughness, band_ratio, is_native_lossy); printf(" " ANSI_BOLD "Decision:" ANSI_RESET " "); if (peak_db < -90.0) { @@ -723,18 +742,18 @@ static void render_visual_spectrum( char line[128]; snprintf(line, sizeof(line), " cutoff_ratio=%.3f < expected for bitrate (%lld kbps)", - cutoff_ratio, (long long)(bitrate / 1000)); + eff_cutoff_ratio, (long long)(bitrate / 1000)); printf(" %s " ANSI_GREEN "✓" ANSI_RESET, line); printf("\n"); const char *src_hint = ""; - if (cutoff_ratio < 0.70) src_hint = "≤ 64 kbps source"; - else if (cutoff_ratio < 0.80) src_hint = "96–128 kbps source"; - else if (cutoff_ratio < 0.88) src_hint = "128–192 kbps source"; + if (eff_cutoff_ratio < 0.70) src_hint = "≤ 64 kbps source"; + else if (eff_cutoff_ratio < 0.80) src_hint = "96–128 kbps source"; + else if (eff_cutoff_ratio < 0.88) src_hint = "128–192 kbps source"; printf(" → " ANSI_BOLD "Suggest %s" ANSI_RESET, src_hint); printf("\n"); } else { - printf(" cutoff_ratio=%.3f within expected range for this bitrate " ANSI_GREEN "✓" ANSI_RESET, cutoff_ratio); + printf(" cutoff_ratio=%.3f within expected range for this bitrate " ANSI_GREEN "✓" ANSI_RESET, eff_cutoff_ratio); printf("\n"); } } else { @@ -1129,11 +1148,12 @@ static int process_file(const char *filename, const Options *opts) } double cutoff_hz = 0, steepness = 0, noise_db = 0; - double roughness = 0, band_ratio = 0; + double roughness = 0, band_ratio = 0, extended_cutoff = 0; int is_transcode = detect_cutoff(&analyzer, opts->threshold_db, opts->sensitivity, &cutoff_hz, &steepness, &noise_db, - &roughness, &band_ratio); + &roughness, &band_ratio, + &extended_cutoff); double nyquist = sample_rate / 2.0; double peak_mag = 0; @@ -1155,7 +1175,9 @@ static int process_file(const char *filename, const Options *opts) return 2; } - double cutoff_ratio = cutoff_hz / nyquist; + double effective_cutoff = cutoff_hz; + if (extended_cutoff > effective_cutoff) effective_cutoff = extended_cutoff; + double cutoff_ratio = effective_cutoff / nyquist; int upscaled = 0; if (is_native_lossy) { double expected_min = 0.90; @@ -1167,7 +1189,8 @@ static int process_file(const char *filename, const Options *opts) render_visual_spectrum( &analyzer, - cutoff_hz, steepness, noise_db, roughness, band_ratio, + cutoff_hz, effective_cutoff, steepness, noise_db, + roughness, band_ratio, is_transcode, is_native_lossy, upscaled, peak_db, opts->threshold_db, filename, fmt_name, @@ -1211,7 +1234,9 @@ static int process_file(const char *filename, const Options *opts) return 2; } - double cutoff_ratio = cutoff_hz / nyquist; + double effective_cutoff = cutoff_hz; + if (extended_cutoff > effective_cutoff) effective_cutoff = extended_cutoff; + double cutoff_ratio = effective_cutoff / nyquist; int upscaled = 0; if (is_native_lossy) {