mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
212 lines
7.2 KiB
Rust
212 lines
7.2 KiB
Rust
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<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 ClockModel {
|
|
/// 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<JackClient>>| 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<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(())
|
|
}
|
|
}
|
|
|
|
//#[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:?}");
|
|
//}
|
|
//}
|