use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::{Color, Style};
use crate::audio::BinMapper;
const BAR_WIDTH: u16 = 2;
const BAR_GAP: u16 = 1;
const BAR_BLOCKS: [&str; 8] = [
" ", "\u{2581}", "\u{2582}", "\u{2583}", "\u{2584}", "\u{2585}", "\u{2586}", "\u{2587}",
];
const FULL_BLOCK: &str = "\u{2588}";
pub fn parse_color(name: &str) -> Color {
match name.to_lowercase().as_str() {
"black" => Color::Black,
"red" => Color::Red,
"green" => Color::Green,
"yellow" => Color::Yellow,
"blue" => Color::Blue,
"magenta" => Color::Magenta,
"cyan" => Color::Cyan,
"white" => Color::White,
"darkgray" | "dark_gray" => Color::DarkGray,
"lightred" | "light_red" => Color::LightRed,
"lightgreen" | "light_green" => Color::LightGreen,
"lightyellow" | "light_yellow" => Color::LightYellow,
"lightblue" | "light_blue" => Color::LightBlue,
"lightmagenta" | "light_magenta" => Color::LightMagenta,
"lightcyan" | "light_cyan" => Color::LightCyan,
_ => Color::Magenta,
}
}
fn calc_num_bars(inner_w: u16) -> usize {
let bar_step = BAR_WIDTH + BAR_GAP;
if inner_w >= BAR_WIDTH {
((inner_w + BAR_GAP) / bar_step) as usize
} else {
1
}
}
pub struct Visualizer {
pub bin_mapper: BinMapper,
pub bar_values: Vec<f32>,
pub bar_color: Color,
}
impl Visualizer {
pub fn new(bar_color_name: &str) -> Self {
Self {
bin_mapper: BinMapper::new(1),
bar_values: vec![0.0f32; 1],
bar_color: parse_color(bar_color_name),
}
}
fn ensure_bars(&mut self, num_bars: usize) {
if self.bin_mapper.num_bars() != num_bars {
self.bin_mapper = BinMapper::new(num_bars);
self.bar_values.resize(num_bars, 0.0);
}
}
pub fn render(&mut self, buf: &mut Buffer, area: Rect, spectrum: &[f32]) {
let num_bars = calc_num_bars(area.width);
self.ensure_bars(num_bars);
self.bin_mapper.bin_into(spectrum, &mut self.bar_values);
let height = area.height;
let bar_step = BAR_WIDTH + BAR_GAP;
let style = Style::default().fg(self.bar_color);
for (i, &val) in self.bar_values.iter().enumerate() {
let x_start = area.x + (i as u16) * bar_step;
if x_start + BAR_WIDTH > area.x + area.width {
break;
}
let h = (val * height as f32 * 8.0) as u16;
let full_rows = (h / 8).min(height);
let frac = (h % 8) as usize;
for row in 0..full_rows {
let y = area.y + height - 1 - row;
for dx in 0..BAR_WIDTH {
if let Some(cell) = buf.cell_mut((x_start + dx, y)) {
cell.set_symbol(FULL_BLOCK).set_style(style);
}
}
}
if frac > 0 && full_rows < height {
let y = area.y + height - 1 - full_rows;
for dx in 0..BAR_WIDTH {
if let Some(cell) = buf.cell_mut((x_start + dx, y)) {
cell.set_symbol(BAR_BLOCKS[frac]).set_style(style);
}
}
}
}
}
}