From fc0a398702dd0d3ba4dd6b4856a188899e6a8782 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 27 Dec 2024 18:05:35 +0100 Subject: [PATCH] add GrooveboxStatus and try to autostretch sampler --- crates/tek/src/core/color.rs | 2 - crates/tek/src/midi/midi_note.rs | 21 ++++++-- crates/tek/src/tui/app_groovebox.rs | 19 ++++--- crates/tek/src/tui/app_sampler.rs | 47 ++++++++++------- crates/tek/src/tui/phrase_editor.rs | 7 ++- crates/tek/src/tui/piano_h.rs | 6 ++- crates/tek/src/tui/piano_h/piano_h_keys.rs | 12 +++-- crates/tek/src/tui/status.rs | 1 + crates/tek/src/tui/status/status_groovebox.rs | 52 +++++++++++++++++++ 9 files changed, 129 insertions(+), 38 deletions(-) create mode 100644 crates/tek/src/tui/status/status_groovebox.rs diff --git a/crates/tek/src/core/color.rs b/crates/tek/src/core/color.rs index a935c3fa..193efebe 100644 --- a/crates/tek/src/core/color.rs +++ b/crates/tek/src/core/color.rs @@ -4,14 +4,12 @@ pub use ratatui::prelude::Color; pub trait HasColor { fn color (&self) -> ItemColor; - fn color_mut (&mut self) -> &mut ItemColor; } #[macro_export] macro_rules! has_color { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { impl $(<$($L),*$($T $(: $U)?),*>)? HasColor for $Struct $(<$($L),*$($T),*>)? { fn color (&$self) -> ItemColor { $cb } - fn color_mut (&mut $self) -> &mut ItemColor { &mut $cb } } } } diff --git a/crates/tek/src/midi/midi_note.rs b/crates/tek/src/midi/midi_note.rs index 8452d1fb..e13127cc 100644 --- a/crates/tek/src/midi/midi_note.rs +++ b/crates/tek/src/midi/midi_note.rs @@ -121,6 +121,7 @@ pub struct MidiPointModel { /// Length of note that will be inserted, in pulses pub note_len: Arc, } + impl Default for MidiPointModel { fn default () -> Self { Self { @@ -130,20 +131,32 @@ impl Default for MidiPointModel { } } } -pub trait MidiPoint { + +pub trait NotePoint { fn note_len (&self) -> usize; fn set_note_len (&self, x: usize); fn note_point (&self) -> usize; fn set_note_point (&self, x: usize); - fn time_point (&self) -> usize; - fn set_time_point (&self, x: usize); fn note_end (&self) -> usize { self.note_point() + self.note_len() } } -impl MidiPoint for MidiPointModel { + +pub trait TimePoint { + fn time_point (&self) -> usize; + fn set_time_point (&self, x: usize); +} + +pub trait MidiPoint: NotePoint + TimePoint {} + +impl MidiPoint for T {} + +impl NotePoint for MidiPointModel { fn note_len (&self) -> usize { self.note_len.load(Relaxed)} fn set_note_len (&self, x: usize) { self.note_len.store(x, Relaxed) } fn note_point (&self) -> usize { self.note_point.load(Relaxed).min(127) } fn set_note_point (&self, x: usize) { self.note_point.store(x.min(127), Relaxed) } +} + +impl TimePoint for MidiPointModel { fn time_point (&self) -> usize { self.time_point.load(Relaxed) } fn set_time_point (&self, x: usize) { self.time_point.store(x, Relaxed) } } diff --git a/crates/tek/src/tui/app_groovebox.rs b/crates/tek/src/tui/app_groovebox.rs index fad1c95f..05f1ece1 100644 --- a/crates/tek/src/tui/app_groovebox.rs +++ b/crates/tek/src/tui/app_groovebox.rs @@ -3,6 +3,7 @@ use super::*; use KeyCode::{Char, Delete, Tab, Up, Down, Left, Right}; pub struct GrooveboxTui { + pub size: Measure, pub sequencer: SequencerTui, pub sampler: SamplerTui, pub split: u16, @@ -24,6 +25,7 @@ from_jack!(|jack|GrooveboxTui { sampler: SamplerTui::try_from(jack)?, split: 16, focus: GrooveboxFocus::Sequencer, + size: Measure::new(), } }); @@ -34,13 +36,16 @@ pub enum GrooveboxFocus { audio!(|self:GrooveboxTui,_client,_process|Control::Continue); -render!(|self:GrooveboxTui|Bsp::n( - Fixed::h(2, SequencerStatus::from(&self.sequencer)), - Fill::h(Bsp::s( - Tui::min_y(30, &self.sequencer), - Fill::h(&self.sampler), - )), -)); +render!(|self:GrooveboxTui|Fill::wh(Bsp::n( + Fixed::h(2, GrooveboxStatus::from(self)), + Fill::h(lay!([ + Fill::h(&self.size), + Fill::h(Bsp::s( + Tui::min_y(20, &self.sequencer), + Tui::min_y(20, &self.sampler), + )) + ])), +))); pub enum GrooveboxCommand { Sequencer(SequencerCommand), diff --git a/crates/tek/src/tui/app_sampler.rs b/crates/tek/src/tui/app_sampler.rs index 04fc9db1..ed10f73d 100644 --- a/crates/tek/src/tui/app_sampler.rs +++ b/crates/tek/src/tui/app_sampler.rs @@ -24,6 +24,7 @@ pub struct SamplerTui { pub size: Measure, /// Lowest note displayed pub note_lo: AtomicUsize, + color: ItemColor } render!(|self: SamplerTui|{ @@ -32,28 +33,18 @@ render!(|self: SamplerTui|{ let fg = TuiTheme::g(200); let bg = TuiTheme::g(50); let border = Fill::wh(Outer(Style::default().fg(fg).bg(bg))); - let with_border = |x|lay!([border, Tui::inset_xy(1, 1, &x)]); - with_border(Fill::wh(Bsp::s( - "kyp", + let with_border = |x|lay!([ + border, + Tui::inset_xy(1, 1, Fill::wh(&x)) + ]); + Tui::bg(bg, Fill::wh(with_border(Bsp::s( + "Sampler", Bsp::e( Fixed::w(keys_width, keys()), - Fill::wh(lay!([&self.size, Fill::wh("nymka")])), + Fill::wh(lay!([&self.size, Fill::wh("Sample")])), ), - ))) + )))) }); - -struct SamplerKeys<'a>(&'a SamplerTui); -has_color!(|self: SamplerKeys<'a>|ItemColor::default()); -render!(|self: SamplerKeys<'a>|render(|to|Ok(render_keys_v::(to, self)))); -impl<'a> NoteRange for SamplerKeys<'a> { - fn note_lo (&self) -> &AtomicUsize { &self.0.note_lo } - fn note_axis (&self) -> &AtomicUsize { &self.0.size.y } -} - -pub enum SamplerMode { - // Load sample from path - Import(usize, FileBrowser), -} from_jack!(|jack|SamplerTui{ let midi_in = jack.read().unwrap().client().register_port("in", MidiIn::default())?; let audio_outs = vec![ @@ -66,6 +57,7 @@ from_jack!(|jack|SamplerTui{ mode: None, size: Measure::new(), note_lo: 36.into(), + color: ItemColor::default(), state: Sampler { jack: jack.clone(), name: "Sampler".into(), @@ -79,6 +71,25 @@ from_jack!(|jack|SamplerTui{ }, } }); + +struct SamplerKeys<'a>(&'a SamplerTui); +has_color!(|self: SamplerKeys<'a>|self.0.color); +render!(|self: SamplerKeys<'a>|render(|to|Ok(render_keys_v(to, self)))); +impl<'a> NoteRange for SamplerKeys<'a> { + fn note_lo (&self) -> &AtomicUsize { &self.0.note_lo } + fn note_axis (&self) -> &AtomicUsize { &self.0.size.y } +} +impl<'a> NotePoint for SamplerKeys<'a> { + fn note_len (&self) -> usize {0/*TODO*/} + fn set_note_len (&self, x: usize) {} + fn note_point (&self) -> usize {0/*TODO*/} + fn set_note_point (&self, x: usize) {} +} + +pub enum SamplerMode { + // Load sample from path + Import(usize, FileBrowser), +} handle!(|self:SamplerTui,input|SamplerCommand::execute_with_state(self, input)); pub enum SamplerCommand { Import(FileBrowserCommand), diff --git a/crates/tek/src/tui/phrase_editor.rs b/crates/tek/src/tui/phrase_editor.rs index 30f56684..f61779b1 100644 --- a/crates/tek/src/tui/phrase_editor.rs +++ b/crates/tek/src/tui/phrase_editor.rs @@ -140,11 +140,14 @@ impl NoteRange for MidiEditorModel { fn note_axis (&self) -> &AtomicUsize { self.mode.note_axis() } } -impl MidiPoint for MidiEditorModel { - fn note_len (&self) -> usize { self.mode.note_len()} +impl NotePoint for MidiEditorModel { + fn note_len (&self) -> usize { self.mode.note_len() } fn set_note_len (&self, x: usize) { self.mode.set_note_len(x) } fn note_point (&self) -> usize { self.mode.note_point() } fn set_note_point (&self, x: usize) { self.mode.set_note_point(x) } +} + +impl TimePoint for MidiEditorModel { fn time_point (&self) -> usize { self.mode.time_point() } fn set_time_point (&self, x: usize) { self.mode.set_time_point(x) } } diff --git a/crates/tek/src/tui/piano_h.rs b/crates/tek/src/tui/piano_h.rs index 58645814..3ed7840c 100644 --- a/crates/tek/src/tui/piano_h.rs +++ b/crates/tek/src/tui/piano_h.rs @@ -141,11 +141,13 @@ impl NoteRange for PianoHorizontal { fn note_lo (&self) -> &AtomicUsize { self.range.note_lo() } fn note_axis (&self) -> &AtomicUsize { self.range.note_axis() } } -impl MidiPoint for PianoHorizontal { - fn note_len (&self) -> usize { self.point.note_len()} +impl NotePoint for PianoHorizontal { + fn note_len (&self) -> usize { self.point.note_len() } fn set_note_len (&self, x: usize) { self.point.set_note_len(x) } fn note_point (&self) -> usize { self.point.note_point() } fn set_note_point (&self, x: usize) { self.point.set_note_point(x) } +} +impl TimePoint for PianoHorizontal { fn time_point (&self) -> usize { self.point.time_point() } fn set_time_point (&self, x: usize) { self.point.set_time_point(x) } } diff --git a/crates/tek/src/tui/piano_h/piano_h_keys.rs b/crates/tek/src/tui/piano_h/piano_h_keys.rs index 0fd34c09..2dbab758 100644 --- a/crates/tek/src/tui/piano_h/piano_h_keys.rs +++ b/crates/tek/src/tui/piano_h/piano_h_keys.rs @@ -2,14 +2,20 @@ use crate::*; use super::note_y_iter; pub struct PianoHorizontalKeys<'a>(pub(crate) &'a PianoHorizontal); -render!(|self: PianoHorizontalKeys<'a>|render(|to|Ok(render_keys_v::(to, self)))); +render!(|self: PianoHorizontalKeys<'a>|render(|to|Ok(render_keys_v(to, self)))); has_color!(|self: PianoHorizontalKeys<'a>|self.0.color.base); impl<'a> NoteRange for PianoHorizontalKeys<'a> { fn note_lo (&self) -> &AtomicUsize { &self.0.note_lo() } fn note_axis (&self) -> &AtomicUsize { &self.0.note_axis() } } +impl<'a> NotePoint for PianoHorizontalKeys<'a> { + fn note_len (&self) -> usize { self.0.note_len() } + fn set_note_len (&self, x: usize) { self.0.set_note_len(x) } + fn note_point (&self) -> usize { self.0.note_point() } + fn set_note_point (&self, x: usize) { self.0.set_note_point(x) } +} -pub fn render_keys_v (to: &mut E::Output, state: &T) { +pub fn render_keys_v (to: &mut TuiOutput, state: &T) { let color = state.color(); let note_lo = state.note_lo().get(); let note_hi = state.note_hi(); @@ -17,7 +23,7 @@ pub fn render_keys_v (to: &mut E::Output, s let [x, y0, w, h] = to.area().xywh(); let key_style = Some(Style::default().fg(Color::Rgb(192, 192, 192)).bg(Color::Rgb(0, 0, 0))); let off_style = Some(Style::default().fg(TuiTheme::g(160))); - let on_style = Some(Style::default().fg(TuiTheme::g(255)).bg(color.light.rgb).bold()); + let on_style = Some(Style::default().fg(TuiTheme::g(255)).bg(color.rgb).bold()); for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { to.blit(&to_key(note), x, screen_y, key_style); if note > 127 { diff --git a/crates/tek/src/tui/status.rs b/crates/tek/src/tui/status.rs index 8359e9da..bafbe953 100644 --- a/crates/tek/src/tui/status.rs +++ b/crates/tek/src/tui/status.rs @@ -1,3 +1,4 @@ mod status_arranger; pub(crate) use self::status_arranger::*; mod status_edit; pub(crate) use self::status_edit::*; mod status_sequencer; pub(crate) use self::status_sequencer::*; +mod status_groovebox; pub(crate) use self::status_groovebox::*; diff --git a/crates/tek/src/tui/status/status_groovebox.rs b/crates/tek/src/tui/status/status_groovebox.rs new file mode 100644 index 00000000..65d77a2d --- /dev/null +++ b/crates/tek/src/tui/status/status_groovebox.rs @@ -0,0 +1,52 @@ +use crate::*; + +/// Status bar for sequencer app +#[derive(Clone)] +pub struct GrooveboxStatus { + pub(crate) width: usize, + pub(crate) cpu: Option, + pub(crate) size: String, + pub(crate) res: String, + pub(crate) playing: bool, +} +from!(|state:&GrooveboxTui|GrooveboxStatus = { + let samples = state.sequencer.clock.chunk.load(Relaxed); + let rate = state.sequencer.clock.timebase.sr.get(); + let buffer = samples as f64 / rate; + let width = state.size.w(); + Self { + width, + playing: state.sequencer.clock.is_rolling(), + cpu: state.sequencer.perf.percentage().map(|cpu|format!("│{cpu:.01}%")), + size: format!("{}x{}│", width, state.size.h()), + res: format!("│{}s│{:.1}kHz│{:.1}ms│", samples, rate / 1000., buffer * 1000.), + } +}); +render!(|self: GrooveboxStatus|Fixed::h(2, lay!([ + Self::help(), + Fill::wh(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))), +]))); +impl GrooveboxStatus { + fn help () -> impl Render { + let single = |binding, command|row!([" ", col!([ + Tui::fg(TuiTheme::yellow(), binding), + command + ])]); + let double = |(b1, c1), (b2, c2)|col!([ + row!([" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,]), + row!([" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,]), + ]); + Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!([ + single("SPACE", "play/pause"), + double(("▲▼▶◀", "cursor"), ("Ctrl", "scroll"), ), + double(("a", "append"), ("s", "set note"),), + double((",.", "length"), ("<>", "triplet"), ), + double(("[]", "phrase"), ("{}", "order"), ), + double(("q", "enqueue"), ("e", "edit"), ), + double(("c", "color"), ("", ""),), + ])) + } + fn stats (&self) -> impl Render + use<'_> { + row!([&self.cpu, &self.res, &self.size]) + } +}