diff --git a/crates/tek/src/core.rs b/crates/tek/src/core.rs index 0a469b98..6c4c98df 100644 --- a/crates/tek/src/core.rs +++ b/crates/tek/src/core.rs @@ -24,15 +24,18 @@ pub use self::{ } pub trait Gettable { + /// Returns current value fn get (&self) -> T; } pub trait Mutable: Gettable { - fn set (&mut self, value: T); + /// Sets new value, returns old + fn set (&mut self, value: T) -> T; } pub trait InteriorMutable: Gettable { - fn set (&self, value: T); + /// Sets new value, returns old + fn set (&self, value: T) -> T; } /// Standard result type. diff --git a/crates/tek/src/time.rs b/crates/tek/src/time.rs index 4c37e243..0ef9a5db 100644 --- a/crates/tek/src/time.rs +++ b/crates/tek/src/time.rs @@ -1,13 +1,9 @@ -pub(crate) mod bpm; pub(crate) use bpm::*; pub(crate) mod clock; pub(crate) use clock::*; pub(crate) mod moment; pub(crate) use moment::*; pub(crate) mod perf; pub(crate) use perf::*; -pub(crate) mod ppq; pub(crate) use ppq::*; -pub(crate) mod quant; pub(crate) use quant::*; +pub(crate) mod pulse; pub(crate) use pulse::*; pub(crate) mod sr; pub(crate) use sr::*; -pub(crate) mod timebase; pub(crate) use timebase::*; pub(crate) mod unit; pub(crate) use unit::*; -pub(crate) mod usec; pub(crate) use usec::*; /// (pulses, name), assuming 96 PPQ pub const NOTE_DURATIONS: [(usize, &str);26] = [ diff --git a/crates/tek/src/time/bpm.rs b/crates/tek/src/time/bpm.rs deleted file mode 100644 index 30fffb9d..00000000 --- a/crates/tek/src/time/bpm.rs +++ /dev/null @@ -1,5 +0,0 @@ -use crate::*; - -/// Tempo in beats per minute -#[derive(Debug, Default)] pub struct BeatsPerMinute(AtomicF64); -impl_time_unit!(BeatsPerMinute); diff --git a/crates/tek/src/time/moment.rs b/crates/tek/src/time/moment.rs index aafaef6a..79e0a68a 100644 --- a/crates/tek/src/time/moment.rs +++ b/crates/tek/src/time/moment.rs @@ -20,6 +20,7 @@ pub struct Moment { /// Current time in MIDI pulses pub pulse: Pulse, } + impl Moment { pub fn zero (timebase: &Arc) -> Self { Self { usec: 0.into(), sample: 0.into(), pulse: 0.into(), timebase: timebase.clone() } @@ -67,3 +68,114 @@ impl Moment { self.timebase.format_beats_1(self.pulse.get()) } } + +/// Temporal resolutions: sample rate, tempo, MIDI pulses per quaver (beat) +#[derive(Debug, Clone)] +pub struct Timebase { + /// Audio samples per second + pub sr: SampleRate, + /// MIDI beats per minute + pub bpm: BeatsPerMinute, + /// MIDI ticks per beat + pub ppq: PulsesPerQuaver, +} + +impl Timebase { + /// Specify sample rate, BPM and PPQ + 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. + #[inline] pub fn pulses_between_samples (&self, start: usize, end: usize) -> TicksIterator { + TicksIterator { spp: self.samples_per_pulse(), sample: start, start, end } + } + /// Return the duration fo a beat in microseconds + #[inline] pub fn usec_per_beat (&self) -> f64 { 60_000_000f64 / self.bpm.get() } + /// Return the number of beats in a second + #[inline] pub fn beat_per_second (&self) -> f64 { self.bpm.get() / 60f64 } + /// Return the number of microseconds corresponding to a note of the given duration + #[inline] pub fn note_to_usec (&self, (num, den): (f64, f64)) -> f64 { + 4.0 * self.usec_per_beat() * num / den + } + /// Return duration of a pulse in microseconds (BPM-dependent) + #[inline] pub fn pulse_per_usec (&self) -> f64 { self.ppq.get() / self.usec_per_beat() } + /// Return duration of a pulse in microseconds (BPM-dependent) + #[inline] pub 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] pub 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] pub fn pulses_to_usec (&self, pulse: f64) -> f64 { pulse / self.usec_per_pulse() } + /// Return number of pulses in a second (BPM-dependent) + #[inline] pub 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] pub fn pulses_per_sample (&self) -> f64 { + self.usec_per_pulse() / self.sr.usec_per_sample() + } + /// Return number of samples in a pulse (SR- and BPM-dependent) + #[inline] pub fn samples_per_pulse (&self) -> f64 { + self.sr.get() / self.pulses_per_second() + } + /// Convert a number of pulses to a sample number (SR- and BPM-dependent) + #[inline] pub 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] pub fn samples_to_pulse (&self, s: f64) -> f64 { + s / self.pulses_per_sample() + } + /// Return the number of samples corresponding to a note of the given duration + #[inline] pub fn note_to_samples (&self, note: (f64, f64)) -> f64 { + self.usec_to_sample(self.note_to_usec(note)) + } + /// Return the number of samples corresponding to the given number of microseconds + #[inline] pub fn usec_to_sample (&self, usec: f64) -> f64 { + usec * self.sr.get() / 1000f64 + } + /// Return the quantized position of a moment in time given a step + #[inline] pub 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] pub 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 starting from 0 + #[inline] pub fn format_beats_0 (&self, pulse: f64) -> String { + 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) }; + format!("{}.{}.{pulses:02}", beats / 4, beats % 4) + } + /// Format a number of pulses into Beat.Bar starting from 0 + #[inline] pub fn format_beats_0_short (&self, pulse: f64) -> String { + let pulse = pulse as usize; + let ppq = self.ppq.get() as usize; + let beats = if ppq > 0 { pulse / ppq } else { 0 }; + format!("{}.{}", beats / 4, beats % 4) + } + /// Format a number of pulses into Beat.Bar.Pulse starting from 1 + #[inline] pub fn format_beats_1 (&self, pulse: f64) -> String { + 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) }; + format!("{}.{}.{pulses:02}", beats / 4 + 1, beats % 4 + 1) + } + /// Format a number of pulses into Beat.Bar.Pulse starting from 1 + #[inline] pub fn format_beats_1_short (&self, pulse: f64) -> String { + let pulse = pulse as usize; + let ppq = self.ppq.get() as usize; + let beats = if ppq > 0 { pulse / ppq } else { 0 }; + format!("{}.{}", beats / 4 + 1, beats % 4 + 1) + } +} + +impl Default for Timebase { + fn default () -> Self { Self::new(48000f64, 150f64, DEFAULT_PPQ) } +} diff --git a/crates/tek/src/time/ppq.rs b/crates/tek/src/time/pulse.rs similarity index 63% rename from crates/tek/src/time/ppq.rs rename to crates/tek/src/time/pulse.rs index 964b36eb..52096580 100644 --- a/crates/tek/src/time/ppq.rs +++ b/crates/tek/src/time/pulse.rs @@ -1,6 +1,7 @@ use crate::*; pub const DEFAULT_PPQ: f64 = 96.0; + /// FIXME: remove this and use PPQ from timebase everywhere: pub const PPQ: usize = 96; @@ -12,6 +13,34 @@ impl_time_unit!(PulsesPerQuaver); #[derive(Debug, Default)] pub struct Pulse(AtomicF64); impl_time_unit!(Pulse); +/// Tempo in beats per minute +#[derive(Debug, Default)] pub struct BeatsPerMinute(AtomicF64); +impl_time_unit!(BeatsPerMinute); + +/// Quantization setting for launching clips +#[derive(Debug, Default)] pub struct LaunchSync(AtomicF64); +impl_time_unit!(LaunchSync); +impl LaunchSync { + pub fn next (&self) -> f64 { + next_note_length(self.get() as usize) as f64 + } + pub fn prev (&self) -> f64 { + prev_note_length(self.get() as usize) as f64 + } +} + +/// Quantization setting for notes +#[derive(Debug, Default)] pub struct Quantize(AtomicF64); +impl_time_unit!(Quantize); +impl Quantize { + pub fn next (&self) -> f64 { + next_note_length(self.get() as usize) as f64 + } + pub fn prev (&self) -> f64 { + prev_note_length(self.get() as usize) as f64 + } +} + /// Iterator that emits subsequent ticks within a range. pub struct TicksIterator { pub spp: f64, diff --git a/crates/tek/src/time/quant.rs b/crates/tek/src/time/quant.rs deleted file mode 100644 index dc05e271..00000000 --- a/crates/tek/src/time/quant.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::*; - -/// Quantization setting for launching clips -#[derive(Debug, Default)] pub struct LaunchSync(AtomicF64); -impl_time_unit!(LaunchSync); -impl LaunchSync { - pub fn next (&self) -> f64 { - next_note_length(self.get() as usize) as f64 - } - pub fn prev (&self) -> f64 { - prev_note_length(self.get() as usize) as f64 - } -} - -/// Quantization setting for notes -#[derive(Debug, Default)] pub struct Quantize(AtomicF64); -impl_time_unit!(Quantize); -impl Quantize { - pub fn next (&self) -> f64 { - next_note_length(self.get() as usize) as f64 - } - pub fn prev (&self) -> f64 { - prev_note_length(self.get() as usize) as f64 - } -} diff --git a/crates/tek/src/time/sr.rs b/crates/tek/src/time/sr.rs index 1d3edd75..bdaf5190 100644 --- a/crates/tek/src/time/sr.rs +++ b/crates/tek/src/time/sr.rs @@ -1,5 +1,17 @@ use crate::*; +/// Timestamp in microseconds +#[derive(Debug, Default)] pub struct Microsecond(AtomicF64); +impl_time_unit!(Microsecond); +impl Microsecond { + #[inline] pub fn format_msu (&self) -> String { + let usecs = self.get() as usize; + let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000); + let (minutes, seconds) = (seconds / 60, seconds % 60); + format!("{minutes}:{seconds:02}:{msecs:03}") + } +} + /// Audio sample rate in Hz (samples per second) #[derive(Debug, Default)] pub struct SampleRate(AtomicF64); impl_time_unit!(SampleRate); diff --git a/crates/tek/src/time/timebase.rs b/crates/tek/src/time/timebase.rs deleted file mode 100644 index cc898b92..00000000 --- a/crates/tek/src/time/timebase.rs +++ /dev/null @@ -1,112 +0,0 @@ -use crate::*; - -/// Temporal resolutions: sample rate, tempo, MIDI pulses per quaver (beat) -#[derive(Debug, Clone)] -pub struct Timebase { - /// Audio samples per second - pub sr: SampleRate, - /// MIDI beats per minute - pub bpm: BeatsPerMinute, - /// MIDI ticks per beat - pub ppq: PulsesPerQuaver, -} - -impl Timebase { - /// Specify sample rate, BPM and PPQ - 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. - #[inline] pub fn pulses_between_samples (&self, start: usize, end: usize) -> TicksIterator { - TicksIterator { spp: self.samples_per_pulse(), sample: start, start, end } - } - /// Return the duration fo a beat in microseconds - #[inline] pub fn usec_per_beat (&self) -> f64 { 60_000_000f64 / self.bpm.get() } - /// Return the number of beats in a second - #[inline] pub fn beat_per_second (&self) -> f64 { self.bpm.get() / 60f64 } - /// Return the number of microseconds corresponding to a note of the given duration - #[inline] pub fn note_to_usec (&self, (num, den): (f64, f64)) -> f64 { - 4.0 * self.usec_per_beat() * num / den - } - /// Return duration of a pulse in microseconds (BPM-dependent) - #[inline] pub fn pulse_per_usec (&self) -> f64 { self.ppq.get() / self.usec_per_beat() } - /// Return duration of a pulse in microseconds (BPM-dependent) - #[inline] pub 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] pub 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] pub fn pulses_to_usec (&self, pulse: f64) -> f64 { pulse / self.usec_per_pulse() } - /// Return number of pulses in a second (BPM-dependent) - #[inline] pub 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] pub fn pulses_per_sample (&self) -> f64 { - self.usec_per_pulse() / self.sr.usec_per_sample() - } - /// Return number of samples in a pulse (SR- and BPM-dependent) - #[inline] pub fn samples_per_pulse (&self) -> f64 { - self.sr.get() / self.pulses_per_second() - } - /// Convert a number of pulses to a sample number (SR- and BPM-dependent) - #[inline] pub 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] pub fn samples_to_pulse (&self, s: f64) -> f64 { - s / self.pulses_per_sample() - } - /// Return the number of samples corresponding to a note of the given duration - #[inline] pub fn note_to_samples (&self, note: (f64, f64)) -> f64 { - self.usec_to_sample(self.note_to_usec(note)) - } - /// Return the number of samples corresponding to the given number of microseconds - #[inline] pub fn usec_to_sample (&self, usec: f64) -> f64 { - usec * self.sr.get() / 1000f64 - } - /// Return the quantized position of a moment in time given a step - #[inline] pub 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] pub 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 starting from 0 - #[inline] pub fn format_beats_0 (&self, pulse: f64) -> String { - 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) }; - format!("{}.{}.{pulses:02}", beats / 4, beats % 4) - } - /// Format a number of pulses into Beat.Bar starting from 0 - #[inline] pub fn format_beats_0_short (&self, pulse: f64) -> String { - let pulse = pulse as usize; - let ppq = self.ppq.get() as usize; - let beats = if ppq > 0 { pulse / ppq } else { 0 }; - format!("{}.{}", beats / 4, beats % 4) - } - /// Format a number of pulses into Beat.Bar.Pulse starting from 1 - #[inline] pub fn format_beats_1 (&self, pulse: f64) -> String { - 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) }; - format!("{}.{}.{pulses:02}", beats / 4 + 1, beats % 4 + 1) - } - /// Format a number of pulses into Beat.Bar.Pulse starting from 1 - #[inline] pub fn format_beats_1_short (&self, pulse: f64) -> String { - let pulse = pulse as usize; - let ppq = self.ppq.get() as usize; - let beats = if ppq > 0 { pulse / ppq } else { 0 }; - format!("{}.{}", beats / 4 + 1, beats % 4 + 1) - } -} - -impl Default for Timebase { - fn default () -> Self { Self::new(48000f64, 150f64, DEFAULT_PPQ) } -} diff --git a/crates/tek/src/time/unit.rs b/crates/tek/src/time/unit.rs index 940cd65b..0abdaaa3 100644 --- a/crates/tek/src/time/unit.rs +++ b/crates/tek/src/time/unit.rs @@ -1,3 +1,4 @@ +use crate::*; /// A unit of time, represented as an atomic 64-bit float. /// @@ -5,14 +6,9 @@ /// 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 { - /// Returns current value - fn get (&self) -> f64; - /// Sets new value, returns old - fn set (&self, value: f64) -> f64; -} +pub trait TimeUnit: InteriorMutable {} -/// Implement arithmetic for a unit of time +/// 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 { @@ -36,14 +32,17 @@ pub trait TimeUnit { /// Define and implement a unit of time #[macro_export] macro_rules! impl_time_unit { ($T:ident) => { - impl TimeUnit for $T { + impl Gettable for $T { fn get (&self) -> f64 { self.0.load(Ordering::Relaxed) } + } + impl InteriorMutable for $T { fn set (&self, value: f64) -> f64 { let old = self.get(); self.0.store(value, Ordering::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}); diff --git a/crates/tek/src/time/usec.rs b/crates/tek/src/time/usec.rs deleted file mode 100644 index 8f768861..00000000 --- a/crates/tek/src/time/usec.rs +++ /dev/null @@ -1,13 +0,0 @@ -use crate::*; - -/// Timestamp in microseconds -#[derive(Debug, Default)] pub struct Microsecond(AtomicF64); -impl_time_unit!(Microsecond); -impl Microsecond { - #[inline] pub fn format_msu (&self) -> String { - let usecs = self.get() as usize; - let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000); - let (minutes, seconds) = (seconds / 60, seconds % 60); - format!("{minutes}:{seconds:02}:{msecs:03}") - } -}