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) {
|
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 spectrum_row = if self.spectrum_enabled { 1 } else { 0 };
|
||||||
let now_playing_layout = Layout::default()
|
let now_playing_layout = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints([
|
.constraints([
|
||||||
Constraint::Min(6),
|
Constraint::Min(3),
|
||||||
Constraint::Max(1),
|
Constraint::Length(1),
|
||||||
Constraint::Max(spectrum_row),
|
Constraint::Length(spectrum_row),
|
||||||
])
|
])
|
||||||
.split(area);
|
.split(area);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -172,11 +172,15 @@ fn spawn_spectrum_task(
|
||||||
let count = tap.frame_count();
|
let count = tap.frame_count();
|
||||||
if count != last_count {
|
if count != last_count {
|
||||||
last_count = count;
|
last_count = count;
|
||||||
|
if !was_active {
|
||||||
|
debug!("spectrum: audio flowing, streaming bars");
|
||||||
|
}
|
||||||
was_active = true;
|
was_active = true;
|
||||||
let bins = analyzer.analyze(&tap.snapshot());
|
let bins = analyzer.analyze(&tap.snapshot());
|
||||||
let _ = update_tx.send(Update::Spectrum(SpectrumFrame { bins }));
|
let _ = update_tx.send(Update::Spectrum(SpectrumFrame { bins }));
|
||||||
} else if was_active {
|
} else if was_active {
|
||||||
// Playback just went idle: drop the bars to the floor once.
|
// Playback just went idle: drop the bars to the floor once.
|
||||||
|
debug!("spectrum: audio idle, bars to zero");
|
||||||
was_active = false;
|
was_active = false;
|
||||||
let _ = update_tx.send(Update::Spectrum(SpectrumFrame {
|
let _ = update_tx.send(Update::Spectrum(SpectrumFrame {
|
||||||
bins: vec![0.0; spectrum::SPECTRUM_BINS],
|
bins: vec![0.0; spectrum::SPECTRUM_BINS],
|
||||||
|
|
|
||||||
|
|
@ -60,14 +60,22 @@ impl SpectrumAnalyzer {
|
||||||
return vec![0.0; SPECTRUM_BINS];
|
return vec![0.0; SPECTRUM_BINS];
|
||||||
}
|
}
|
||||||
let n = self.window.len() as f32;
|
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)
|
(0..SPECTRUM_BINS)
|
||||||
.map(|bar| {
|
.map(|bar| {
|
||||||
let lo = self.edges[bar];
|
let lo = self.edges[bar];
|
||||||
let hi = self.edges[bar + 1].max(lo + 1).min(self.output.len());
|
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].
|
// 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)
|
((db - MIN_DB) / -MIN_DB).clamp(0.0, 1.0)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue