diff --git a/crates/tek_api/src/api_clock.rs b/crates/tek_api/src/api_clock.rs index e5300f8a..fad5d82b 100644 --- a/crates/tek_api/src/api_clock.rs +++ b/crates/tek_api/src/api_clock.rs @@ -47,7 +47,7 @@ pub trait ClockApi: Send + Sync { /// Handle to JACK transport fn transport_handle (&self) -> &Arc; /// Playback state - fn transport_state (&self) -> &Arc>>; + fn transport_state (&self) -> &Arc>>; /// Global sample and usec at which playback started fn transport_offset (&self) -> &Arc>>; diff --git a/crates/tek_tui/src/tui_apps.rs b/crates/tek_tui/src/tui_apps.rs index 17daf512..5bd4a9f6 100644 --- a/crates/tek_tui/src/tui_apps.rs +++ b/crates/tek_tui/src/tui_apps.rs @@ -48,4 +48,5 @@ pub struct ArrangerTui { pub midi_buf: Vec>>, pub editor: PhraseEditorModel, pub focus: FocusState>, + pub perf: PerfModel, } diff --git a/crates/tek_tui/src/tui_content.rs b/crates/tek_tui/src/tui_content.rs index 1c1e1ed3..55e0c102 100644 --- a/crates/tek_tui/src/tui_content.rs +++ b/crates/tek_tui/src/tui_content.rs @@ -5,7 +5,7 @@ impl<'a, T: TransportViewState> Content for TransportView<'a, T> { fn content (&self) -> impl Widget { let state = self.0; let selected = state.transport_selected(); - lay!( + row!( selected.wrap(TransportFocus::PlayPause, &Styled( None, match *state.transport_state().read().unwrap() { @@ -14,27 +14,22 @@ impl<'a, T: TransportViewState> Content for TransportView<'a, T> { Some(TransportState::Stopped) => "⏹ STOPPED", _ => "???", } - ).min_xy(11, 2).push_x(1)).align_x().fill_x(), - - row!( - selected.wrap(TransportFocus::Bpm, &Outset::X(1u16, { - let bpm = state.transport_bpm_value(); - row! { "BPM ", format!("{}.{:03}", bpm as usize, (bpm * 1000.0) % 1000.0) } - })), - //let quant = state.focus().wrap(state.focused(), TransportFocus::Quant, &Outset::X(1u16, row! { - //"QUANT ", ppq_to_name(state.0.quant as usize) - //})), - selected.wrap(TransportFocus::Sync, &Outset::X(1u16, row! { - "SYNC ", pulses_to_name(state.transport_sync_value() as usize) - })) - ).align_w().fill_x(), - + ).min_xy(11, 2).push_x(1)), + selected.wrap(TransportFocus::Bpm, &Outset::X(1u16, { + let bpm = state.transport_bpm_value(); + row! { "BPM ", format!("{}.{:03}", bpm as usize, (bpm * 1000.0) % 1000.0) } + })), + //let quant = state.focus().wrap(state.focused(), TransportFocus::Quant, &Outset::X(1u16, row! { + //"QUANT ", ppq_to_name(state.0.quant as usize) + //})), + selected.wrap(TransportFocus::Sync, &Outset::X(1u16, row! { + "SYNC ", pulses_to_name(state.transport_sync_value() as usize) + })), selected.wrap(TransportFocus::Clock, &{ let time1 = state.transport_format_beat(); let time2 = state.transport_format_msu(); row!("B" ,time1.as_str(), " T", time2.as_str()).outset_x(1) }).align_e().fill_x(), - ).fill_x().bg(Color::Rgb(40, 50, 30)) } } diff --git a/crates/tek_tui/src/tui_focus.rs b/crates/tek_tui/src/tui_focus.rs index 8a29a328..767231f9 100644 --- a/crates/tek_tui/src/tui_focus.rs +++ b/crates/tek_tui/src/tui_focus.rs @@ -114,7 +114,13 @@ macro_rules! impl_focus { impl_focus!(TransportTui TransportFocus [ //&[Menu], - &[Content(Bpm), Content(Sync), Content(PlayPause), Content(Clock), Content(Quant)], + &[ + Content(PlayPause), + Content(Bpm), + Content(Sync), + Content(Clock), + Content(Quant) + ], ]); impl_focus!(SequencerTui SequencerFocus [ @@ -126,9 +132,9 @@ impl_focus!(SequencerTui SequencerFocus [ //Menu, //], &[ + Content(Transport(TransportFocus::PlayPause)), Content(Transport(TransportFocus::Bpm)), Content(Transport(TransportFocus::Sync)), - Content(Transport(TransportFocus::PlayPause)), Content(Transport(TransportFocus::Clock)), Content(Transport(TransportFocus::Quant)) ], @@ -150,9 +156,9 @@ impl_focus!(ArrangerTui ArrangerFocus [ //Menu, //], &[ + Content(Transport(TransportFocus::PlayPause)), Content(Transport(TransportFocus::Bpm)), Content(Transport(TransportFocus::Sync)), - Content(Transport(TransportFocus::PlayPause)), Content(Transport(TransportFocus::Clock)), Content(Transport(TransportFocus::Quant)) ], &[ diff --git a/crates/tek_tui/src/tui_impls.rs b/crates/tek_tui/src/tui_impls.rs index 00132683..e9841eca 100644 --- a/crates/tek_tui/src/tui_impls.rs +++ b/crates/tek_tui/src/tui_impls.rs @@ -9,7 +9,6 @@ macro_rules! impl_jack_api { } } } - macro_rules! impl_clock_api { ($Struct:ident $(:: $field:ident)*) => { impl ClockApi for $Struct { @@ -34,7 +33,6 @@ macro_rules! impl_clock_api { } } } - macro_rules! impl_has_phrases { ($Struct:ident $(:: $field:ident)*) => { impl HasPhrases for $Struct { @@ -47,7 +45,6 @@ macro_rules! impl_has_phrases { } } } - macro_rules! impl_midi_player { ($Struct:ident $(:: $field:ident)*) => { impl HasPhrase for $Struct { @@ -116,7 +113,6 @@ macro_rules! impl_midi_player { impl MidiPlayerApi for $Struct {} } } - macro_rules! impl_phrases_control { ($Struct:ident $(:: $field:ident)*) => { impl PhrasesControl for $Struct { @@ -135,7 +131,6 @@ macro_rules! impl_phrases_control { } } } - macro_rules! impl_phrase_editor_control { ( $Struct:ident $(:: $field:ident)* @@ -179,6 +174,68 @@ macro_rules! impl_phrase_editor_control { } } } +macro_rules! impl_phrases_view_state { + ($Struct:ident $(:: $field:ident)* [$self1:ident: $focus:expr] [$self2:ident: $enter:expr]) => { + impl PhrasesViewState for $Struct { + fn phrases_focused (&$self1) -> bool { + $focus + } + fn phrases_entered (&$self2) -> bool { + $enter + } + fn phrases (&self) -> &Vec>> { + &self$(.$field)*.phrases + } + fn phrase_index (&self) -> usize { + self$(.$field)*.phrase.load(Ordering::Relaxed) + } + fn phrases_mode (&self) -> &Option { + &self$(.$field)*.mode + } + } + } +} +macro_rules! impl_phrase_view_state { + ($Struct:ident $(:: $field:ident)* [$self1:ident : $focused:expr] [$self2:ident : $entered:expr]) => { + impl PhraseViewState for $Struct { + fn phrase_editing (&self) -> &Option>> { + &self$(.$field)*.phrase + } + fn phrase_editor_focused (&$self1) -> bool { + $focused + //self$(.$field)*.focus.is_focused() + } + fn phrase_editor_entered (&$self2) -> bool { + $entered + //self$(.$field)*.focus.is_entered() + } + fn phrase_editor_size (&self) -> &Measure { + todo!() + } + fn keys (&self) -> &Buffer { + &self$(.$field)*.keys + } + fn buffer (&self) -> &BigBuffer { + &self$(.$field)*.buffer + } + fn note_len (&self) -> usize { + self$(.$field)*.note_len + } + fn note_axis (&self) -> &RwLock> { + &self$(.$field)*.note_axis + } + fn time_axis (&self) -> &RwLock> { + &self$(.$field)*.time_axis + } + fn now (&self) -> &Arc { + &self$(.$field)*.now + } + fn size (&self) -> &Measure { + &self$(.$field)*.size + } + } + } +} impl_jack_api!(TransportTui::jack); impl_jack_api!(SequencerTui::jack); @@ -211,3 +268,28 @@ impl_phrase_editor_control!(ArrangerTui [self: todo!()] [self, phrase: todo!()] ); + +impl_phrases_view_state!(PhrasesModel + [self: false] + [self: false] +); +impl_phrases_view_state!(SequencerTui::phrases + [self: self.focused() == AppFocus::Content(SequencerFocus::Phrases)] + [self: self.focused() == AppFocus::Content(SequencerFocus::Phrases)] +); +impl_phrases_view_state!(ArrangerTui::phrases + [self: self.focused() == AppFocus::Content(ArrangerFocus::Phrases)] + [self: self.focused() == AppFocus::Content(ArrangerFocus::Phrases)] +); +impl_phrase_view_state!(PhraseEditorModel + [self: true] + [self: true] +); +impl_phrase_view_state!(SequencerTui::editor + [self: self.focused() == AppFocus::Content(SequencerFocus::PhraseEditor)] + [self: self.entered() && self.focused() == AppFocus::Content(SequencerFocus::PhraseEditor)] +); +impl_phrase_view_state!(ArrangerTui::editor + [self: self.focused() == AppFocus::Content(ArrangerFocus::PhraseEditor)] + [self: self.entered() && self.focused() == AppFocus::Content(ArrangerFocus::PhraseEditor)]) +; diff --git a/crates/tek_tui/src/tui_init.rs b/crates/tek_tui/src/tui_init.rs index 613c1816..238f04d4 100644 --- a/crates/tek_tui/src/tui_init.rs +++ b/crates/tek_tui/src/tui_init.rs @@ -59,7 +59,8 @@ impl TryFrom<&Arc>> for ArrangerTui { status_bar: None, midi_buf: vec![vec![];65536], note_buf: vec![], - focus: FocusState::Entered(AppFocus::Content(ArrangerFocus::Transport(TransportFocus::Bpm))) + focus: FocusState::Entered(AppFocus::Content(ArrangerFocus::Transport(TransportFocus::Bpm))), + perf: PerfModel::default(), }) } } diff --git a/crates/tek_tui/src/tui_jack.rs b/crates/tek_tui/src/tui_jack.rs index 768f2f51..711940e2 100644 --- a/crates/tek_tui/src/tui_jack.rs +++ b/crates/tek_tui/src/tui_jack.rs @@ -8,10 +8,15 @@ impl Audio for TransportTui { impl Audio for SequencerTui { fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { + // Start profiling cycle let t0 = self.perf.get_t0(); + + // Update transport clock if ClockAudio(self).process(client, scope) == Control::Quit { return Control::Quit } + + // Update MIDI sequencer if PlayerAudio( &mut self.player, &mut self.note_buf, @@ -19,13 +24,37 @@ impl Audio for SequencerTui { ).process(client, scope) == Control::Quit { return Control::Quit } + + // Update sequencer playhead indicator + //self.now().set(0.); + //if let Some((ref started_at, Some(ref playing))) = self.player.play_phrase { + //let phrase = phrase.read().unwrap(); + //if *playing.read().unwrap() == *phrase { + //let pulse = self.current().pulse.get(); + //let start = started_at.pulse.get(); + //let now = (pulse - start) % phrase.length as f64; + //self.now().set(now); + //} + //} + + // End profiling cycle self.perf.update(t0, scope); + Control::Continue } } impl Audio for ArrangerTui { #[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { + // Start profiling cycle + let t0 = self.perf.get_t0(); + + // Update transport clock + if ClockAudio(self).process(client, scope) == Control::Quit { + return Control::Quit + } + + // Update MIDI sequencers if TracksAudio( &mut self.tracks, &mut self.note_buf, @@ -34,7 +63,9 @@ impl Audio for ArrangerTui { ).process(client, scope) == Control::Quit { return Control::Quit } + // FIXME: one of these per playing track + self.now().set(0.); if let ArrangerSelection::Clip(t, s) = self.selected { let phrase = self.scenes().get(s).map(|scene|scene.clips.get(t)); if let Some(Some(Some(phrase))) = phrase { @@ -46,13 +77,15 @@ impl Audio for ArrangerTui { let start = started_at.pulse.get(); let now = (pulse - start) % phrase.length as f64; self.now().set(now); - return Control::Continue } } } } } - self.now().set(0.); + + // End profiling cycle + self.perf.update(t0, scope); + return Control::Continue } } diff --git a/crates/tek_tui/src/tui_view.rs b/crates/tek_tui/src/tui_view.rs index e3fa4961..3c4ccb04 100644 --- a/crates/tek_tui/src/tui_view.rs +++ b/crates/tek_tui/src/tui_view.rs @@ -68,34 +68,6 @@ pub trait PhrasesViewState: Send + Sync { fn phrase_index (&self) -> usize; fn phrases_mode (&self) -> &Option; } -macro_rules! impl_phrases_view_state { - ($Struct:ident $(:: $field:ident)* [$self1:ident: $focus:expr] [$self2:ident: $enter:expr]) => { - impl PhrasesViewState for $Struct { - fn phrases_focused (&$self1) -> bool { - $focus - } - fn phrases_entered (&$self2) -> bool { - $enter - } - fn phrases (&self) -> &Vec>> { - &self$(.$field)*.phrases - } - fn phrase_index (&self) -> usize { - self$(.$field)*.phrase.load(Ordering::Relaxed) - } - fn phrases_mode (&self) -> &Option { - &self$(.$field)*.mode - } - } - } -} -impl_phrases_view_state!(PhrasesModel [self: false] [self: false]); -impl_phrases_view_state!(SequencerTui::phrases - [self: self.focused() == AppFocus::Content(SequencerFocus::Phrases)] - [self: self.focused() == AppFocus::Content(SequencerFocus::Phrases)]); -impl_phrases_view_state!(ArrangerTui::phrases - [self: self.focused() == AppFocus::Content(ArrangerFocus::Phrases)] - [self: self.focused() == AppFocus::Content(ArrangerFocus::Phrases)]); pub trait PhraseViewState: Send + Sync { fn phrase_editing (&self) -> &Option>>; @@ -110,56 +82,6 @@ pub trait PhraseViewState: Send + Sync { fn now (&self) -> &Arc; fn size (&self) -> &Measure; } -macro_rules! impl_phrase_view_state { - ($Struct:ident $(:: $field:ident)* [$self1:ident : $focused:expr] [$self2:ident : $entered:expr]) => { - impl PhraseViewState for $Struct { - fn phrase_editing (&self) -> &Option>> { - &self$(.$field)*.phrase - } - fn phrase_editor_focused (&$self1) -> bool { - $focused - //self$(.$field)*.focus.is_focused() - } - fn phrase_editor_entered (&$self2) -> bool { - $entered - //self$(.$field)*.focus.is_entered() - } - fn phrase_editor_size (&self) -> &Measure { - todo!() - } - fn keys (&self) -> &Buffer { - &self$(.$field)*.keys - } - fn buffer (&self) -> &BigBuffer { - &self$(.$field)*.buffer - } - fn note_len (&self) -> usize { - self$(.$field)*.note_len - } - fn note_axis (&self) -> &RwLock> { - &self$(.$field)*.note_axis - } - fn time_axis (&self) -> &RwLock> { - &self$(.$field)*.time_axis - } - fn now (&self) -> &Arc { - &self$(.$field)*.now - } - fn size (&self) -> &Measure { - &self$(.$field)*.size - } - } - } -} -impl_phrase_view_state!(PhraseEditorModel - [self: true] - [self: true]); -impl_phrase_view_state!(SequencerTui::editor - [self: self.focused() == AppFocus::Content(SequencerFocus::PhraseEditor)] - [self: self.entered() && self.focused() == AppFocus::Content(SequencerFocus::PhraseEditor)]); -impl_phrase_view_state!(ArrangerTui::editor - [self: self.focused() == AppFocus::Content(ArrangerFocus::PhraseEditor)] - [self: self.entered() && self.focused() == AppFocus::Content(ArrangerFocus::PhraseEditor)]); fn track_widths (tracks: &[ArrangerTrack]) -> Vec<(usize, usize)> { let mut widths = vec![];