use crate::processing::FrameData;
use crate::visualizations::render::quantize_color;
use crate::visualizations::Visualization;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::Color;
use std::collections::VecDeque;
const MAX_HISTORY: usize = 8192;
pub struct Waveform {
history: VecDeque<f32>,
color: Color,
beat_envelope: f32,
quant_step: u8,
}
impl Default for Waveform {
fn default() -> Self {
Self::new()
}
}
impl Waveform {
pub fn new() -> Self {
Self {
history: VecDeque::with_capacity(MAX_HISTORY),
color: Color::from_u32(0x0000ff88),
beat_envelope: 0.0,
quant_step: 16,
}
}
}
impl Visualization for Waveform {
fn name(&self) -> &str {
"waveform"
}
fn update(&mut self, frame: &FrameData) {
for &s in &frame.waveform {
self.history.push_back(s);
}
while self.history.len() > MAX_HISTORY {
self.history.pop_front();
}
self.beat_envelope = frame.beat.envelope;
}
fn render(&mut self, area: Rect, buf: &mut Buffer) {
if area.width == 0 || area.height == 0 || self.history.is_empty() {
return;
}
let mid_y = area.y + area.height / 2;
let draw_color = quantize_color(
if let Color::Rgb(r, g, b) = self.color {
let brightness = 0.3 + self.beat_envelope * 0.7;
Color::Rgb(
(r as f32 * brightness) as u8,
(g as f32 * brightness) as u8,
(b as f32 * brightness) as u8,
)
} else {
self.color
},
self.quant_step,
);
let history_len = self.history.len();
for x in 0..area.width {
let sample_idx = (x as usize * history_len) / area.width as usize;
let sample = self.history[sample_idx.min(history_len - 1)];
let half_h = area.height as f32 / 2.0;
let y_offset = (-sample * half_h) as i16;
let y = (mid_y as i16 + y_offset)
.clamp(area.y as i16, (area.y + area.height - 1) as i16) as u16;
buf[(area.x + x, y)]
.set_char('\u{2022}') .set_fg(draw_color);
let (y_start, y_end) = if y < mid_y { (y, mid_y) } else { (mid_y, y) };
for fill_y in y_start..=y_end {
if fill_y >= area.y && fill_y < area.y + area.height {
buf[(area.x + x, fill_y)]
.set_char('\u{2502}') .set_fg(draw_color);
}
}
buf[(area.x + x, y)].set_char('\u{2022}').set_fg(draw_color);
}
}
fn set_quantization_step(&mut self, step: u8) {
self.quant_step = step;
}
fn apply_config(&mut self, config: &toml::Value) {
if let Some(color_str) = config.get("color").and_then(|v| v.as_str()) {
if let Some(c) = parse_hex_color(color_str) {
self.color = c;
}
}
}
fn save_config(&self) -> toml::Value {
let mut table = toml::value::Table::new();
if let Color::Rgb(r, g, b) = self.color {
table.insert(
"color".to_string(),
toml::Value::String(format!("#{:02x}{:02x}{:02x}", r, g, b)),
);
}
toml::Value::Table(table)
}
}
fn parse_hex_color(s: &str) -> Option<Color> {
let hex = s.trim_start_matches('#');
if hex.len() != 6 {
return None;
}
let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
Some(Color::Rgb(r, g, b))
}