tek/src/clock.rs
2025-01-02 13:04:57 +01:00

230 lines
7.8 KiB
Rust

use crate::*;
pub mod clock_tui; pub use self::clock_tui::*;
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) -> &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 }
}
}
}
/// Hosts the JACK callback for updating the temporal pointer and playback status.
pub struct ClockAudio<'a, T: HasClock>(pub &'a mut T);
impl<T: HasClock> 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<u32>),
Pause(Option<u32>),
SeekUsec(f64),
SeekSample(f64),
SeekPulse(f64),
SetBpm(f64),
SetQuant(f64),
SetSync(f64),
}
impl<T: HasClock> Command<T> for ClockCommand {
fn execute (self, state: &mut T) -> Perhaps<Self> {
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 Clock {
/// JACK transport handle.
pub transport: Arc<Transport>,
/// Global temporal resolution (shared by [Moment] fields)
pub timebase: Arc<Timebase>,
/// Current global sample and usec (monotonic from JACK clock)
pub global: Arc<Moment>,
/// Global sample and usec at which playback started
pub started: Arc<RwLock<Option<Moment>>>,
/// Playback offset (when playing not from start)
pub offset: Arc<Moment>,
/// Current playhead position
pub playhead: Arc<Moment>,
/// Note quantization factor
pub quant: Arc<Quantize>,
/// Launch quantization factor
pub sync: Arc<LaunchSync>,
/// Size of buffer in samples
pub chunk: Arc<AtomicUsize>,
}
from!(|jack: &Arc<RwLock<JackConnection>>| Clock = {
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 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 timebase (&self) -> &Arc<Timebase> {
&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<u32>) -> 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<u32>) -> 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(())
}
pub fn bbt (&self) -> ::jack::contrib::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;
::jack::contrib::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
}
}
}
//#[cfg(test)]
//mod test {
//use super::*;
//#[test]
//fn test_samples_to_ticks () {
//let ticks = Ticks(12.3).between_samples(0, 100).collect::<Vec<_>>();
//println!("{ticks:?}");
//}
//}