From 0b9970b550b114c07d80b41617634f2560a6a77d Mon Sep 17 00:00:00 2001 From: Test User Date: Wed, 22 Jul 2026 00:00:58 +0200 Subject: [PATCH] Make the spectrum bars actually visible Three fixes so the spectrum shows for real audio: the analyzer now takes the peak magnitude per band with single-sided (2/N) scaling instead of the mean (averaging diluted a strong component into the quiet bins around it, leaving near-zero bars that render as blank spaces); the now-playing rows use a hard Length(1) so the spectrum row cannot be squeezed out by the info block; and the server logs when it starts/stops streaming bars so the live path is diagnosable. Co-Authored-By: Claude Opus 4.8 (1M context) --- cbd-tui/src/app/now_playing.rs | 10 ++++++---- crabidy-server/src/lib.rs | 4 ++++ crabidy-server/src/spectrum.rs | 14 +++++++++++--- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/cbd-tui/src/app/now_playing.rs b/cbd-tui/src/app/now_playing.rs index 1bc2615..d02cb62 100644 --- a/cbd-tui/src/app/now_playing.rs +++ b/cbd-tui/src/app/now_playing.rs @@ -100,14 +100,16 @@ impl NowPlaying { } pub fn render(&self, f: &mut Frame, area: Rect) { - // Info block, progress row, then (when enabled) a spectrum row. + // Info block fills, then a fixed progress row and (when enabled) + // a fixed spectrum row. Length(1) is hard-fixed so the rows are + // never squeezed out by the block above them. let spectrum_row = if self.spectrum_enabled { 1 } else { 0 }; let now_playing_layout = Layout::default() .direction(Direction::Vertical) .constraints([ - Constraint::Min(6), - Constraint::Max(1), - Constraint::Max(spectrum_row), + Constraint::Min(3), + Constraint::Length(1), + Constraint::Length(spectrum_row), ]) .split(area); diff --git a/crabidy-server/src/lib.rs b/crabidy-server/src/lib.rs index 5fee402..5e7656c 100644 --- a/crabidy-server/src/lib.rs +++ b/crabidy-server/src/lib.rs @@ -172,11 +172,15 @@ fn spawn_spectrum_task( let count = tap.frame_count(); if count != last_count { last_count = count; + if !was_active { + debug!("spectrum: audio flowing, streaming bars"); + } was_active = true; let bins = analyzer.analyze(&tap.snapshot()); let _ = update_tx.send(Update::Spectrum(SpectrumFrame { bins })); } else if was_active { // Playback just went idle: drop the bars to the floor once. + debug!("spectrum: audio idle, bars to zero"); was_active = false; let _ = update_tx.send(Update::Spectrum(SpectrumFrame { bins: vec![0.0; spectrum::SPECTRUM_BINS], diff --git a/crabidy-server/src/spectrum.rs b/crabidy-server/src/spectrum.rs index 3263cb1..36f5bbf 100644 --- a/crabidy-server/src/spectrum.rs +++ b/crabidy-server/src/spectrum.rs @@ -60,14 +60,22 @@ impl SpectrumAnalyzer { return vec![0.0; SPECTRUM_BINS]; } let n = self.window.len() as f32; - let magnitude = |c: &Complex| c.norm() / n; + // Single-sided amplitude (2/N): a full-scale tone reads near 1.0 + // at its bin, so the bars fill for ordinary listening levels. + let magnitude = |c: &Complex| c.norm() * 2.0 / n; (0..SPECTRUM_BINS) .map(|bar| { let lo = self.edges[bar]; let hi = self.edges[bar + 1].max(lo + 1).min(self.output.len()); - let avg = self.output[lo..hi].iter().map(magnitude).sum::() / (hi - lo) as f32; + // Peak within the band, not the mean: a strong component + // must light its bar instead of being diluted by the + // quiet bins around it (which averaging does). + let peak = self.output[lo..hi] + .iter() + .map(magnitude) + .fold(0.0_f32, f32::max); // Log-compress: dBFS mapped onto [0, 1]. - let db = 20.0 * (avg + 1e-9).log10(); + let db = 20.0 * (peak + 1e-9).log10(); ((db - MIN_DB) / -MIN_DB).clamp(0.0, 1.0) }) .collect()