From cdd7cb192d5abdabe567ef4bc0eaa6b7957b2e72 Mon Sep 17 00:00:00 2001 From: Maxim Isaev Date: Sun, 10 May 2026 01:43:00 +0300 Subject: [PATCH] fix(analysis): map waveform bins to decoded length, not inflated n_frames Container-reported frame counts can exceed decoded samples on some VBR or badly tagged files; using max() squashed energy into the leading bins. --- .../src/analysis_cache/compute.rs | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src-tauri/crates/psysonic-analysis/src/analysis_cache/compute.rs b/src-tauri/crates/psysonic-analysis/src/analysis_cache/compute.rs index 7cb12b62..47890be7 100644 --- a/src-tauri/crates/psysonic-analysis/src/analysis_cache/compute.rs +++ b/src-tauri/crates/psysonic-analysis/src/analysis_cache/compute.rs @@ -334,13 +334,21 @@ fn decode_scan_pcm( let mut fed_any_frames = false; let mut sample_idx: u64 = 0; let mut loop_i: u32 = 0; - // Fixed timeline from metadata when available; otherwise fall back to decoded - // length (full-buffer analysis only — partial byte windows still shift, but - // then we usually lack n_frames anyway). - let bin_grid_frames = timeline_hint - .map(|n| n.max(decoded_frames)) - .unwrap_or(decoded_frames) - .max(1); + // Bin mapping must use the decoded mono sample count. When the container + // reports `n_frames` **larger** than what we actually decoded (bad VBR tags, + // wrong duration in headers) but the buffer is already the full file — all + // CPU-seed paths pass a complete artifact — using `max(n_frames, decoded)` + // squashes the entire waveform into the leading bins ("only the start"). + if let Some(n) = timeline_hint { + if n > decoded_frames { + crate::app_deprintln!( + "[analysis][waveform] bin_grid: ignore container n_frames={} (> decoded {}) — map bins to decoded length", + n, + decoded_frames + ); + } + } + let bin_grid_frames = decoded_frames.max(1); loop { let packet = match format.next_packet() {