use cursive_core::{
Printer, Vec2, View,
theme::{BaseColor, Color, ColorStyle}
};
use rand::{
Rng, thread_rng,
prelude::{
SliceRandom as _,
IteratorRandom as _
},
rngs::ThreadRng
};
use audiovis::RawVisualizer;
use mlounge_core::library::Song;
pub struct MatrixVisualizer {
rand: ThreadRng,
wave: RawVisualizer,
latest_data: Vec<f32>,
art_path: String,
colors: Vec<Color>,
width: usize,
height: usize,
droplets: Vec<Droplet>
}
impl MatrixVisualizer {
pub fn new() -> MatrixVisualizer {
MatrixVisualizer {
rand: thread_rng(),
wave: RawVisualizer::new(),
latest_data: vec![],
art_path: String::new(),
colors: vec![Color::Rgb(90, 174, 222)],
width: 0,
height: 0,
droplets: vec![]
}
}
fn add_droplet(&mut self) {
if self.width == 0 || self.height == 0 {
return;
}
let x = self.rand.gen_range(0..self.width);
let length = self.rand.gen_range(20..self.height * 2);
let color = self.colors.choose(&mut self.rand).unwrap();
self.droplets.push(Droplet::new(x, length, *color, &mut self.rand));
}
pub fn update(&mut self, song: &Song) {
let mut latest_rev = self.wave.get_wave_data(self.width / 2);
let mut latest = latest_rev.clone();
latest.reverse();
latest.append(&mut latest_rev);
latest.truncate(self.width);
self.latest_data = latest;
self.droplets.retain(|droplet| droplet.tail_y <= self.height as isize);
for droplet in &mut self.droplets {
droplet.flicker = false;
droplet.fall(&mut self.rand);
}
if let Some(path) = song.get_art_path() {
if self.art_path != path {
self.colors.clear();
self.art_path = path;
self.colors = song.cursive_album_colors();
}
}
if self.droplets.len() <= self.width / 3 && !self.art_path.is_empty() {
self.add_droplet();
}
if self.latest_data.len() < 4 { return; }
for droplet in &mut self.droplets {
if let Some(freq) = self.latest_data.get(droplet.x) {
droplet.flicker = *freq >= 0.25;
}
}
}
}
impl View for MatrixVisualizer {
fn draw(&self, printer: &Printer) {
for droplet in &self.droplets {
droplet.print(printer);
}
}
fn required_size(&mut self, constraint: Vec2) -> Vec2 {
self.width = constraint.x;
self.height = constraint.y;
constraint
}
}
impl Default for MatrixVisualizer {
fn default() -> Self { Self::new() }
}
struct Droplet {
x: usize,
y: usize,
tail_y: isize,
chars: String,
color: Color,
flicker: bool
}
impl Droplet {
fn print(&self, printer: &Printer) {
for (i, c) in self.chars.chars().enumerate(){
let style = ColorStyle::new(
if self.flicker { Color::Light(BaseColor::White) } else { self.color },
Color::TerminalDefault
);
if i > 0 {
printer.with_color(style, |printer| {
if self.y as isize - i as isize >= 0 {
printer.print((self.x, self.y - i), &c.to_string());
}
});
}
else {
printer.print((self.x, self.y + i), &c.to_string());
}
}
}
fn new(x: usize, length: usize, color: Color, rand: &mut ThreadRng) -> Droplet {
Droplet {
x,
y: 0,
tail_y: -(length as isize),
chars: gen_rand_str(length, rand),
color,
flicker: false
}
}
fn fall(&mut self, rand: &mut ThreadRng) {
self.chars.insert(0, get_rand_char(rand));
self.chars.pop();
self.y += 1;
self.tail_y += 1;
}
}
fn gen_rand_str(length: usize, rand: &mut ThreadRng) -> String {
std::iter::repeat(())
.map(|_| get_rand_char(rand))
.take(length)
.collect()
}
fn get_rand_char(rand: &mut ThreadRng) -> char {
let valid_chars = (33..127).chain(161..255);
char::from_u32(valid_chars.choose(rand).unwrap()).unwrap()
}