use crate::*; pub mod microsecond; pub(crate) use self::microsecond::*; pub mod moment; pub(crate) use self::moment::*; pub mod perf; pub(crate) use self::perf::*; pub mod pulse; pub(crate) use self::pulse::*; pub mod sample_count; pub(crate) use self::sample_count::*; pub mod sample_rate; pub(crate) use self::sample_rate::*; pub mod timebase; pub(crate) use self::timebase::*; pub mod unit; pub(crate) use self::unit::*; pub trait HasClock: Send + Sync { fn clock (&self) -> &ClockModel; } #[macro_export] macro_rules! has_clock { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { impl $(<$($L),*$($T $(: $U)?),*>)? HasClock for $Struct $(<$($L),*$($T),*>)? { fn clock (&$self) -> &ClockModel { $cb } } } } /// Hosts the JACK callback for updating the temporal pointer and playback status. pub struct ClockAudio<'a, T: HasClock>(pub &'a mut T); impl Audio for ClockAudio<'_, T> { #[inline] fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { self.0.clock().update_from_scope(scope).unwrap(); Control::Continue } } #[derive(Clone, Debug, PartialEq)] pub enum ClockCommand { Play(Option), Pause(Option), SeekUsec(f64), SeekSample(f64), SeekPulse(f64), SetBpm(f64), SetQuant(f64), SetSync(f64), } impl Command for ClockCommand { fn execute (self, state: &mut T) -> Perhaps { use ClockCommand::*; match self { Play(start) => state.clock().play_from(start)?, Pause(pause) => state.clock().pause_at(pause)?, 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)))), }; Ok(None) } } #[derive(Clone)] pub struct ClockModel { /// JACK transport handle. pub transport: Arc, /// Global temporal resolution (shared by [Moment] 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>>, /// Playback offset (when playing not from start) pub offset: Arc, /// Current playhead position pub playhead: Arc, /// Note quantization factor pub quant: Arc, /// Launch quantization factor pub sync: Arc, /// Size of buffer in samples pub chunk: Arc, } from!(|jack: &Arc>| ClockModel = { let jack = jack.read().unwrap(); let chunk = jack.client().buffer_size(); let transport = jack.client().transport(); let timebase = Arc::new(Timebase::default()); Self { quant: Arc::new(24.into()), sync: Arc::new(384.into()), transport: Arc::new(transport), chunk: Arc::new((chunk as usize).into()), global: Arc::new(Moment::zero(&timebase)), playhead: Arc::new(Moment::zero(&timebase)), offset: Arc::new(Moment::zero(&timebase)), started: RwLock::new(None).into(), timebase, } }); 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("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.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.playhead.pulse.get() as usize; if pulse % sync == 0 { pulse } else { (pulse / sync + 1) * sync } } /// Start playing, optionally seeking to a given location beforehand pub fn play_from (&self, start: Option) -> Usually<()> { if let Some(start) = start { self.transport.locate(start)?; } self.transport.start()?; Ok(()) } /// 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.locate(pause)?; } Ok(()) } /// Is currently paused? pub fn is_stopped (&self) -> bool { self.started.read().unwrap().is_none() } /// Is currently playing? pub fn is_rolling (&self) -> bool { self.started.read().unwrap().is_some() } /// Update chunk size pub fn set_chunk (&self, n_frames: usize) { self.chunk.store(n_frames, Relaxed); } pub fn update_from_scope (&self, scope: &ProcessScope) -> Usually<()> { // Store buffer length self.set_chunk(scope.n_frames() as usize); // Store reported global frame and usec let CycleTimes { current_frames, current_usecs, .. } = scope.cycle_times()?; self.global.sample.set(current_frames as f64); self.global.usec.set(current_usecs as f64); // If transport has just started or just stopped, // update starting point: let mut started = self.started.write().unwrap(); match (self.transport.query_state()?, started.as_ref()) { (TransportState::Rolling, None) => { let moment = Moment::zero(&self.timebase); moment.sample.set(current_frames as f64); moment.usec.set(current_usecs as f64); *started = Some(moment); }, (TransportState::Stopped, Some(_)) => { *started = None; }, _ => {} }; self.playhead.update_from_sample(started.as_ref() .map(|started|current_frames as f64 - started.sample.get()) .unwrap_or(0.)); Ok(()) } } //#[cfg(test)] //mod test { //use super::*; //#[test] //fn test_samples_to_ticks () { //let ticks = Ticks(12.3).between_samples(0, 100).collect::>(); //println!("{ticks:?}"); //} //}