diff --git a/crates/tek_api/src/api_clock.rs b/crates/tek_api/src/api_clock.rs index 9a8a7b5b..366d2c1f 100644 --- a/crates/tek_api/src/api_clock.rs +++ b/crates/tek_api/src/api_clock.rs @@ -1,5 +1,9 @@ use crate::*; +pub trait HasClock: Send + Sync { + fn clock (&self) -> &ClockModel; +} + #[derive(Clone, Debug, PartialEq)] pub enum ClockCommand { Play(Option), @@ -12,113 +16,150 @@ pub enum ClockCommand { SetSync(f64), } -impl Command for ClockCommand { +impl Command for ClockCommand { fn execute (self, state: &mut T) -> Perhaps { use ClockCommand::*; match self { - Play(start) => state.play_from(start)?, - Pause(pause) => state.pause_at(pause)?, - SeekUsec(usec) => state.current().update_from_usec(usec), - SeekSample(sample) => state.current().update_from_sample(sample), - SeekPulse(pulse) => state.current().update_from_pulse(pulse), - SetBpm(bpm) => return Ok(Some(SetBpm(state.timebase().bpm.set(bpm)))), - SetQuant(quant) => return Ok(Some(SetQuant(state.quant().set(quant)))), - SetSync(sync) => return Ok(Some(SetSync(state.sync().set(sync)))), + Play(start) => state.clock().play_from(start)?, + Pause(pause) => state.clock().pause_at(pause)?, + SeekUsec(usec) => state.clock().current.update_from_usec(usec), + SeekSample(sample) => state.clock().current.update_from_sample(sample), + SeekPulse(pulse) => state.clock().current.update_from_pulse(pulse), + SetBpm(bpm) => return Ok(Some(SetBpm(state.clock().timebase().bpm.set(bpm)))), + SetQuant(quant) => return Ok(Some(SetQuant(state.clock().quant.set(quant)))), + SetSync(sync) => return Ok(Some(SetSync(state.clock().sync.set(sync)))), }; Ok(None) } } -pub trait ClockApi: Send + Sync { - /// Note quantization factor - fn quant (&self) -> &Arc; - /// Launch quantization factor - fn sync (&self) -> &Arc; - /// Current moment in time - fn current (&self) -> &Arc; - /// Current temporal resolutions - fn timebase (&self) -> &Arc { &self.current().timebase } - /// Current sample rate - fn sr (&self) -> &SampleRate { &self.timebase().sr } - /// Current tempo - fn bpm (&self) -> &BeatsPerMinute { &self.timebase().bpm } - /// Current MIDI resolution - fn ppq (&self) -> &PulsesPerQuaver { &self.timebase().ppq } - /// Handle to JACK transport - fn transport_handle (&self) -> &Arc; +#[derive(Clone)] +pub struct ClockModel { + /// JACK transport handle. + pub transport: Arc, /// Playback state - fn transport_state (&self) -> &Arc>>; + pub playing: Arc>>, /// Global sample and usec at which playback started - fn transport_offset (&self) -> &Arc>>; + pub started: Arc>>, + /// Current moment in time + pub current: Arc, + /// Note quantization factor + pub quant: Arc, + /// Launch quantization factor + pub sync: Arc, +} - fn next_launch_pulse (&self) -> usize { - let sync = self.sync().get() as usize; - let pulse = self.current().pulse.get() as usize; +impl From<&Arc> for ClockModel { + fn from (transport: &Arc) -> Self { + Self { + playing: RwLock::new(None).into(), + started: RwLock::new(None).into(), + current: Instant::default().into(), + quant: Arc::new(24.into()), + sync: Arc::new(384.into()), + transport: transport.clone(), + } + } +} + +impl std::fmt::Debug for ClockModel { + fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + f.debug_struct("editor") + .field("playing", &self.playing) + .field("started", &self.started) + .field("current", &self.current) + .field("quant", &self.quant) + .field("sync", &self.sync) + .finish() + } +} + +impl ClockModel { + pub fn timebase (&self) -> &Arc { + &self.current.timebase + } + /// Current sample rate + pub fn sr (&self) -> &SampleRate { + &self.timebase().sr + } + /// Current tempo + pub fn bpm (&self) -> &BeatsPerMinute { + &self.timebase().bpm + } + /// Current MIDI resolution + pub fn ppq (&self) -> &PulsesPerQuaver { + &self.timebase().ppq + } + /// Next pulse that matches launch sync (for phrase switchover) + pub fn next_launch_pulse (&self) -> usize { + let sync = self.sync.get() as usize; + let pulse = self.current.pulse.get() as usize; if pulse % sync == 0 { pulse } else { (pulse / sync + 1) * sync } } - - fn play_from (&mut self, start: Option) -> Usually<()> { + /// Start playing, optionally seeking to a given location beforehand + pub fn play_from (&self, start: Option) -> Usually<()> { if let Some(start) = start { - self.transport_handle().locate(start)?; + self.transport.locate(start)?; } - self.transport_handle().start()?; + self.transport.start()?; self.update_transport_state() } - fn is_rolling (&self) -> bool { - *self.transport_state().read().unwrap() == Some(TransportState::Rolling) - } - - fn pause_at (&mut self, pause: Option) -> Usually<()> { - self.transport_handle().stop()?; + /// Pause, optionally seeking to a given location afterwards + pub fn pause_at (&self, pause: Option) -> Usually<()> { + self.transport.stop()?; if let Some(pause) = pause { - self.transport_handle().locate(pause)?; + self.transport.locate(pause)?; } self.update_transport_state() } - fn is_stopped (&self) -> bool { - *self.transport_state().read().unwrap() == Some(TransportState::Stopped) - } - - fn update_transport_state (&self) -> Usually<()> { - *self.transport_state().write().unwrap() = Some(self.transport_handle().query_state()?); + /// Update the state according to JACK transport + pub fn update_transport_state (&self) -> Usually<()> { + *self.playing.write().unwrap() = Some(self.transport.query_state()?); Ok(()) } - - fn toggle_play (&self) -> Usually<()> { - let playing = self.transport_state().read().unwrap().expect("1st sample has not been processed yet"); - let playing = match playing { - TransportState::Stopped => { - self.transport_handle().start()?; - Some(TransportState::Starting) - }, - _ => { - self.transport_handle().stop()?; - self.transport_handle().locate(0)?; - Some(TransportState::Stopped) - }, - }; - *self.transport_state().write().unwrap() = playing; - Ok(()) + /// Is currently paused? + pub fn is_stopped (&self) -> bool { + *self.playing.read().unwrap() == Some(TransportState::Stopped) } + /// Is currently playing? + pub fn is_rolling (&self) -> bool { + *self.playing.read().unwrap() == Some(TransportState::Rolling) + } + //fn toggle_play (&self) -> Usually<()> { + //let playing = self.playing.read().unwrap().expect("1st sample has not been processed yet"); + //let playing = match playing { + //TransportState::Stopped => { + //self.transport.start()?; + //Some(TransportState::Starting) + //}, + //_ => { + //self.transport.stop()?; + //self.transport.locate(0)?; + //Some(TransportState::Stopped) + //}, + //}; + //*self.playing.write().unwrap() = playing; + //Ok(()) + //} } /// Hosts the JACK callback for updating the temporal pointer and playback status. -pub struct ClockAudio<'a, T: ClockApi>(pub &'a mut T); +pub struct ClockAudio<'a, T: HasClock>(pub &'a mut T); -impl<'a, T: ClockApi> Audio for ClockAudio<'a, T> { +impl<'a, T: HasClock> Audio for ClockAudio<'a, T> { #[inline] fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { - let state = &mut self.0; + let state = self.0.clock(); let times = scope.cycle_times().unwrap(); let CycleTimes { current_frames, current_usecs, next_usecs: _, period_usecs: _ } = times; let _chunk_size = scope.n_frames() as usize; - let transport = state.transport_handle().query().unwrap(); - state.current().sample.set(transport.pos.frame() as f64); - let mut playing = state.transport_state().write().unwrap(); - let mut started = state.transport_offset().write().unwrap(); + let transport = state.transport.query().unwrap(); + state.current.sample.set(transport.pos.frame() as f64); + let mut playing = state.playing.write().unwrap(); + let mut started = state.started.write().unwrap(); if *playing != Some(transport.state) { match transport.state { TransportState::Rolling => { @@ -134,7 +175,7 @@ impl<'a, T: ClockApi> Audio for ClockAudio<'a, T> { if *playing == Some(TransportState::Stopped) { *started = None; } - state.current().update_from_usec(match *started { + state.current.update_from_usec(match *started { Some((_, usecs)) => current_usecs as f64 - usecs as f64, None => 0. }); diff --git a/crates/tek_api/src/api_player.rs b/crates/tek_api/src/api_player.rs index 81a0f93a..7c9029a1 100644 --- a/crates/tek_api/src/api_player.rs +++ b/crates/tek_api/src/api_player.rs @@ -7,7 +7,7 @@ pub trait HasPlayer: JackApi { pub trait MidiPlayerApi: MidiInputApi + MidiOutputApi + Send + Sync {} -pub trait HasPhrase: ClockApi { +pub trait HasPhrase: HasClock { fn reset (&self) -> bool; fn reset_mut (&mut self) -> &mut bool; @@ -21,23 +21,22 @@ pub trait HasPhrase: ClockApi { fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option>>)>; fn enqueue_next (&mut self, phrase: Option<&Arc>>) { - let start = self.next_launch_pulse(); - *self.next_phrase_mut() = Some(( - Instant::from_pulse(self.timebase(), start as f64), - phrase.map(|p|p.clone()) - )); + let start = self.clock().next_launch_pulse() as f64; + let instant = Instant::from_pulse(&self.clock().timebase(), start); + let phrase = phrase.map(|p|p.clone()); + *self.next_phrase_mut() = Some((instant, phrase)); *self.reset_mut() = true; } fn pulses_since_start (&self) -> Option { if let Some((started, Some(_))) = self.play_phrase().as_ref() { - Some(self.current().pulse.get() - started.pulse.get()) + Some(self.clock().current.pulse.get() - started.pulse.get()) } else { None } } } -pub trait MidiInputApi: ClockApi + HasPhrase { +pub trait MidiInputApi: HasPhrase { fn midi_ins (&self) -> &Vec>; fn midi_ins_mut (&mut self) -> &mut Vec>; fn has_midi_ins (&self) -> bool { @@ -71,38 +70,40 @@ pub trait MidiInputApi: ClockApi + HasPhrase { let sample0 = scope.last_frame_time() as usize; // For highlighting keys and note repeat let notes_in = self.notes_in().clone(); - if let (true, Some((started, ref phrase))) = (self.is_rolling(), self.play_phrase().clone()) { - let start = started.sample.get() as usize; - let quant = self.quant().get(); - let timebase = self.timebase().clone(); - let monitoring = self.monitoring(); - let recording = self.recording(); - for input in self.midi_ins_mut().iter() { - for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { - if let LiveEvent::Midi { message, .. } = event { - if monitoring { - midi_buf[sample].push(bytes.to_vec()) - } - if recording { - if let Some(phrase) = phrase { - let mut phrase = phrase.write().unwrap(); - let length = phrase.length; - phrase.record_event({ - let sample = (sample0 + sample - start) as f64; - let pulse = timebase.samples_to_pulse(sample); - let quantized = (pulse / quant).round() * quant; - let looped = quantized as usize % length; - looped - }, message); + if self.clock().is_rolling() { + if let Some((started, ref phrase)) = self.play_phrase().clone() { + let start = started.sample.get() as usize; + let quant = self.clock().quant.get(); + let timebase = self.clock().timebase().clone(); + let monitoring = self.monitoring(); + let recording = self.recording(); + for input in self.midi_ins_mut().iter() { + for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { + if let LiveEvent::Midi { message, .. } = event { + if monitoring { + midi_buf[sample].push(bytes.to_vec()) } + if recording { + if let Some(phrase) = phrase { + let mut phrase = phrase.write().unwrap(); + let length = phrase.length; + phrase.record_event({ + let sample = (sample0 + sample - start) as f64; + let pulse = timebase.samples_to_pulse(sample); + let quantized = (pulse / quant).round() * quant; + let looped = quantized as usize % length; + looped + }, message); + } + } + update_keys(&mut*notes_in.write().unwrap(), &message); } - update_keys(&mut*notes_in.write().unwrap(), &message); } } } - } - if let (true, Some((start_at, phrase))) = (self.is_rolling(), &self.next_phrase()) { - // TODO switch to next phrase and record into it + if let Some((start_at, phrase)) = &self.next_phrase() { + // TODO switch to next phrase and record into it + } } } @@ -125,7 +126,7 @@ pub trait MidiInputApi: ClockApi + HasPhrase { } -pub trait MidiOutputApi: ClockApi + HasPhrase { +pub trait MidiOutputApi: HasPhrase { fn midi_outs (&self) -> &Vec>; fn midi_outs_mut (&mut self) -> &mut Vec>; @@ -162,14 +163,14 @@ pub trait MidiOutputApi: ClockApi + HasPhrase { ) -> bool { let mut next = false; // Write MIDI events from currently playing phrase (if any) to MIDI output buffer - if self.is_rolling() { + if self.clock().is_rolling() { let sample0 = scope.last_frame_time() as usize; let samples = scope.n_frames() as usize; // If no phrase is playing, prepare for switchover immediately next = self.play_phrase().is_none(); let phrase = self.play_phrase(); - let started0 = self.transport_offset(); - let timebase = self.timebase(); + let started0 = &self.clock().started; + let timebase = self.clock().timebase(); let notes_out = self.notes_out(); let next_phrase = self.next_phrase(); if let Some((started, phrase)) = phrase { @@ -232,18 +233,18 @@ pub trait MidiOutputApi: ClockApi + HasPhrase { note_buffer: &mut Vec, output_buffer: &mut Vec>> ) { - if self.is_rolling() { + if self.clock().is_rolling() { let sample0 = scope.last_frame_time() as usize; //let samples = scope.n_frames() as usize; if let Some((start_at, phrase)) = &self.next_phrase() { let start = start_at.sample.get() as usize; - let sample = self.transport_offset().read().unwrap().unwrap().0; + let sample = self.clock().started.read().unwrap().unwrap().0; // If it's time to switch to the next phrase: if start <= sample0.saturating_sub(sample) { // Samples elapsed since phrase was supposed to start let skipped = sample0 - start; // Switch over to enqueued phrase - let started = Instant::from_sample(&self.timebase(), start as f64); + let started = Instant::from_sample(&self.clock().timebase(), start as f64); *self.play_phrase_mut() = Some((started, phrase.clone())); // Unset enqueuement (TODO: where to implement looping?) *self.next_phrase_mut() = None diff --git a/crates/tek_tui/src/lib.rs b/crates/tek_tui/src/lib.rs index 92af2e72..76001ba7 100644 --- a/crates/tek_tui/src/lib.rs +++ b/crates/tek_tui/src/lib.rs @@ -91,30 +91,6 @@ impl TuiTheme { } } -macro_rules! impl_clock_api { - ($Struct:ident $(:: $field:ident)*) => { - impl ClockApi for $Struct { - fn quant (&self) -> &Arc { - &self$(.$field)*.quant - } - fn sync (&self) -> &Arc { - &self$(.$field)*.sync - } - fn current (&self) -> &Arc { - &self$(.$field)*.current - } - fn transport_handle (&self) -> &Arc { - &self$(.$field)*.transport - } - fn transport_state (&self) -> &Arc>> { - &self$(.$field)*.playing - } - fn transport_offset (&self) -> &Arc>> { - &self$(.$field)*.started - } - } - } -} macro_rules! impl_midi_player { ($Struct:ident $(:: $field:ident)*) => { impl HasPhrase for $Struct { @@ -184,12 +160,6 @@ macro_rules! impl_midi_player { } } -impl_clock_api!(TransportTui::clock); -impl_clock_api!(SequencerTui::clock); -impl_clock_api!(ArrangerTui::clock); -impl_clock_api!(PhrasePlayerModel::clock); -impl_clock_api!(ArrangerTrack::player::clock); - impl_midi_player!(SequencerTui::player); impl_midi_player!(ArrangerTrack::player); impl_midi_player!(PhrasePlayerModel); @@ -197,18 +167,6 @@ impl_midi_player!(PhrasePlayerModel); use std::fmt::{Debug, Formatter, Error}; type DebugResult = std::result::Result<(), Error>; -impl Debug for ClockModel { - fn fmt (&self, f: &mut Formatter<'_>) -> DebugResult { - f.debug_struct("editor") - .field("playing", &self.playing) - .field("started", &self.started) - .field("current", &self.current) - .field("quant", &self.quant) - .field("sync", &self.sync) - .finish() - } -} - impl Debug for TransportTui { fn fmt (&self, f: &mut Formatter<'_>) -> DebugResult { f.debug_struct("Measure") diff --git a/crates/tek_tui/src/tui_app_arranger.rs b/crates/tek_tui/src/tui_app_arranger.rs index 04312f37..2e634a59 100644 --- a/crates/tek_tui/src/tui_app_arranger.rs +++ b/crates/tek_tui/src/tui_app_arranger.rs @@ -54,6 +54,16 @@ impl TryFrom<&Arc>> for ArrangerTui { } } +impl HasClock for ArrangerTui { + fn clock (&self) -> &ClockModel { + &self.clock + } +} +impl HasClock for ArrangerTrack { + fn clock (&self) -> &ClockModel { + &self.player.clock() + } +} impl HasPhrases for ArrangerTui { fn phrases (&self) -> &Vec>> { &self.phrases.phrases diff --git a/crates/tek_tui/src/tui_app_sequencer.rs b/crates/tek_tui/src/tui_app_sequencer.rs index 968ca516..a7680395 100644 --- a/crates/tek_tui/src/tui_app_sequencer.rs +++ b/crates/tek_tui/src/tui_app_sequencer.rs @@ -39,6 +39,12 @@ impl TryFrom<&Arc>> for SequencerTui { } } +impl HasClock for SequencerTui { + fn clock (&self) -> &ClockModel { + &self.clock + } +} + impl HasPhrases for SequencerTui { fn phrases (&self) -> &Vec>> { &self.phrases.phrases diff --git a/crates/tek_tui/src/tui_app_transport.rs b/crates/tek_tui/src/tui_app_transport.rs index 5c784da7..4cc717b5 100644 --- a/crates/tek_tui/src/tui_app_transport.rs +++ b/crates/tek_tui/src/tui_app_transport.rs @@ -23,6 +23,12 @@ impl TryFrom<&Arc>> for TransportTui { } } +impl HasClock for TransportTui { + fn clock (&self) -> &ClockModel { + &self.clock + } +} + /// Which item of the transport toolbar is focused #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum TransportFocus { diff --git a/crates/tek_tui/src/tui_control_arranger.rs b/crates/tek_tui/src/tui_control_arranger.rs index 98fe36f5..86af7149 100644 --- a/crates/tek_tui/src/tui_control_arranger.rs +++ b/crates/tek_tui/src/tui_control_arranger.rs @@ -89,8 +89,8 @@ impl ArrangerControl for ArrangerTui { track.enqueue_next(phrase.as_ref()); } } - if self.is_stopped() { - self.play_from(Some(0))?; + 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(); diff --git a/crates/tek_tui/src/tui_control_sequencer.rs b/crates/tek_tui/src/tui_control_sequencer.rs index 8e9030ee..1e2b4b8b 100644 --- a/crates/tek_tui/src/tui_control_sequencer.rs +++ b/crates/tek_tui/src/tui_control_sequencer.rs @@ -52,11 +52,11 @@ pub fn to_sequencer_command (state: &SequencerTui, input: &TuiInput) -> Option Clock( - if state.is_stopped() { Play(None) } else { Pause(None) } + if state.clock().is_stopped() { Play(None) } else { Pause(None) } ), // Play from start/rewind to start key!(Shift-Char(' ')) => Clock( - if state.is_stopped() { Play(Some(0)) } else { Pause(Some(0)) } + if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) } ), // Edit phrase key!(Char('e')) => match state.focused() { diff --git a/crates/tek_tui/src/tui_control_transport.rs b/crates/tek_tui/src/tui_control_transport.rs index f59f750a..6ef05509 100644 --- a/crates/tek_tui/src/tui_control_transport.rs +++ b/crates/tek_tui/src/tui_control_transport.rs @@ -28,7 +28,7 @@ impl Command for TransportCommand { } } -pub trait TransportControl: ClockApi + FocusGrid + HasEnter { +pub trait TransportControl: HasClock + FocusGrid + HasEnter { fn transport_focused (&self) -> Option; } @@ -73,36 +73,36 @@ where Some(match input.event() { key!(Left) => Focus(FocusCommand::Prev), key!(Right) => Focus(FocusCommand::Next), - key!(Char(' ')) => Clock(if state.is_stopped() { + key!(Char(' ')) => Clock(if state.clock().is_stopped() { ClockCommand::Play(None) } else { ClockCommand::Pause(None) }), - key!(Shift-Char(' ')) => Clock(if state.is_stopped() { + key!(Shift-Char(' ')) => Clock(if state.clock().is_stopped() { ClockCommand::Play(Some(0)) } else { ClockCommand::Pause(Some(0)) }), _ => match state.transport_focused().unwrap() { TransportFocus::Bpm => match input.event() { - key!(Char(',')) => Clock(SetBpm(state.bpm().get() - 1.0)), - key!(Char('.')) => Clock(SetBpm(state.bpm().get() + 1.0)), - key!(Char('<')) => Clock(SetBpm(state.bpm().get() - 0.001)), - key!(Char('>')) => Clock(SetBpm(state.bpm().get() + 0.001)), + key!(Char(',')) => Clock(SetBpm(state.clock().bpm().get() - 1.0)), + key!(Char('.')) => Clock(SetBpm(state.clock().bpm().get() + 1.0)), + key!(Char('<')) => Clock(SetBpm(state.clock().bpm().get() - 0.001)), + key!(Char('>')) => Clock(SetBpm(state.clock().bpm().get() + 0.001)), _ => return None, }, TransportFocus::Quant => match input.event() { - key!(Char(',')) => Clock(SetQuant(state.quant().prev())), - key!(Char('.')) => Clock(SetQuant(state.quant().next())), - key!(Char('<')) => Clock(SetQuant(state.quant().prev())), - key!(Char('>')) => Clock(SetQuant(state.quant().next())), + key!(Char(',')) => Clock(SetQuant(state.clock().quant.prev())), + key!(Char('.')) => Clock(SetQuant(state.clock().quant.next())), + key!(Char('<')) => Clock(SetQuant(state.clock().quant.prev())), + key!(Char('>')) => Clock(SetQuant(state.clock().quant.next())), _ => return None, }, TransportFocus::Sync => match input.event() { - key!(Char(',')) => Clock(SetSync(state.sync().prev())), - key!(Char('.')) => Clock(SetSync(state.sync().next())), - key!(Char('<')) => Clock(SetSync(state.sync().prev())), - key!(Char('>')) => Clock(SetSync(state.sync().next())), + key!(Char(',')) => Clock(SetSync(state.clock().sync.prev())), + key!(Char('.')) => Clock(SetSync(state.clock().sync.next())), + key!(Char('<')) => Clock(SetSync(state.clock().sync.prev())), + key!(Char('>')) => Clock(SetSync(state.clock().sync.next())), _ => return None, }, TransportFocus::Clock => match input.event() { @@ -114,14 +114,14 @@ where }, TransportFocus::PlayPause => match input.event() { key!(Enter) => Clock( - if state.is_stopped() { + if state.clock().is_stopped() { ClockCommand::Play(None) } else { ClockCommand::Pause(None) } ), key!(Shift-Enter) => Clock( - if state.is_stopped() { + if state.clock().is_stopped() { ClockCommand::Play(Some(0)) } else { ClockCommand::Pause(Some(0)) diff --git a/crates/tek_tui/src/tui_model_clock.rs b/crates/tek_tui/src/tui_model_clock.rs index 0a66bb1e..c7b7e813 100644 --- a/crates/tek_tui/src/tui_model_clock.rs +++ b/crates/tek_tui/src/tui_model_clock.rs @@ -1,33 +1 @@ use crate::*; - -#[derive(Clone)] -pub struct ClockModel { - /// JACK transport handle. - pub(crate) transport: Arc, - /// Playback state - pub(crate) playing: Arc>>, - /// Global sample and usec at which playback started - pub(crate) started: Arc>>, - /// Current moment in time - pub(crate) current: Arc, - /// Note quantization factor - pub(crate) quant: Arc, - /// Launch quantization factor - pub(crate) sync: Arc, - /// TODO: Enable metronome? - pub(crate) metronome: bool, -} - -impl From<&Arc> for ClockModel { - fn from (transport: &Arc) -> Self { - Self { - playing: RwLock::new(None).into(), - started: RwLock::new(None).into(), - current: Instant::default().into(), - quant: Arc::new(24.into()), - sync: Arc::new(384.into()), - transport: transport.clone(), - metronome: false, - } - } -} diff --git a/crates/tek_tui/src/tui_model_phrase_player.rs b/crates/tek_tui/src/tui_model_phrase_player.rs index 2e18fb33..33a782de 100644 --- a/crates/tek_tui/src/tui_model_phrase_player.rs +++ b/crates/tek_tui/src/tui_model_phrase_player.rs @@ -46,3 +46,9 @@ impl From<&ClockModel> for PhrasePlayerModel { } } } + +impl HasClock for PhrasePlayerModel { + fn clock (&self) -> &ClockModel { + &self.clock + } +} diff --git a/crates/tek_tui/src/tui_view_arranger.rs b/crates/tek_tui/src/tui_view_arranger.rs index 5795bdf6..a60d26af 100644 --- a/crates/tek_tui/src/tui_view_arranger.rs +++ b/crates/tek_tui/src/tui_view_arranger.rs @@ -91,8 +91,8 @@ pub fn arranger_content_vertical ( view: &ArrangerTui, factor: usize ) -> impl Widget + use<'_> { - let timebase = view.timebase(); - let current = view.current(); + let timebase = view.clock().timebase(); + let current = &view.clock().current; let tracks = view.tracks(); let scenes = view.scenes(); let cols = track_widths(tracks); diff --git a/crates/tek_tui/src/tui_view_transport.rs b/crates/tek_tui/src/tui_view_transport.rs index b52bbb1a..ab49ef8b 100644 --- a/crates/tek_tui/src/tui_view_transport.rs +++ b/crates/tek_tui/src/tui_view_transport.rs @@ -54,22 +54,18 @@ impl Content for TransportView { } } -impl<'a, T> From<&'a T> for TransportView -where - T: ClockApi, - Option: From<&'a T> -{ +impl<'a, T: HasClock> From<&'a T> for TransportView where Option: From<&'a T> { fn from (state: &'a T) -> Self { let selected = state.into(); Self { selected, focused: selected.is_some(), - state: state.transport_state().read().unwrap().clone(), - bpm: state.bpm().get(), - sync: state.sync().get(), - quant: state.quant().get(), - beat: state.current().format_beat(), - msu: state.current().usec.format_msu(), + state: state.clock().playing.read().unwrap().clone(), + bpm: state.clock().bpm().get(), + sync: state.clock().sync.get(), + quant: state.clock().quant.get(), + beat: state.clock().current.format_beat(), + msu: state.clock().current.usec.format_msu(), } } }