mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
139 lines
5.1 KiB
Rust
139 lines
5.1 KiB
Rust
use crate::*;
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub enum ClockCommand {
|
|
Play(Option<u32>),
|
|
Pause(Option<u32>),
|
|
SeekUsec(f64),
|
|
SeekSample(f64),
|
|
SeekPulse(f64),
|
|
SetBpm(f64),
|
|
SetQuant(f64),
|
|
SetSync(f64),
|
|
}
|
|
|
|
impl<T: ClockApi> Command<T> for ClockCommand {
|
|
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
|
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<Quantize>;
|
|
/// Launch quantization factor
|
|
fn sync (&self) -> &Arc<LaunchSync>;
|
|
/// Current moment in time
|
|
fn current (&self) -> &Arc<Instant>;
|
|
/// Current temporal resolutions
|
|
fn timebase (&self) -> &Arc<Timebase> { &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<Transport>;
|
|
/// Playback state
|
|
fn transport_state (&self) -> &Arc<RwLock<Option<TransportState>>>;
|
|
/// Global sample and usec at which playback started
|
|
fn transport_offset (&self) -> &Arc<RwLock<Option<(usize, usize)>>>;
|
|
|
|
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<u32>) -> 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<u32>) -> 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
|
|
}
|
|
}
|