diff --git a/crates/tek_sequencer/src/arranger.rs b/crates/tek_sequencer/src/arranger.rs index 89d468d5..eff3877f 100644 --- a/crates/tek_sequencer/src/arranger.rs +++ b/crates/tek_sequencer/src/arranger.rs @@ -46,6 +46,8 @@ pub enum ArrangerStatusBar { } /// Represents the tracks and scenes of the composition. pub struct Arrangement { + /// Global timebase + pub clock: Arc, /// Name of arranger pub name: Arc>, /// Collection of phrases. @@ -218,8 +220,9 @@ impl FocusGrid for Arranger { } /// General methods for arrangement impl Arrangement { - pub fn new (name: &str, phrases: &Arc>>) -> Self { + pub fn new (clock: &Arc, name: &str, phrases: &Arc>>) -> Self { Self { + clock: clock.clone(), name: Arc::new(RwLock::new(name.into())), mode: ArrangementViewMode::Vertical(2), selected: ArrangementFocus::Clip(0, 0), @@ -391,8 +394,8 @@ impl Arrangement { &mut self, name: Option<&str>, color: Option ) -> Usually<&mut ArrangementTrack> { self.tracks.push(name.map_or_else( - || ArrangementTrack::new(&self.track_default_name(), color), - |name| ArrangementTrack::new(name, color), + || ArrangementTrack::new(&self.clock, &self.track_default_name(), color), + |name| ArrangementTrack::new(&self.clock, name, color), )); let index = self.tracks.len() - 1; Ok(&mut self.tracks[index]) @@ -519,11 +522,11 @@ impl Arrangement { } } impl ArrangementTrack { - pub fn new (name: &str, color: Option) -> Self { + pub fn new (clock: &Arc, name: &str, color: Option) -> Self { Self { name: Arc::new(RwLock::new(name.into())), inputs: vec![], - player: PhrasePlayer::new(), + player: PhrasePlayer::new(clock), outputs: vec![], width: name.len() + 2, color: color.unwrap_or_else(random_color) diff --git a/crates/tek_sequencer/src/arranger_cli.rs b/crates/tek_sequencer/src/arranger_cli.rs index 10dcdce3..0ef78fd0 100644 --- a/crates/tek_sequencer/src/arranger_cli.rs +++ b/crates/tek_sequencer/src/arranger_cli.rs @@ -19,11 +19,12 @@ pub struct ArrangerCli { impl ArrangerCli { /// Run the arranger TUI from CLI arguments. fn run (&self) -> Usually<()> { - let jack = Client::new("tek_arranger", ClientOptions::NO_START_SERVER)?.0; - let jack = JackClient::Inactive(jack); - let transport = Arc::new(RwLock::new(TransportToolbar::new(None, Some(jack.transport())))); - let phrases = Arc::new(RwLock::new(PhrasePool::new())); - let mut arrangement = Arrangement::new("", &phrases); + let jack = Client::new("tek_arranger", ClientOptions::NO_START_SERVER)?.0; + let jack = JackClient::Inactive(jack); + let transport = TransportToolbar::new(None, Some(jack.transport())); + let phrases = Arc::new(RwLock::new(PhrasePool::new())); + let mut arrangement = Arrangement::new(&transport.clock, "", &phrases); + let transport = Arc::new(RwLock::new(transport)); if let Some(name) = self.name.as_ref() { *arrangement.name.write().unwrap() = name.clone(); } diff --git a/crates/tek_sequencer/src/arranger_tui.rs b/crates/tek_sequencer/src/arranger_tui.rs index 32a69c12..b43b15da 100644 --- a/crates/tek_sequencer/src/arranger_tui.rs +++ b/crates/tek_sequencer/src/arranger_tui.rs @@ -122,7 +122,6 @@ impl<'a> Content for VerticalArranger<'a, Tui> { let rows = Scene::ppqs(state.scenes.as_slice(), *factor); let tracks = state.tracks.as_ref() as &[ArrangementTrack]; let scenes = state.scenes.as_ref(); - //panic!("{scenes:#?} {rows:#?}"); let offset = 3 + Scene::longest_name(scenes) as u16; // x of 1st track let bg = state.color; let clip_bg = Color::Rgb(40, 50, 30); @@ -131,6 +130,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> { let border_lo = Color::Rgb(70, 80, 50); let border_fg = if self.0.focused { border_hi } else { border_lo }; let border = Lozenge(Style::default().bg(border_bg).fg(border_fg)); + let title_h = 3u16; Layers::new(move |add|{ let rows: &[(usize, usize)] = rows.as_ref(); let cols: &[(usize, usize)] = cols.as_ref(); @@ -165,10 +165,17 @@ impl<'a> Content for VerticalArranger<'a, Tui> { // track titles let track_titles = row!((track, w) in tracks.iter().zip(cols.iter().map(|col|col.0))=>{ - let name = track.name.read().unwrap(); + let name = track.name.read().unwrap(); let max_w = w.saturating_sub(1).min(name.len()).max(2); - (&format!("▎{}", &name[0..max_w]).as_str()) - .min_xy(w as u16, 2) + let name = format!("▎{}", &name[0..max_w]); + let time1 = track.player.phrase.as_ref() + .map(|_|format!("▎{:>}", track.player.now.load(Ordering::Relaxed))) + .unwrap_or(String::from("▎")); + let time2 = track.player.switch_at.as_ref() + .map(|t|format!("▎{:>}", t.load(Ordering::Relaxed))) + .unwrap_or(String::from("▎")); + col!(name, time1, time2) + .min_xy(w as u16, title_h) .bg(track.color) .push_x(offset - 1) }); @@ -218,16 +225,22 @@ impl<'a> Content for VerticalArranger<'a, Tui> { let focused = state.focused; let selected = state.selected; let get_track_area = |t: usize| [ - offset + area.x() + cols[t].1 as u16 - 1, area.y(), - cols[t].0 as u16, area.h(), + offset + area.x() + cols[t].1 as u16 - 1, + area.y(), + cols[t].0 as u16, + area.h(), ]; let get_scene_area = |s: usize| [ - area.x(), 2 + area.y() + (rows[s].1 / PPQ) as u16, - area.w(), (rows[s].0 / PPQ) as u16 + area.x(), + title_h + area.y() + (rows[s].1 / PPQ) as u16, + area.w(), + (rows[s].0 / PPQ) as u16 ]; let get_clip_area = |t: usize, s: usize| [ - offset+area.x()+cols[t].1 as u16 - 1, 2+area.y()+(rows[s].1/PPQ) as u16, - cols[t].0 as u16, (rows[s].0 / PPQ) as u16 + offset+area.x()+cols[t].1 as u16 - 1, + title_h + area.y() + (rows[s].1/PPQ) as u16, + cols[t].0 as u16, + (rows[s].0 / PPQ) as u16 ]; let mut track_area: Option<[u16;4]> = None; let mut scene_area: Option<[u16;4]> = None; diff --git a/crates/tek_sequencer/src/sequencer.rs b/crates/tek_sequencer/src/sequencer.rs index 93f6f7d6..f40605b7 100644 --- a/crates/tek_sequencer/src/sequencer.rs +++ b/crates/tek_sequencer/src/sequencer.rs @@ -113,6 +113,8 @@ pub struct PhraseEditor { /// Phrase player. pub struct PhrasePlayer { _engine: PhantomData, + /// Global timebase + pub clock: Arc, /// Phrase being played pub phrase: Option>>, /// Next phrase @@ -328,9 +330,10 @@ impl std::cmp::PartialEq for Phrase { } impl Eq for Phrase {} impl PhrasePlayer { - pub fn new () -> Self { + pub fn new (clock: &Arc) -> Self { Self { _engine: Default::default(), + clock: clock.clone(), phrase: None, next_phrase: None, now: Arc::new(0.into()), diff --git a/crates/tek_sequencer/src/transport.rs b/crates/tek_sequencer/src/transport.rs index 36890e87..9e9ff3d8 100644 --- a/crates/tek_sequencer/src/transport.rs +++ b/crates/tek_sequencer/src/transport.rs @@ -76,7 +76,6 @@ impl TransportToolbar { clock: Option<&Arc>, transport: Option, ) -> Self { - let timebase = Timebase::default(); Self { _engine: Default::default(), focused: false, @@ -88,6 +87,7 @@ impl TransportToolbar { clock: if let Some(clock) = clock { clock.clone() } else { + let timebase = Timebase::default(); Arc::new(TransportTime { playing: Some(TransportState::Stopped).into(), quant: 24.into(),