From 274316ccddaef48c24123fe12599fb9773bc0c58 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 21 Dec 2024 20:10:46 +0100 Subject: [PATCH] wip: try to figure out saner semantics for arranger render modes (of which there are 1) --- crates/tek/src/tui/app_arranger.rs | 124 +++++++++++++------------- crates/tek/src/tui/app_transport.rs | 26 ------ crates/tek/src/tui/arranger_mode_v.rs | 121 ++++++++++++------------- crates/tek/src/tui/tui_border.rs | 18 ++++ 4 files changed, 140 insertions(+), 149 deletions(-) diff --git a/crates/tek/src/tui/app_arranger.rs b/crates/tek/src/tui/app_arranger.rs index 2816e8dc..feee07ea 100644 --- a/crates/tek/src/tui/app_arranger.rs +++ b/crates/tek/src/tui/app_arranger.rs @@ -19,6 +19,57 @@ pub struct ArrangerTui { pub perf: PerfModel, pub show_pool: bool, } +impl ArrangerTui { + pub fn selected (&self) -> ArrangerSelection { + self.selected + } + pub fn selected_mut (&mut self) -> &mut ArrangerSelection { + &mut self.selected + } + pub fn activate (&mut self) -> Usually<()> { + if let ArrangerSelection::Scene(s) = self.selected { + for (t, track) in self.tracks.iter_mut().enumerate() { + let phrase = self.scenes[s].clips[t].clone(); + if track.player.play_phrase.is_some() || phrase.is_some() { + track.player.enqueue_next(phrase.as_ref()); + } + } + if self.clock().is_stopped() { + self.clock().play_from(Some(0))?; + } + } else if let ArrangerSelection::Clip(t, s) = self.selected { + let phrase = self.scenes()[s].clips[t].clone(); + self.tracks_mut()[t].player.enqueue_next(phrase.as_ref()); + }; + Ok(()) + } + pub fn selected_phrase (&self) -> Option>> { + self.selected_scene()?.clips.get(self.selected.track()?)?.clone() + } + pub fn toggle_loop (&mut self) { + if let Some(phrase) = self.selected_phrase() { + phrase.write().unwrap().toggle_loop() + } + } + pub fn randomize_color (&mut self) { + match self.selected { + ArrangerSelection::Mix => { + self.color = ItemPalette::random() + }, + ArrangerSelection::Track(t) => { + self.tracks_mut()[t].color = ItemPalette::random() + }, + ArrangerSelection::Scene(s) => { + self.scenes_mut()[s].color = ItemPalette::random() + }, + ArrangerSelection::Clip(t, s) => { + if let Some(phrase) = &self.scenes_mut()[s].clips[t] { + phrase.write().unwrap().color = ItemPalette::random(); + } + } + } + } +} from_jack!(|jack| ArrangerTui { let clock = ClockModel::from(jack); let phrase = Arc::new(RwLock::new(Phrase::new( @@ -43,23 +94,20 @@ from_jack!(|jack| ArrangerTui { jack: jack.clone(), } }); +impl ArrangerTui { + fn render_mode (state: &Self) -> impl Render { + match state.mode { + ArrangerMode::H => todo!("horizontal arranger"), + ArrangerMode::V(factor) => Self::render_mode_v(state, factor), + } + } +} render!(|self: ArrangerTui|{ let arranger = ||lay!(|add|{ let color = self.color; add(&Fill::wh(Tui::bg(color.darkest.rgb, ())))?; add(&Fill::wh(Lozenge(Style::default().fg(color.light.rgb).bg(color.darker.rgb))))?; - match self.mode { - ArrangerMode::H => todo!("horizontal arranger"), - ArrangerMode::V(factor) => add(&lay!(![ - ArrangerVColSep::from(self), - ArrangerVRowSep::from((self, factor)), - col!(![ - ArrangerVHead::from(self), - ArrangerVBody::from((self, factor)), - ]), - ArrangerVCursor::from((self, factor)), - ])), - } + add(&Self::render_mode(self)) }); let play = Fixed::wh(5, 2, PlayPause(self.clock.is_rolling())); let transport = TransportView::from((self, None, true)); @@ -261,6 +309,7 @@ pub enum ArrangerMode { /// Tracks are rows H, } +render!(|self: ArrangerMode|{}); /// Arranger display mode can be cycled impl ArrangerMode { @@ -279,57 +328,6 @@ impl ArrangerMode { fn any_size (_: E::Size) -> Perhaps{ Ok(Some([0.into(),0.into()].into())) } -impl ArrangerTui { - pub fn selected (&self) -> ArrangerSelection { - self.selected - } - pub fn selected_mut (&mut self) -> &mut ArrangerSelection { - &mut self.selected - } - pub fn activate (&mut self) -> Usually<()> { - if let ArrangerSelection::Scene(s) = self.selected { - for (t, track) in self.tracks.iter_mut().enumerate() { - let phrase = self.scenes[s].clips[t].clone(); - if track.player.play_phrase.is_some() || phrase.is_some() { - track.player.enqueue_next(phrase.as_ref()); - } - } - if self.clock().is_stopped() { - self.clock().play_from(Some(0))?; - } - } else if let ArrangerSelection::Clip(t, s) = self.selected { - let phrase = self.scenes()[s].clips[t].clone(); - self.tracks_mut()[t].player.enqueue_next(phrase.as_ref()); - }; - Ok(()) - } - pub fn selected_phrase (&self) -> Option>> { - self.selected_scene()?.clips.get(self.selected.track()?)?.clone() - } - pub fn toggle_loop (&mut self) { - if let Some(phrase) = self.selected_phrase() { - phrase.write().unwrap().toggle_loop() - } - } - pub fn randomize_color (&mut self) { - match self.selected { - ArrangerSelection::Mix => { - self.color = ItemPalette::random() - }, - ArrangerSelection::Track(t) => { - self.tracks_mut()[t].color = ItemPalette::random() - }, - ArrangerSelection::Scene(s) => { - self.scenes_mut()[s].color = ItemPalette::random() - }, - ArrangerSelection::Clip(t, s) => { - if let Some(phrase) = &self.scenes_mut()[s].clips[t] { - phrase.write().unwrap().color = ItemPalette::random(); - } - } - } - } -} //impl Command for ArrangerClipCommand { //fn execute (self, state: &mut T) -> Perhaps { diff --git a/crates/tek/src/tui/app_transport.rs b/crates/tek/src/tui/app_transport.rs index 7c058152..6014a9bf 100644 --- a/crates/tek/src/tui/app_transport.rs +++ b/crates/tek/src/tui/app_transport.rs @@ -34,21 +34,16 @@ impl std::fmt::Debug for TransportTui { pub struct TransportView { color: ItemPalette, focused: bool, - sr: String, bpm: String, ppq: String, beat: String, - global_sample: String, global_second: String, - started: bool, - current_sample: f64, current_second: f64, } - impl From<(&T, Option, bool)> for TransportView { fn from ((state, color, focused): (&T, Option, bool)) -> Self { let clock = state.clock(); @@ -84,19 +79,14 @@ impl From<(&T, Option, bool)> for TransportView { } } } - - render!(|self: TransportView|{ - let color = self.color; - struct Field<'a>(&'a str, &'a str, &'a ItemPalette); render!(|self: Field<'a>|row!([ Tui::fg_bg(self.2.lightest.rgb, self.2.darkest.rgb, Tui::bold(true, self.0)), Tui::fg_bg(self.2.lighter.rgb, self.2.darkest.rgb, "│"), Tui::fg_bg(self.2.lighter.rgb, self.2.base.rgb, format!("{:>10}", self.1)), ])); - Tui::bg(color.base.rgb, Fill::w(row!([ //PlayPause(self.started), " ", col!([ @@ -109,9 +99,7 @@ render!(|self: TransportView|{ Field(" Smpl", format!("{:.1}k", self.current_sample).as_str(), &color), ]), ]))) - }); - pub struct PlayPause(pub bool); render!(|self: PlayPause|Tui::bg( if self.0{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)}, @@ -127,7 +115,6 @@ render!(|self: PlayPause|Tui::bg( ]))) })) )); - impl HasFocus for TransportTui { type Item = TransportFocus; fn focused (&self) -> Self::Item { @@ -137,7 +124,6 @@ impl HasFocus for TransportTui { self.focus = to } } - /// Which item of the transport toolbar is focused #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum TransportFocus { @@ -157,7 +143,6 @@ impl FocusWrap for TransportFocus { lay!([corners, /*highlight,*/ *content]) } } - impl FocusWrap for Option { fn wrap <'a, W: Render> (self, focus: TransportFocus, content: &'a W) -> impl Render + 'a @@ -168,41 +153,34 @@ impl FocusWrap for Option { lay!([corners, /*highlight,*/ *content]) } } - pub trait TransportControl: HasClock + { fn transport_focused (&self) -> Option; } - impl TransportControl for TransportTui { fn transport_focused (&self) -> Option { Some(self.focus) } } - #[derive(Clone, Debug, PartialEq)] pub enum TransportCommand { Focus(FocusCommand), Clock(ClockCommand), } - command!(|self:TransportCommand,state:TransportTui|match self { //Self::Focus(cmd) => cmd.execute(state)?.map(Self::Focus), Self::Clock(cmd) => cmd.execute(state)?.map(Self::Clock), _ => unreachable!(), }); - //command!(|self:TransportFocus,state:TransportTui|{ //if let FocusCommand::Set(to) = self { state.set_focused(to); } //Ok(None) //}); - impl InputToCommand for TransportCommand { fn input_to_command (state: &TransportTui, input: &TuiInput) -> Option { to_transport_command(state, input) .or_else(||to_focus_command(input).map(TransportCommand::Focus)) } } - pub fn to_transport_command (state: &T, input: &TuiInput) -> Option where T: TransportControl, @@ -246,7 +224,6 @@ where } }) } - fn to_bpm_command (input: &TuiInput, bpm: f64) -> Option { Some(match input.event() { key_pat!(Char(',')) => Clock(SetBpm(bpm - 1.0)), @@ -256,7 +233,6 @@ fn to_bpm_command (input: &TuiInput, bpm: f64) -> Option { _ => return None, }) } - fn to_quant_command (input: &TuiInput, quant: &Quantize) -> Option { Some(match input.event() { key_pat!(Char(',')) => Clock(SetQuant(quant.prev())), @@ -266,7 +242,6 @@ fn to_quant_command (input: &TuiInput, quant: &Quantize) -> Option return None, }) } - fn to_sync_command (input: &TuiInput, sync: &LaunchSync) -> Option { Some(match input.event() { key_pat!(Char(',')) => Clock(SetSync(sync.prev())), @@ -276,7 +251,6 @@ fn to_sync_command (input: &TuiInput, sync: &LaunchSync) -> Option return None, }) } - fn to_seek_command (input: &TuiInput) -> Option { Some(match input.event() { key_pat!(Char(',')) => todo!("transport seek bar"), diff --git a/crates/tek/src/tui/arranger_mode_v.rs b/crates/tek/src/tui/arranger_mode_v.rs index 44aa0538..20ccfac3 100644 --- a/crates/tek/src/tui/arranger_mode_v.rs +++ b/crates/tek/src/tui/arranger_mode_v.rs @@ -1,6 +1,48 @@ use crate::*; const HEADER_H: u16 = 3; const SCENES_W_OFFSET: u16 = 3; +impl ArrangerTui { + pub fn render_mode_v (state: &ArrangerTui, factor: usize) -> impl Render { + lay!([ + ArrangerVColSep::from(state), ArrangerVRowSep::from((state, factor)), + col!([ArrangerVHead::from(state), ArrangerVBody::from((state, factor))]), + ArrangerVCursor::from((state, factor)), + ]) + } +} +pub struct ArrangerVColSep { fg: Color, cols: Vec<(usize, usize)>, scenes_w: u16, } +from!(|state:&ArrangerTui|ArrangerVColSep = Self { + fg: TuiTheme::separator_fg(false), + cols: ArrangerTrack::widths(state.tracks()), + scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(state.scenes()) as u16, +}); +render!(|self: ArrangerVColSep|render(move|to: &mut TuiOutput|{ + let style = Some(Style::default().fg(self.fg)); + Ok(for x in self.cols.iter().map(|col|col.1) { + let x = self.scenes_w + to.area().x() + x as u16; + for y in to.area().y()..to.area().y2() { + to.blit(&"▎", x, y, style); + } + }) +})); +pub struct ArrangerVRowSep { fg: Color, rows: Vec<(usize, usize)>, } +from!(|args:(&ArrangerTui, usize)|ArrangerVRowSep = Self { + fg: TuiTheme::separator_fg(false), + rows: ArrangerScene::ppqs(args.0.scenes(), args.1), +}); +render!(|self: ArrangerVRowSep|render(move|to: &mut TuiOutput|{ + Ok(for y in self.rows.iter().map(|row|row.1) { + let y = to.area().y() + (y / PPQ) as u16 + 1; + if y >= to.buffer.area.height { break } + for x in to.area().x()..to.area().x2().saturating_sub(2) { + 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 = self.fg; + } + } + }) +})); pub struct ArrangerVHead<'a> { scenes_w: u16, timebase: &'a Arc, @@ -46,74 +88,34 @@ impl<'a> ArrangerVHead<'a> { } /// beats elapsed fn format_elapsed (track: &ArrangerTrack, timebase: &Arc) -> impl Render { + let mut result = String::new(); if let Some((_, Some(phrase))) = track.player.play_phrase().as_ref() { let length = phrase.read().unwrap().length; let elapsed = track.player.pulses_since_start().unwrap(); let elapsed = timebase.format_beats_1_short( (elapsed as usize % length) as f64 ); - format!("+{elapsed:>}") - } else { - String::new() + result = format!("+{elapsed:>}") } + result } /// beats until switchover fn format_until_next (track: &ArrangerTrack, current: &Arc) -> Option> { let timebase = ¤t.timebase; - track.player.next_phrase().as_ref().map(|(t, _)|{ + let mut result = String::new(); + if let Some((t, _)) = track.player.next_phrase().as_ref() { let target = t.pulse.get(); let current = current.pulse.get(); if target > current { let remaining = target - current; - format!("-{:>}", timebase.format_beats_0_short(remaining)) - } else { - String::new() + result = format!("-{:>}", timebase.format_beats_0_short(remaining)) } - }) + } + Some(result) } } -pub struct ArrangerVColSep { - cols: Vec<(usize, usize)>, - scenes_w: u16, - sep_fg: Color, -} -from!(|state:&ArrangerTui|ArrangerVColSep = Self { - cols: ArrangerTrack::widths(state.tracks()), - scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(state.scenes()) as u16, - sep_fg: TuiTheme::separator_fg(false), -}); -render!(|self: ArrangerVColSep|render(move|to: &mut TuiOutput|{ - let style = Some(Style::default().fg(self.sep_fg)); - Ok(for x in self.cols.iter().map(|col|col.1) { - let x = self.scenes_w + to.area().x() + x as u16; - for y in to.area().y()..to.area().y2() { - to.blit(&"▎", x, y, style); - } - }) -})); -pub struct ArrangerVRowSep { - rows: Vec<(usize, usize)>, - sep_fg: Color, -} -from!(|args:(&ArrangerTui, usize)|ArrangerVRowSep = Self { - rows: ArrangerScene::ppqs(args.0.scenes(), args.1), - sep_fg: TuiTheme::separator_fg(false), -}); -render!(|self: ArrangerVRowSep|render(move|to: &mut TuiOutput|{ - Ok(for y in self.rows.iter().map(|row|row.1) { - let y = to.area().y() + (y / PPQ) as u16 + 1; - if y >= to.buffer.area.height { break } - for x in to.area().x()..to.area().x2().saturating_sub(2) { - 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 = self.sep_fg; - } - } - }) -})); pub struct ArrangerVCursor { cols: Vec<(usize, usize)>, rows: Vec<(usize, usize)>, @@ -139,9 +141,9 @@ render!(|self: ArrangerVCursor|render(move|to: &mut TuiOutput|{ area.w(), (self.rows[s].0 / PPQ) as u16 ]; let get_clip_area = |t: usize, s: usize| [ - self.scenes_w + area.x() + self.cols[t].1 as u16, + (self.scenes_w + area.x() + self.cols[t].1 as u16).saturating_sub(1), HEADER_H + area.y() + (self.rows[s].1/PPQ) as u16, - self.cols[t].0 as u16, + self.cols[t].0 as u16 + 2, (self.rows[s].0 / PPQ) as u16 ]; let mut track_area: Option<[u16;4]> = None; @@ -177,21 +179,20 @@ render!(|self: ArrangerVCursor|render(move|to: &mut TuiOutput|{ to.render_in(if let Some(clip_area) = clip_area { clip_area } else if let Some(track_area) = track_area { track_area.clip_h(HEADER_H) } else if let Some(scene_area) = scene_area { scene_area.clip_w(self.scenes_w) } - else { area.clip_w(self.scenes_w).clip_h(HEADER_H) }, &CORNERS)? + else { area.clip_w(self.scenes_w).clip_h(HEADER_H) }, &RETICLE)? }) })); - pub struct ArrangerVBody<'a> { - size: &'a Measure, - scenes: &'a Vec, - tracks: &'a Vec, - rows: Vec<(usize, usize)>, + size: &'a Measure, + scenes: &'a Vec, + tracks: &'a Vec, + rows: Vec<(usize, usize)>, } from!(<'a>|args:(&'a ArrangerTui, usize)|ArrangerVBody<'a> = Self { - size: &args.0.size, - scenes: &args.0.scenes, - tracks: &args.0.tracks, - rows: ArrangerScene::ppqs(args.0.scenes(), args.1), + size: &args.0.size, + scenes: &args.0.scenes, + tracks: &args.0.tracks, + rows: ArrangerScene::ppqs(args.0.scenes(), args.1), }); render!(|self: ArrangerVBody<'a>|Fixed::h( (self.size.h() as u16).saturating_sub(HEADER_H), diff --git a/crates/tek/src/tui/tui_border.rs b/crates/tek/src/tui/tui_border.rs index 14abf9f0..ae12a207 100644 --- a/crates/tek/src/tui/tui_border.rs +++ b/crates/tek/src/tui/tui_border.rs @@ -206,6 +206,16 @@ border! { const N0: &'static str = "⎴"; const S0: &'static str = "⎵"; fn style (&self) -> Option