mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-08 20:56:43 +01:00
invoke timebase callback, persists state but doesn't seem to do anything
This commit is contained in:
parent
ae69e87dc9
commit
003329aa1b
10 changed files with 350 additions and 329 deletions
208
src/time.rs
208
src/time.rs
|
|
@ -1,9 +1,205 @@
|
|||
pub(crate) mod clock; pub(crate) use clock::*;
|
||||
pub(crate) mod moment; pub(crate) use moment::*;
|
||||
pub(crate) mod perf; pub(crate) use perf::*;
|
||||
pub(crate) mod pulse; pub(crate) use pulse::*;
|
||||
pub(crate) mod sr; pub(crate) use sr::*;
|
||||
pub(crate) mod unit; pub(crate) use unit::*;
|
||||
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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue