use crate::*; pub trait HasClock: Send + Sync { fn clock (&self) -> &Clock; fn clock_mut (&mut self) -> &mut Clock; } #[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) -> &Clock { &$cb } fn clock_mut (&mut $self) -> &mut Clock { &mut $cb } } } } #[derive(Clone, Debug, PartialEq)] pub enum ClockCommand { Play(Option), Pause(Option), SeekUsec(f64), SeekSample(f64), SeekPulse(f64), SetBpm(f64), SetQuant(f64), SetSync(f64), } provide_num!(u32: |self: Clock| {}); provide!(f64: |self: Clock| {}); atom_command!(ClockCommand: |state: Clock| { ("play" [] Self::Play(None)) ("play" [t: u32] Self::Play(t)) ("pause" [] Self::Pause(None)) ("pause" [t: u32] Self::Pause(t)) ("toggle" [] if state.is_rolling() { Self::Pause(None) } else { Self::Play(None) }) ("toggle" [t: u32] if state.is_rolling() { Self::Pause(t) } else { Self::Play(t) }) ("seek/usec" [t: f64] Self::SeekUsec(t.expect("no usec"))) ("seek/pulse" [t: f64] Self::SeekPulse(t.expect("no pulse"))) ("seek/sample" [t: f64] Self::SeekSample(t.expect("no sample"))) ("set/bpm" [t: f64] Self::SetBpm(t.expect("no bpm"))) ("set/sync" [t: f64] Self::SetSync(t.expect("no sync"))) ("set/quant" [t: f64] Self::SetQuant(t.expect("no quant"))) }); impl Command for ClockCommand { fn execute (self, state: &mut T) -> Perhaps { self.execute(state.clock_mut()) } } impl Command for ClockCommand { fn execute (self, state: &mut Clock) -> Perhaps { use ClockCommand::*; match self { Play(start) => state.play_from(start)?, Pause(pause) => state.pause_at(pause)?, SeekUsec(usec) => state.playhead.update_from_usec(usec), SeekSample(sample) => state.playhead.update_from_sample(sample), SeekPulse(pulse) => state.playhead.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)))), }; Ok(None) } } #[derive(Clone, Default)] pub struct Clock { /// 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, /// For syncing the clock to an external source pub midi_in: Arc>>, /// For syncing other devices to this clock pub midi_out: Arc>>, /// For emitting a metronome pub click_out: Arc>>, } impl std::fmt::Debug for Clock { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { f.debug_struct("Clock") .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 Clock { pub fn new (jack: &Jack, bpm: Option) -> Usually { let (chunk, transport) = jack.with_client(|c|(c.buffer_size(), c.transport())); let timebase = Arc::new(Timebase::default()); let clock = Self { quant: Arc::new(24.into()), sync: Arc::new(384.into()), transport: Arc::new(Some(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, midi_in: Arc::new(RwLock::new(Some(JackMidiIn::new(jack, "M/clock", &[])?))), midi_out: Arc::new(RwLock::new(Some(JackMidiOut::new(jack, "clock/M", &[])?))), click_out: Arc::new(RwLock::new(Some(JackAudioOut::new(jack, "click", &[])?))), }; if let Some(bpm) = bpm { clock.timebase.bpm.set(bpm); } Ok(clock) } 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(transport) = self.transport.as_ref() { if let Some(start) = start { transport.locate(start)?; } transport.start()?; } Ok(()) } /// Pause, optionally seeking to a given location afterwards pub fn pause_at (&self, pause: Option) -> Usually<()> { if let Some(transport) = self.transport.as_ref() { transport.stop()?; if let Some(pause) = pause { 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); let mut started = self.started.write().unwrap(); // If transport has just started or just stopped, // update starting point: if let Some(transport) = self.transport.as_ref() { match (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(()) } pub fn bbt (&self) -> PositionBBT { let pulse = self.playhead.pulse.get() as i32; let ppq = self.timebase.ppq.get() as i32; let bpm = self.timebase.bpm.get(); let bar = (pulse / ppq) / 4; PositionBBT { bar: 1 + bar, beat: 1 + (pulse / ppq) % 4, tick: (pulse % ppq), bar_start_tick: (bar * 4 * ppq) as f64, beat_type: 4., beats_per_bar: 4., beats_per_minute: bpm, ticks_per_beat: ppq as f64 } } }