wip: simplify time traits (f64-first)

This commit is contained in:
🪞👃🪞 2024-11-01 14:55:03 +02:00
parent 66f9afe500
commit 98073dd40c

View file

@ -1,30 +1,94 @@
use crate::*;
use std::iter::Iterator;
/// Any numeric type that represents time
pub trait TimeUnit: Copy + Display + PartialOrd + PartialEq
+ Add<Self, Output=Self> + Mul<Self, Output=Self>
+ Div<Self, Output=Self> + Rem<Self, Output=Self> {}
impl<T> TimeUnit for T where T: Copy + Display + PartialOrd + PartialEq
+ Add<Self, Output=Self> + Mul<Self, Output=Self>
+ Div<Self, Output=Self> + Rem<Self, Output=Self> {}
/// Integer time unit, such as samples, pulses, or microseconds
pub trait TimeInteger: TimeUnit + From<usize> + Into<usize> + Copy {}
impl<T> TimeInteger for T where T: TimeUnit + From<usize> + Into<usize> + Copy {}
/// Floating time unit, such as beats or seconds
pub trait TimeFloat: TimeUnit + From<f64> + Into<f64> + Copy {}
impl<T> TimeFloat for T where T: TimeUnit + From<f64> + Into<f64> + 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<Timebase>,
/// 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<f64> for TimeUnit {
fn from (value: f64) -> Self { Self(value.into()) }
}
impl From<usize> for TimeUnit {
fn from (value: usize) -> Self { Self((value as f64).into()) }
}
pub trait TimeUnitMethods<T> {
fn get (&self) -> T;
fn set (&self, value: T);
}
impl TimeUnitMethods<f64> for TimeUnit {
fn get (&self) -> f64 { self.0.load(Ordering::Relaxed) }
fn set (&self, value: f64) { self.0.store(value, Ordering::Relaxed) }
}
impl TimeUnitMethods<usize> 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::<f64>::get(self).into()) }
}
macro_rules! impl_op {
($Op:ident, $method:ident, |$a:ident,$b:ident|{$impl:expr}) => {
impl $Op<Self> for TimeUnit {
type Output = Self;
#[inline] fn $method (self, other: Self) -> Self {
let $a = TimeUnitMethods::<f64>::get(&self);
let $b = TimeUnitMethods::<f64>::get(&other);
Self($impl.into())
}
}
impl $Op<usize> for TimeUnit {
type Output = Self;
#[inline] fn $method (self, other: usize) -> Self {
let $a = TimeUnitMethods::<f64>::get(&self);
let $b = other as f64;
Self($impl.into())
}
}
impl $Op<f64> for TimeUnit {
type Output = Self;
#[inline] fn $method (self, other: f64) -> Self {
let $a = TimeUnitMethods::<f64>::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<AtomicF64>, b: impl Into<AtomicF64>, p: impl Into<AtomicF64>) -> Self {
pub fn new (s: impl Into<TimeUnit>, b: impl Into<TimeUnit>, p: impl Into<TimeUnit>) -> 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<Timebase>,
/// 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<Timebase>, 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<U: TimeUnit> {
/// 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<TimeUnit>) -> 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<U> {
usecs * self.sample_per_usec()
#[inline] fn usecs_to_sample (&self, usecs: impl Into<TimeUnit>) -> f64 {
usecs.into() * self.sample_per_usec()
}
}
impl SampleRate<f64> 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<f64> 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<U: TimeFloat> {
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<U> {
#[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<U> {
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 <E: Iterator<Item=(U, U)> + Sized, T> (
&self, step: (U, U), events: E
) -> Vec<(U, U)> {
#[inline] fn quantize_into <E: Iterator<Item=(TimeUnit, TimeUnit)> + Sized, T> (
&self, step: (TimeUnit, TimeUnit), events: E
) -> Vec<(TimeUnit, TimeUnit)> {
events.map(|(time, event)|(self.quantize(step, time).0, event)).collect()
}
}
impl BeatsPerMinute<f64> 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<f64> 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<U: TimeUnit> {
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<U>
{
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<U>
{
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<U>
{
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<U> + BeatsPerMinute<U>
{
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<U>
{
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<U> + BeatsPerMinute<U>
{
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<U> + BeatsPerMinute<U>
{
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<U> + BeatsPerMinute<U>
{
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<U> + BeatsPerMinute<U>
{
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<f64> 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<f64> 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<U: TimeUnit> {
fn sample (&self) -> U;
fn set_sample (&self, sample: U);
}
impl SamplePosition<usize> 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<U: TimeUnit> {
fn pulse (&self) -> U;
fn set_pulse (&self, pulse: U);
}
impl PulsePosition<f64> 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<usize> for Instant {
#[inline] fn pulse (&self) -> usize { self.pulse.load(Ordering::Relaxed) as usize }
#[inline] fn set_pulse (&self, p: usize) { PulsePosition::<f64>::set_pulse(self, p as f64) }
}
pub trait UsecPosition<U: TimeUnit> {
fn usec (&self) -> U;
fn set_usec (&self, usec: U);
#[inline] fn format_current_usec (&self) -> String where U: From<usize> {
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<usize> 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<U: TimeUnit> {
fn sync (&self) -> U;
fn set_sync (&self, sync: U);
#[inline] fn next_launch_pulse (&self) -> U where U: TimeInteger, Self: PulsePosition<U> {
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<T> {
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<T> { fn quant (&self) -> &TimeUnit; }
/// (pulses, name), assuming 96 PPQ
pub const NOTE_DURATIONS: [(usize, &str);26] = [
(1, "1/384"),