Krolyxon
Commit 61cb83e

rmptuisrcscreensvisualizer.rs

104 lines · 3.3 KB · Rust

1//! Screen 6: spectrum visualizer fed by MPD's fifo output.
2
3use std::sync::Arc;
4
5use ratatui::{Frame, layout::Rect, text::Line};
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 title(&self, _ctx: &Ctx) -> Line<'static> {
33 Line::from("Music visualizer")
34 }
35
36 fn draw(&mut self, frame: &mut Frame, area: Rect, ctx: &Ctx) {
37 let theme = ctx.theme();
38 let config = &ctx.config.visualizer;
39 let inner = area;
40 let spectrum = Arc::clone(&self.spectrum.borrow_and_update());
41 if let Some(error) = &spectrum.error {
42 let fifo = config.fifo.display().to_string();
43 ui::draw_placeholder(
44 frame,
45 inner,
46 &[
47 error.as_str(),
48 "",
49 "Add a fifo output to mpd.conf and restart MPD:",
50 "",
51 "audio_output {",
52 " type \"fifo\"",
53 " name \"Visualizer feed\"",
54 &format!(" path \"{fifo}\""),
55 " format \"44100:16:2\"",
56 "}",
57 ],
58 theme,
59 );
60 return;
61 }
62 if inner.width == 0 || inner.height == 0 {
63 return;
64 }
65
66 let stride = config.bar_width + config.bar_gap;
67 let count = usize::from((inner.width + config.bar_gap) / stride);
68 let heights = self.bars.update(
69 &spectrum,
70 count,
71 config.min_frequency,
72 config.max_frequency,
73 config.falloff,
74 );
75 let colors = &theme.visualizer;
76 let rows = inner.height;
77 // Center the bars horizontally.
78 let used = (count as u16 * stride).saturating_sub(config.bar_gap);
79 let left = inner.x + inner.width.saturating_sub(used) / 2;
80 let buffer = frame.buffer_mut();
81 for (i, &height) in heights.iter().enumerate() {
82 let eighths = (height * f32::from(rows) * 8.0).round() as u32;
83 for row in 0..rows {
84 let filled = eighths.saturating_sub(u32::from(row) * 8).min(8) as usize;
85 if filled == 0 {
86 break;
87 }
88 let y = inner.bottom() - 1 - row;
89 let level = usize::from(row) * colors.len() / usize::from(rows);
90 let color = colors[level.min(colors.len() - 1)];
91 for dx in 0..config.bar_width {
92 let x = left + i as u16 * stride + dx;
93 if x < inner.right() {
94 buffer[(x, y)].set_symbol(EIGHTHS[filled]).set_fg(color);
95 }
96 }
97 }
98 }
99 }
100
101 fn handle_action(&mut self, _action: Action, _ctx: &mut Ctx) -> bool {
102 false
103 }
104}