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) <noreply@anthropic.com>
This commit is contained in:
parent
572be04206
commit
0b9970b550
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -60,14 +60,22 @@ impl SpectrumAnalyzer {
|
|||
return vec![0.0; SPECTRUM_BINS];
|
||||
}
|
||||
let n = self.window.len() as f32;
|
||||
let magnitude = |c: &Complex<f32>| 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<f32>| 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::<f32>() / (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()
|
||||
|
|
|
|||
Loading…
Reference in New Issue