simplify time traits

This commit is contained in:
🪞👃🪞 2024-11-01 13:31:27 +02:00
parent ad2f75bee6
commit 66f9afe500
7 changed files with 151 additions and 165 deletions

View file

@ -22,20 +22,98 @@ impl<T> TimeFloat for T where T: TimeUnit + From<f64> + Into<f64> + Copy {}
/// MIDI ticks per beat
ppq: AtomicF64,
}
impl Timebase {
/// Specify sample rate, BPM and PPQ
pub fn new (s: impl Into<AtomicF64>, b: impl Into<AtomicF64>, p: impl Into<AtomicF64>) -> Self {
Self { sr: s.into(), bpm: b.into(), ppq: p.into() }
}
/// Iterate over ticks between start and end.
pub fn pulses_between_samples (&self, start: usize, end: usize) -> TicksIterator {
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,
usec: AtomicUsize,
/// Current time in audio samples
sample: AtomicUsize,
sample: AtomicUsize,
/// Current time in MIDI pulses
pulse: AtomicF64,
pulse: AtomicF64,
}
impl Instant {
pub fn from_usec (timebase: &Arc<Timebase>, usec: usize) -> 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(),
timebase: timebase.clone(),
}
}
pub fn from_sample (timebase: &Arc<Timebase>, sample: usize) -> 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(),
timebase: timebase.clone(),
}
}
pub fn from_pulse (timebase: &Arc<Timebase>, 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(),
timebase: timebase.clone(),
}
}
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);
}
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);
}
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);
}
pub fn format_beat (&self) -> String {
self.format_beats_float(self.pulse())
}
}
/// Defines samples per tick.
pub struct Ticks(pub f64);
/// Iterator that emits subsequent ticks within a range.
pub struct TicksIterator(f64, usize, usize, usize);
pub struct TicksIterator {
fpt: f64,
sample: usize,
start: usize,
end: usize,
}
impl Iterator for TicksIterator {
type Item = (usize, usize);
fn next (&mut self) -> Option<Self::Item> {
loop {
if self.sample > self.end { return None }
let fpt = self.fpt;
let sample = self.sample as f64;
let start = self.start;
let end = self.end;
self.sample += 1;
//println!("{fpt} {sample} {start} {end}");
let jitter = sample.rem_euclid(fpt); // ramps
let next_jitter = (sample + 1.0).rem_euclid(fpt);
if jitter > next_jitter { // at crossing:
let time = (sample as usize) % (end as usize-start as usize);
let tick = (sample / fpt) as usize;
return Some((time, tick))
}
}
}
}
/// Trait for struct that defines a sample rate in hertz (samples per second)
pub trait SampleRate<U: TimeUnit> {
/// Get the sample rate
@ -59,6 +137,14 @@ pub trait SampleRate<U: TimeUnit> {
usecs * 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> {
/// Get the tempo
@ -97,6 +183,14 @@ pub trait BeatsPerMinute<U: TimeFloat> {
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;
@ -158,7 +252,7 @@ pub trait PulsesPerQuaver<U: TimeUnit> {
{
self.sr() / self.pulses_per_second()
}
#[inline] fn format_beats (&self, pulse: U) -> String where U: TimeInteger {
#[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)
@ -169,19 +263,47 @@ pub trait PulsesPerQuaver<U: TimeUnit> {
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.);
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);
#[inline] fn format_beat (&self) -> String
where Self: PulsesPerQuaver<U>, U: TimeInteger
{
self.format_beats(self.pulse())
}
}
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;
@ -193,6 +315,10 @@ pub trait UsecPosition<U: TimeUnit> {
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);
@ -211,98 +337,6 @@ pub trait Quantize<T> {
fn set_quant (&self, quant: T);
}
impl Default for Timebase { fn default () -> Self { Self::new(48000f64, 150f64, 96f64) } }
impl Timebase {
pub fn new (s: impl Into<AtomicF64>, b: impl Into<AtomicF64>, p: impl Into<AtomicF64>) -> Self {
Self { sr: s.into(), bpm: b.into(), ppq: p.into() }
}
/// Iterate over ticks between start and end.
pub fn pulses_between_samples (&self, start: usize, end: usize) -> TicksIterator {
TicksIterator(self.samples_per_pulse(), start, start, end)
}
}
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 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 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 SampleRate<f64> for Instant {
#[inline] fn sr (&self) -> f64 { self.timebase.sr() }
#[inline] fn set_sr (&self, sr: f64) { self.timebase.set_sr(sr); }
}
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); }
}
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); }
}
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); }
}
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); }
}
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) }
}
impl Instant {
pub fn from_usec (timebase: &Arc<Timebase>, usec: usize) -> 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(),
timebase: timebase.clone(),
}
}
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);
}
pub fn from_sample (timebase: &Arc<Timebase>, sample: usize) -> 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(),
timebase: timebase.clone(),
}
}
pub fn update_from_sample (&self, sample: usize) {
self.set_sample(sample);
self.set_usec(self.timebase.samples_to_usec(sample as f64) as usize);
self.set_pulse(self.timebase.samples_to_pulse(sample as f64));
}
pub fn from_pulse (timebase: &Arc<Timebase>, 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(),
timebase: timebase.clone(),
}
}
pub fn update_from_pulse (&self, pulse: f64) {
self.set_pulse(pulse);
self.set_usec(self.timebase.pulses_to_usec(pulse) as usize);
self.set_sample(self.timebase.pulses_to_sample(pulse) as usize);
}
}
/// (pulses, name), assuming 96 PPQ
pub const NOTE_DURATIONS: [(usize, &str);26] = [
(1, "1/384"),
@ -349,27 +383,6 @@ pub fn pulses_to_name (pulses: usize) -> &'static str {
for (length, name) in &NOTE_DURATIONS { if *length == pulses { return name } }
""
}
impl Iterator for TicksIterator {
type Item = (usize, usize);
fn next (&mut self) -> Option<Self::Item> {
loop {
if self.1 > self.3 { return None }
let fpt = self.0;
let sample = self.1 as f64;
let start = self.2;
let end = self.3;
self.1 = self.1 + 1;
//println!("{fpt} {sample} {start} {end}");
let jitter = sample.rem_euclid(fpt); // ramps
let next_jitter = (sample + 1.0).rem_euclid(fpt);
if jitter > next_jitter { // at crossing:
let time = (sample as usize) % (end as usize-start as usize);
let tick = (sample / fpt) as usize;
return Some((time, tick))
}
}
}
}
#[cfg(test)]
mod test {
use super::*;