use crate::*; use ::std::sync::{Arc, RwLock, atomic::{AtomicUsize, Ordering::*}}; use ::atomic_float::AtomicF64; use ::tengri::{draw::*, term::*}; impl +AsMut> HasClock for T {} pub trait HasClock: AsRef + AsMut { fn clock (&self) -> &Clock { self.as_ref() } fn clock_mut (&mut self) -> &mut Clock { self.as_mut() } } /// The source of time. /// /// ``` /// let clock = tek::Clock::default(); /// ``` #[derive(Clone, Default)] pub struct Clock { /// JACK transport handle. pub transport: Arc>, /// Global temporal resolution (shared by [Moment] fields) pub timebase: Arc, /// Current global sample and usec (monotonic from JACK clock) pub global: Arc, /// Global sample and usec at which playback started pub started: Arc>>, /// Playback offset (when playing not from start) pub offset: Arc, /// Current playhead position pub playhead: Arc, /// Note quantization factor pub quant: Arc, /// Launch quantization factor pub sync: Arc, /// Size of buffer in samples pub chunk: Arc, // Cache of formatted strings pub view_cache: Arc>, /// For syncing the clock to an external source #[cfg(feature = "port")] pub midi_in: Arc>>, /// For syncing other devices to this clock #[cfg(feature = "port")] pub midi_out: Arc>>, /// For emitting a metronome #[cfg(feature = "port")] pub click_out: Arc>>, } /// Quantization setting for launching clips /// /// ``` /// /// ``` #[derive(Debug, Default)] pub struct LaunchSync (pub(crate) AtomicF64); /// Quantization setting for notes /// /// ``` /// /// ``` #[derive(Debug, Default)] pub struct Quantize (pub(crate) AtomicF64); /// A unit of time, represented as an atomic 64-bit float. /// /// According to https://stackoverflow.com/a/873367, as per IEEE754, /// every integer between 1 and 2^53 can be represented exactly. /// This should mean that, even at 192kHz sampling rate, over 1 year of audio /// can be clocked in microseconds with f64 without losing precision. pub trait TimeUnit: InteriorMutable {} pub const DEFAULT_PPQ: f64 = 96.0; /// FIXME: remove this and use PPQ from timebase everywhere: pub const PPQ: usize = 96; /// (pulses, name), assuming 96 PPQ pub const NOTE_DURATIONS: [(usize, &str);26] = [ (1, "1/384"), (2, "1/192"), (3, "1/128"), (4, "1/96"), (6, "1/64"), (8, "1/48"), (12, "1/32"), (16, "1/24"), (24, "1/16"), (32, "1/12"), (48, "1/8"), (64, "1/6"), (96, "1/4"), (128, "1/3"), (192, "1/2"), (256, "2/3"), (384, "1/1"), (512, "4/3"), (576, "3/2"), (768, "2/1"), (1152, "3/1"), (1536, "4/1"), (2304, "6/1"), (3072, "8/1"), (3456, "9/1"), (6144, "16/1"), ]; pub const NOTE_NAMES: [&str; 128] = [ "C0", "C#0", "D0", "D#0", "E0", "F0", "F#0", "G0", "G#0", "A0", "A#0", "B0", "C1", "C#1", "D1", "D#1", "E1", "F1", "F#1", "G1", "G#1", "A1", "A#1", "B1", "C2", "C#2", "D2", "D#2", "E2", "F2", "F#2", "G2", "G#2", "A2", "A#2", "B2", "C3", "C#3", "D3", "D#3", "E3", "F3", "F#3", "G3", "G#3", "A3", "A#3", "B3", "C4", "C#4", "D4", "D#4", "E4", "F4", "F#4", "G4", "G#4", "A4", "A#4", "B4", "C5", "C#5", "D5", "D#5", "E5", "F5", "F#5", "G5", "G#5", "A5", "A#5", "B5", "C6", "C#6", "D6", "D#6", "E6", "F6", "F#6", "G6", "G#6", "A6", "A#6", "B6", "C7", "C#7", "D7", "D#7", "E7", "F7", "F#7", "G7", "G#7", "A7", "A#7", "B7", "C8", "C#8", "D8", "D#8", "E8", "F8", "F#8", "G8", "G#8", "A8", "A#8", "B8", "C9", "C#9", "D9", "D#9", "E9", "F9", "F#9", "G9", "G#9", "A9", "A#9", "B9", "C10", "C#10", "D10", "D#10", "E10", "F10", "F#10", "G10", ]; def_command!(ClockCommand: |clock: Clock| { SeekUsec { usec: f64 } => { clock.playhead.update_from_usec(*usec); Ok(None) }, SeekSample { sample: f64 } => { clock.playhead.update_from_sample(*sample); Ok(None) }, SeekPulse { pulse: f64 } => { clock.playhead.update_from_pulse(*pulse); Ok(None) }, SetBpm { bpm: f64 } => Ok(Some( Self::SetBpm { bpm: clock.timebase().bpm.set(*bpm) })), SetQuant { quant: f64 } => Ok(Some( Self::SetQuant { quant: clock.quant.set(*quant) })), SetSync { sync: f64 } => Ok(Some( Self::SetSync { sync: clock.sync.set(*sync) })), Play { position: Option } => { clock.play_from(*position)?; Ok(None) /* TODO Some(Pause(previousPosition)) */ }, Pause { position: Option } => { clock.pause_at(*position)?; Ok(None) }, TogglePlayback { position: u32 } => Ok(if clock.is_rolling() { clock.pause_at(Some(*position))?; None } else { clock.play_from(Some(*position))?; None }), }); impl LaunchSync { pub fn next (&self) -> f64 { note_duration_next(self.get() as usize) as f64 } pub fn prev (&self) -> f64 { note_duration_prev(self.get() as usize) as f64 } } impl Quantize { pub fn next (&self) -> f64 { note_duration_next(self.get() as usize) as f64 } pub fn prev (&self) -> f64 { note_duration_prev(self.get() as usize) as f64 } } /// Implement an arithmetic operation for a unit of time #[macro_export] macro_rules! impl_op { ($T:ident, $Op:ident, $method:ident, |$a:ident,$b:ident|{$impl:expr}) => { impl $Op for $T { type Output = Self; #[inline] fn $method (self, other: Self) -> Self::Output { let $a = self.get(); let $b = other.get(); Self($impl.into()) } } impl $Op for $T { type Output = Self; #[inline] fn $method (self, other: usize) -> Self::Output { let $a = self.get(); let $b = other as f64; Self($impl.into()) } } impl $Op for $T { type Output = Self; #[inline] fn $method (self, other: f64) -> Self::Output { let $a = self.get(); let $b = other; Self($impl.into()) } } } } /// Define and implement a unit of time #[macro_export] macro_rules! impl_time_unit { ($T:ident) => { impl Gettable for $T { fn get (&self) -> f64 { self.0.load(Relaxed) } } impl InteriorMutable for $T { fn set (&self, value: f64) -> f64 { let old = self.get(); self.0.store(value, Relaxed); old } } impl TimeUnit for $T {} impl_op!($T, Add, add, |a, b|{a + b}); impl_op!($T, Sub, sub, |a, b|{a - b}); impl_op!($T, Mul, mul, |a, b|{a * b}); impl_op!($T, Div, div, |a, b|{a / b}); impl_op!($T, Rem, rem, |a, b|{a % b}); impl From for $T { fn from (value: f64) -> Self { Self(value.into()) } } impl From for $T { fn from (value: usize) -> Self { Self((value as f64).into()) } } impl From<$T> for f64 { fn from (value: $T) -> Self { value.get() } } impl From<$T> for usize { fn from (value: $T) -> Self { value.get() as usize } } impl From<&$T> for f64 { fn from (value: &$T) -> Self { value.get() } } impl From<&$T> for usize { fn from (value: &$T) -> Self { value.get() as usize } } impl Clone for $T { fn clone (&self) -> Self { Self(self.get().into()) } } } } impl std::fmt::Debug for Clock { fn fmt (&self, f: &mut 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 new (jack: &Jack<'static>, bpm: Option) -> Usually { let (chunk, transport) = jack.with_client(|c|(c.buffer_size(), c.transport())); let timebase = Arc::new(Timebase::default()); let clock = Self { quant: Arc::new(24.into()), sync: Arc::new(384.into()), transport: Arc::new(Some(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, midi_in: Arc::new(RwLock::new(Some(MidiInput::new(jack, &"M/clock", &[])?))), midi_out: Arc::new(RwLock::new(Some(MidiOutput::new(jack, &"clock/M", &[])?))), click_out: Arc::new(RwLock::new(Some(AudioOutput::new(jack, &"click", &[])?))), ..Default::default() }; if let Some(bpm) = bpm { clock.timebase.bpm.set(bpm); } Ok(clock) } pub fn timebase (&self) -> &Arc { &self.timebase } /// Current sample rate pub fn sr (&self) -> &SampleRate { &self.timebase.sr } /// Current tempo pub fn bpm (&self) -> &Bpm { &self.timebase.bpm } /// Current MIDI resolution pub fn ppq (&self) -> &Ppq { &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) -> Usually<()> { if let Some(transport) = self.transport.as_ref() { if let Some(start) = start { transport.locate(start)?; } transport.start()?; } Ok(()) } /// Pause, optionally seeking to a given location afterwards pub fn pause_at (&self, pause: Option) -> Usually<()> { if let Some(transport) = self.transport.as_ref() { transport.stop()?; if let Some(pause) = pause { 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); let mut started = self.started.write().unwrap(); // If transport has just started or just stopped, // update starting point: if let Some(transport) = self.transport.as_ref() { match (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) -> 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; 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 } } pub fn next_launch_instant (&self) -> Moment { Moment::from_pulse(self.timebase(), self.next_launch_pulse() as f64) } /// Get index of first sample to populate. /// /// Greater than 0 means that the first pulse of the clip /// falls somewhere in the middle of the chunk. pub fn get_sample_offset (&self, scope: &ProcessScope, started: &Moment) -> usize{ (scope.last_frame_time() as usize).saturating_sub( started.sample.get() as usize + self.started.read().unwrap().as_ref().unwrap().sample.get() as usize ) } // Get iterator that emits sample paired with pulse. // // * Sample: index into output buffer at which to write MIDI event // * Pulse: index into clip from which to take the MIDI event // // Emitted for each sample of the output buffer that corresponds to a MIDI pulse. pub fn get_pulses (&self, scope: &ProcessScope, offset: usize) -> Ticker { self.timebase().pulses_between_samples(offset, offset + scope.n_frames() as usize) } } impl Clock { fn _todo_provide_u32 (&self) -> u32 { todo!() } fn _todo_provide_opt_u32 (&self) -> Option { todo!() } fn _todo_provide_f64 (&self) -> f64 { todo!() } } impl Act for ClockCommand { fn act (&self, state: &mut T) -> Perhaps { self.act(state.clock_mut()) // awesome } } #[cfg(feature = "clock")] impl_has!(Clock: |self: Track|self.sequencer.clock); impl_default!(Timebase: Self::new(48000f64, 150f64, DEFAULT_PPQ)); impl_time_unit!(SampleCount); impl_time_unit!(SampleRate); impl_time_unit!(Microsecond); impl_time_unit!(Quantize); impl_time_unit!(Ppq); impl_time_unit!(Pulse); impl_time_unit!(Bpm); impl_time_unit!(LaunchSync); mod moment; pub use self::moment::*; mod ticker; pub use self::ticker::*; mod timebase; pub use self::timebase::*; mod clock_view; pub use self::clock_view::*;