use crate::*; #[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.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)))), }; 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; /// Playback state fn transport_state (&self) -> &Arc>>; /// Global sample and usec at which playback started fn transport_offset (&self) -> &Arc>>; 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<()> { if let Some(start) = start { self.transport_handle().locate(start)?; } self.transport_handle().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()?; if let Some(pause) = pause { self.transport_handle().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()?); 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(()) } } /// Hosts the JACK callback for updating the temporal pointer and playback status. pub struct ClockAudio<'a, T: ClockApi>(pub &'a mut T); impl<'a, T: ClockApi> Audio for ClockAudio<'a, T> { #[inline] fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { let state = &mut self.0; 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(); 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; } state.current().update_from_usec(match *started { Some((_, usecs)) => current_usecs as f64 - usecs as f64, None => 0. }); Control::Continue } }