From 54057afad8e2f63abca9343bf44db7228a291afc Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 28 Nov 2024 17:39:07 +0100 Subject: [PATCH] update clock model --- crates/tek_api/src/api_clock.rs | 124 +++++++++++------------ crates/tek_api/src/api_player.rs | 6 +- crates/tek_tui/src/tui_app_sequencer.rs | 2 +- crates/tek_tui/src/tui_view_arranger.rs | 2 +- crates/tek_tui/src/tui_view_transport.rs | 6 +- 5 files changed, 66 insertions(+), 74 deletions(-) diff --git a/crates/tek_api/src/api_clock.rs b/crates/tek_api/src/api_clock.rs index 59602706..2368cf21 100644 --- a/crates/tek_api/src/api_clock.rs +++ b/crates/tek_api/src/api_clock.rs @@ -22,9 +22,9 @@ impl Command for ClockCommand { match self { 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), + SeekUsec(usec) => state.clock().playhead.update_from_usec(usec), + SeekSample(sample) => state.clock().playhead.update_from_sample(sample), + SeekPulse(pulse) => state.clock().playhead.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)))), @@ -37,12 +37,14 @@ impl Command for ClockCommand { pub struct ClockModel { /// JACK transport handle. pub transport: Arc, - /// Playback state - pub playing: Arc>>, + /// Global temporal resolution (shared by [Instant] fields) + pub timebase: Arc, + /// Current global sample and usec (monotonic from JACK clock) + pub global: Arc, /// Global sample and usec at which playback started - pub started: Arc>>, - /// Current moment in time - pub current: Arc, + pub started: Arc>>, + /// Current playhead position + pub playhead: Arc, /// Note quantization factor pub quant: Arc, /// Launch quantization factor @@ -56,14 +58,16 @@ impl From<&Arc>> for ClockModel { let jack = jack.read().unwrap(); let chunk = jack.client().buffer_size(); let transport = jack.client().transport(); + let timebase = Arc::new(Timebase::default()); 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: Arc::new(transport), chunk: Arc::new((chunk as usize).into()), + global: Arc::new(Instant::zero(&timebase)), + playhead: Arc::new(Instant::zero(&timebase)), + started: RwLock::new(None).into(), + timebase, } } } @@ -71,35 +75,37 @@ impl From<&Arc>> for ClockModel { impl std::fmt::Debug for ClockModel { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { f.debug_struct("ClockModel") - .field("playing", &self.playing) - .field("started", &self.started) - .field("current", &self.current) - .field("quant", &self.quant) - .field("sync", &self.sync) + .field("timebase", &self.timebase) + .field("chunk", &self.chunk) + .field("quant", &self.quant) + .field("sync", &self.sync) + .field("global", &self.global) + .field("playhead", &self.playhead) + .field("started", &self.started) .finish() } } impl ClockModel { pub fn timebase (&self) -> &Arc { - &self.current.timebase + &self.timebase } /// Current sample rate pub fn sr (&self) -> &SampleRate { - &self.timebase().sr + &self.timebase.sr } /// Current tempo pub fn bpm (&self) -> &BeatsPerMinute { - &self.timebase().bpm + &self.timebase.bpm } /// Current MIDI resolution pub fn ppq (&self) -> &PulsesPerQuaver { - &self.timebase().ppq + &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; + let pulse = self.playhead.pulse.get() as usize; if pulse % sync == 0 { pulse } else { @@ -112,7 +118,7 @@ impl ClockModel { self.transport.locate(start)?; } self.transport.start()?; - self.update_transport_state() + Ok(()) } /// Pause, optionally seeking to a given location afterwards pub fn pause_at (&self, pause: Option) -> Usually<()> { @@ -120,20 +126,42 @@ impl ClockModel { if let Some(pause) = pause { self.transport.locate(pause)?; } - self.update_transport_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(()) } /// Is currently paused? pub fn is_stopped (&self) -> bool { - *self.playing.read().unwrap() == Some(TransportState::Stopped) + self.started.read().unwrap().is_none() } /// Is currently playing? pub fn is_rolling (&self) -> bool { - *self.playing.read().unwrap() == Some(TransportState::Rolling) + self.started.read().unwrap().is_some() + } + /// Update chunk size + pub fn set_chunk (&self, n_frames: usize) { + self.chunk.store(n_frames, Ordering::Relaxed); + } + pub fn update_from_scope (&self, scope: &ProcessScope) -> Usually<()> { + self.set_chunk(scope.n_frames() as usize); + let CycleTimes { current_frames, current_usecs, .. } = scope.cycle_times()?; + let mut started = self.started.write().unwrap(); + match self.transport.query_state()? { + TransportState::Rolling => { + if started.is_none() { + *started = Some(Instant::from_sample(&self.timebase, current_frames as f64)); + } + }, + TransportState::Stopped => { + if started.is_some() { + *started = None; + } + }, + _ => {} + }; + self.playhead.update_from_sample(match *started { + Some(ref instant) => current_frames as f64 - instant.sample.get(), + None => 0. + }); + Ok(()) } } @@ -142,43 +170,7 @@ pub struct ClockAudio<'a, T: HasClock>(pub &'a mut T); impl<'a, T: HasClock> Audio for ClockAudio<'a, T> { #[inline] fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { - let state = self.0.clock(); - - // Update chunk size - state.chunk.store(scope.n_frames() as usize, Ordering::Relaxed); - - // Query transport state - let transport = state.transport.query().unwrap(); - - // FIXME duplicated - state.current.sample.set(transport.pos.frame() as f64); - - // Update play/pause state and starting point - let CycleTimes { current_frames, current_usecs, .. } = scope.cycle_times().unwrap(); - let mut playing = state.playing.write().unwrap(); - let mut started = state.started.write().unwrap(); - if *playing != Some(transport.state) { - match transport.state { - TransportState::Rolling => { - *started = Some((current_frames as usize, current_usecs as usize)) - }, - TransportState::Stopped => { - *started = None - }, - _ => {} - } - }; - *playing = Some(transport.state); - if *playing == Some(TransportState::Stopped) { - *started = None; - } - - // FIXME duplicated - state.current.update_from_usec(match *started { - Some((_, usecs)) => current_usecs as f64 - usecs as f64, - None => 0. - }); - + self.0.clock().update_from_scope(scope).unwrap(); Control::Continue } } diff --git a/crates/tek_api/src/api_player.rs b/crates/tek_api/src/api_player.rs index 2a9fff92..5bad0911 100644 --- a/crates/tek_api/src/api_player.rs +++ b/crates/tek_api/src/api_player.rs @@ -16,7 +16,7 @@ pub trait HasPlayPhrase: HasClock { fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option>>)>; fn pulses_since_start (&self) -> Option { if let Some((started, Some(_))) = self.play_phrase().as_ref() { - Some(self.clock().current.pulse.get() - started.pulse.get()) + Some(self.clock().playhead.pulse.get() - started.pulse.get()) } else { None } @@ -141,7 +141,7 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts { // First sample to populate. Greater than 0 means that the first // pulse of the phrase falls somewhere in the middle of the chunk. let sample = started.sample.get() as usize; - let sample = sample + started0.read().unwrap().unwrap().0; + let sample = sample + started0.read().unwrap().as_ref().unwrap().sample.get() as usize; let sample = sample0.saturating_sub(sample); // Iterator that emits sample (index into output buffer at which to write MIDI event) // paired with pulse (index into phrase from which to take the MIDI event) for each @@ -200,7 +200,7 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts { //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.clock().started.read().unwrap().unwrap().0; + let sample = self.clock().started.read().unwrap().as_ref().unwrap().sample.get() as usize; // If it's time to switch to the next phrase: if start <= sample0.saturating_sub(sample) { // Samples elapsed since phrase was supposed to start diff --git a/crates/tek_tui/src/tui_app_sequencer.rs b/crates/tek_tui/src/tui_app_sequencer.rs index f9098057..d2c08e68 100644 --- a/crates/tek_tui/src/tui_app_sequencer.rs +++ b/crates/tek_tui/src/tui_app_sequencer.rs @@ -131,7 +131,7 @@ impl From<&SequencerTui> for SequencerStatusBar { use SequencerFocus::*; use TransportFocus::*; let samples = state.clock.chunk.load(Ordering::Relaxed); - let rate = state.clock.current.timebase.sr.get() as f64; + let rate = state.clock.timebase.sr.get() as f64; let buffer = samples as f64 / rate; let default_help = &[("", "⏎", " enter"), ("", "✣", " navigate")]; Self { diff --git a/crates/tek_tui/src/tui_view_arranger.rs b/crates/tek_tui/src/tui_view_arranger.rs index b4f0eb29..2994c624 100644 --- a/crates/tek_tui/src/tui_view_arranger.rs +++ b/crates/tek_tui/src/tui_view_arranger.rs @@ -92,7 +92,7 @@ pub fn arranger_content_vertical ( factor: usize ) -> impl Widget + use<'_> { let timebase = view.clock().timebase(); - let current = &view.clock().current; + let current = &view.clock().playhead; 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 ab49ef8b..03c7e868 100644 --- a/crates/tek_tui/src/tui_view_transport.rs +++ b/crates/tek_tui/src/tui_view_transport.rs @@ -60,12 +60,12 @@ impl<'a, T: HasClock> From<&'a T> for TransportView where Option Self { selected, focused: selected.is_some(), - state: state.clock().playing.read().unwrap().clone(), + state: Some(state.clock().transport.query_state().unwrap()), 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(), + beat: state.clock().playhead.format_beat(), + msu: state.clock().playhead.usec.format_msu(), } } }