diff --git a/crates/tek/src/tui/app_sequencer.rs b/crates/tek/src/tui/app_sequencer.rs index 0a12eebd..468a0794 100644 --- a/crates/tek/src/tui/app_sequencer.rs +++ b/crates/tek/src/tui/app_sequencer.rs @@ -8,31 +8,22 @@ use PhraseCommand::*; impl TryFrom<&Arc>> for SequencerTui { type Error = Box; fn try_from (jack: &Arc>) -> Usually { - let clock = ClockModel::from(jack); - - let mut phrase = Phrase::default(); - phrase.name = "New".into(); - phrase.color = ItemColor::random().into(); - phrase.set_length(384); - - let mut phrases = PhraseListModel::default(); - let phrase = Arc::new(RwLock::new(phrase)); - phrases.phrases.push(phrase.clone()); - phrases.phrase.store(1, Ordering::Relaxed); - - let mut player = PhrasePlayerModel::from(&clock); - player.play_phrase = Some((Moment::zero(&clock.timebase), Some(phrase.clone()))); - + let phrase = Arc::new(RwLock::new(Phrase::new( + "New", + true, + 4 * clock.timebase.ppq.get() as usize, + None, + Some(ItemColor::random().into()) + ))); Ok(Self { jack: jack.clone(), - clock, - phrases, - player, + phrases: PhraseListModel::from(&phrase), editor: PhraseEditorModel::from(&phrase), + player: PhrasePlayerModel::from((&clock, &phrase)), + clock, size: Measure::new(), cursor: (0, 0), - entered: false, split: 20, midi_buf: vec![vec![];65536], note_buf: vec![], @@ -53,7 +44,6 @@ pub struct SequencerTui { pub size: Measure, pub cursor: (usize, usize), pub split: u16, - pub entered: bool, pub note_buf: Vec, pub midi_buf: Vec>>, pub focus: SequencerFocus, @@ -105,181 +95,6 @@ impl Audio for SequencerTui { } } -render!(|self: SequencerTui|{ - - let content = lay!([self.size, Tui::split_up(false, 1, - Tui::fill_xy(SequencerStatusBar::from(self)), - Tui::split_right(false, if self.size.w() > 60 { 20 } else if self.size.w() > 40 { 15 } else { 10 }, - Tui::fixed_x(20, Tui::split_down(false, 4, col!([ - PhraseSelector::play_phrase(&self.player), - PhraseSelector::next_phrase(&self.player), - ]), Tui::split_up(false, 2, - PhraseSelector::edit_phrase(&self.editor.phrase.read().unwrap()), - PhraseListView::from(self), - ))), - col!([ - Tui::fixed_y(2, TransportView::from(( - self, - self.player.play_phrase().as_ref().map(|(_,p)|p.as_ref().map(|p|p.read().unwrap().color)).flatten(), - if let SequencerFocus::Transport(_) = self.focus { - true - } else { - false - } - ))), - self.editor - ]), - ) - )]); - - pub struct PhraseSelector { - pub(crate) title: &'static str, - pub(crate) name: String, - pub(crate) color: ItemPalette, - pub(crate) time: String, - } - - // TODO: Display phrases always in order of appearance - render!(|self: PhraseSelector|Tui::fixed_y(2, col!([ - lay!(move|add|{ - add(&Tui::push_x(1, Tui::fg(TuiTheme::g(240), self.title)))?; - add(&Tui::bg(self.color.base.rgb, Tui::fill_x(Tui::inset_x(1, Tui::fill_x(Tui::at_e( - Tui::fg(self.color.lightest.rgb, &self.time)))))))?; - Ok(()) - }), - Tui::bg(self.color.base.rgb, - Tui::fg(self.color.lightest.rgb, - Tui::bold(true, self.name.clone()))), - ]))); - - impl PhraseSelector { - // beats elapsed - pub fn play_phrase (state: &T) -> Self { - let (name, color) = if let Some((_, Some(phrase))) = state.play_phrase() { - let Phrase { ref name, color, .. } = *phrase.read().unwrap(); - (name.clone(), color) - } else { - ("".to_string(), ItemPalette::from(TuiTheme::g(64))) - }; - let time = if let Some(elapsed) = state.pulses_since_start_looped() { - format!("+{:>}", state.clock().timebase.format_beats_0(elapsed)) - } else { - String::from("") - }; - Self { title: "Now:", time, name, color, } - } - // beats until switchover - pub fn next_phrase (state: &T) -> Self { - let (time, name, color) = if let Some((t, Some(phrase))) = state.next_phrase() { - let Phrase { ref name, color, .. } = *phrase.read().unwrap(); - let time = { - let target = t.pulse.get(); - let current = state.clock().playhead.pulse.get(); - if target > current { - let remaining = target - current; - format!("-{:>}", state.clock().timebase.format_beats_0(remaining)) - } else { - String::new() - } - }; - (time, name.clone(), color) - } else { - ("".into(), "".into(), TuiTheme::g(64).into()) - }; - Self { title: "Next:", time, name, color, } - } - pub fn edit_phrase (phrase: &Option>>) -> Self { - let (time, name, color) = if let Some(phrase) = phrase { - let phrase = phrase.read().unwrap(); - (format!("{}", phrase.length), phrase.name.clone(), phrase.color) - } else { - ("".to_string(), "".to_string(), ItemPalette::from(TuiTheme::g(64))) - }; - Self { title: "Editing:", time, name, color } - } - } - content -}); - -impl HasClock for SequencerTui { - fn clock (&self) -> &ClockModel { - &self.clock - } -} - -impl HasPhrases for SequencerTui { - fn phrases (&self) -> &Vec>> { - &self.phrases.phrases - } - fn phrases_mut (&mut self) -> &mut Vec>> { - &mut self.phrases.phrases - } -} - -impl HasPhraseList for SequencerTui { - fn phrases_focused (&self) -> bool { - true - } - fn phrases_entered (&self) -> bool { - true - } - fn phrases_mode (&self) -> &Option { - &self.phrases.mode - } - fn phrase_index (&self) -> usize { - self.phrases.phrase.load(Ordering::Relaxed) - } -} - -impl HasEditor for SequencerTui { - fn editor (&self) -> &PhraseEditorModel { - &self.editor - } - fn editor_focused (&self) -> bool { - false - } - fn editor_entered (&self) -> bool { - true - } -} - -impl HasFocus for SequencerTui { - type Item = SequencerFocus; - /// Get the currently focused item. - fn focused (&self) -> Self::Item { - self.focus - } - /// Get the currently focused item. - fn set_focused (&mut self, to: Self::Item) { - self.focus = to - } -} - -impl Into> for SequencerFocus { - fn into (self) -> Option { - if let Self::Transport(transport) = self { - Some(transport) - } else { - None - } - } -} - -impl From<&SequencerTui> for Option { - fn from (state: &SequencerTui) -> Self { - match state.focus { - Transport(focus) => Some(focus), - _ => None - } - } -} - -impl Handle for SequencerTui { - fn handle (&mut self, i: &TuiInput) -> Perhaps { - SequencerCommand::execute_with_state(self, i) - } -} - #[derive(Clone, Debug)] pub enum SequencerCommand { Focus(FocusCommand), @@ -378,6 +193,98 @@ pub fn to_sequencer_command (state: &SequencerTui, input: &TuiInput) -> Option 60 { 20 } else if self.size.w() > 40 { 15 } else { 10 }, + Tui::fixed_x(20, Tui::split_down(false, 4, col!([ + PhraseSelector::play_phrase(&self.player), + PhraseSelector::next_phrase(&self.player), + ]), Tui::split_up(false, 2, + PhraseSelector::edit_phrase(&self.editor.phrase.read().unwrap()), + PhraseListView::from(self), + ))), + col!([ + Tui::fixed_y(2, TransportView::from(( + self, + self.player.play_phrase().as_ref().map(|(_,p)|p.as_ref().map(|p|p.read().unwrap().color)).flatten(), + if let SequencerFocus::Transport(_) = self.focus { + true + } else { + false + } + ))), + self.editor + ]), + ) +)])); + +pub struct PhraseSelector { + pub(crate) title: &'static str, + pub(crate) name: String, + pub(crate) color: ItemPalette, + pub(crate) time: String, +} + +// TODO: Display phrases always in order of appearance +render!(|self: PhraseSelector|Tui::fixed_y(2, col!([ + lay!(move|add|{ + add(&Tui::push_x(1, Tui::fg(TuiTheme::g(240), self.title)))?; + add(&Tui::bg(self.color.base.rgb, Tui::fill_x(Tui::inset_x(1, Tui::fill_x(Tui::at_e( + Tui::fg(self.color.lightest.rgb, &self.time)))))))?; + Ok(()) + }), + Tui::bg(self.color.base.rgb, + Tui::fg(self.color.lightest.rgb, + Tui::bold(true, self.name.clone()))), +]))); + +impl PhraseSelector { + // beats elapsed + pub fn play_phrase (state: &T) -> Self { + let (name, color) = if let Some((_, Some(phrase))) = state.play_phrase() { + let Phrase { ref name, color, .. } = *phrase.read().unwrap(); + (name.clone(), color) + } else { + ("".to_string(), ItemPalette::from(TuiTheme::g(64))) + }; + let time = if let Some(elapsed) = state.pulses_since_start_looped() { + format!("+{:>}", state.clock().timebase.format_beats_0(elapsed)) + } else { + String::from("") + }; + Self { title: "Now:", time, name, color, } + } + // beats until switchover + pub fn next_phrase (state: &T) -> Self { + let (time, name, color) = if let Some((t, Some(phrase))) = state.next_phrase() { + let Phrase { ref name, color, .. } = *phrase.read().unwrap(); + let time = { + let target = t.pulse.get(); + let current = state.clock().playhead.pulse.get(); + if target > current { + let remaining = target - current; + format!("-{:>}", state.clock().timebase.format_beats_0(remaining)) + } else { + String::new() + } + }; + (time, name.clone(), color) + } else { + ("".into(), "".into(), TuiTheme::g(64).into()) + }; + Self { title: "Next:", time, name, color, } + } + pub fn edit_phrase (phrase: &Option>>) -> Self { + let (time, name, color) = if let Some(phrase) = phrase { + let phrase = phrase.read().unwrap(); + (format!("{}", phrase.length), phrase.name.clone(), phrase.color) + } else { + ("".to_string(), "".to_string(), ItemPalette::from(TuiTheme::g(64))) + }; + Self { title: "Editing:", time, name, color } + } +} + impl TransportControl for SequencerTui { fn transport_focused (&self) -> Option { match self.focus { @@ -386,3 +293,82 @@ impl TransportControl for SequencerTui { } } } + +impl HasClock for SequencerTui { + fn clock (&self) -> &ClockModel { + &self.clock + } +} + +impl HasPhrases for SequencerTui { + fn phrases (&self) -> &Vec>> { + &self.phrases.phrases + } + fn phrases_mut (&mut self) -> &mut Vec>> { + &mut self.phrases.phrases + } +} + +impl HasPhraseList for SequencerTui { + fn phrases_focused (&self) -> bool { + true + } + fn phrases_entered (&self) -> bool { + true + } + fn phrases_mode (&self) -> &Option { + &self.phrases.mode + } + fn phrase_index (&self) -> usize { + self.phrases.phrase.load(Ordering::Relaxed) + } +} + +impl HasEditor for SequencerTui { + fn editor (&self) -> &PhraseEditorModel { + &self.editor + } + fn editor_focused (&self) -> bool { + false + } + fn editor_entered (&self) -> bool { + true + } +} + +impl HasFocus for SequencerTui { + type Item = SequencerFocus; + /// Get the currently focused item. + fn focused (&self) -> Self::Item { + self.focus + } + /// Get the currently focused item. + fn set_focused (&mut self, to: Self::Item) { + self.focus = to + } +} + +impl Into> for SequencerFocus { + fn into (self) -> Option { + if let Self::Transport(transport) = self { + Some(transport) + } else { + None + } + } +} + +impl From<&SequencerTui> for Option { + fn from (state: &SequencerTui) -> Self { + match state.focus { + Transport(focus) => Some(focus), + _ => None + } + } +} + +impl Handle for SequencerTui { + fn handle (&mut self, i: &TuiInput) -> Perhaps { + SequencerCommand::execute_with_state(self, i) + } +} diff --git a/crates/tek/src/tui/phrase_list.rs b/crates/tek/src/tui/phrase_list.rs index d014e134..5d862dcd 100644 --- a/crates/tek/src/tui/phrase_list.rs +++ b/crates/tek/src/tui/phrase_list.rs @@ -42,6 +42,15 @@ impl Default for PhraseListModel { } } +impl From<&Arc>> for PhraseListModel { + fn from (phrase: &Arc>) -> Self { + let mut model = Self::default(); + model.phrases.push(phrase.clone()); + model.phrase.store(1, Ordering::Relaxed); + model + } +} + impl HasPhrases for PhraseListModel { fn phrases (&self) -> &Vec>> { &self.phrases diff --git a/crates/tek/src/tui/phrase_player.rs b/crates/tek/src/tui/phrase_player.rs index e0928a40..0d70dd70 100644 --- a/crates/tek/src/tui/phrase_player.rs +++ b/crates/tek/src/tui/phrase_player.rs @@ -57,6 +57,14 @@ impl From<&ClockModel> for PhrasePlayerModel { } } +impl From<(&ClockModel, &Arc>)> for PhrasePlayerModel { + fn from ((clock, phrase): (&ClockModel, &Arc>)) -> Self { + let mut model = Self::from(clock); + model.play_phrase = Some((Moment::zero(&clock.timebase), Some(phrase.clone()))); + model + } +} + impl HasClock for PhrasePlayerModel { fn clock (&self) -> &ClockModel { &self.clock