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::{
56    buffer::Buffer,
57    layout::{Constraint, Layout, Rect},
58    style::Color,
59    widgets::Widget,
60};
61
62/// An equalizer widget with multiple frequency bands.
63///
64/// The equalizer is a vertical bar chart where each band represents a frequency range.
65///
66/// # Example
67///
68/// ```
69/// # use ratatui::widgets::Widget;
70/// # let area = ratatui::layout::Rect::default();
71/// # let mut buf = ratatui::buffer::Buffer::empty(area);
72/// use tui_equalizer::{Band, Equalizer};
73///
74/// let equalizer = Equalizer {
75///     bands: vec![
76///         Band::from(0.5),
77///         Band::from(0.8),
78///         Band::from(0.3),
79///     ],
80///     brightness: 1.0,
81/// };
82/// equalizer.render(area, &mut buf);
83/// ```
84#[derive(Debug, Clone)]
85pub struct Equalizer {
86    /// A vector of `Band` structs representing each frequency band.
87    pub bands: Vec<Band>,
88    pub brightness: f64,
89}
90
91/// A struct representing a single frequency band in the equalizer.
92#[derive(Debug, Clone)]
93pub struct Band {
94    /// The normalized value of the band, where the maximum is 1.0.
95    pub value: f64,
96}
97
98impl From<f64> for Band {
99    fn from(value: f64) -> Self {
100        Self { value }
101    }
102}
103
104impl Widget for Equalizer {
105    fn render(self, area: Rect, buf: &mut Buffer) {
106        let areas = Layout::horizontal(vec![Constraint::Length(2); self.bands.len()]).split(area);
107        for (band, area) in zip(self.bands, areas.iter()) {
108            band.render(*area, buf, self.brightness);
109        }
110    }
111}
112
113impl Band {
114    fn render(self, area: Rect, buf: &mut Buffer, brightness: f64) {
115        let value = self.value.clamp(0.0, 1.0);
116        let height = (value * area.height as f64) as u16;
117
118        // Calculate the color gradient step
119        let color_step = 1.0 / area.height as f64;
120
121        // Iterate over each segment and render it with the corresponding color
122        for i in 0..height {
123            // Green to Yellow to Red gradient
124            let v = i as f64 * color_step;
125            let vv = 1.0 - v;
126            let br = brightness.clamp(0.0, 1.0) * 255.0;
127            let r = if v < 0.5 { v * 2.0 * br } else { br } as u8;
128            let g = if v < 0.5 { br } else { vv * 2.0 * br } as u8;
129            let b = 0;
130            let color = Color::Rgb(r, g, b);
131            buf[(area.left(), area.bottom().saturating_sub(i + 1))]
132                .set_fg(color)
133                .set_symbol(ratatui::symbols::bar::HALF);
134        }
135    }
136}