diff --git a/crates/tek_core/src/time.rs b/crates/tek_core/src/time.rs index 57ace68d..05bdc577 100644 --- a/crates/tek_core/src/time.rs +++ b/crates/tek_core/src/time.rs @@ -1,30 +1,94 @@ use crate::*; use std::iter::Iterator; -/// Any numeric type that represents time -pub trait TimeUnit: Copy + Display + PartialOrd + PartialEq - + Add + Mul - + Div + Rem {} -impl TimeUnit for T where T: Copy + Display + PartialOrd + PartialEq - + Add + Mul - + Div + Rem {} -/// Integer time unit, such as samples, pulses, or microseconds -pub trait TimeInteger: TimeUnit + From + Into + Copy {} -impl TimeInteger for T where T: TimeUnit + From + Into + Copy {} -/// Floating time unit, such as beats or seconds -pub trait TimeFloat: TimeUnit + From + Into + Copy {} -impl TimeFloat for T where T: TimeUnit + From + Into + Copy {} -/// Defines global temporal resolutions. -#[derive(Debug)] pub struct Timebase { + +pub const DEFAULT_PPQ: f64 = 96.0; + +/// The unit of time, 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. +#[derive(Debug, Default)] +pub struct TimeUnit(AtomicF64); +/// Temporal resolutions: sample rate, tempo, MIDI pulses per quaver (beat) +#[derive(Debug, Clone)] +pub struct Timebase { /// Audio samples per second - sr: AtomicF64, + pub sr: TimeUnit, /// MIDI beats per minute - bpm: AtomicF64, + pub bpm: TimeUnit, /// MIDI ticks per beat - ppq: AtomicF64, + pub ppq: TimeUnit, } +/// A point in time in all time scales (microsecond, sample, MIDI pulse) +#[derive(Debug, Default, Clone)] +pub struct Instant { + pub timebase: Arc, + /// Current time in microseconds + pub usec: TimeUnit, + /// Current time in audio samples + pub sample: TimeUnit, + /// Current time in MIDI pulses + pub pulse: TimeUnit, +} +impl From for TimeUnit { + fn from (value: f64) -> Self { Self(value.into()) } +} +impl From for TimeUnit { + fn from (value: usize) -> Self { Self((value as f64).into()) } +} +pub trait TimeUnitMethods { + fn get (&self) -> T; + fn set (&self, value: T); +} +impl TimeUnitMethods for TimeUnit { + fn get (&self) -> f64 { self.0.load(Ordering::Relaxed) } + fn set (&self, value: f64) { self.0.store(value, Ordering::Relaxed) } +} +impl TimeUnitMethods for TimeUnit { + fn get (&self) -> usize { self.0.load(Ordering::Relaxed) as usize } + fn set (&self, value: usize) { self.0.store(value as f64, Ordering::Relaxed) } +} +impl Clone for TimeUnit { + fn clone (&self) -> Self { Self(TimeUnitMethods::::get(self).into()) } +} +macro_rules! impl_op { + ($Op:ident, $method:ident, |$a:ident,$b:ident|{$impl:expr}) => { + impl $Op for TimeUnit { + type Output = Self; + #[inline] fn $method (self, other: Self) -> Self { + let $a = TimeUnitMethods::::get(&self); + let $b = TimeUnitMethods::::get(&other); + Self($impl.into()) + } + } + impl $Op for TimeUnit { + type Output = Self; + #[inline] fn $method (self, other: usize) -> Self { + let $a = TimeUnitMethods::::get(&self); + let $b = other as f64; + Self($impl.into()) + } + } + impl $Op for TimeUnit { + type Output = Self; + #[inline] fn $method (self, other: f64) -> Self { + let $a = TimeUnitMethods::::get(&self); + let $b = other; + Self($impl.into()) + } + } + } +} +impl_op!(Add, add, |a, b|{a + b}); +impl_op!(Sub, sub, |a, b|{a - b}); +impl_op!(Mul, mul, |a, b|{a * b}); +impl_op!(Div, div, |a, b|{a / b}); +impl_op!(Rem, rem, |a, b|{a % b}); impl Timebase { /// Specify sample rate, BPM and PPQ - pub fn new (s: impl Into, b: impl Into, p: impl Into) -> Self { + pub fn new (s: impl Into, b: impl Into, p: impl Into) -> Self { Self { sr: s.into(), bpm: b.into(), ppq: p.into() } } /// Iterate over ticks between start and end. @@ -32,16 +96,7 @@ impl Timebase { TicksIterator { fpt: self.samples_per_pulse(), sample: start, start, end } } } -/// Represents a point in time in all scales -#[derive(Debug, Default)] pub struct Instant { - pub timebase: Arc, - /// Current time in microseconds - usec: AtomicUsize, - /// Current time in audio samples - sample: AtomicUsize, - /// Current time in MIDI pulses - pulse: AtomicF64, -} +impl Default for Timebase { fn default () -> Self { Self::new(48000f64, 150f64, 96f64) } } impl Instant { pub fn from_usec (timebase: &Arc, usec: usize) -> Self { Self { @@ -68,22 +123,22 @@ impl Instant { } } pub fn update_from_usec (&self, usec: usize) { - self.set_usec(usec); - self.set_pulse(self.timebase.usecs_to_pulse(usec as f64)); - self.set_sample(self.timebase.usecs_to_sample(usec as f64) as usize); + self.usec.set(usec); + self.pulse.set(self.timebase.usecs_to_pulse(usec as f64)); + self.sample.set(self.timebase.usecs_to_sample(usec as f64)); } pub fn update_from_sample (&self, sample: usize) { - self.set_usec(self.timebase.samples_to_usec(sample as f64) as usize); - self.set_pulse(self.timebase.samples_to_pulse(sample as f64)); - self.set_sample(sample); + self.usec.set(self.timebase.samples_to_usec(sample as f64)); + self.pulse.set(self.timebase.samples_to_pulse(sample as f64)); + self.sample.set(sample); } pub fn update_from_pulse (&self, pulse: f64) { - self.set_usec(self.timebase.pulses_to_usec(pulse) as usize); - self.set_pulse(pulse); - self.set_sample(self.timebase.pulses_to_sample(pulse) as usize); + self.usec.set(self.timebase.pulses_to_usec(pulse)); + self.pulse.set(pulse); + self.sample.set(self.timebase.pulses_to_sample(pulse)); } pub fn format_beat (&self) -> String { - self.format_beats_float(self.pulse()) + self.format_beats(self.pulse().get()) } } /// Iterator that emits subsequent ticks within a range. @@ -114,229 +169,128 @@ impl Iterator for TicksIterator { } } } -/// Trait for struct that defines a sample rate in hertz (samples per second) -pub trait SampleRate { +/// Something that defines a sample rate in hertz (samples per second) +pub trait SampleRate { /// Get the sample rate - fn sr (&self) -> U; - /// Set the sample rate - fn set_sr (&self, sr: U); + fn sr (&self) -> &TimeUnit; /// Return the duration of a sample in microseconds (floating) - #[inline] fn usec_per_sample (&self) -> U where U: TimeFloat { - U::from(1_000_000f64 / self.sr().into()) + #[inline] fn usec_per_sample (&self) -> TimeUnit { + TimeUnit::from(1_000_000f64) / self.sr().get() } /// Return the duration of a sample in microseconds (floating) - #[inline] fn sample_per_usec (&self) -> U where U: TimeFloat { - U::from(self.sr().into() / 1_000_000f64) + #[inline] fn sample_per_usec (&self) -> TimeUnit { + TimeUnit::from(1_000_000f64) / self.sr().get() } /// Convert a number of samples to microseconds (floating) - #[inline] fn samples_to_usec (&self, samples: U) -> U where U: TimeFloat { - samples * self.usec_per_sample() + #[inline] fn samples_to_usec (&self, samples: impl Into) -> f64 { + samples.into() * self.usec_per_sample() } /// Convert a number of microseconds to samples (floating) - #[inline] fn usecs_to_sample (&self, usecs: U) -> U where U: TimeFloat, Self: SampleRate { - usecs * self.sample_per_usec() + #[inline] fn usecs_to_sample (&self, usecs: impl Into) -> f64 { + usecs.into() * self.sample_per_usec() } } -impl SampleRate for Timebase { - #[inline] fn sr (&self) -> f64 { self.sr.load(Ordering::Relaxed) } - #[inline] fn set_sr (&self, sr: f64) { self.sr.store(sr, Ordering::Relaxed); } -} -impl SampleRate for Instant { - #[inline] fn sr (&self) -> f64 { self.timebase.sr() } - #[inline] fn set_sr (&self, sr: f64) { self.timebase.set_sr(sr); } -} -/// Trait for struct that defines a tempo in beats per minute -pub trait BeatsPerMinute { +impl SampleRate for Timebase { #[inline] fn sr (&self) -> &TimeUnit { &self.sr } } +impl SampleRate for Instant { #[inline] fn sr (&self) -> &TimeUnit { self.timebase.sr() } } +/// Something that defines a tempo in beats per minute +pub trait BeatsPerMinute { /// Get the tempo - fn bpm (&self) -> U; - /// Set the tempo - fn set_bpm (&self, bpm: U); + fn bpm (&self) -> &TimeUnit; /// Return the duration fo a beat in microseconds - #[inline] fn usec_per_beat (&self) -> U { - U::from(60_000_000f64) / self.bpm() - } + #[inline] fn usec_per_beat (&self) -> f64 { 60_000_000f64 / self.bpm().get() } /// Return the number of beats in a second - #[inline] fn beat_per_second (&self) -> U { - self.bpm() / U::from(60_000_000f64) - } + #[inline] fn beat_per_second (&self) -> f64 { self.bpm().get() / 60_000_000f64 } /// Return the number of microseconds corresponding to a note of the given duration - #[inline] fn note_to_usec (&self, (num, den): (U, U)) -> U { - U::from(4.0) * self.usec_per_beat() * num / den + #[inline] fn note_to_usec (&self, (num, den): (f64, f64)) -> f64 { + 4.0 * self.usec_per_beat() * num / den } /// Return the number of samples corresponding to a note of the given duration - #[inline] fn note_to_samples (&self, note: (U, U)) -> U where Self: SampleRate { + #[inline] fn note_to_samples (&self, note: (f64, f64)) -> TimeUnit { self.usec_to_sample(self.note_to_usec(note)) } /// Return the number of samples corresponding to the given number of microseconds - #[inline] fn usec_to_sample (&self, usec: U) -> U where Self: SampleRate { - usec * self.sr() / U::from(1000f64) + #[inline] fn usec_to_sample (&self, usec: f64) -> TimeUnit { + usec * self.sr() / TimeUnit::from(1000f64) } /// Return the quantized position of a moment in time given a step - #[inline] fn quantize (&self, step: (U, U), time: U) -> (U, U) { + #[inline] fn quantize (&self, step: (f64, f64), time: f64) -> (f64, f64) { let step = self.note_to_usec(step); (time / step, time % step) } /// Quantize a collection of events - #[inline] fn quantize_into + Sized, T> ( - &self, step: (U, U), events: E - ) -> Vec<(U, U)> { + #[inline] fn quantize_into + Sized, T> ( + &self, step: (TimeUnit, TimeUnit), events: E + ) -> Vec<(TimeUnit, TimeUnit)> { events.map(|(time, event)|(self.quantize(step, time).0, event)).collect() } } -impl BeatsPerMinute for Timebase { - #[inline] fn bpm (&self) -> f64 { self.bpm.load(Ordering::Relaxed) } - #[inline] fn set_bpm (&self, bpm: f64) { self.bpm.store(bpm, Ordering::Relaxed); } -} -impl BeatsPerMinute for Instant { - #[inline] fn bpm (&self) -> f64 { self.timebase.bpm() } - #[inline] fn set_bpm (&self, bpm: f64) { self.timebase.set_bpm(bpm); } -} -/// Trait for struct that defines a MIDI resolution in pulses per quaver (beat) -pub trait PulsesPerQuaver { - const DEFAULT_PPQ: U; - /// Get the PPQ - fn ppq (&self) -> U; - /// Set the PPQ - fn set_ppq (&self, ppq: U); +impl BeatsPerMinute for Timebase { #[inline] fn bpm (&self) -> &TimeUnit { &self.bpm } } +impl BeatsPerMinute for Instant { #[inline] fn bpm (&self) -> &TimeUnit { self.timebase.bpm() } } +/// Something that defines a MIDI resolution in pulses per quaver (beat) +pub trait PulsesPerQuaver { + // Get the PPQ + fn ppq (&self) -> &TimeUnit; /// Return duration of a pulse in microseconds (BPM-dependent) - #[inline] fn pulse_per_usec (&self) -> U - where U: TimeFloat, Self: BeatsPerMinute - { - self.ppq() / self.usec_per_beat() - } + #[inline] fn pulse_per_usec (&self) -> f64 { self.ppq().get() / self.usec_per_beat() } /// Return duration of a pulse in microseconds (BPM-dependent) - #[inline] fn usec_per_pulse (&self) -> U - where U: TimeFloat, Self: BeatsPerMinute - { - self.usec_per_beat() / self.ppq() - } + #[inline] fn usec_per_pulse (&self) -> f64 { self.usec_per_beat() / self.ppq().get() } /// Return number of pulses to which a number of microseconds corresponds (BPM-dependent) - #[inline] fn usecs_to_pulse (&self, usec: U) -> U - where U: TimeFloat, Self: BeatsPerMinute - { - usec * self.pulse_per_usec() - } + #[inline] fn usecs_to_pulse (&self, usec: f64) -> f64 { usec * self.pulse_per_usec() } /// Convert a number of pulses to a sample number (SR- and BPM-dependent) - #[inline] fn pulses_to_usec (&self, pulse: U) -> U - where U: TimeFloat, Self: SampleRate + BeatsPerMinute - { - pulse / self.usec_per_pulse() - } + #[inline] fn pulses_to_usec (&self, pulse: f64) -> f64 { pulse / self.usec_per_pulse() } /// Return number of pulses in a second (BPM-dependent) - #[inline] fn pulses_per_second (&self) -> U - where U: TimeFloat, Self: BeatsPerMinute - { - self.beat_per_second() * self.ppq() - } + #[inline] fn pulses_per_second (&self) -> f64 { self.beat_per_second() * self.ppq().get() } /// Return fraction of a pulse to which a sample corresponds (SR- and BPM-dependent) - #[inline] fn pulses_per_sample (&self) -> U - where U: TimeFloat, Self: SampleRate + BeatsPerMinute - { - self.usec_per_pulse() / self.usec_per_sample() - } + #[inline] fn pulses_per_sample (&self) -> f64 { self.usec_per_pulse() / self.usec_per_sample() } /// Convert a number of pulses to a sample number (SR- and BPM-dependent) - #[inline] fn pulses_to_sample (&self, p: U) -> U - where U: TimeFloat, Self: SampleRate + BeatsPerMinute - { - self.pulses_per_sample() * p - } + #[inline] fn pulses_to_sample (&self, p: f64) -> f64 { self.pulses_per_sample() * p } /// Convert a number of samples to a pulse number (SR- and BPM-dependent) - #[inline] fn samples_to_pulse (&self, s: U) -> U - where U: TimeFloat, Self: SampleRate + BeatsPerMinute - { - s / self.pulses_per_sample() - } + #[inline] fn samples_to_pulse (&self, s: f64) -> f64 { s / self.pulses_per_sample() } /// Return number of samples in a pulse (SR- and BPM-dependent) - #[inline] fn samples_per_pulse (&self) -> U - where U: TimeFloat, Self: SampleRate + BeatsPerMinute - { - self.sr() / self.pulses_per_second() - } - #[inline] fn format_beats_int (&self, pulse: U) -> String where U: TimeInteger { - let ppq = self.ppq(); - let (beats, pulses) = if ppq > U::from(0) { - (pulse / ppq, pulse % ppq) - } else { - (U::from(0), U::from(0)) - }; - let bars = (beats / U::from(4)) + U::from(1); - let beats = (beats % U::from(4)) + U::from(1); - format!("{bars}.{beats}.{pulses:02}") - } - #[inline] fn format_beats_float (&self, pulse: U) -> String where U: TimeFloat { - let ppq = self.ppq(); - let (beats, pulses) = if ppq > U::from(0.) { - (pulse / ppq, pulse % ppq) - } else { - (U::from(0.), U::from(0.)) - }; - let bars = (beats / U::from(4.)) + U::from(1.); - let beats = (beats % U::from(4.)) + U::from(1.); + #[inline] fn samples_per_pulse (&self) -> f64 { self.sr() / self.pulses_per_second() } + /// Format a number of pulses into Beat.Bar.Pulse + #[inline] fn format_beats (&self, pulse: f64) -> String { + let ppq: usize = self.ppq().get(); + let (beats, pulses) = if ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) }; + let bars = ((beats / 4) + 1) as usize; + let beats = ((beats % 4) + 1) as usize; format!("{bars}.{beats}.{pulses:02}") } } -impl PulsesPerQuaver for Timebase { - const DEFAULT_PPQ: f64 = 96f64; - #[inline] fn ppq (&self) -> f64 { self.ppq.load(Ordering::Relaxed) } - #[inline] fn set_ppq (&self, ppq: f64) { self.ppq.store(ppq, Ordering::Relaxed); } -} -impl PulsesPerQuaver for Instant { - const DEFAULT_PPQ: f64 = 96f64; - #[inline] fn ppq (&self) -> f64 { self.timebase.ppq() } - #[inline] fn set_ppq (&self, ppq: f64) { self.timebase.set_ppq(ppq); } -} -pub trait SamplePosition { - fn sample (&self) -> U; - fn set_sample (&self, sample: U); -} -impl SamplePosition for Instant { - #[inline] fn sample (&self) -> usize { self.sample.load(Ordering::Relaxed) } - #[inline] fn set_sample (&self, s: usize) { self.sample.store(s, Ordering::Relaxed); } -} -pub trait PulsePosition { - fn pulse (&self) -> U; - fn set_pulse (&self, pulse: U); -} -impl PulsePosition for Instant { - #[inline] fn pulse (&self) -> f64 { self.pulse.load(Ordering::Relaxed) } - #[inline] fn set_pulse (&self, p: f64) { self.pulse.store(p, Ordering::Relaxed); } -} -impl PulsePosition for Instant { - #[inline] fn pulse (&self) -> usize { self.pulse.load(Ordering::Relaxed) as usize } - #[inline] fn set_pulse (&self, p: usize) { PulsePosition::::set_pulse(self, p as f64) } -} -pub trait UsecPosition { - fn usec (&self) -> U; - fn set_usec (&self, usec: U); - #[inline] fn format_current_usec (&self) -> String where U: From { - let usecs = self.usec(); - let (seconds, msecs) = (usecs / U::from(1000000), usecs / U::from(1000) % U::from(1000)); - let (minutes, seconds) = (seconds / U::from(60), seconds % U::from(60)); +impl PulsesPerQuaver for Timebase { #[inline] fn ppq (&self) -> &TimeUnit { &self.ppq } } +impl PulsesPerQuaver for Instant { #[inline] fn ppq (&self) -> &TimeUnit { self.timebase.ppq() } } +/// Something that refers to a point in time in samples +pub trait SamplePosition { fn sample (&self) -> &TimeUnit; } +impl SamplePosition for Instant { #[inline] fn sample (&self) -> &TimeUnit { &self.sample } } +/// Something that refers to a point in time in MIDI pulses +pub trait PulsePosition { fn pulse (&self) -> &TimeUnit; } +impl PulsePosition for Instant { #[inline] fn pulse (&self) -> &TimeUnit { &self.pulse } } +/// Something that refers to a point in time in microseconds +pub trait UsecPosition { + fn usec (&self) -> &TimeUnit; + #[inline] fn format_current_usec (&self) -> String { + let usecs: usize = self.usec().get(); + let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000); + let (minutes, seconds) = (seconds / 60, seconds % 60); format!("{minutes}:{seconds:02}:{msecs:03}") } } -impl UsecPosition for Instant { - #[inline] fn usec (&self) -> usize { self.usec.load(Ordering::Relaxed) } - #[inline] fn set_usec (&self, u: usize) { self.usec.store(u, Ordering::Relaxed); } -} -pub trait LaunchSync { - fn sync (&self) -> U; - fn set_sync (&self, sync: U); - #[inline] fn next_launch_pulse (&self) -> U where U: TimeInteger, Self: PulsePosition { - let sync = self.sync(); - let pulse = self.pulse(); - if pulse % sync == U::from(0) { +impl UsecPosition for Instant { #[inline] fn usec (&self) -> &TimeUnit { &self.usec } } +/// Something that defines launch quantization +pub trait LaunchSync { + fn sync (&self) -> &TimeUnit; + #[inline] fn next_launch_pulse (&self) -> f64 where Self: PulsePosition { + let sync: f64 = self.sync().get(); + let pulse = self.pulse().get(); + if pulse % sync == 0. { pulse } else { - (pulse / sync + U::from(1)) * sync + (pulse / sync + 1.) * sync } } } -pub trait Quantize { - fn quant (&self) -> T; - fn set_quant (&self, quant: T); -} -impl Default for Timebase { fn default () -> Self { Self::new(48000f64, 150f64, 96f64) } } +/// Something that defines note quantization +pub trait Quantize { fn quant (&self) -> &TimeUnit; } /// (pulses, name), assuming 96 PPQ pub const NOTE_DURATIONS: [(usize, &str);26] = [ (1, "1/384"),