From f5fbc11b2471d2a2304b22ae94cf658fb075bd00 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 15 Sep 2024 17:26:54 +0300 Subject: [PATCH] update all to use TuiOutput; still slow? --- crates/tek_core/src/tui.rs | 12 +- crates/tek_mixer/src/lib.rs | 11 - crates/tek_mixer/src/mixer.rs | 56 +++ crates/tek_mixer/src/mixer_cli.rs | 26 -- crates/tek_mixer/src/mixer_handle.rs | 57 --- crates/tek_mixer/src/mixer_main.rs | 25 ++ crates/tek_mixer/src/plugin.rs | 117 +++++++ crates/tek_mixer/src/plugin_handle.rs | 65 ---- crates/tek_mixer/src/plugin_view.rs | 54 --- crates/tek_mixer/src/sample.rs | 79 ----- crates/tek_mixer/src/sample_add.rs | 294 ---------------- crates/tek_mixer/src/sampler.rs | 468 +++++++++++++++++++++++++ crates/tek_mixer/src/sampler_edn.rs | 7 - crates/tek_mixer/src/sampler_handle.rs | 44 --- crates/tek_mixer/src/sampler_view.rs | 55 --- crates/tek_mixer/src/track.rs | 102 ++++++ crates/tek_mixer/src/track_handle.rs | 38 -- crates/tek_mixer/src/track_view.rs | 67 ---- crates/tek_sequencer/src/arranger.rs | 55 +-- crates/tek_sequencer/src/sequencer.rs | 93 ++--- crates/tek_sequencer/src/transport.rs | 36 +- 21 files changed, 873 insertions(+), 888 deletions(-) delete mode 100644 crates/tek_mixer/src/mixer_handle.rs delete mode 100644 crates/tek_mixer/src/plugin_handle.rs delete mode 100644 crates/tek_mixer/src/plugin_view.rs delete mode 100644 crates/tek_mixer/src/sample.rs delete mode 100644 crates/tek_mixer/src/sample_add.rs delete mode 100644 crates/tek_mixer/src/sampler_edn.rs delete mode 100644 crates/tek_mixer/src/sampler_handle.rs delete mode 100644 crates/tek_mixer/src/sampler_view.rs delete mode 100644 crates/tek_mixer/src/track_handle.rs delete mode 100644 crates/tek_mixer/src/track_view.rs diff --git a/crates/tek_core/src/tui.rs b/crates/tek_core/src/tui.rs index 524a5da5..6184794f 100644 --- a/crates/tek_core/src/tui.rs +++ b/crates/tek_core/src/tui.rs @@ -23,8 +23,8 @@ pub struct TuiInput { exited: Arc, } pub struct TuiOutput { - buffer: Buffer, - area: [u16;4], + pub buffer: Buffer, + pub area: [u16;4], } impl Engine for Tui { @@ -227,6 +227,7 @@ impl Tui { let updates = self.buffer.diff(&buffer); self.backend.draw(updates.into_iter()).expect("failed to render"); std::mem::swap(&mut self.buffer, &mut buffer); + buffer.reset(); buffer } } @@ -319,9 +320,10 @@ impl Widget for Styled<&str> { Ok(Some([self.1.len() as u16, 1])) } fn render (&self, to: &mut TuiOutput) -> Usually<()> { - todo!() - //let _ = self.layout(to.area().wh())?.unwrap(); - //Ok(Some([to.area.x(), to.area.y(), self.1.len() as u16, 1])) + // FIXME + let [x, y, ..] = to.area(); + let [w, h] = self.layout(to.area().wh())?.unwrap(); + Ok(to.blit(&self.1, x, y, None)) } } diff --git a/crates/tek_mixer/src/lib.rs b/crates/tek_mixer/src/lib.rs index 411a172d..107263d8 100644 --- a/crates/tek_mixer/src/lib.rs +++ b/crates/tek_mixer/src/lib.rs @@ -11,23 +11,12 @@ pub(crate) use std::fs::read_dir; submod! { mixer - mixer_cli - mixer_handle plugin - plugin_handle plugin_lv2 plugin_lv2_gui - plugin_view plugin_vst2 plugin_vst3 - sample - sample_add sampler - sampler_edn - sampler_handle - sampler_view track - track_handle - track_view voice } diff --git a/crates/tek_mixer/src/mixer.rs b/crates/tek_mixer/src/mixer.rs index ec0f6d30..1642c57b 100644 --- a/crates/tek_mixer/src/mixer.rs +++ b/crates/tek_mixer/src/mixer.rs @@ -41,3 +41,59 @@ impl Content for Mixer { }) } } + +impl Handle for Mixer { + fn handle (&mut self, engine: &TuiInput) -> Perhaps { + if let TuiEvent::Input(crossterm::event::Event::Key(event)) = engine.event() { + + match event.code { + //KeyCode::Char('c') => { + //if event.modifiers == KeyModifiers::CONTROL { + //self.exit(); + //} + //}, + KeyCode::Down => { + self.selected_track = (self.selected_track + 1) % self.tracks.len(); + println!("{}", self.selected_track); + return Ok(Some(true)) + }, + KeyCode::Up => { + if self.selected_track == 0 { + self.selected_track = self.tracks.len() - 1; + } else { + self.selected_track -= 1; + } + println!("{}", self.selected_track); + return Ok(Some(true)) + }, + KeyCode::Left => { + if self.selected_column == 0 { + self.selected_column = 6 + } else { + self.selected_column -= 1; + } + return Ok(Some(true)) + }, + KeyCode::Right => { + if self.selected_column == 6 { + self.selected_column = 0 + } else { + self.selected_column += 1; + } + return Ok(Some(true)) + }, + _ => { + println!("\n{event:?}"); + } + } + + } + Ok(None) + } +} + +//pub const ACTIONS: [(&'static str, &'static str);2] = [ + //("+/-", "Adjust"), + //("Ins/Del", "Add/remove track"), +//]; + diff --git a/crates/tek_mixer/src/mixer_cli.rs b/crates/tek_mixer/src/mixer_cli.rs index 46b179b2..e69de29b 100644 --- a/crates/tek_mixer/src/mixer_cli.rs +++ b/crates/tek_mixer/src/mixer_cli.rs @@ -1,26 +0,0 @@ -use tek_core::clap::{self, Parser}; -use crate::*; -#[derive(Debug, Parser)] -#[command(version, about, long_about = None)] -pub struct MixerCli { - /// Name of JACK client - #[arg(short, long)] name: Option, - /// Number of tracks - #[arg(short, long)] channels: Option, -} -impl Mixer { - pub fn from_args () -> Usually { - let args = MixerCli::parse(); - let mut mix = Self::new("")?; - if let Some(name) = args.name { - mix.name = name.clone(); - } - if let Some(channels) = args.channels { - for channel in 0..channels { - mix.track_add(&format!("Track {}", channel + 1), 1)?; - } - } - Ok(mix) - } -} - diff --git a/crates/tek_mixer/src/mixer_handle.rs b/crates/tek_mixer/src/mixer_handle.rs deleted file mode 100644 index 6099a84b..00000000 --- a/crates/tek_mixer/src/mixer_handle.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::*; - -impl Handle for Mixer { - fn handle (&mut self, engine: &TuiInput) -> Perhaps { - if let TuiEvent::Input(crossterm::event::Event::Key(event)) = engine.event() { - - match event.code { - //KeyCode::Char('c') => { - //if event.modifiers == KeyModifiers::CONTROL { - //self.exit(); - //} - //}, - KeyCode::Down => { - self.selected_track = (self.selected_track + 1) % self.tracks.len(); - println!("{}", self.selected_track); - return Ok(Some(true)) - }, - KeyCode::Up => { - if self.selected_track == 0 { - self.selected_track = self.tracks.len() - 1; - } else { - self.selected_track -= 1; - } - println!("{}", self.selected_track); - return Ok(Some(true)) - }, - KeyCode::Left => { - if self.selected_column == 0 { - self.selected_column = 6 - } else { - self.selected_column -= 1; - } - return Ok(Some(true)) - }, - KeyCode::Right => { - if self.selected_column == 6 { - self.selected_column = 0 - } else { - self.selected_column += 1; - } - return Ok(Some(true)) - }, - _ => { - println!("\n{event:?}"); - } - } - - } - Ok(None) - } -} - -//pub const ACTIONS: [(&'static str, &'static str);2] = [ - //("+/-", "Adjust"), - //("Ins/Del", "Add/remove track"), -//]; - diff --git a/crates/tek_mixer/src/mixer_main.rs b/crates/tek_mixer/src/mixer_main.rs index 3d6ae965..a34a45b2 100644 --- a/crates/tek_mixer/src/mixer_main.rs +++ b/crates/tek_mixer/src/mixer_main.rs @@ -1,5 +1,30 @@ //! Multi-track mixer include!("lib.rs"); +use tek_core::clap::{self, Parser}; +#[derive(Debug, Parser)] +#[command(version, about, long_about = None)] +pub struct MixerCli { + /// Name of JACK client + #[arg(short, long)] name: Option, + /// Number of tracks + #[arg(short, long)] channels: Option, +} +impl Mixer { + pub fn from_args () -> Usually { + let args = MixerCli::parse(); + let mut mix = Self::new("")?; + if let Some(name) = args.name { + mix.name = name.clone(); + } + if let Some(channels) = args.channels { + for channel in 0..channels { + mix.track_add(&format!("Track {}", channel + 1), 1)?; + } + } + Ok(mix) + } +} + pub fn main () -> Usually<()> { Tui::run(Arc::new(RwLock::new(crate::Mixer::from_args()?)))?; Ok(()) diff --git a/crates/tek_mixer/src/plugin.rs b/crates/tek_mixer/src/plugin.rs index 7132323e..c6652daf 100644 --- a/crates/tek_mixer/src/plugin.rs +++ b/crates/tek_mixer/src/plugin.rs @@ -108,3 +108,120 @@ pub enum PluginKind { }, VST3, } + +impl Handle for Plugin { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + match from.event() { + key!(KeyCode::Up) => { + self.selected = self.selected.saturating_sub(1); + Ok(Some(true)) + }, + key!(KeyCode::Down) => { + self.selected = (self.selected + 1).min(match &self.plugin { + Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1, + _ => unimplemented!() + }); + Ok(Some(true)) + }, + key!(KeyCode::PageUp) => { + self.selected = self.selected.saturating_sub(8); + Ok(Some(true)) + }, + key!(KeyCode::PageDown) => { + self.selected = (self.selected + 10).min(match &self.plugin { + Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1, + _ => unimplemented!() + }); + Ok(Some(true)) + }, + key!(KeyCode::Char(',')) => { + match self.plugin.as_mut() { + Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => { + let index = port_list[self.selected].index; + if let Some(value) = instance.control_input(index) { + instance.set_control_input(index, value - 0.01); + } + }, + _ => {} + } + Ok(Some(true)) + }, + key!(KeyCode::Char('.')) => { + match self.plugin.as_mut() { + Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => { + let index = port_list[self.selected].index; + if let Some(value) = instance.control_input(index) { + instance.set_control_input(index, value + 0.01); + } + }, + _ => {} + } + Ok(Some(true)) + }, + key!(KeyCode::Char('g')) => { + match self.plugin { + Some(PluginKind::LV2(ref mut plugin)) => { + plugin.ui_thread = Some(run_lv2_ui(LV2PluginUI::new()?)?); + }, + Some(_) => unreachable!(), + None => {} + } + Ok(Some(true)) + }, + _ => Ok(None) + } + } +} + +impl Widget for Plugin { + type Engine = Tui; + fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> { + Ok(Some(to)) + } + fn render (&self, to: &mut TuiOutput) -> Usually<()> { + let area = to.area(); + let [x, y, _, height] = area; + let mut width = 20u16; + match &self.plugin { + Some(PluginKind::LV2(LV2Plugin { port_list, instance, .. })) => { + let start = self.selected.saturating_sub((height as usize / 2).saturating_sub(1)); + let end = start + height as usize - 2; + //draw_box(buf, Rect { x, y, width, height }); + for i in start..end { + if let Some(port) = port_list.get(i) { + let value = if let Some(value) = instance.control_input(port.index) { + value + } else { + port.default_value + }; + //let label = &format!("C·· M·· {:25} = {value:.03}", port.name); + let label = &format!("{:25} = {value:.03}", port.name); + width = width.max(label.len() as u16 + 4); + let style = if i == self.selected { + Some(Style::default().green()) + } else { + None + } ; + to.blit(&label, x + 2, y + 1 + i as u16 - start as u16, style); + } else { + break + } + } + }, + _ => {} + }; + draw_header(self, to, x, y, width)?; + Ok(()) + } +} + +fn draw_header (state: &Plugin, to: &mut TuiOutput, x: u16, y: u16, w: u16) -> Usually { + let style = Style::default().gray(); + let label1 = format!(" {}", state.name); + to.blit(&label1, x + 1, y, Some(style.white().bold())); + if let Some(ref path) = state.path { + let label2 = format!("{}…", &path[..((w as usize - 10).min(path.len()))]); + to.blit(&label2, x + 2 + label1.len() as u16, y, Some(style.not_dim())); + } + Ok(Rect { x, y, width: w, height: 1 }) +} diff --git a/crates/tek_mixer/src/plugin_handle.rs b/crates/tek_mixer/src/plugin_handle.rs deleted file mode 100644 index 4acc53a8..00000000 --- a/crates/tek_mixer/src/plugin_handle.rs +++ /dev/null @@ -1,65 +0,0 @@ -use crate::*; - -impl Handle for Plugin { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - match from.event() { - key!(KeyCode::Up) => { - self.selected = self.selected.saturating_sub(1); - Ok(Some(true)) - }, - key!(KeyCode::Down) => { - self.selected = (self.selected + 1).min(match &self.plugin { - Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1, - _ => unimplemented!() - }); - Ok(Some(true)) - }, - key!(KeyCode::PageUp) => { - self.selected = self.selected.saturating_sub(8); - Ok(Some(true)) - }, - key!(KeyCode::PageDown) => { - self.selected = (self.selected + 10).min(match &self.plugin { - Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1, - _ => unimplemented!() - }); - Ok(Some(true)) - }, - key!(KeyCode::Char(',')) => { - match self.plugin.as_mut() { - Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => { - let index = port_list[self.selected].index; - if let Some(value) = instance.control_input(index) { - instance.set_control_input(index, value - 0.01); - } - }, - _ => {} - } - Ok(Some(true)) - }, - key!(KeyCode::Char('.')) => { - match self.plugin.as_mut() { - Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => { - let index = port_list[self.selected].index; - if let Some(value) = instance.control_input(index) { - instance.set_control_input(index, value + 0.01); - } - }, - _ => {} - } - Ok(Some(true)) - }, - key!(KeyCode::Char('g')) => { - match self.plugin { - Some(PluginKind::LV2(ref mut plugin)) => { - plugin.ui_thread = Some(run_lv2_ui(LV2PluginUI::new()?)?); - }, - Some(_) => unreachable!(), - None => {} - } - Ok(Some(true)) - }, - _ => Ok(None) - } - } -} diff --git a/crates/tek_mixer/src/plugin_view.rs b/crates/tek_mixer/src/plugin_view.rs deleted file mode 100644 index b9e359c3..00000000 --- a/crates/tek_mixer/src/plugin_view.rs +++ /dev/null @@ -1,54 +0,0 @@ -use crate::*; - -impl Widget for Plugin { - type Engine = Tui; - fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> { - Ok(Some(to)) - } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { - let area = to.area(); - let [x, y, _, height] = area; - let mut width = 20u16; - match &self.plugin { - Some(PluginKind::LV2(LV2Plugin { port_list, instance, .. })) => { - let start = self.selected.saturating_sub((height as usize / 2).saturating_sub(1)); - let end = start + height as usize - 2; - //draw_box(buf, Rect { x, y, width, height }); - for i in start..end { - if let Some(port) = port_list.get(i) { - let value = if let Some(value) = instance.control_input(port.index) { - value - } else { - port.default_value - }; - //let label = &format!("C·· M·· {:25} = {value:.03}", port.name); - let label = &format!("{:25} = {value:.03}", port.name); - width = width.max(label.len() as u16 + 4); - let style = if i == self.selected { - Some(Style::default().green()) - } else { - None - } ; - to.blit(&label, x + 2, y + 1 + i as u16 - start as u16, style)?; - } else { - break - } - } - }, - _ => {} - }; - draw_header(self, to, x, y, width)?; - Ok(Some([x, y, width, height])) - } -} - -fn draw_header (state: &Plugin, to: &mut Tui, x: u16, y: u16, w: u16) -> Usually { - let style = Style::default().gray(); - let label1 = format!(" {}", state.name); - to.blit(&label1, x + 1, y, Some(style.white().bold()))?; - if let Some(ref path) = state.path { - let label2 = format!("{}…", &path[..((w as usize - 10).min(path.len()))]); - to.blit(&label2, x + 2 + label1.len() as u16, y, Some(style.not_dim()))?; - } - Ok(Rect { x, y, width: w, height: 1 }) -} diff --git a/crates/tek_mixer/src/sample.rs b/crates/tek_mixer/src/sample.rs deleted file mode 100644 index 015f6178..00000000 --- a/crates/tek_mixer/src/sample.rs +++ /dev/null @@ -1,79 +0,0 @@ -use super::*; - -/// A sound sample. -#[derive(Default, Debug)] -pub struct Sample { - pub name: String, - pub start: usize, - pub end: usize, - pub channels: Vec>, - pub rate: Option, -} - -impl Sample { - pub fn from_edn <'e> (dir: &str, args: &[Edn<'e>]) -> Usually<(Option, Arc>)> { - let mut name = String::new(); - let mut file = String::new(); - let mut midi = None; - let mut start = 0usize; - edn!(edn in args { - Edn::Map(map) => { - if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) { - name = String::from(*n); - } - if let Some(Edn::Str(f)) = map.get(&Edn::Key(":file")) { - file = String::from(*f); - } - if let Some(Edn::Int(i)) = map.get(&Edn::Key(":start")) { - start = *i as usize; - } - if let Some(Edn::Int(m)) = map.get(&Edn::Key(":midi")) { - midi = Some(u7::from(*m as u8)); - } - }, - _ => panic!("unexpected in sample {name}"), - }); - let (end, data) = read_sample_data(&format!("{dir}/{file}"))?; - Ok((midi, Arc::new(RwLock::new(Self::new(&name, start, end, data))))) - } - pub fn new (name: &str, start: usize, end: usize, channels: Vec>) -> Self { - Self { name: name.to_string(), start, end, channels, rate: None } - } - pub fn play (sample: &Arc>, after: usize, velocity: &u7) -> Voice { - Voice { - sample: sample.clone(), - after, - position: sample.read().unwrap().start, - velocity: velocity.as_int() as f32 / 127.0, - } - } -} - - -/// Load sample from WAV and assign to MIDI note. -#[macro_export] macro_rules! sample { - ($note:expr, $name:expr, $src:expr) => {{ - let (end, data) = read_sample_data($src)?; - ( - u7::from_int_lossy($note).into(), - Sample::new($name, 0, end, data).into() - ) - }}; -} - -/// Read WAV from file -pub fn read_sample_data (src: &str) -> Usually<(usize, Vec>)> { - let mut channels: Vec> = vec![]; - for channel in wavers::Wav::from_path(src)?.channels() { - channels.push(channel); - } - let mut end = 0; - let mut data: Vec> = vec![]; - for samples in channels.iter() { - let channel = Vec::from(samples.as_ref()); - end = end.max(channel.len()); - data.push(channel); - } - Ok((end, data)) -} - diff --git a/crates/tek_mixer/src/sample_add.rs b/crates/tek_mixer/src/sample_add.rs deleted file mode 100644 index c984754d..00000000 --- a/crates/tek_mixer/src/sample_add.rs +++ /dev/null @@ -1,294 +0,0 @@ -use super::*; - -use std::fs::File; -use symphonia::core::codecs::CODEC_TYPE_NULL; -use symphonia::core::errors::Error; -use symphonia::core::io::MediaSourceStream; -use symphonia::core::probe::Hint; -use symphonia::core::audio::SampleBuffer; -use symphonia::default::get_codecs; - -pub struct AddSampleModal { - exited: bool, - dir: PathBuf, - subdirs: Vec, - files: Vec, - cursor: usize, - offset: usize, - sample: Arc>, - voices: Arc>>, - _search: Option, -} - -impl Exit for AddSampleModal { - fn exited (&self) -> bool { - self.exited - } - fn exit (&mut self) { - self.exited = true - } -} - -impl Widget for AddSampleModal { - type Engine = Tui; - fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> { - todo!() - //Align::Center(()).layout(to) - } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { - todo!() - //let area = to.area(); - //to.make_dim(); - //let area = center_box( - //area, - //64.max(area.w().saturating_sub(8)), - //20.max(area.w().saturating_sub(8)), - //); - //to.fill_fg(area, Color::Reset); - //to.fill_bg(area, Nord::bg_lo(true, true)); - //to.fill_char(area, ' '); - //to.blit(&format!("{}", &self.dir.to_string_lossy()), area.x()+2, area.y()+1, Some(Style::default().bold()))?; - //to.blit(&"Select sample:", area.x()+2, area.y()+2, Some(Style::default().bold()))?; - //for (i, (is_dir, name)) in self.subdirs.iter() - //.map(|path|(true, path)) - //.chain(self.files.iter().map(|path|(false, path))) - //.enumerate() - //.skip(self.offset) - //{ - //if i >= area.h() as usize - 4 { - //break - //} - //let t = if is_dir { "" } else { "" }; - //let line = format!("{t} {}", name.to_string_lossy()); - //let line = &line[..line.len().min(area.w() as usize - 4)]; - //to.blit(&line, area.x() + 2, area.y() + 3 + i as u16, Some(if i == self.cursor { - //Style::default().green() - //} else { - //Style::default().white() - //}))?; - //} - //Lozenge(Style::default()).draw(to) - } -} - -impl Handle for AddSampleModal { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - if handle_keymap(self, &from.event(), KEYMAP_ADD_SAMPLE)? { - return Ok(Some(true)) - } - Ok(Some(true)) - } -} - -impl AddSampleModal { - pub fn new ( - sample: &Arc>, - voices: &Arc>> - ) -> Usually { - let dir = std::env::current_dir()?; - let (subdirs, files) = scan(&dir)?; - Ok(Self { - exited: false, - dir, - subdirs, - files, - cursor: 0, - offset: 0, - sample: sample.clone(), - voices: voices.clone(), - _search: None - }) - } - fn rescan (&mut self) -> Usually<()> { - scan(&self.dir).map(|(subdirs, files)|{ - self.subdirs = subdirs; - self.files = files; - }) - } - fn prev (&mut self) { - self.cursor = self.cursor.saturating_sub(1); - } - fn next (&mut self) { - self.cursor = self.cursor + 1; - } - fn try_preview (&mut self) -> Usually<()> { - if let Some(path) = self.cursor_file() { - if let Ok(sample) = Sample::from_file(&path) { - *self.sample.write().unwrap() = sample; - self.voices.write().unwrap().push( - Sample::play(&self.sample, 0, &u7::from(100u8)) - ); - } - //load_sample(&path)?; - //let src = std::fs::File::open(&path)?; - //let mss = MediaSourceStream::new(Box::new(src), Default::default()); - //let mut hint = Hint::new(); - //if let Some(ext) = path.extension() { - //hint.with_extension(&ext.to_string_lossy()); - //} - //let meta_opts: MetadataOptions = Default::default(); - //let fmt_opts: FormatOptions = Default::default(); - //if let Ok(mut probed) = symphonia::default::get_probe() - //.format(&hint, mss, &fmt_opts, &meta_opts) - //{ - //panic!("{:?}", probed.format.metadata()); - //}; - } - Ok(()) - } - fn cursor_dir (&self) -> Option { - if self.cursor < self.subdirs.len() { - Some(self.dir.join(&self.subdirs[self.cursor])) - } else { - None - } - } - fn cursor_file (&self) -> Option { - if self.cursor < self.subdirs.len() { - return None - } - let index = self.cursor.saturating_sub(self.subdirs.len()); - if index < self.files.len() { - Some(self.dir.join(&self.files[index])) - } else { - None - } - } - fn pick (&mut self) -> Usually { - if self.cursor == 0 { - if let Some(parent) = self.dir.parent() { - self.dir = parent.into(); - self.rescan()?; - self.cursor = 0; - return Ok(false) - } - } - if let Some(dir) = self.cursor_dir() { - self.dir = dir; - self.rescan()?; - self.cursor = 0; - return Ok(false) - } - if let Some(path) = self.cursor_file() { - let (end, channels) = read_sample_data(&path.to_string_lossy())?; - let mut sample = self.sample.write().unwrap(); - sample.name = path.file_name().unwrap().to_string_lossy().into(); - sample.end = end; - sample.channels = channels; - return Ok(true) - } - return Ok(false) - } -} - -pub const KEYMAP_ADD_SAMPLE: &'static [KeyBinding] = keymap!(AddSampleModal { - [Esc, NONE, "sampler/add/close", "close help dialog", |modal: &mut AddSampleModal|{ - modal.exit(); - Ok(true) - }], - [Up, NONE, "sampler/add/prev", "select previous entry", |modal: &mut AddSampleModal|{ - modal.prev(); - Ok(true) - }], - [Down, NONE, "sampler/add/next", "select next entry", |modal: &mut AddSampleModal|{ - modal.next(); - Ok(true) - }], - [Enter, NONE, "sampler/add/enter", "activate selected entry", |modal: &mut AddSampleModal|{ - if modal.pick()? { - modal.exit(); - } - Ok(true) - }], - [Char('p'), NONE, "sampler/add/preview", "preview selected entry", |modal: &mut AddSampleModal|{ - modal.try_preview()?; - Ok(true) - }] -}); - -fn scan (dir: &PathBuf) -> Usually<(Vec, Vec)> { - let (mut subdirs, mut files) = read_dir(dir)? - .fold((vec!["..".into()], vec![]), |(mut subdirs, mut files), entry|{ - let entry = entry.expect("failed to read drectory entry"); - let meta = entry.metadata().expect("failed to read entry metadata"); - if meta.is_file() { - files.push(entry.file_name()); - } else if meta.is_dir() { - subdirs.push(entry.file_name()); - } - (subdirs, files) - }); - subdirs.sort(); - files.sort(); - Ok((subdirs, files)) -} - -impl Sample { - fn from_file (path: &PathBuf) -> Usually { - let mut sample = Self::default(); - sample.name = path.file_name().unwrap().to_string_lossy().into(); - // Use file extension if present - let mut hint = Hint::new(); - if let Some(ext) = path.extension() { - hint.with_extension(&ext.to_string_lossy()); - } - let probed = symphonia::default::get_probe().format( - &hint, - MediaSourceStream::new( - Box::new(File::open(path)?), - Default::default(), - ), - &Default::default(), - &Default::default() - )?; - let mut format = probed.format; - let mut decoder = get_codecs().make( - &format.tracks().iter() - .find(|t| t.codec_params.codec != CODEC_TYPE_NULL) - .expect("no tracks found") - .codec_params, - &Default::default() - )?; - loop { - match format.next_packet() { - Ok(packet) => { - // Decode a packet - let decoded = match decoder.decode(&packet) { - Ok(decoded) => decoded, - Err(err) => { return Err(err.into()); } - }; - // Determine sample rate - let spec = *decoded.spec(); - if let Some(rate) = sample.rate { - if rate != spec.rate as usize { - panic!("sample rate changed"); - } - } else { - sample.rate = Some(spec.rate as usize); - } - // Determine channel count - while sample.channels.len() < spec.channels.count() { - sample.channels.push(vec![]); - } - // Load sample - let mut samples = SampleBuffer::new( - decoded.frames() as u64, - spec - ); - if samples.capacity() > 0 { - samples.copy_interleaved_ref(decoded); - for frame in samples.samples().chunks(spec.channels.count()) { - for (chan, frame) in frame.iter().enumerate() { - sample.channels[chan].push(*frame) - } - } - } - }, - Err(Error::IoError(_)) => break decoder.last_decoded(), - Err(err) => return Err(err.into()), - }; - }; - sample.end = sample.channels.iter().fold(0, |l, c|l + c.len()); - Ok(sample) - } -} diff --git a/crates/tek_mixer/src/sampler.rs b/crates/tek_mixer/src/sampler.rs index 08b8b620..d784dbbc 100644 --- a/crates/tek_mixer/src/sampler.rs +++ b/crates/tek_mixer/src/sampler.rs @@ -144,3 +144,471 @@ impl Audio for Sampler { Control::Continue } } +impl Handle for Sampler { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + match from.event() { + key!(KeyCode::Up) => { + self.cursor.0 = if self.cursor.0 == 0 { + self.mapped.len() + self.unmapped.len() - 1 + } else { + self.cursor.0 - 1 + }; + Ok(Some(true)) + }, + key!(KeyCode::Down) => { + self.cursor.0 = (self.cursor.0 + 1) % (self.mapped.len() + self.unmapped.len()); + Ok(Some(true)) + }, + key!(KeyCode::Char('p')) => { + if let Some(sample) = self.sample() { + self.voices.write().unwrap().push(Sample::play(sample, 0, &100.into())); + } + Ok(Some(true)) + }, + key!(KeyCode::Char('a')) => { + let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![]))); + *self.modal.lock().unwrap() = Some(Exit::boxed(AddSampleModal::new(&sample, &self.voices)?)); + self.unmapped.push(sample); + Ok(Some(true)) + }, + key!(KeyCode::Char('r')) => { + if let Some(sample) = self.sample() { + *self.modal.lock().unwrap() = Some(Exit::boxed(AddSampleModal::new(&sample, &self.voices)?)); + } + Ok(Some(true)) + }, + key!(KeyCode::Enter) => { + if let Some(sample) = self.sample() { + self.editing = Some(sample.clone()); + } + Ok(Some(true)) + } + _ => Ok(None) + } + } +} + +impl Widget for Sampler { + type Engine = Tui; + fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> { + todo!() + } + fn render (&self, to: &mut TuiOutput) -> Usually<()> { + tui_render_sampler(self, to) + } +} + +pub fn tui_render_sampler (sampler: &Sampler, to: &mut TuiOutput) -> Usually<()> { + let [x, y, _, height] = to.area(); + let style = Style::default().gray(); + let title = format!(" {} ({})", sampler.name, sampler.voices.read().unwrap().len()); + to.blit(&title, x+1, y, Some(style.white().bold().not_dim())); + let mut width = title.len() + 2; + let mut y1 = 1; + let mut j = 0; + for (note, sample) in sampler.mapped.iter() + .map(|(note, sample)|(Some(note), sample)) + .chain(sampler.unmapped.iter().map(|sample|(None, sample))) + { + if y1 >= height { + break + } + let active = j == sampler.cursor.0; + width = width.max( + draw_sample(to, x, y + y1, note, &*sample.read().unwrap(), active)? + ); + y1 = y1 + 1; + j = j + 1; + } + let height = ((2 + y1) as u16).min(height); + //Ok(Some([x, y, (width as u16).min(to.area().w()), height])) + Ok(()) +} + +fn draw_sample ( + to: &mut TuiOutput, x: u16, y: u16, note: Option<&u7>, sample: &Sample, focus: bool +) -> Usually { + let style = if focus { Style::default().green() } else { Style::default() }; + if focus { + to.blit(&"🬴", x+1, y, Some(style.bold())); + } + let label1 = format!("{:3} {:12}", + note.map(|n|n.to_string()).unwrap_or(String::default()), + sample.name); + let label2 = format!("{:>6} {:>6} +0.0", + sample.start, + sample.end); + to.blit(&label1, x+2, y, Some(style.bold())); + to.blit(&label2, x+3+label1.len()as u16, y, Some(style)); + Ok(label1.len() + label2.len() + 4) +} + +/// A sound sample. +#[derive(Default, Debug)] +pub struct Sample { + pub name: String, + pub start: usize, + pub end: usize, + pub channels: Vec>, + pub rate: Option, +} + +impl Sample { + pub fn from_edn <'e> (dir: &str, args: &[Edn<'e>]) -> Usually<(Option, Arc>)> { + let mut name = String::new(); + let mut file = String::new(); + let mut midi = None; + let mut start = 0usize; + edn!(edn in args { + Edn::Map(map) => { + if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) { + name = String::from(*n); + } + if let Some(Edn::Str(f)) = map.get(&Edn::Key(":file")) { + file = String::from(*f); + } + if let Some(Edn::Int(i)) = map.get(&Edn::Key(":start")) { + start = *i as usize; + } + if let Some(Edn::Int(m)) = map.get(&Edn::Key(":midi")) { + midi = Some(u7::from(*m as u8)); + } + }, + _ => panic!("unexpected in sample {name}"), + }); + let (end, data) = read_sample_data(&format!("{dir}/{file}"))?; + Ok((midi, Arc::new(RwLock::new(Self::new(&name, start, end, data))))) + } + pub fn new (name: &str, start: usize, end: usize, channels: Vec>) -> Self { + Self { name: name.to_string(), start, end, channels, rate: None } + } + pub fn play (sample: &Arc>, after: usize, velocity: &u7) -> Voice { + Voice { + sample: sample.clone(), + after, + position: sample.read().unwrap().start, + velocity: velocity.as_int() as f32 / 127.0, + } + } +} + + +/// Load sample from WAV and assign to MIDI note. +#[macro_export] macro_rules! sample { + ($note:expr, $name:expr, $src:expr) => {{ + let (end, data) = read_sample_data($src)?; + ( + u7::from_int_lossy($note).into(), + Sample::new($name, 0, end, data).into() + ) + }}; +} + +/// Read WAV from file +pub fn read_sample_data (src: &str) -> Usually<(usize, Vec>)> { + let mut channels: Vec> = vec![]; + for channel in wavers::Wav::from_path(src)?.channels() { + channels.push(channel); + } + let mut end = 0; + let mut data: Vec> = vec![]; + for samples in channels.iter() { + let channel = Vec::from(samples.as_ref()); + end = end.max(channel.len()); + data.push(channel); + } + Ok((end, data)) +} + +use std::fs::File; +use symphonia::core::codecs::CODEC_TYPE_NULL; +use symphonia::core::errors::Error; +use symphonia::core::io::MediaSourceStream; +use symphonia::core::probe::Hint; +use symphonia::core::audio::SampleBuffer; +use symphonia::default::get_codecs; + +pub struct AddSampleModal { + exited: bool, + dir: PathBuf, + subdirs: Vec, + files: Vec, + cursor: usize, + offset: usize, + sample: Arc>, + voices: Arc>>, + _search: Option, +} + +impl Exit for AddSampleModal { + fn exited (&self) -> bool { + self.exited + } + fn exit (&mut self) { + self.exited = true + } +} + +impl Widget for AddSampleModal { + type Engine = Tui; + fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> { + todo!() + //Align::Center(()).layout(to) + } + fn render (&self, to: &mut TuiOutput) -> Usually<()> { + todo!() + //let area = to.area(); + //to.make_dim(); + //let area = center_box( + //area, + //64.max(area.w().saturating_sub(8)), + //20.max(area.w().saturating_sub(8)), + //); + //to.fill_fg(area, Color::Reset); + //to.fill_bg(area, Nord::bg_lo(true, true)); + //to.fill_char(area, ' '); + //to.blit(&format!("{}", &self.dir.to_string_lossy()), area.x()+2, area.y()+1, Some(Style::default().bold()))?; + //to.blit(&"Select sample:", area.x()+2, area.y()+2, Some(Style::default().bold()))?; + //for (i, (is_dir, name)) in self.subdirs.iter() + //.map(|path|(true, path)) + //.chain(self.files.iter().map(|path|(false, path))) + //.enumerate() + //.skip(self.offset) + //{ + //if i >= area.h() as usize - 4 { + //break + //} + //let t = if is_dir { "" } else { "" }; + //let line = format!("{t} {}", name.to_string_lossy()); + //let line = &line[..line.len().min(area.w() as usize - 4)]; + //to.blit(&line, area.x() + 2, area.y() + 3 + i as u16, Some(if i == self.cursor { + //Style::default().green() + //} else { + //Style::default().white() + //}))?; + //} + //Lozenge(Style::default()).draw(to) + } +} + +impl Handle for AddSampleModal { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + if handle_keymap(self, &from.event(), KEYMAP_ADD_SAMPLE)? { + return Ok(Some(true)) + } + Ok(Some(true)) + } +} + +impl AddSampleModal { + pub fn new ( + sample: &Arc>, + voices: &Arc>> + ) -> Usually { + let dir = std::env::current_dir()?; + let (subdirs, files) = scan(&dir)?; + Ok(Self { + exited: false, + dir, + subdirs, + files, + cursor: 0, + offset: 0, + sample: sample.clone(), + voices: voices.clone(), + _search: None + }) + } + fn rescan (&mut self) -> Usually<()> { + scan(&self.dir).map(|(subdirs, files)|{ + self.subdirs = subdirs; + self.files = files; + }) + } + fn prev (&mut self) { + self.cursor = self.cursor.saturating_sub(1); + } + fn next (&mut self) { + self.cursor = self.cursor + 1; + } + fn try_preview (&mut self) -> Usually<()> { + if let Some(path) = self.cursor_file() { + if let Ok(sample) = Sample::from_file(&path) { + *self.sample.write().unwrap() = sample; + self.voices.write().unwrap().push( + Sample::play(&self.sample, 0, &u7::from(100u8)) + ); + } + //load_sample(&path)?; + //let src = std::fs::File::open(&path)?; + //let mss = MediaSourceStream::new(Box::new(src), Default::default()); + //let mut hint = Hint::new(); + //if let Some(ext) = path.extension() { + //hint.with_extension(&ext.to_string_lossy()); + //} + //let meta_opts: MetadataOptions = Default::default(); + //let fmt_opts: FormatOptions = Default::default(); + //if let Ok(mut probed) = symphonia::default::get_probe() + //.format(&hint, mss, &fmt_opts, &meta_opts) + //{ + //panic!("{:?}", probed.format.metadata()); + //}; + } + Ok(()) + } + fn cursor_dir (&self) -> Option { + if self.cursor < self.subdirs.len() { + Some(self.dir.join(&self.subdirs[self.cursor])) + } else { + None + } + } + fn cursor_file (&self) -> Option { + if self.cursor < self.subdirs.len() { + return None + } + let index = self.cursor.saturating_sub(self.subdirs.len()); + if index < self.files.len() { + Some(self.dir.join(&self.files[index])) + } else { + None + } + } + fn pick (&mut self) -> Usually { + if self.cursor == 0 { + if let Some(parent) = self.dir.parent() { + self.dir = parent.into(); + self.rescan()?; + self.cursor = 0; + return Ok(false) + } + } + if let Some(dir) = self.cursor_dir() { + self.dir = dir; + self.rescan()?; + self.cursor = 0; + return Ok(false) + } + if let Some(path) = self.cursor_file() { + let (end, channels) = read_sample_data(&path.to_string_lossy())?; + let mut sample = self.sample.write().unwrap(); + sample.name = path.file_name().unwrap().to_string_lossy().into(); + sample.end = end; + sample.channels = channels; + return Ok(true) + } + return Ok(false) + } +} + +pub const KEYMAP_ADD_SAMPLE: &'static [KeyBinding] = keymap!(AddSampleModal { + [Esc, NONE, "sampler/add/close", "close help dialog", |modal: &mut AddSampleModal|{ + modal.exit(); + Ok(true) + }], + [Up, NONE, "sampler/add/prev", "select previous entry", |modal: &mut AddSampleModal|{ + modal.prev(); + Ok(true) + }], + [Down, NONE, "sampler/add/next", "select next entry", |modal: &mut AddSampleModal|{ + modal.next(); + Ok(true) + }], + [Enter, NONE, "sampler/add/enter", "activate selected entry", |modal: &mut AddSampleModal|{ + if modal.pick()? { + modal.exit(); + } + Ok(true) + }], + [Char('p'), NONE, "sampler/add/preview", "preview selected entry", |modal: &mut AddSampleModal|{ + modal.try_preview()?; + Ok(true) + }] +}); + +fn scan (dir: &PathBuf) -> Usually<(Vec, Vec)> { + let (mut subdirs, mut files) = read_dir(dir)? + .fold((vec!["..".into()], vec![]), |(mut subdirs, mut files), entry|{ + let entry = entry.expect("failed to read drectory entry"); + let meta = entry.metadata().expect("failed to read entry metadata"); + if meta.is_file() { + files.push(entry.file_name()); + } else if meta.is_dir() { + subdirs.push(entry.file_name()); + } + (subdirs, files) + }); + subdirs.sort(); + files.sort(); + Ok((subdirs, files)) +} + +impl Sample { + fn from_file (path: &PathBuf) -> Usually { + let mut sample = Self::default(); + sample.name = path.file_name().unwrap().to_string_lossy().into(); + // Use file extension if present + let mut hint = Hint::new(); + if let Some(ext) = path.extension() { + hint.with_extension(&ext.to_string_lossy()); + } + let probed = symphonia::default::get_probe().format( + &hint, + MediaSourceStream::new( + Box::new(File::open(path)?), + Default::default(), + ), + &Default::default(), + &Default::default() + )?; + let mut format = probed.format; + let mut decoder = get_codecs().make( + &format.tracks().iter() + .find(|t| t.codec_params.codec != CODEC_TYPE_NULL) + .expect("no tracks found") + .codec_params, + &Default::default() + )?; + loop { + match format.next_packet() { + Ok(packet) => { + // Decode a packet + let decoded = match decoder.decode(&packet) { + Ok(decoded) => decoded, + Err(err) => { return Err(err.into()); } + }; + // Determine sample rate + let spec = *decoded.spec(); + if let Some(rate) = sample.rate { + if rate != spec.rate as usize { + panic!("sample rate changed"); + } + } else { + sample.rate = Some(spec.rate as usize); + } + // Determine channel count + while sample.channels.len() < spec.channels.count() { + sample.channels.push(vec![]); + } + // Load sample + let mut samples = SampleBuffer::new( + decoded.frames() as u64, + spec + ); + if samples.capacity() > 0 { + samples.copy_interleaved_ref(decoded); + for frame in samples.samples().chunks(spec.channels.count()) { + for (chan, frame) in frame.iter().enumerate() { + sample.channels[chan].push(*frame) + } + } + } + }, + Err(Error::IoError(_)) => break decoder.last_decoded(), + Err(err) => return Err(err.into()), + }; + }; + sample.end = sample.channels.iter().fold(0, |l, c|l + c.len()); + Ok(sample) + } +} diff --git a/crates/tek_mixer/src/sampler_edn.rs b/crates/tek_mixer/src/sampler_edn.rs deleted file mode 100644 index 190062c9..00000000 --- a/crates/tek_mixer/src/sampler_edn.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::*; - -impl Sampler { -} - -impl Sample { -} diff --git a/crates/tek_mixer/src/sampler_handle.rs b/crates/tek_mixer/src/sampler_handle.rs deleted file mode 100644 index a3496479..00000000 --- a/crates/tek_mixer/src/sampler_handle.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::*; -impl Handle for Sampler { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - match from.event() { - key!(KeyCode::Up) => { - self.cursor.0 = if self.cursor.0 == 0 { - self.mapped.len() + self.unmapped.len() - 1 - } else { - self.cursor.0 - 1 - }; - Ok(Some(true)) - }, - key!(KeyCode::Down) => { - self.cursor.0 = (self.cursor.0 + 1) % (self.mapped.len() + self.unmapped.len()); - Ok(Some(true)) - }, - key!(KeyCode::Char('p')) => { - if let Some(sample) = self.sample() { - self.voices.write().unwrap().push(Sample::play(sample, 0, &100.into())); - } - Ok(Some(true)) - }, - key!(KeyCode::Char('a')) => { - let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![]))); - *self.modal.lock().unwrap() = Some(Exit::boxed(AddSampleModal::new(&sample, &self.voices)?)); - self.unmapped.push(sample); - Ok(Some(true)) - }, - key!(KeyCode::Char('r')) => { - if let Some(sample) = self.sample() { - *self.modal.lock().unwrap() = Some(Exit::boxed(AddSampleModal::new(&sample, &self.voices)?)); - } - Ok(Some(true)) - }, - key!(KeyCode::Enter) => { - if let Some(sample) = self.sample() { - self.editing = Some(sample.clone()); - } - Ok(Some(true)) - } - _ => Ok(None) - } - } -} diff --git a/crates/tek_mixer/src/sampler_view.rs b/crates/tek_mixer/src/sampler_view.rs deleted file mode 100644 index f824e2c5..00000000 --- a/crates/tek_mixer/src/sampler_view.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::*; - -impl Widget for Sampler { - type Engine = Tui; - fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> { - todo!() - } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { - tui_render_sampler(self, to) - } -} - -pub fn tui_render_sampler (sampler: &Sampler, to: &mut Tui) -> Perhaps<[u16;4]> { - let [x, y, _, height] = to.area(); - let style = Style::default().gray(); - let title = format!(" {} ({})", sampler.name, sampler.voices.read().unwrap().len()); - to.blit(&title, x+1, y, Some(style.white().bold().not_dim()))?; - let mut width = title.len() + 2; - let mut y1 = 1; - let mut j = 0; - for (note, sample) in sampler.mapped.iter() - .map(|(note, sample)|(Some(note), sample)) - .chain(sampler.unmapped.iter().map(|sample|(None, sample))) - { - if y1 >= height { - break - } - let active = j == sampler.cursor.0; - width = width.max( - draw_sample(to, x, y + y1, note, &*sample.read().unwrap(), active)? - ); - y1 = y1 + 1; - j = j + 1; - } - let height = ((2 + y1) as u16).min(height); - Ok(Some([x, y, (width as u16).min(to.area().w()), height])) -} - -fn draw_sample ( - to: &mut Tui, x: u16, y: u16, note: Option<&u7>, sample: &Sample, focus: bool -) -> Usually { - let style = if focus { Style::default().green() } else { Style::default() }; - if focus { - to.blit(&"🬴", x+1, y, Some(style.bold()))?; - } - let label1 = format!("{:3} {:12}", - note.map(|n|n.to_string()).unwrap_or(String::default()), - sample.name); - let label2 = format!("{:>6} {:>6} +0.0", - sample.start, - sample.end); - to.blit(&label1, x+2, y, Some(style.bold()))?; - to.blit(&label2, x+3+label1.len()as u16, y, Some(style))?; - Ok(label1.len() + label2.len() + 4) -} diff --git a/crates/tek_mixer/src/track.rs b/crates/tek_mixer/src/track.rs index b7746beb..a5d7c71b 100644 --- a/crates/tek_mixer/src/track.rs +++ b/crates/tek_mixer/src/track.rs @@ -105,3 +105,105 @@ const SYM_NAME: &'static str = ":name"; const SYM_GAIN: &'static str = ":gain"; const SYM_SAMPLER: &'static str = "sampler"; const SYM_LV2: &'static str = "lv2"; + +impl Handle for Track { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + match from.event() { + //, NONE, "chain_cursor_up", "move cursor up", || { + key!(KeyCode::Up) => { + Ok(Some(true)) + }, + // , NONE, "chain_cursor_down", "move cursor down", || { + key!(KeyCode::Down) => { + Ok(Some(true)) + }, + // Left, NONE, "chain_cursor_left", "move cursor left", || { + key!(KeyCode::Left) => { + //if let Some(track) = app.arranger.track_mut() { + //track.device = track.device.saturating_sub(1); + //return Ok(true) + //} + Ok(Some(true)) + }, + // , NONE, "chain_cursor_right", "move cursor right", || { + key!(KeyCode::Right) => { + //if let Some(track) = app.arranger.track_mut() { + //track.device = (track.device + 1).min(track.devices.len().saturating_sub(1)); + //return Ok(true) + //} + Ok(Some(true)) + }, + // , NONE, "chain_mode_switch", "switch the display mode", || { + key!(KeyCode::Char('`')) => { + //app.chain_mode = !app.chain_mode; + Ok(Some(true)) + }, + _ => Ok(None) + } + } +} + +impl Content for Track { + type Engine = Tui; + fn content (&self) -> impl Widget { + TrackView { + chain: Some(&self), + direction: tek_core::Direction::Right, + focused: true, + entered: true, + //pub channels: u8, + //pub input_ports: Vec>, + //pub pre_gain_meter: f64, + //pub gain: f64, + //pub insert_ports: Vec>, + //pub return_ports: Vec>, + //pub post_gain_meter: f64, + //pub post_insert_meter: f64, + //pub level: f64, + //pub pan: f64, + //pub output_ports: Vec>, + //pub post_fader_meter: f64, + //pub route: String, + } + } +} +pub struct TrackView<'a, E: Engine> { + pub chain: Option<&'a Track>, + pub direction: Direction, + pub focused: bool, + pub entered: bool, +} +impl<'a> Widget for TrackView<'a, Tui> { + type Engine = Tui; + fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { + todo!() + } + fn render (&self, to: &mut TuiOutput) -> Usually<()> { + todo!(); + //let mut area = to.area(); + //if let Some(chain) = self.chain { + //match self.direction { + //Direction::Down => area.width = area.width.min(40), + //Direction::Right => area.width = area.width.min(10), + //_ => { unimplemented!() }, + //} + //to.fill_bg(to.area(), Nord::bg_lo(self.focused, self.entered)); + //let mut split = Split::new(self.direction); + //for device in chain.devices.as_slice().iter() { + //split = split.add_ref(device); + //} + //let (area, areas) = split.render_areas(to)?; + //if self.focused && self.entered && areas.len() > 0 { + //Corners(Style::default().green().not_dim()).draw(to.with_rect(areas[0]))?; + //} + //Ok(Some(area)) + //} else { + //let [x, y, width, height] = area; + //let label = "No chain selected"; + //let x = x + (width - label.len() as u16) / 2; + //let y = y + height / 2; + //to.blit(&label, x, y, Some(Style::default().dim().bold()))?; + //Ok(Some(area)) + //} + } +} diff --git a/crates/tek_mixer/src/track_handle.rs b/crates/tek_mixer/src/track_handle.rs deleted file mode 100644 index 563670f1..00000000 --- a/crates/tek_mixer/src/track_handle.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::*; - -impl Handle for Track { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - match from.event() { - //, NONE, "chain_cursor_up", "move cursor up", || { - key!(KeyCode::Up) => { - Ok(Some(true)) - }, - // , NONE, "chain_cursor_down", "move cursor down", || { - key!(KeyCode::Down) => { - Ok(Some(true)) - }, - // Left, NONE, "chain_cursor_left", "move cursor left", || { - key!(KeyCode::Left) => { - //if let Some(track) = app.arranger.track_mut() { - //track.device = track.device.saturating_sub(1); - //return Ok(true) - //} - Ok(Some(true)) - }, - // , NONE, "chain_cursor_right", "move cursor right", || { - key!(KeyCode::Right) => { - //if let Some(track) = app.arranger.track_mut() { - //track.device = (track.device + 1).min(track.devices.len().saturating_sub(1)); - //return Ok(true) - //} - Ok(Some(true)) - }, - // , NONE, "chain_mode_switch", "switch the display mode", || { - key!(KeyCode::Char('`')) => { - //app.chain_mode = !app.chain_mode; - Ok(Some(true)) - }, - _ => Ok(None) - } - } -} diff --git a/crates/tek_mixer/src/track_view.rs b/crates/tek_mixer/src/track_view.rs deleted file mode 100644 index 50d5e7bc..00000000 --- a/crates/tek_mixer/src/track_view.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::*; -use tek_core::Direction; - -impl Content for Track { - type Engine = Tui; - fn content (&self) -> impl Widget { - TrackView { - chain: Some(&self), - direction: tek_core::Direction::Right, - focused: true, - entered: true, - //pub channels: u8, - //pub input_ports: Vec>, - //pub pre_gain_meter: f64, - //pub gain: f64, - //pub insert_ports: Vec>, - //pub return_ports: Vec>, - //pub post_gain_meter: f64, - //pub post_insert_meter: f64, - //pub level: f64, - //pub pan: f64, - //pub output_ports: Vec>, - //pub post_fader_meter: f64, - //pub route: String, - } - } -} -pub struct TrackView<'a, E: Engine> { - pub chain: Option<&'a Track>, - pub direction: Direction, - pub focused: bool, - pub entered: bool, -} -impl<'a> Widget for TrackView<'a, Tui> { - type Engine = Tui; - fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { - todo!() - } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { - todo!(); - //let mut area = to.area(); - //if let Some(chain) = self.chain { - //match self.direction { - //Direction::Down => area.width = area.width.min(40), - //Direction::Right => area.width = area.width.min(10), - //_ => { unimplemented!() }, - //} - //to.fill_bg(to.area(), Nord::bg_lo(self.focused, self.entered)); - //let mut split = Split::new(self.direction); - //for device in chain.devices.as_slice().iter() { - //split = split.add_ref(device); - //} - //let (area, areas) = split.render_areas(to)?; - //if self.focused && self.entered && areas.len() > 0 { - //Corners(Style::default().green().not_dim()).draw(to.with_rect(areas[0]))?; - //} - //Ok(Some(area)) - //} else { - //let [x, y, width, height] = area; - //let label = "No chain selected"; - //let x = x + (width - label.len() as u16) / 2; - //let y = y + height / 2; - //to.blit(&label, x, y, Some(Style::default().dim().bold()))?; - //Ok(Some(area)) - //} - } -} diff --git a/crates/tek_sequencer/src/arranger.rs b/crates/tek_sequencer/src/arranger.rs index 2916bd7d..37d75ffc 100644 --- a/crates/tek_sequencer/src/arranger.rs +++ b/crates/tek_sequencer/src/arranger.rs @@ -380,17 +380,17 @@ struct ColumnSeparators<'a>(u16, &'a [(usize, usize)]); impl<'a> Widget for ColumnSeparators<'a> { type Engine = Tui; - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + fn render (&self, to: &mut TuiOutput) -> Usually<()> { let area = to.area(); let Self(offset, cols) = self; let style = Some(Style::default().fg(Nord::SEPARATOR)); for (_, x) in cols.iter() { let x = offset + area.x() + *x as u16 - 1; for y in area.y()..area.y2() { - to.blit(&"▎", x, y, style)?; + to.blit(&"▎", x, y, style); } } - Ok(Some(area)) + Ok(()) } } @@ -398,23 +398,23 @@ struct RowSeparators<'a>(&'a [(usize, usize)]); impl<'a> Widget for RowSeparators<'a> { type Engine = Tui; - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + fn render (&self, to: &mut TuiOutput) -> Usually<()> { let area = to.area(); let Self(rows) = self; for (_, y) in rows.iter() { let y = area.y() + (*y / 96) as u16 + 1; - if y >= to.buffer().area.height { + if y >= to.buffer.area.height { break } for x in area.x()..area.x2().saturating_sub(2) { - if x < to.buffer().area.x && y < to.buffer().area.y { - let cell = to.buffer().get_mut(x, y); + if x < to.buffer.area.x && y < to.buffer.area.y { + let cell = to.buffer.get_mut(x, y); cell.modifier = Modifier::UNDERLINED; cell.underline_color = Nord::SEPARATOR; } } } - Ok(Some(area)) + Ok(()) } } @@ -424,7 +424,7 @@ struct CursorFocus<'a>( impl<'a> Widget for CursorFocus<'a> { type Engine = Tui; - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + fn render (&self, to: &mut TuiOutput) -> Usually<()> { let area = to.area(); let Self(selected, offset, cols, rows) = *self; let get_track_area = |t: usize| [ @@ -483,7 +483,8 @@ impl<'a> Widget for CursorFocus<'a> { } else if let Some(scene_area) = scene_area { to.fill_bg(scene_area, COLOR_BG0); } - Ok(Some(area)) + //Ok(Some(area)) + Ok(()) } } @@ -606,7 +607,7 @@ impl<'a> Widget for TrackNameColumn<'a> { fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { todo!() } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + fn render (&self, to: &mut TuiOutput) -> Usually<()> { todo!(); //let Self(tracks, selected) = self; //let yellow = Some(Style::default().yellow().bold().not_dim()); @@ -638,7 +639,7 @@ impl<'a> Widget for TrackMonitorColumn<'a> { fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { todo!() } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + fn render (&self, to: &mut TuiOutput) -> Usually<()> { todo!(); //let Self(tracks) = self; //let mut area = to.area(); @@ -671,7 +672,7 @@ impl<'a> Widget for TrackRecordColumn<'a> { fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { todo!() } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + fn render (&self, to: &mut TuiOutput) -> Usually<()> { todo!(); //let Self(tracks) = self; //let mut area = to.area(); @@ -704,7 +705,7 @@ impl<'a> Widget for TrackOverdubColumn<'a> { fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { todo!() } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + fn render (&self, to: &mut TuiOutput) -> Usually<()> { todo!(); //let Self(tracks) = self; //let mut area = to.area(); @@ -740,7 +741,7 @@ impl<'a> Widget for TrackEraseColumn<'a> { fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { todo!() } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + fn render (&self, to: &mut TuiOutput) -> Usually<()> { todo!(); //let Self(tracks) = self; //let mut area = to.area(); @@ -771,7 +772,7 @@ impl<'a> Widget for TrackGainColumn<'a> { fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { todo!() } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + fn render (&self, to: &mut TuiOutput) -> Usually<()> { todo!(); //let Self(tracks) = self; //let mut area = to.area(); @@ -802,7 +803,7 @@ impl<'a> Widget for TrackScenesColumn<'a> { fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { todo!() } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + fn render (&self, to: &mut TuiOutput) -> Usually<()> { let Self(tracks, scenes, selected) = self; let area = to.area(); let mut x2 = 0; @@ -815,11 +816,11 @@ impl<'a> Widget for TrackScenesColumn<'a> { Style::default().dim() }); for y in y+1..y+height { - to.blit(&"│", x + x2, y, sep)?; + to.blit(&"│", x + x2, y, sep); } let name = scene.name.read().unwrap(); let mut x3 = name.len() as u16; - to.blit(&*name, x + x2, y, sep)?; + to.blit(&*name, x + x2, y, sep); for (i, clip) in scene.clips.iter().enumerate() { let active_track = selected.track() == Some(i); if let Some(clip) = clip { @@ -832,13 +833,14 @@ impl<'a> Widget for TrackScenesColumn<'a> { Style::default().not_dim().yellow().bold() } else { Style::default().not_dim() - }))?; + })); x3 = x3.max(label.len() as u16) } } x2 = x2 + x3 + 1; } - Ok(Some([x, y, x2, height])) + //Ok(Some([x, y, x2, height])) + Ok(()) } } @@ -870,7 +872,7 @@ impl Widget for ArrangerRenameModal { fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { todo!() } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + fn render (&self, to: &mut TuiOutput) -> Usually<()> { let area = to.area(); let y = area.y() + area.h() / 2; let bg_area = [1, y - 1, area.w() - 2, 3]; @@ -883,12 +885,13 @@ impl Widget for ArrangerRenameModal { ArrangerFocus::Clip(_, _) => "Rename clip:", }; let style = Some(Style::default().not_bold().white().not_dim()); - to.blit(&label, area.x() + 3, y, style)?; + to.blit(&label, area.x() + 3, y, style); let style = Some(Style::default().bold().white().not_dim()); - to.blit(&self.value, area.x() + 3 + label.len() as u16 + 1, y, style)?; + to.blit(&self.value, area.x() + 3 + label.len() as u16 + 1, y, style); let style = Some(Style::default().bold().white().not_dim().reversed()); - to.blit(&"▂", area.x() + 3 + label.len() as u16 + 1 + self.cursor as u16, y, style)?; - Ok(Some(area)) + to.blit(&"▂", area.x() + 3 + label.len() as u16 + 1 + self.cursor as u16, y, style); + //Ok(Some(area)) + Ok(()) } } diff --git a/crates/tek_sequencer/src/sequencer.rs b/crates/tek_sequencer/src/sequencer.rs index d1bf6598..262d3adc 100644 --- a/crates/tek_sequencer/src/sequencer.rs +++ b/crates/tek_sequencer/src/sequencer.rs @@ -203,12 +203,13 @@ impl Widget for Sequencer { fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { todo!() } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + fn render (&self, to: &mut TuiOutput) -> Usually<()> { self.horizontal_draw(to)?; if self.focused && self.entered { Corners(Style::default().green().not_dim()).draw(to)?; } - Ok(Some(to.area())) + //Ok(Some(to.area())) + Ok(()) } } @@ -404,7 +405,7 @@ impl Sequencer { const H_KEYS_OFFSET: usize = 5; - pub(crate) fn horizontal_draw <'a> (&self, to: &mut Tui) -> Usually<()> { + pub(crate) fn horizontal_draw <'a> (&self, to: &mut TuiOutput) -> Usually<()> { let area = to.area(); Split::down(|add|{ add(&SequenceName(&self))?; @@ -451,13 +452,13 @@ impl<'a> Widget for SequenceName<'a> { fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { todo!() } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + fn render (&self, to: &mut TuiOutput) -> Usually<()> { let [x, y, ..] = to.area(); let frame = [x, y, 10, 4]; Lozenge(Style::default().fg(Nord::BG2)).draw(to.with_rect(frame))?; - to.blit(&"Name:", x + 1, y + 1, STYLE_LABEL)?; - to.blit(&*self.0.name.read().unwrap(), x + 1, y + 2, STYLE_VALUE)?; - Ok(Some(frame)) + to.blit(&"Name:", x + 1, y + 1, STYLE_LABEL); + to.blit(&*self.0.name.read().unwrap(), x + 1, y + 2, STYLE_VALUE); + Ok(()) } } @@ -468,15 +469,15 @@ impl Widget for SequenceRange { fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { todo!() } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + fn render (&self, to: &mut TuiOutput) -> Usually<()> { let [x, y, ..] = to.area(); let frame = [x, y, 10, 6]; Lozenge(Style::default().fg(Nord::BG2)).draw(to.with_rect(frame))?; - to.blit(&"Start: ", x + 1, y + 1, STYLE_LABEL)?; - to.blit(&" 1.1.1", x + 1, y + 2, STYLE_VALUE)?; - to.blit(&"End: ", x + 1, y + 3, STYLE_LABEL)?; - to.blit(&" 2.1.1", x + 1, y + 4, STYLE_VALUE)?; - Ok(Some(frame)) + to.blit(&"Start: ", x + 1, y + 1, STYLE_LABEL); + to.blit(&" 1.1.1", x + 1, y + 2, STYLE_VALUE); + to.blit(&"End: ", x + 1, y + 3, STYLE_LABEL); + to.blit(&" 2.1.1", x + 1, y + 4, STYLE_VALUE); + Ok(()) } } @@ -487,16 +488,16 @@ impl Widget for SequenceLoopRange { fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { todo!() } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + fn render (&self, to: &mut TuiOutput) -> Usually<()> { let [x, y, ..] = to.area(); let range = [x, y, 10, 7]; Lozenge(Style::default().fg(Nord::BG2)).draw(to.with_rect(range))?; - to.blit(&"Loop [ ]", x + 1, y + 1, STYLE_LABEL)?; - to.blit(&"From: ", x + 1, y + 2, STYLE_LABEL)?; - to.blit(&" 1.1.1", x + 1, y + 3, STYLE_VALUE)?; - to.blit(&"Length: ", x + 1, y + 4, STYLE_LABEL)?; - to.blit(&" 1.0.0", x + 1, y + 5, STYLE_VALUE)?; - Ok(Some(range)) + to.blit(&"Loop [ ]", x + 1, y + 1, STYLE_LABEL); + to.blit(&"From: ", x + 1, y + 2, STYLE_LABEL); + to.blit(&" 1.1.1", x + 1, y + 3, STYLE_VALUE); + to.blit(&"Length: ", x + 1, y + 4, STYLE_LABEL); + to.blit(&" 1.0.0", x + 1, y + 5, STYLE_VALUE); + Ok(()) } } @@ -507,18 +508,18 @@ impl Widget for SequenceNoteRange { fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { todo!() } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + fn render (&self, to: &mut TuiOutput) -> Usually<()> { let [x, y, ..] = to.area(); let range = [x, y, 10, 9]; Lozenge(Style::default().fg(Nord::BG2)).draw(to.with_rect(range))?; - to.blit(&"Notes: ", x + 1, y + 1, STYLE_LABEL)?; - to.blit(&"C#0-C#9 ", x + 1, y + 2, STYLE_VALUE)?; - to.blit(&"[ /2 ]", x + 1, y + 3, STYLE_LABEL)?; - to.blit(&"[ x2 ]", x + 1, y + 4, STYLE_LABEL)?; - to.blit(&"[ Rev ]", x + 1, y + 5, STYLE_LABEL)?; - to.blit(&"[ Inv ]", x + 1, y + 6, STYLE_LABEL)?; - to.blit(&"[ Dup ]", x + 1, y + 7, STYLE_LABEL)?; - Ok(Some(to.area())) + to.blit(&"Notes: ", x + 1, y + 1, STYLE_LABEL); + to.blit(&"C#0-C#9 ", x + 1, y + 2, STYLE_VALUE); + to.blit(&"[ /2 ]", x + 1, y + 3, STYLE_LABEL); + to.blit(&"[ x2 ]", x + 1, y + 4, STYLE_LABEL); + to.blit(&"[ Rev ]", x + 1, y + 5, STYLE_LABEL); + to.blit(&"[ Inv ]", x + 1, y + 6, STYLE_LABEL); + to.blit(&"[ Dup ]", x + 1, y + 7, STYLE_LABEL); + Ok(()) } } @@ -529,19 +530,19 @@ impl<'a> Widget for SequenceKeys<'a> { fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { todo!() } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + fn render (&self, to: &mut TuiOutput) -> Usually<()> { let area = to.area(); if area.h() < 2 { - return Ok(Some(area)) + return Ok(()) } let area = [area.x(), area.y() + 1, 5, area.h() - 2]; - buffer_update(to.buffer(), area, &|cell, x, y|{ + to.buffer_update(area, &|cell, x, y|{ let y = y + self.0.note_axis.start as u16; if x < self.0.keys.area.width && y < self.0.keys.area.height { *cell = self.0.keys.get(x, y).clone() } }); - Ok(Some(area)) + Ok(()) } } @@ -552,10 +553,10 @@ impl<'a> Widget for SequenceNotes<'a> { fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { todo!() } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + fn render (&self, to: &mut TuiOutput) -> Usually<()> { let area = to.area(); if area.h() < 2 { - return Ok(Some(area)) + return Ok(())//Some(area)) } let area = [ area.x() + Sequencer::H_KEYS_OFFSET as u16, @@ -574,7 +575,7 @@ impl<'a> Widget for SequenceNotes<'a> { }); } }); - Ok(Some(area)) + Ok(())//Some(area)) } } @@ -585,15 +586,17 @@ impl<'a> Widget for SequenceCursor<'a> { fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { todo!() } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + fn render (&self, to: &mut TuiOutput) -> Usually<()> { let area = to.area(); if let (Some(time), Some(note)) = (self.0.time_axis.point, self.0.note_axis.point) { let x = area.x() + Sequencer::H_KEYS_OFFSET as u16 + time as u16; let y = area.y() + 1 + note as u16 / 2; let c = if note % 2 == 0 { "▀" } else { "▄" }; - to.blit(&c, x, y, self.0.style_focus()) + to.blit(&c, x, y, self.0.style_focus()); + Ok(()) } else { - Ok(Some([0,0,0,0])) + //Ok(Some([0,0,0,0])) + Ok(()) } } } @@ -605,12 +608,13 @@ impl<'a> Widget for SequenceZoom<'a> { fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { todo!() } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + fn render (&self, to: &mut TuiOutput) -> Usually<()> { let area = to.area(); let quant = ppq_to_name(self.0.time_axis.scale); let quant_x = area.x() + area.w() - 1 - quant.len() as u16; let quant_y = area.y() + area.h() - 2; - to.blit(&quant, quant_x, quant_y, self.0.style_focus()) + to.blit(&quant, quant_x, quant_y, self.0.style_focus()); + Ok(()) } } @@ -621,7 +625,7 @@ impl<'a> Widget for SequenceTimer<'a> { fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { todo!() } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + fn render (&self, to: &mut TuiOutput) -> Usually<()> { let area = to.area(); let phrase = self.1.read().unwrap(); let (time0, time_z, now) = ( @@ -634,9 +638,10 @@ impl<'a> Widget for SequenceTimer<'a> { let step = (time0 + x2) * time_z; let next_step = (time0 + x2 + 1) * time_z; let style = Sequencer::::style_timer_step(now, step as usize, next_step as usize); - to.blit(&"-", x as u16, area.y(), Some(style))?; + to.blit(&"-", x as u16, area.y(), Some(style)); } - return Ok(Some([area.x(), area.y(), area.w(), 1])) + //return Ok(Some([area.x(), area.y(), area.w(), 1])) + Ok(()) } } diff --git a/crates/tek_sequencer/src/transport.rs b/crates/tek_sequencer/src/transport.rs index bffdd3cb..6e1d083d 100644 --- a/crates/tek_sequencer/src/transport.rs +++ b/crates/tek_sequencer/src/transport.rs @@ -321,13 +321,13 @@ impl Widget for TransportBPM { area.expect_min(10, 1)?; Ok(Some([10, 1])) } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + fn render (&self, to: &mut TuiOutput) -> Usually<()> { let area = to.area(); let [x, y, ..] = area; let Self { value, focused, .. } = self; - to.blit(&"BPM", x, y, Some(NOT_DIM))?; + to.blit(&"BPM", x, y, Some(NOT_DIM)); let bpm = format!("{}.{:03}", value, (value * 1000.0) % 1000.0); - to.blit(&bpm, x, y + 1, Some(NOT_DIM_BOLD))?; + to.blit(&bpm, x, y + 1, Some(NOT_DIM_BOLD)); let width = bpm.len() as u16; let area = [x, y, (width + 2).max(10), 2]; if *focused { @@ -335,7 +335,8 @@ impl Widget for TransportBPM { CORNERS.draw(to)?; to.fill_bg(area, COLOR_BG1); } - Ok(Some(area)) + //Ok(Some(area)) + Ok(()) } } @@ -375,20 +376,21 @@ impl Widget for TransportQuantize { area.expect_min(10, 1)?; Ok(Some([10, 1])) } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + fn render (&self, to: &mut TuiOutput) -> Usually<()> { let [x, y, ..] = to.area(); let Self { value, focused, .. } = self; - to.blit(&"QUANT", x, y, Some(NOT_DIM))?; + to.blit(&"QUANT", x, y, Some(NOT_DIM)); let name = ppq_to_name(*value as usize); let width = name.len() as u16; - to.blit(&name, x, y + 1, Some(NOT_DIM_BOLD))?; + to.blit(&name, x, y + 1, Some(NOT_DIM_BOLD)); let area = [x, y, (width + 2).max(10), 2]; if *focused { let area = [area.x() - 1, area.y(), area.w() - 1, area.h() ]; CORNERS.draw(to)?; to.fill_bg(area, COLOR_BG1); } - Ok(Some(area)) + //Ok(Some(area)) + Ok(()) } } @@ -428,20 +430,21 @@ impl Widget for TransportSync { area.expect_min(10, 1)?; Ok(Some([10, 1])) } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + fn render (&self, to: &mut TuiOutput) -> Usually<()> { let [x, y, ..] = to.area(); let Self { value, focused, .. } = self; - to.blit(&"SYNC", x, y, Some(NOT_DIM))?; + to.blit(&"SYNC", x, y, Some(NOT_DIM)); let name = ppq_to_name(*value as usize); let width = name.len() as u16; - to.blit(&name, x, y + 1, Some(NOT_DIM_BOLD))?; + to.blit(&name, x, y + 1, Some(NOT_DIM_BOLD)); let area = [x, y, (width + 2).max(10), 2]; if *focused { let area = [area.x() - 1, area.y(), area.w() - 1, area.h() ]; CORNERS.draw(to)?; to.fill_bg(area, COLOR_BG1); } - Ok(Some(area)) + //Ok(Some(area)) + Ok(()) } } @@ -474,7 +477,7 @@ impl Widget for TransportClock { area.expect_min(20, 1)?; Ok(Some([20, 1])) } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + fn render (&self, to: &mut TuiOutput) -> Usually<()> { let [x, y, width, _] = to.area(); let Self { frame: _frame, pulse, ppq, usecs, focused, .. } = self; let (beats, pulses) = if *ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) }; @@ -482,9 +485,9 @@ impl Widget for TransportClock { let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000); let (minutes, seconds) = (seconds / 60, seconds % 60); let timer = format!("{bars}.{beats}.{pulses:02}"); - to.blit(&timer, x + width - timer.len() as u16 - 1, y + 0, Some(NOT_DIM))?; + to.blit(&timer, x + width - timer.len() as u16 - 1, y + 0, Some(NOT_DIM)); let timer = format!("{minutes}:{seconds:02}:{msecs:03}"); - to.blit(&timer, x + width - timer.len() as u16 - 1, y + 1, Some(NOT_DIM))?; + to.blit(&timer, x + width - timer.len() as u16 - 1, y + 1, Some(NOT_DIM)); let area = to.area(); let area = [area.x(), area.y(), area.w() + 1, area.h()]; if *focused { @@ -492,7 +495,8 @@ impl Widget for TransportClock { CORNERS.draw(to)?; to.fill_bg(area, COLOR_BG1); } - Ok(Some(area)) + //Ok(Some(area)) + Ok(()) } }