tui_equalizer/
lib.rs

1//! An equalizer widget for [Ratatui] with multiple frequency bands.
2//!
3//! The equalizer is a vertical bar chart where each band represents a frequency range. Each band
4//! can display a value from 0.0 to 1.0, where 1.0 is the maximum value.
5//!
6//! ![Made with VHS](https://vhs.charm.sh/vhs-FiRQkkDAUEnH2BrPbUx5i.gif)
7//!
8//! This demo can be found in the examples folder in the git repo.
9//!
10//! ```shell
11//! cargo run --example demo
12//! ```
13//!
14//! Inspired by [a comment in the ratatui
15//! repo](https://github.com/ratatui/ratatui/issues/1325#issuecomment-2335095486).
16//!
17//! # Example
18//!
19//! ```rust
20//! # use ratatui::{widgets::Widget, layout::Rect, buffer::Buffer};
21//! # let area = Rect::default();
22//! # let mut buf = Buffer::empty(area);
23//! use tui_equalizer::{Band, Equalizer};
24//!
25//! let equalizer = Equalizer {
26//!     bands: vec![
27//!         Band::from(0.5),
28//!         Band::from(0.8),
29//!         Band::from(0.3),
30//!     ],
31//!     brightness: 1.0,
32//! };
33//! equalizer.render(area, &mut buf);
34//! ```
35//!
36//! # License
37//!
38//! Copyright (c) Josh McKinney
39//!
40//! This project is licensed under either of:
41//!
42//! - Apache License, Version 2.0 ([LICENSE-APACHE] or <http://www.apache.org/licenses/LICENSE-2.0>)
43//! - MIT license ([LICENSE-MIT] or <http://opensource.org/licenses/MIT>)
44//!
45//! at your option.
46//!
47//! [LICENSE-APACHE]: ./LICENSE-APACHE
48//! [LICENSE-MIT]: ./LICENSE-MIT
49//!
50//!
51//! [Ratatui]: https://crates.io/crates/ratatui
52
53use std::iter::zip;
54
55use ratatui_core::{
56    buffer::Buffer,
57    layout::{Constraint, Layout, Rect},
58    style::Color,
59    symbols,
60    widgets::Widget,
61};
62
63/// An equalizer widget with multiple frequency bands.
64///
65/// The equalizer is a vertical bar chart where each band represents a frequency range.
66///
67/// # Example
68///
69/// ```
70/// # use ratatui::widgets::Widget;
71/// # let area = ratatui::layout::Rect::default();
72/// # let mut buf = ratatui::buffer::Buffer::empty(area);
73/// use tui_equalizer::{Band, Equalizer};
74///
75/// let equalizer = Equalizer {
76///     bands: vec![
77///         Band::from(0.5),
78///         Band::from(0.8),
79///         Band::from(0.3),
80///     ],
81///     brightness: 1.0,
82/// };
83/// equalizer.render(area, &mut buf);
84/// ```
85#[derive(Debug, Clone)]
86pub struct Equalizer {
87    /// A vector of `Band` structs representing each frequency band.
88    pub bands: Vec<Band>,
89    pub brightness: f64,
90}
91
92/// A struct representing a single frequency band in the equalizer.
93#[derive(Debug, Clone)]
94pub struct Band {
95    /// The normalized value of the band, where the maximum is 1.0.
96    pub value: f64,
97}
98
99impl From<f64> for Band {
100    fn from(value: f64) -> Self {
101        Self { value }
102    }
103}
104
105impl Widget for Equalizer {
106    fn render(self, area: Rect, buf: &mut Buffer) {
107        let areas = Layout::horizontal(vec![Constraint::Length(2); self.bands.len()]).split(area);
108        for (band, area) in zip(self.bands, areas.iter()) {
109            band.render(*area, buf, self.brightness);
110        }
111    }
112}
113
114impl Band {
115    fn render(self, area: Rect, buf: &mut Buffer, brightness: f64) {
116        let value = self.value.clamp(0.0, 1.0);
117        let height = (value * area.height as f64) as u16;
118
119        // Calculate the color gradient step
120        let color_step = 1.0 / area.height as f64;
121
122        // Iterate over each segment and render it with the corresponding color
123        for i in 0..height {
124            // Green to Yellow to Red gradient
125            let v = i as f64 * color_step;
126            let vv = 1.0 - v;
127            let br = brightness.clamp(0.0, 1.0) * 255.0;
128            let r = if v < 0.5 { v * 2.0 * br } else { br } as u8;
129            let g = if v < 0.5 { br } else { vv * 2.0 * br } as u8;
130            let b = 0;
131            let color = Color::Rgb(r, g, b);
132            buf[(area.left(), area.bottom().saturating_sub(i + 1))]
133                .set_fg(color)
134                .set_symbol(symbols::bar::HALF);
135        }
136    }
137}