From dc38fd3d52c82c40fa6778b2c9723bc43f450bf5 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 1 Nov 2024 15:15:49 +0200 Subject: [PATCH] wip: simplify time traits (combine BPM and PPQ) --- crates/tek_core/src/time.rs | 190 +++++++++++++++++------------------- 1 file changed, 87 insertions(+), 103 deletions(-) diff --git a/crates/tek_core/src/time.rs b/crates/tek_core/src/time.rs index 05bdc577..d6586f11 100644 --- a/crates/tek_core/src/time.rs +++ b/crates/tek_core/src/time.rs @@ -11,6 +11,10 @@ pub const DEFAULT_PPQ: f64 = 96.0; /// can be clocked in microseconds with f64 without losing precision. #[derive(Debug, Default)] pub struct TimeUnit(AtomicF64); +impl TimeUnit { + fn get (&self) -> f64 { self.0.load(Ordering::Relaxed) } + fn set (&self, value: f64) { self.0.store(value, Ordering::Relaxed) } +} /// Temporal resolutions: sample rate, tempo, MIDI pulses per quaver (beat) #[derive(Debug, Clone)] pub struct Timebase { @@ -26,57 +30,34 @@ pub struct Timebase { pub struct Instant { pub timebase: Arc, /// Current time in microseconds - pub usec: TimeUnit, + pub usec: TimeUnit, /// Current time in audio samples - pub sample: TimeUnit, + 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()) } + 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()) } } +impl Into for TimeUnit { fn into (self) -> f64 { self.get() } } +impl Into for TimeUnit { fn into (self) -> usize { self.get() as usize } } +impl Into for &TimeUnit { fn into (self) -> f64 { self.get() } } +impl Into for &TimeUnit { fn into (self) -> usize { self.get() as usize } } +impl Clone for TimeUnit { fn clone (&self) -> Self { Self(self.get().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()) + 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 TimeUnit { - type Output = Self; - #[inline] fn $method (self, other: usize) -> Self { - let $a = TimeUnitMethods::::get(&self); - let $b = other as f64; - Self($impl.into()) + 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 TimeUnit { - type Output = Self; - #[inline] fn $method (self, other: f64) -> Self { - let $a = TimeUnitMethods::::get(&self); - let $b = other; - Self($impl.into()) + type Output = Self; #[inline] fn $method (self, other: f64) -> Self::Output { + let $a = self.get(); let $b = other; Self($impl.into()) } } } @@ -98,38 +79,38 @@ impl Timebase { } impl Default for Timebase { fn default () -> Self { Self::new(48000f64, 150f64, 96f64) } } impl Instant { - pub fn from_usec (timebase: &Arc, usec: usize) -> Self { + pub fn from_usec (timebase: &Arc, usec: f64) -> Self { Self { usec: usec.into(), - sample: (timebase.usecs_to_sample(usec as f64) as usize).into(), - pulse: timebase.usecs_to_pulse(usec as f64).into(), + sample: timebase.usecs_to_sample(usec).into(), + pulse: timebase.usecs_to_pulse(usec).into(), timebase: timebase.clone(), } } - pub fn from_sample (timebase: &Arc, sample: usize) -> Self { + pub fn from_sample (timebase: &Arc, sample: f64) -> Self { Self { sample: sample.into(), - usec: (timebase.samples_to_usec(sample as f64) as usize).into(), - pulse: timebase.samples_to_pulse(sample as f64).into(), + usec: timebase.samples_to_usec(sample).into(), + pulse: timebase.samples_to_pulse(sample).into(), timebase: timebase.clone(), } } pub fn from_pulse (timebase: &Arc, pulse: f64) -> Self { Self { pulse: pulse.into(), - sample: (timebase.pulses_to_sample(pulse) as usize).into(), - usec: (timebase.pulses_to_usec(pulse) as usize).into(), + sample: timebase.pulses_to_sample(pulse).into(), + usec: timebase.pulses_to_usec(pulse).into(), timebase: timebase.clone(), } } - pub fn update_from_usec (&self, usec: usize) { + pub fn update_from_usec (&self, usec: f64) { 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)); + self.pulse.set(self.timebase.usecs_to_pulse(usec)); + self.sample.set(self.timebase.usecs_to_sample(usec)); } - pub fn update_from_sample (&self, sample: usize) { - self.usec.set(self.timebase.samples_to_usec(sample as f64)); - self.pulse.set(self.timebase.samples_to_pulse(sample as f64)); + pub fn update_from_sample (&self, sample: f64) { + self.usec.set(self.timebase.samples_to_usec(sample)); + self.pulse.set(self.timebase.samples_to_pulse(sample)); self.sample.set(sample); } pub fn update_from_pulse (&self, pulse: f64) { @@ -174,28 +155,23 @@ pub trait SampleRate { /// Get the sample rate fn sr (&self) -> &TimeUnit; /// Return the duration of a sample in microseconds (floating) - #[inline] fn usec_per_sample (&self) -> TimeUnit { - TimeUnit::from(1_000_000f64) / self.sr().get() - } + #[inline] fn usec_per_sample (&self) -> f64 { 1_000_000f64 / self.sr().get() } /// Return the duration of a sample in microseconds (floating) - #[inline] fn sample_per_usec (&self) -> TimeUnit { - TimeUnit::from(1_000_000f64) / self.sr().get() - } + #[inline] fn sample_per_usec (&self) -> f64 { self.sr().get() / 1_000_000f64 } /// Convert a number of samples to microseconds (floating) - #[inline] fn samples_to_usec (&self, samples: impl Into) -> f64 { - samples.into() * self.usec_per_sample() - } + #[inline] fn samples_to_usec (&self, samples: f64) -> f64 { samples * self.usec_per_sample() } /// Convert a number of microseconds to samples (floating) - #[inline] fn usecs_to_sample (&self, usecs: impl Into) -> f64 { - usecs.into() * self.sample_per_usec() - } + #[inline] fn usecs_to_sample (&self, usecs: f64) -> f64 { usecs * self.sample_per_usec() } } 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 { +/// Something that defines a tempo in BPM (beats per minute) +/// and a MIDI resolution in pulses per beat (PPQ, pulses per quaver) +pub trait MIDITime { /// Get the tempo fn bpm (&self) -> &TimeUnit; + // Get the PPQ + fn ppq (&self) -> &TimeUnit; /// Return the duration fo a beat in microseconds #[inline] fn usec_per_beat (&self) -> f64 { 60_000_000f64 / self.bpm().get() } /// Return the number of beats in a second @@ -204,32 +180,6 @@ pub trait BeatsPerMinute { #[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: (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: 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: (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: (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) -> &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) -> f64 { self.ppq().get() / self.usec_per_beat() } /// Return duration of a pulse in microseconds (BPM-dependent) @@ -241,24 +191,58 @@ pub trait PulsesPerQuaver { /// Return number of pulses in a second (BPM-dependent) #[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) -> 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: 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: f64) -> f64 { s / self.pulses_per_sample() } + #[inline] fn pulses_per_sample (&self) -> f64 where Self: SampleRate { + self.usec_per_pulse() / self.usec_per_sample() + } /// Return number of samples in a pulse (SR- and BPM-dependent) - #[inline] fn samples_per_pulse (&self) -> f64 { self.sr() / self.pulses_per_second() } + #[inline] fn samples_per_pulse (&self) -> f64 where Self: SampleRate { + self.sr().get() / self.pulses_per_second() + } + /// Convert a number of pulses to a sample number (SR- and BPM-dependent) + #[inline] fn pulses_to_sample (&self, p: f64) -> f64 where Self: SampleRate { + 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: f64) -> f64 where Self: SampleRate { + s / self.pulses_per_sample() + } + /// Return the number of samples corresponding to a note of the given duration + #[inline] fn note_to_samples (&self, note: (f64, f64)) -> f64 where Self: SampleRate { + 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: f64) -> f64 where Self: SampleRate { + usec * self.sr().get() / 1000f64 + } + /// Return the quantized position of a moment in time given a step + #[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: (f64, f64), events: E + ) -> Vec<(f64, f64)> { + events.map(|(time, event)|(self.quantize(step, time).0, event)).collect() + } /// Format a number of pulses into Beat.Bar.Pulse #[inline] fn format_beats (&self, pulse: f64) -> String { - let ppq: usize = self.ppq().get(); + let pulse = pulse as usize; + let ppq = self.ppq().get() as usize; 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 { #[inline] fn ppq (&self) -> &TimeUnit { &self.ppq } } -impl PulsesPerQuaver for Instant { #[inline] fn ppq (&self) -> &TimeUnit { self.timebase.ppq() } } +impl MIDITime for Timebase { + #[inline] fn bpm (&self) -> &TimeUnit { &self.bpm } + #[inline] fn ppq (&self) -> &TimeUnit { &self.ppq } +} +impl MIDITime for Instant { + #[inline] fn bpm (&self) -> &TimeUnit { &self.timebase.bpm() } + #[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 } } @@ -269,7 +253,7 @@ impl PulsePosition for Instant { #[inline] fn pulse (&self) -> &TimeUnit { &self pub trait UsecPosition { fn usec (&self) -> &TimeUnit; #[inline] fn format_current_usec (&self) -> String { - let usecs: usize = self.usec().get(); + let usecs: usize = self.usec().get() as usize; let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000); let (minutes, seconds) = (seconds / 60, seconds % 60); format!("{minutes}:{seconds:02}:{msecs:03}")