use crate::notes::{Note, BaseNote, Notes, NoteValue, ProcerNote, ProcerNotes}; use std::collections::VecDeque; use std::io::Write; use std::fmt::{Debug}; use realfft::FftNum; //use clap::{Args,Subcommand}; //use std::io; use crossterm::terminal; use crossterm::style::{Stylize, Color}; use tui::{backend::CrosstermBackend, Terminal}; use serde::{Serialize, Deserialize}; use ansi_colours::rgb_from_ansi256; trait ToRgbVal { fn to_rgb_value(&self) -> (u8, u8, u8); } impl ToRgbVal for Color { fn to_rgb_value(&self) -> (u8, u8, u8) { match self { Color::Rgb {r, g, b} => (*r, *g, *b), // I'll use the tango colours for the colour constants // but the Ansi lookup table for the Ansi versions Color::Reset => (0, 0, 0), Color::Black => (0, 0, 0), Color::DarkRed => (204, 0, 0), Color::DarkGreen => (78, 154, 6), Color::DarkYellow => (196, 160, 0), Color::DarkBlue => (54, 101, 164), Color::DarkMagenta => (117, 80, 123), Color::DarkCyan => (6, 152, 154), Color::Grey => (211, 215, 207), Color::DarkGrey => (85, 87, 83), Color::Red => (239, 41, 41), Color::Green => (138, 226, 52), Color::Yellow => (252, 233, 79), Color::Blue => (114, 159, 207), Color::Magenta => (173, 127, 168), Color::Cyan => (52, 226, 226), Color::White => (238, 238, 236), Color::AnsiValue(ansi) => rgb_from_ansi256(*ansi), } } } #[derive(Clone, Serialize, Deserialize)] pub struct ColourScheme { pub a: Color, pub as_: Color, pub b: Color, pub c: Color, pub cs: Color, pub d: Color, pub ds: Color, pub e: Color, pub f: Color, pub fs: Color, pub g: Color, pub gs: Color, } impl ColourScheme { fn get_colour(&self, note: Notes) -> Color { match note { Notes::C => self.c, Notes::Cs => self.cs, Notes::D => self.d, Notes::Ds => self.ds, Notes::E => self.e, Notes::F => self.f, Notes::Fs => self.fs, Notes::G => self.g, Notes::Gs => self.gs, Notes::A => self.a, Notes::As => self.as_, Notes::B => self.b, } } } impl Default for ColourScheme { fn default() -> Self { let c = Color::Rgb {r: 140, g: 255, b: 210}; return Self { a: c, as_: c, b: c, c: c, cs: c, d: c, ds: c, e: c, f: c, fs: c, g: c, gs: c, } } } pub trait Outputer { fn setup(&mut self); fn handle_pnotes(&mut self, notes: &mut ProcerNotes); } pub enum Outputers { Simple(SimpleOutputer), LineLayout(LineLayout), Tui(TuiOutputer), } pub struct SimpleOutputer; pub struct LineLayout { term_width: usize, notes_per_line: usize, last_line: String, empty: String, last_was_empty: bool, use_colour: bool, //max_c: (f32, f32, f32), colours: ColourScheme, max_threshold: f32, //threshold: f32, } pub struct TuiOutputer { terminal: Terminal>, } impl Outputer for SimpleOutputer { fn setup(&mut self) {} fn handle_pnotes(&mut self, notes: &mut ProcerNotes) { notes.0.sort_unstable_by(|pn1, pn2| (-(pn1.3)).partial_cmp(&-(pn2.3)).unwrap()); print!("\r{:-80}", notes); std::io::stdout().flush().unwrap(); } } pub fn line_note_char(note: &Note) -> char { match note.note.0 { Notes::C => 'c', Notes::Cs => match note.octave % 10 { 0 => '0', 1 => '1', 2 => '2', 3 => '3', 4 => '4', 5 => '5', 6 => '6', 7 => '7', 8 => '8', 9 => '9', _ => ' ', }, Notes::D => 'd', Notes::Ds => ' ', Notes::E => 'e', Notes::F => 'f', Notes::Fs => ' ', Notes::G => 'g', Notes::Gs => ' ', Notes::A => 'a', Notes::As => ' ', Notes::B => 'b' } } impl LineLayout { pub fn new(max_threshold: f32, use_colour: bool, colours: ColourScheme, notes: &[Note]) -> Self { let term_width = terminal::size().unwrap().0 as usize; let note_count = std::cmp::min(notes.len(), term_width); return Self { term_width, use_colour, colours, max_threshold, notes_per_line: note_count, last_line: notes[0..note_count].iter().map(line_note_char).collect(), empty: " ".repeat(term_width), last_was_empty: true, } } pub fn line_bw(&self, note_count: usize, notes: &ProcerNotes) { print!("\r"); let itx: String = notes.0[0..note_count].iter().map(|note| { if note.3 >= notes.1 { '▉' } else { ' ' } }).collect(); print!("{}\n{}", itx, self.last_line); std::io::stdout().flush().unwrap(); } pub fn line_colour(&self, note_count: usize, notes: &ProcerNotes) { print!("\r"); // TODO: do grouping to avoid excessive control characters // for now this just does the simplest thing though for note in ¬es.0[0..note_count] { if note.3 >= notes.1 { let mut bright: f32 = note.3.to_f32().unwrap() / self.max_threshold; if bright > 1.0 { bright = 1.0; } let (r, g, b) = self.colours.get_colour(note.0.note.0).to_rgb_value(); print!("{}", ' '.on(Color::Rgb { r: (r as f32 * bright) as u8, g: (g as f32 * bright) as u8, b: (b as f32 * bright) as u8, })); } else { print!(" "); } } print!("\n{}", self.last_line); std::io::stdout().flush().unwrap(); } } impl Outputer for LineLayout { fn setup(&mut self) {} fn handle_pnotes(&mut self, notes: &mut ProcerNotes) { let notes_len = notes.0.len(); let note_count = std::cmp::min(notes_len, self.notes_per_line); if notes.0[0..note_count].iter().all(|note| note.3 < notes.1) { if self.last_was_empty { return } else { self.last_was_empty = true; } } else { self.last_was_empty = false; } match self.use_colour { true => self.line_colour(note_count, notes), false => self.line_bw(note_count, notes) } } } impl TuiOutputer { pub fn new() -> Result { let stdout = std::io::stdout(); let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend)?; return Ok(Self { terminal, }) } } impl Outputer for TuiOutputer { fn setup(&mut self) {} fn handle_pnotes(&mut self, notes: &mut ProcerNotes) { //self.terminal.draw(|frame| {}) } } impl Outputer for Outputers { fn setup(&mut self) { match self { Outputers::Simple(so) => >::setup(so), Outputers::LineLayout(llo) => >::setup(llo), Outputers::Tui(to) => >::setup(to) } } fn handle_pnotes(&mut self, notes: &mut ProcerNotes) { match self { Outputers::Simple(so) => so.handle_pnotes(notes), Outputers::LineLayout(llo) => llo.handle_pnotes(notes), Outputers::Tui(to) => to.handle_pnotes(notes), } } }