Krolyxon
Commit cf9c69a

rmptuisrcscreensvisualizer.rs

107 lines · 3.4 KB · Rust

1//! Screen 6: spectrum visualizer fed by MPD's fifo output.
2
3use std::sync::Arc;
4
5use ratatui::{Frame, layout::Rect};
6use tokio::sync::watch;
7
8use super::{Ctx, Screen};
9use crate::{
10 action::Action,
11 ui,
12 visualizer::{Bars, Spectrum},
13};
14
15const EIGHTHS: [&str; 9] = [" ", "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
16
17pub struct VisualizerScreen {
18 spectrum: watch::Receiver<Arc<Spectrum>>,
19 bars: Bars,
20}
21
22impl VisualizerScreen {
23 pub fn new(spectrum: watch::Receiver<Arc<Spectrum>>) -> Self {
24 Self {
25 spectrum,
26 bars: Bars::default(),
27 }
28 }
29}
30
31impl Screen for VisualizerScreen {
32 fn draw(&mut self, frame: &mut Frame, area: Rect, ctx: &Ctx) {
33 let theme = ctx.theme();
34 let config = &ctx.config.visualizer;
35 let title = match &ctx.mpd.current {
36 Some(song) => format!(" Visualizer · {} ", ui::song_label(song)),
37 None => " Visualizer ".to_owned(),
38 };
39 let block = ui::pane(title, true, theme);
40 let inner = block.inner(area);
41 frame.render_widget(block, area);
42
43 let spectrum = Arc::clone(&self.spectrum.borrow_and_update());
44 if let Some(error) = &spectrum.error {
45 let fifo = config.fifo.display().to_string();
46 ui::draw_placeholder(
47 frame,
48 inner,
49 &[
50 error.as_str(),
51 "",
52 "Add a fifo output to mpd.conf and restart MPD:",
53 "",
54 "audio_output {",
55 " type \"fifo\"",
56 " name \"Visualizer feed\"",
57 &format!(" path \"{fifo}\""),
58 " format \"44100:16:2\"",
59 "}",
60 ],
61 theme,
62 );
63 return;
64 }
65 if inner.width == 0 || inner.height == 0 {
66 return;
67 }
68
69 let stride = config.bar_width + config.bar_gap;
70 let count = usize::from((inner.width + config.bar_gap) / stride);
71 let heights = self.bars.update(
72 &spectrum,
73 count,
74 config.min_frequency,
75 config.max_frequency,
76 config.falloff,
77 );
78 let colors = &theme.visualizer;
79 let rows = inner.height;
80 // Center the bars horizontally.
81 let used = (count as u16 * stride).saturating_sub(config.bar_gap);
82 let left = inner.x + inner.width.saturating_sub(used) / 2;
83 let buffer = frame.buffer_mut();
84 for (i, &height) in heights.iter().enumerate() {
85 let eighths = (height * f32::from(rows) * 8.0).round() as u32;
86 for row in 0..rows {
87 let filled = eighths.saturating_sub(u32::from(row) * 8).min(8) as usize;
88 if filled == 0 {
89 break;
90 }
91 let y = inner.bottom() - 1 - row;
92 let level = usize::from(row) * colors.len() / usize::from(rows);
93 let color = colors[level.min(colors.len() - 1)];
94 for dx in 0..config.bar_width {
95 let x = left + i as u16 * stride + dx;
96 if x < inner.right() {
97 buffer[(x, y)].set_symbol(EIGHTHS[filled]).set_fg(color);
98 }
99 }
100 }
101 }
102 }
103
104 fn handle_action(&mut self, _action: Action, _ctx: &mut Ctx) -> bool {
105 false
106 }
107}