From ddb31b429d52bcfc96fbc8fe9a4be596b342bd8c Mon Sep 17 00:00:00 2001 From: Armin Date: Sat, 4 Jul 2026 09:44:24 +0200 Subject: [PATCH] improve transcode detection at 128/160 --- tcd.c | 125 ++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 100 insertions(+), 25 deletions(-) diff --git a/tcd.c b/tcd.c index d694107..4bc68f1 100644 --- a/tcd.c +++ b/tcd.c @@ -237,14 +237,43 @@ static int detect_cutoff(const Analyzer *a, double threshold_db, } *out_extended_cutoff = ext_cutoff; + /* When the noise floor is above the detection threshold, the cutoff is + determined by noise rather than signal. Compute a noise-aware cutoff + at (noise floor + 10 dB) for the roughness computation so it operates + on signal content rather than noise-dominated bins. */ + double rough_cutoff = cutoff_hz; + if (*out_noise_db > threshold_db) { + double adj_db = *out_noise_db + 10.0; + double adj_thresh = peak * pow(10.0, adj_db / 20.0); + double adj_cutoff = 0; + for (int i = n - 1; i >= 0; i--) { + if (mag[i] >= adj_thresh) { adj_cutoff = (double)i * sr / a->fft_size; break; } + } + if (adj_cutoff > 0) rough_cutoff = adj_cutoff; + } + double roughness = 0.0; - double cutoff_idx = cutoff_hz * a->fft_size / sr; + double cutoff_idx = rough_cutoff * a->fft_size / sr; int lo = (int)(cutoff_idx * 0.60); int hi = (int)(cutoff_idx * 0.95); if (hi >= n) hi = n - 1; if (lo < 1) lo = 1; - if (hi > lo) { + /* Check whether the roughness region is noise-dominated. When the noise + floor is close to or above the detection threshold the cutoff is + determined by noise, and the roughness region may contain mostly noise + bins, producing spuriously high roughness values. If fewer than 30 % + of bins in the region are more than 6 dB above the noise floor, the + roughness is unreliable and we set it to a low value. */ + double noise_mag = pow(10.0, *out_noise_db / 20.0) * peak; + int region_total = hi - lo + 1; + int above_noise = 0; + for (int i = lo; i <= hi; i++) { + if (mag[i] > noise_mag * 2.0) above_noise++; + } + if ((double)above_noise / region_total < 0.30) { + roughness = 0.01; + } else if (hi > lo) { double sum = 0; for (int i = lo; i <= hi; i++) sum += mag[i]; double mean = sum / (hi - lo + 1); @@ -273,10 +302,25 @@ static int detect_cutoff(const Analyzer *a, double threshold_db, : 0.5; *out_band_ratio = band_ratio; - free(mag); - double nyquist = sr / 2.0; - double cutoff_ratio = cutoff_hz / nyquist; + + /* When the noise floor is above the detection threshold, the cutoff + is determined by noise, not signal. Use the noise-aware cutoff + (at noise floor + 10 dB) for decision-making to avoid false positives + from an inflated cutoff ratio where noise dominates the spectrum. */ + double decision_cutoff = cutoff_hz; + if (*out_noise_db > threshold_db) { + double dc_db = *out_noise_db + 10.0; + double dc_thresh = peak * pow(10.0, dc_db / 20.0); + for (int i = n - 1; i >= 0; i--) { + if (mag[i] >= dc_thresh) { + decision_cutoff = (double)i * sr / a->fft_size; + break; + } + } + if (decision_cutoff <= 0) decision_cutoff = cutoff_hz; + } + double cutoff_ratio = decision_cutoff / nyquist; /* Scale detection thresholds by sensitivity (0.0 = least, 0.5 = default, 1.0 = most) */ double bw_factor = 2.0 * (1.0 - sensitivity); @@ -290,10 +334,11 @@ static int detect_cutoff(const Analyzer *a, double threshold_db, int score = 0; - if (cutoff_hz <= 0 || cutoff_ratio >= bypass) { + if (decision_cutoff <= 0 || cutoff_ratio >= bypass) { if (roughness > r1) score = 1; else if (roughness > r2 && band_ratio < b1) score = 1; else if (roughness > r3 && band_ratio < b2) score = 1; + free(mag); return score; } @@ -318,6 +363,7 @@ static int detect_cutoff(const Analyzer *a, double threshold_db, else if (roughness > r3 && band_ratio < b2) score = 1; } + free(mag); return score; } @@ -693,6 +739,22 @@ static void render_visual_spectrum( is_native_lossy ? eff_cutoff_ratio : cutoff_ratio, steepness, roughness, band_ratio, is_native_lossy); + /* Compute noise-aware factor ratio for consistent display with detect_cutoff(). + When the noise floor is above the user's threshold the cutoff is noise- + dominated; use (noise floor + 10 dB) for the factor display instead. */ + double factor_ratio = eff_cutoff_ratio; + if (!is_native_lossy && noise_db > threshold_db) { + double fc_db = noise_db + 10.0; + double fc_thresh = peak * pow(10.0, fc_db / 20.0); + int n_bins = fft_size / 2; + double fc_cut = cutoff_hz; + for (int i = n_bins - 1; i >= 0; i--) { + double m = sqrt(a->power[i] / a->count); + if (m >= fc_thresh) { fc_cut = (double)i * sample_rate / fft_size; break; } + } + if (fc_cut > 0) factor_ratio = fc_cut / nyquist; + } + printf(" " ANSI_BOLD "Decision:" ANSI_RESET " "); if (peak_db < -90.0) { printf(ANSI_RED "SILENT" ANSI_RESET " (no detectable audio content)"); @@ -757,24 +819,25 @@ static void render_visual_spectrum( printf("\n"); } } else { + double fr = factor_ratio; double max_bw_display; - if (cutoff_ratio < 0.50) { + if (fr < 0.50) { max_bw_display = 4000.0; - } else if (cutoff_ratio < 0.70) { + } else if (fr < 0.70) { max_bw_display = 3000.0; - } else if (cutoff_ratio < 0.80) { + } else if (fr < 0.80) { max_bw_display = 2000.0; - } else if (cutoff_ratio < 0.90) { + } else if (fr < 0.90) { max_bw_display = 1200.0; } else { max_bw_display = 500.0; } - int primary_hit = (cutoff_ratio >= 0.99) ? 0 : (steepness < max_bw_display); - int secondary_applies = (cutoff_ratio > 0.80); + int primary_hit = (fr >= 0.99) ? 0 : (steepness < max_bw_display); + int secondary_applies = (fr > 0.80); - if (cutoff_ratio >= 0.99) { - printf(" ① cutoff_ratio=%.3f ≥ 0.99 " ANSI_RED "✗" ANSI_RESET " (full spectrum, no cutoff)", cutoff_ratio); + if (fr >= 0.99) { + printf(" ① cutoff_ratio=%.3f ≥ 0.99 " ANSI_RED "✗" ANSI_RESET " (full spectrum, no cutoff)", fr); printf("\n"); } else { char bw_label[64]; @@ -783,11 +846,11 @@ static void render_visual_spectrum( if (primary_hit) { printf(" ① cutoff_ratio=%.3f, %s " ANSI_GREEN "✓" ANSI_RESET, - cutoff_ratio, bw_label); + fr, bw_label); printf("\n"); } else { printf(" ① cutoff_ratio=%.3f, transition_bw=%.0fHz ≥ %.0fHz threshold " ANSI_RED "✗" ANSI_RESET, - cutoff_ratio, steepness, max_bw_display); + fr, steepness, max_bw_display); printf("\n"); } } @@ -1182,13 +1245,15 @@ static int process_file(const char *filename, const Options *opts) } double effective_cutoff = cutoff_hz; - if (extended_cutoff > effective_cutoff) effective_cutoff = extended_cutoff; + if (extended_cutoff > effective_cutoff && noise_db >= -100.0) + effective_cutoff = extended_cutoff; double cutoff_ratio = effective_cutoff / nyquist; int upscaled = 0; if (is_native_lossy) { double expected_min = 0.90; if (bitrate > 0 && bitrate < 192000) expected_min = 0.75; else if (bitrate > 0 && bitrate < 256000) expected_min = 0.85; + if (noise_db < -80.0) expected_min -= 0.05; if (cutoff_ratio > 0 && cutoff_ratio < expected_min - 0.08) { upscaled = 1; } @@ -1223,6 +1288,8 @@ static int process_file(const char *filename, const Options *opts) printf(ANSI_RESET "\n"); } printf(ANSI_BOLD ANSI_CYAN "Format:" ANSI_RESET " %s\n", fmt_name); + if (bitrate > 0) + printf(ANSI_BOLD ANSI_CYAN "Bitrate:" ANSI_RESET " %lld kbps\n", (long long)(bitrate / 1000)); printf(ANSI_BOLD ANSI_CYAN "Sample rate:" ANSI_RESET " %d Hz\n", sample_rate); printf(ANSI_BOLD ANSI_CYAN "Channels:" ANSI_RESET " %d\n", channels); printf(ANSI_BOLD ANSI_CYAN "Windows:" ANSI_RESET " %d\n", analyzer.count); @@ -1242,7 +1309,8 @@ static int process_file(const char *filename, const Options *opts) } double effective_cutoff = cutoff_hz; - if (extended_cutoff > effective_cutoff) effective_cutoff = extended_cutoff; + if (extended_cutoff > effective_cutoff && noise_db >= -100.0) + effective_cutoff = extended_cutoff; double cutoff_ratio = effective_cutoff / nyquist; int upscaled = 0; @@ -1250,6 +1318,7 @@ static int process_file(const char *filename, const Options *opts) double expected_min = 0.90; if (bitrate > 0 && bitrate < 192000) expected_min = 0.75; else if (bitrate > 0 && bitrate < 256000) expected_min = 0.85; + if (noise_db < -80.0) expected_min -= 0.05; if (cutoff_ratio > 0 && cutoff_ratio < expected_min - 0.08) { upscaled = 1; @@ -1257,16 +1326,22 @@ static int process_file(const char *filename, const Options *opts) if (upscaled) { printf(ANSI_YELLOW "UPSCALED" ANSI_RESET " (re-encoded from a lower-bitrate source)\n"); - printf(ANSI_BOLD ANSI_CYAN "Verdict Info:" ANSI_RESET " cut-off suggests "); - if (cutoff_ratio < 0.70) - printf(ANSI_BOLD "<= 64 kbps" ANSI_RESET " source\n"); - else if (cutoff_ratio < 0.80) - printf(ANSI_BOLD "96-128 kbps" ANSI_RESET " source\n"); - else if (cutoff_ratio < 0.88) - printf(ANSI_BOLD "128-192 kbps" ANSI_RESET " source\n"); } else { printf(ANSI_GREEN "NATIVE" ANSI_RESET " (single encode at this bitrate)\n"); } + printf(ANSI_BOLD ANSI_CYAN "Verdict Info:" ANSI_RESET " cutoff=%.1f%% Nyquist, expected≥%.0f%% for %lld kbps", + 100.0 * cutoff_ratio, 100.0 * expected_min, + bitrate > 0 ? (long long)(bitrate / 1000) : 0); + if (upscaled) { + printf(" → suggests "); + if (cutoff_ratio < 0.70) + printf(ANSI_BOLD "≤ 64 kbps" ANSI_RESET " source"); + else if (cutoff_ratio < 0.80) + printf(ANSI_BOLD "96–128 kbps" ANSI_RESET " source"); + else if (cutoff_ratio < 0.88) + printf(ANSI_BOLD "128–192 kbps" ANSI_RESET " source"); + } + printf("\n"); } else { if (is_transcode) printf(ANSI_YELLOW "TRANSCODE" ANSI_RESET " (lossy -> lossless re-encode detected)\n");