use crate::processing::FrameData;
use crate::visualizations::render::{HalfBlockCanvas, SIN_LUT};
use crate::visualizations::Visualization;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::Color;
use std::f32::consts::PI;
pub struct Plasma {
time: f32,
k1: f32,
k2: f32,
k3: f32,
k4: f32,
rms: f32,
hue_offset: f32,
beat_envelope: f32,
canvas: HalfBlockCanvas,
}
impl Default for Plasma {
fn default() -> Self {
Self::new()
}
}
impl Plasma {
pub fn new() -> Self {
Self {
time: 0.0,
k1: 8.0,
k2: 12.0,
k3: 10.0,
k4: 14.0,
rms: 0.0,
hue_offset: 0.0,
beat_envelope: 0.0,
canvas: HalfBlockCanvas::new(0, 0),
}
}
}
impl Visualization for Plasma {
fn name(&self) -> &str {
"plasma"
}
fn update(&mut self, frame: &FrameData) {
self.rms = frame.rms;
self.beat_envelope = frame.beat.envelope;
let band_count = frame.spectrum.len();
if band_count >= 4 {
let quarter = band_count / 4;
let bass = frame.spectrum[..quarter].iter().sum::<f32>() / quarter as f32;
let low_mid = frame.spectrum[quarter..quarter * 2].iter().sum::<f32>() / quarter as f32;
let high_mid =
frame.spectrum[quarter * 2..quarter * 3].iter().sum::<f32>() / quarter as f32;
let treble = frame.spectrum[quarter * 3..].iter().sum::<f32>() / quarter as f32;
self.k1 = 6.0 + bass * 12.0;
self.k2 = 8.0 + low_mid * 10.0;
self.k3 = 7.0 + high_mid * 14.0;
self.k4 = 10.0 + treble * 8.0;
}
let beat_boost = 1.0 + self.beat_envelope * 0.8;
self.k1 *= beat_boost;
self.k2 *= beat_boost;
self.k3 *= beat_boost;
self.k4 *= beat_boost;
self.hue_offset += 0.02 + frame.peak * 0.05 + self.beat_envelope * 0.08;
self.time += 0.03 + self.rms * 0.05 + self.beat_envelope * 0.06;
}
fn set_quantization_step(&mut self, step: u8) {
self.canvas.set_step(step);
}
fn heavy_rendering(&self) -> bool {
true
}
fn render(&mut self, area: Rect, buf: &mut Buffer) {
if area.width == 0 || area.height == 0 {
return;
}
self.canvas.resize_or_clear(area.width, area.height);
let pw = self.canvas.pixel_width();
let ph = self.canvas.pixel_height();
for py in 0..ph {
let y = py as f32 / ph as f32;
for px in 0..pw {
let x = px as f32 / pw as f32;
let v1 = SIN_LUT.get(x * self.k1 + self.time);
let v2 = SIN_LUT.get(y * self.k2 + self.time * 1.3);
let v3 = SIN_LUT.get((x + y) * self.k3 + self.time * 0.7);
let dist = ((x - 0.5).powi(2) + (y - 0.5).powi(2)).sqrt();
let v4 = SIN_LUT.get(dist * self.k4 + self.time * 1.1);
let v = (v1 + v2 + v3 + v4) / 4.0; let t = (v + 1.0) / 2.0;
let color = plasma_color(t, self.hue_offset);
self.canvas.set(px, py, color);
}
}
self.canvas.render(&area, buf);
}
}
fn plasma_color(t: f32, hue_offset: f32) -> Color {
let hue = (t + hue_offset) % 1.0;
let r = (SIN_LUT.get(hue * 2.0 * PI) * 0.5 + 0.5) * 255.0;
let g = (SIN_LUT.get(hue * 2.0 * PI + 2.094) * 0.5 + 0.5) * 255.0; let b = (SIN_LUT.get(hue * 2.0 * PI + 4.189) * 0.5 + 0.5) * 255.0; Color::Rgb(r as u8, g as u8, b as u8)
}