use super::util;
use crate::app::App;
use ratatui::{
layout::{Constraint, Direction, Layout},
style::{Color, Style},
text::{Line, Span},
widgets::{Bar, BarChart, BarGroup, Block, Borders, Paragraph},
Frame,
};
const BAND_LABELS: [&str; 12] = [
"Sub", "Bass", "Low", "LMid", "Mid", "UMid", "High", "HiMd", "Pres", "Bril", "Air", "Ultra",
];
pub fn draw(f: &mut Frame<'_>, app: &App) {
let margin = util::get_main_layout_margin(app);
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(3), Constraint::Min(10)].as_ref())
.margin(margin)
.split(f.size());
let white = Style::default().fg(app.user_config.theme.text);
let gray = Style::default().fg(app.user_config.theme.inactive);
let tick_rate = app.user_config.behavior.tick_rate_milliseconds;
let info_block = Block::default()
.title(Span::styled(
"Audio Visualization",
Style::default().fg(app.user_config.theme.inactive),
))
.borders(Borders::ALL)
.border_style(Style::default().fg(app.user_config.theme.inactive));
let bar_chart_title = &format!("Spectrum | {} FPS | Press q to exit", 1000 / tick_rate);
let bar_chart_block = Block::default()
.borders(Borders::ALL)
.style(white)
.title(Span::styled(bar_chart_title, gray))
.border_style(gray);
let width = (chunks[1].width as f32 / (1 + BAND_LABELS.len()) as f32).max(3.0);
if let Some(ref spectrum) = app.spectrum_data {
let status_text = if app.audio_capture_active {
"[>] Capturing audio"
} else {
"[||] Paused"
};
let peak_text = format!("Peak: {:.0}%", spectrum.peak * 100.0);
let texts = vec![Line::from(vec![
Span::styled(status_text, Style::default().fg(app.user_config.theme.text)),
Span::raw(" "),
Span::styled(
peak_text,
Style::default().fg(app.user_config.theme.inactive),
),
])];
let p = Paragraph::new(texts)
.block(info_block)
.style(Style::default().fg(app.user_config.theme.text));
f.render_widget(p, chunks[0]);
let bars: Vec<Bar> = spectrum
.bands
.iter()
.enumerate()
.map(|(index, &value)| {
let label = BAND_LABELS.get(index).unwrap_or(&"?");
let bar_value = ((value * 1000.0) as u64).min(800);
let color = if value < 0.25 {
Color::Rgb(0, 200, 0) } else if value < 0.5 {
Color::Rgb(180, 200, 0) } else if value < 0.65 {
Color::Rgb(255, 200, 0) } else if value < 0.75 {
Color::Rgb(255, 140, 0) } else {
Color::Rgb(255, 50, 0) };
Bar::default()
.value(bar_value)
.label(Line::from(*label))
.style(Style::default().fg(color))
.value_style(Style::default().fg(Color::White).bg(color))
})
.collect();
let spectrum_bar = BarChart::default()
.block(bar_chart_block)
.data(BarGroup::default().bars(&bars))
.bar_width(width as u16)
.max(1000); f.render_widget(spectrum_bar, chunks[1]);
} else {
let no_capture_text = vec![
Line::from("No audio capture available"),
Line::from(""),
#[cfg(target_os = "linux")]
Line::from("Hint: Ensure PipeWire or PulseAudio is running with a monitor device"),
#[cfg(target_os = "windows")]
Line::from("Hint: Audio loopback should work automatically on Windows"),
#[cfg(target_os = "macos")]
Line::from("Hint: macOS requires a virtual audio device like BlackHole"),
#[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
Line::from("Hint: Audio capture may not be supported on this platform"),
];
let p = Paragraph::new(no_capture_text)
.block(info_block)
.style(Style::default().fg(app.user_config.theme.text));
f.render_widget(p, chunks[0]);
let empty_p = Paragraph::new("Waiting for audio input...")
.block(bar_chart_block)
.style(Style::default().fg(app.user_config.theme.text));
f.render_widget(empty_p, chunks[1]);
}
}