collect crates/ and deps/

This commit is contained in:
🪞👃🪞 2025-04-19 01:23:43 +03:00
parent 2f8882f6cd
commit 8fa0f8a409
140 changed files with 23 additions and 21 deletions

9
crates/time/Cargo.toml Normal file
View file

@ -0,0 +1,9 @@
[package]
name = "tek_time"
edition = "2021"
version = "0.2.0"
[dependencies]
tengri = { workspace = true }
tek_jack = { workspace = true }
atomic_float = { workspace = true }

234
crates/time/src/clock.rs Normal file
View file

@ -0,0 +1,234 @@
use crate::*;
pub trait HasClock: Send + Sync {
fn clock (&self) -> &Clock;
fn clock_mut (&mut self) -> &mut Clock;
}
#[macro_export] macro_rules! has_clock {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? HasClock for $Struct $(<$($L),*$($T),*>)? {
fn clock (&$self) -> &Clock { &$cb }
fn clock_mut (&mut $self) -> &mut Clock { &mut $cb }
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum ClockCommand {
Play(Option<u32>),
Pause(Option<u32>),
SeekUsec(f64),
SeekSample(f64),
SeekPulse(f64),
SetBpm(f64),
SetQuant(f64),
SetSync(f64),
}
provide_num!(u32: |self: Clock| {});
provide!(f64: |self: Clock| {});
atom_command!(ClockCommand: |state: Clock| {
("play" [] Some(Self::Play(None)))
("play" [t: u32] Some(Self::Play(t)))
("pause" [] Some(Self::Pause(None)))
("pause" [t: u32] Some(Self::Pause(t)))
("toggle" [] Some(if state.is_rolling() { Self::Pause(None) } else { Self::Play(None) }))
("toggle" [t: u32] Some(if state.is_rolling() { Self::Pause(t) } else { Self::Play(t) }))
("seek/usec" [t: f64] Some(Self::SeekUsec(t.expect("no usec"))))
("seek/pulse" [t: f64] Some(Self::SeekPulse(t.expect("no pulse"))))
("seek/sample" [t: f64] Some(Self::SeekSample(t.expect("no sample"))))
("set/bpm" [t: f64] Some(Self::SetBpm(t.expect("no bpm"))))
("set/sync" [t: f64] Some(Self::SetSync(t.expect("no sync"))))
("set/quant" [t: f64] Some(Self::SetQuant(t.expect("no quant"))))
});
impl<T: HasClock> Command<T> for ClockCommand {
fn execute (self, state: &mut T) -> Perhaps<Self> {
self.execute(state.clock_mut())
}
}
impl Command<Clock> for ClockCommand {
fn execute (self, state: &mut Clock) -> Perhaps<Self> {
use ClockCommand::*;
match self {
Play(start) => state.play_from(start)?,
Pause(pause) => state.pause_at(pause)?,
SeekUsec(usec) => state.playhead.update_from_usec(usec),
SeekSample(sample) => state.playhead.update_from_sample(sample),
SeekPulse(pulse) => state.playhead.update_from_pulse(pulse),
SetBpm(bpm) => return Ok(Some(SetBpm(state.timebase().bpm.set(bpm)))),
SetQuant(quant) => return Ok(Some(SetQuant(state.quant.set(quant)))),
SetSync(sync) => return Ok(Some(SetSync(state.sync.set(sync)))),
};
Ok(None)
}
}
#[derive(Clone, Default)]
pub struct Clock {
/// JACK transport handle.
pub transport: Arc<Option<Transport>>,
/// Global temporal resolution (shared by [Moment] fields)
pub timebase: Arc<Timebase>,
/// Current global sample and usec (monotonic from JACK clock)
pub global: Arc<Moment>,
/// Global sample and usec at which playback started
pub started: Arc<RwLock<Option<Moment>>>,
/// Playback offset (when playing not from start)
pub offset: Arc<Moment>,
/// Current playhead position
pub playhead: Arc<Moment>,
/// Note quantization factor
pub quant: Arc<Quantize>,
/// Launch quantization factor
pub sync: Arc<LaunchSync>,
/// Size of buffer in samples
pub chunk: Arc<AtomicUsize>,
/// For syncing the clock to an external source
pub midi_in: Arc<RwLock<Option<JackMidiIn>>>,
/// For syncing other devices to this clock
pub midi_out: Arc<RwLock<Option<JackMidiOut>>>,
/// For emitting a metronome
pub click_out: Arc<RwLock<Option<JackAudioOut>>>,
}
impl std::fmt::Debug for Clock {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("Clock")
.field("timebase", &self.timebase)
.field("chunk", &self.chunk)
.field("quant", &self.quant)
.field("sync", &self.sync)
.field("global", &self.global)
.field("playhead", &self.playhead)
.field("started", &self.started)
.finish()
}
}
impl Clock {
pub fn new (jack: &Jack, bpm: Option<f64>) -> Usually<Self> {
let (chunk, transport) = jack.with_client(|c|(c.buffer_size(), c.transport()));
let timebase = Arc::new(Timebase::default());
let clock = Self {
quant: Arc::new(24.into()),
sync: Arc::new(384.into()),
transport: Arc::new(Some(transport)),
chunk: Arc::new((chunk as usize).into()),
global: Arc::new(Moment::zero(&timebase)),
playhead: Arc::new(Moment::zero(&timebase)),
offset: Arc::new(Moment::zero(&timebase)),
started: RwLock::new(None).into(),
timebase,
midi_in: Arc::new(RwLock::new(Some(JackMidiIn::new(jack, "M/clock", &[])?))),
midi_out: Arc::new(RwLock::new(Some(JackMidiOut::new(jack, "clock/M", &[])?))),
click_out: Arc::new(RwLock::new(Some(JackAudioOut::new(jack, "click", &[])?))),
};
if let Some(bpm) = bpm {
clock.timebase.bpm.set(bpm);
}
Ok(clock)
}
pub fn timebase (&self) -> &Arc<Timebase> {
&self.timebase
}
/// Current sample rate
pub fn sr (&self) -> &SampleRate {
&self.timebase.sr
}
/// Current tempo
pub fn bpm (&self) -> &BeatsPerMinute {
&self.timebase.bpm
}
/// Current MIDI resolution
pub fn ppq (&self) -> &PulsesPerQuaver {
&self.timebase.ppq
}
/// Next pulse that matches launch sync (for phrase switchover)
pub fn next_launch_pulse (&self) -> usize {
let sync = self.sync.get() as usize;
let pulse = self.playhead.pulse.get() as usize;
if pulse % sync == 0 {
pulse
} else {
(pulse / sync + 1) * sync
}
}
/// Start playing, optionally seeking to a given location beforehand
pub fn play_from (&self, start: Option<u32>) -> Usually<()> {
if let Some(transport) = self.transport.as_ref() {
if let Some(start) = start {
transport.locate(start)?;
}
transport.start()?;
}
Ok(())
}
/// Pause, optionally seeking to a given location afterwards
pub fn pause_at (&self, pause: Option<u32>) -> Usually<()> {
if let Some(transport) = self.transport.as_ref() {
transport.stop()?;
if let Some(pause) = pause {
transport.locate(pause)?;
}
}
Ok(())
}
/// Is currently paused?
pub fn is_stopped (&self) -> bool {
self.started.read().unwrap().is_none()
}
/// Is currently playing?
pub fn is_rolling (&self) -> bool {
self.started.read().unwrap().is_some()
}
/// Update chunk size
pub fn set_chunk (&self, n_frames: usize) {
self.chunk.store(n_frames, Relaxed);
}
pub fn update_from_scope (&self, scope: &ProcessScope) -> Usually<()> {
// Store buffer length
self.set_chunk(scope.n_frames() as usize);
// Store reported global frame and usec
let CycleTimes { current_frames, current_usecs, .. } = scope.cycle_times()?;
self.global.sample.set(current_frames as f64);
self.global.usec.set(current_usecs as f64);
let mut started = self.started.write().unwrap();
// If transport has just started or just stopped,
// update starting point:
if let Some(transport) = self.transport.as_ref() {
match (transport.query_state()?, started.as_ref()) {
(TransportState::Rolling, None) => {
let moment = Moment::zero(&self.timebase);
moment.sample.set(current_frames as f64);
moment.usec.set(current_usecs as f64);
*started = Some(moment);
},
(TransportState::Stopped, Some(_)) => {
*started = None;
},
_ => {}
};
}
self.playhead.update_from_sample(started.as_ref()
.map(|started|current_frames as f64 - started.sample.get())
.unwrap_or(0.));
Ok(())
}
pub fn bbt (&self) -> PositionBBT {
let pulse = self.playhead.pulse.get() as i32;
let ppq = self.timebase.ppq.get() as i32;
let bpm = self.timebase.bpm.get();
let bar = (pulse / ppq) / 4;
PositionBBT {
bar: 1 + bar,
beat: 1 + (pulse / ppq) % 4,
tick: (pulse % ppq),
bar_start_tick: (bar * 4 * ppq) as f64,
beat_type: 4.,
beats_per_bar: 4.,
beats_per_minute: bpm,
ticks_per_beat: ppq as f64
}
}
}

57
crates/time/src/lib.rs Normal file
View file

@ -0,0 +1,57 @@
mod clock; pub use self::clock::*;
mod microsecond; pub use self::microsecond::*;
mod moment; pub use self::moment::*;
mod note_duration; pub use self::note_duration::*;
mod perf; pub use self::perf::*;
mod pulse; pub use self::pulse::*;
mod sample_count; pub use self::sample_count::*;
mod sample_rate; pub use self::sample_rate::*;
mod timebase; pub use self::timebase::*;
mod unit; pub use self::unit::*;
pub(crate) use ::tek_jack::{*, jack::{*, contrib::*}};
pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::*}};
pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem};
pub(crate) use ::tengri::{input::*, dsl::*};
pub use ::atomic_float; pub(crate) use atomic_float::*;
/// Standard result type.
pub(crate) type Usually<T> = Result<T, Box<dyn std::error::Error>>;
/// Standard optional result type.
pub(crate) type Perhaps<T> = Result<Option<T>, Box<dyn std::error::Error>>;
pub trait Gettable<T> {
/// Returns current value
fn get (&self) -> T;
}
pub trait Mutable<T>: Gettable<T> {
/// Sets new value, returns old
fn set (&mut self, value: T) -> T;
}
pub trait InteriorMutable<T>: Gettable<T> {
/// Sets new value, returns old
fn set (&self, value: T) -> T;
}
impl Gettable<bool> for AtomicBool {
fn get (&self) -> bool { self.load(Relaxed) }
}
impl InteriorMutable<bool> for AtomicBool {
fn set (&self, value: bool) -> bool { self.swap(value, Relaxed) }
}
impl Gettable<usize> for AtomicUsize {
fn get (&self) -> usize { self.load(Relaxed) }
}
impl InteriorMutable<usize> for AtomicUsize {
fn set (&self, value: usize) -> usize { self.swap(value, Relaxed) }
}
#[cfg(test)] #[test] fn test_time () -> Usually<()> {
// TODO!
Ok(())
}

View file

@ -0,0 +1,15 @@
use crate::*;
/// Timestamp in microseconds
#[derive(Debug, Default)] pub struct Microsecond(AtomicF64);
impl_time_unit!(Microsecond);
impl Microsecond {
#[inline] pub fn format_msu (&self) -> Arc<str> {
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}").into()
}
}

70
crates/time/src/moment.rs Normal file
View file

@ -0,0 +1,70 @@
use crate::*;
#[derive(Debug, Clone)]
pub enum Moment2 {
None,
Zero,
Usec(Microsecond),
Sample(SampleCount),
Pulse(Pulse),
}
/// A point in time in all time scales (microsecond, sample, MIDI pulse)
#[derive(Debug, Default, Clone)]
pub struct Moment {
pub timebase: Arc<Timebase>,
/// Current time in microseconds
pub usec: Microsecond,
/// Current time in audio samples
pub sample: SampleCount,
/// Current time in MIDI pulses
pub pulse: Pulse,
}
impl Moment {
pub fn zero (timebase: &Arc<Timebase>) -> Self {
Self { usec: 0.into(), sample: 0.into(), pulse: 0.into(), timebase: timebase.clone() }
}
pub fn from_usec (timebase: &Arc<Timebase>, usec: f64) -> Self {
Self {
usec: usec.into(),
sample: timebase.sr.usecs_to_sample(usec).into(),
pulse: timebase.usecs_to_pulse(usec).into(),
timebase: timebase.clone(),
}
}
pub fn from_sample (timebase: &Arc<Timebase>, sample: f64) -> Self {
Self {
sample: sample.into(),
usec: timebase.sr.samples_to_usec(sample).into(),
pulse: timebase.samples_to_pulse(sample).into(),
timebase: timebase.clone(),
}
}
pub fn from_pulse (timebase: &Arc<Timebase>, pulse: f64) -> Self {
Self {
pulse: pulse.into(),
sample: timebase.pulses_to_sample(pulse).into(),
usec: timebase.pulses_to_usec(pulse).into(),
timebase: timebase.clone(),
}
}
#[inline] pub fn update_from_usec (&self, usec: f64) {
self.usec.set(usec);
self.pulse.set(self.timebase.usecs_to_pulse(usec));
self.sample.set(self.timebase.sr.usecs_to_sample(usec));
}
#[inline] pub fn update_from_sample (&self, sample: f64) {
self.usec.set(self.timebase.sr.samples_to_usec(sample));
self.pulse.set(self.timebase.samples_to_pulse(sample));
self.sample.set(sample);
}
#[inline] pub fn update_from_pulse (&self, pulse: f64) {
self.usec.set(self.timebase.pulses_to_usec(pulse));
self.pulse.set(pulse);
self.sample.set(self.timebase.pulses_to_sample(pulse));
}
#[inline] pub fn format_beat (&self) -> Arc<str> {
self.timebase.format_beats_1(self.pulse.get()).into()
}
}

View file

@ -0,0 +1,35 @@
pub struct NoteDuration;
/// (pulses, name), assuming 96 PPQ
pub const NOTE_DURATIONS: [(usize, &str);26] = [
(1, "1/384"), (2, "1/192"),
(3, "1/128"), (4, "1/96"),
(6, "1/64"), (8, "1/48"),
(12, "1/32"), (16, "1/24"),
(24, "1/16"), (32, "1/12"),
(48, "1/8"), (64, "1/6"),
(96, "1/4"), (128, "1/3"),
(192, "1/2"), (256, "2/3"),
(384, "1/1"), (512, "4/3"),
(576, "3/2"), (768, "2/1"),
(1152, "3/1"), (1536, "4/1"),
(2304, "6/1"), (3072, "8/1"),
(3456, "9/1"), (6144, "16/1"),
];
impl NoteDuration {
/// Returns the next shorter length
pub fn prev (pulses: usize) -> usize {
for (length, _) in NOTE_DURATIONS.iter().rev() { if *length < pulses { return *length } }
pulses
}
/// Returns the next longer length
pub fn next (pulses: usize) -> usize {
for (length, _) in NOTE_DURATIONS.iter() { if *length > pulses { return *length } }
pulses
}
pub fn pulses_to_name (pulses: usize) -> &'static str {
for (length, name) in NOTE_DURATIONS.iter() { if *length == pulses { return name } }
""
}
}

22
crates/time/src/perf.rs Normal file
View file

@ -0,0 +1,22 @@
use crate::*;
use tengri::tui::PerfModel;
pub trait JackPerfModel {
fn update_from_jack_scope (&self, t0: Option<u64>, scope: &ProcessScope);
}
impl JackPerfModel for PerfModel {
fn update_from_jack_scope (&self, t0: Option<u64>, scope: &ProcessScope) {
if let Some(t0) = t0 {
let t1 = self.clock.raw();
self.used.store(
self.clock.delta_as_nanos(t0, t1) as f64,
Relaxed,
);
self.window.store(
scope.cycle_times().unwrap().period_usecs as f64,
Relaxed,
);
}
}
}

71
crates/time/src/pulse.rs Normal file
View file

@ -0,0 +1,71 @@
use crate::*;
pub const DEFAULT_PPQ: f64 = 96.0;
/// FIXME: remove this and use PPQ from timebase everywhere:
pub const PPQ: usize = 96;
/// MIDI resolution in PPQ (pulses per quarter note)
#[derive(Debug, Default)] pub struct PulsesPerQuaver(AtomicF64);
impl_time_unit!(PulsesPerQuaver);
/// Timestamp in MIDI pulses
#[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 {
NoteDuration::next(self.get() as usize) as f64
}
pub fn prev (&self) -> f64 {
NoteDuration::prev(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 {
NoteDuration::next(self.get() as usize) as f64
}
pub fn prev (&self) -> f64 {
NoteDuration::prev(self.get() as usize) as f64
}
}
/// Iterator that emits subsequent ticks within a range.
pub struct TicksIterator {
pub spp: f64,
pub sample: usize,
pub start: usize,
pub 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 spp = self.spp;
let sample = self.sample as f64;
let start = self.start;
let end = self.end;
self.sample += 1;
//println!("{spp} {sample} {start} {end}");
let jitter = sample.rem_euclid(spp); // ramps
let next_jitter = (sample + 1.0).rem_euclid(spp);
if jitter > next_jitter { // at crossing:
let time = (sample as usize) % (end as usize-start as usize);
let tick = (sample / spp) as usize;
return Some((time, tick))
}
}
}
}

View file

@ -0,0 +1,5 @@
use crate::*;
/// Timestamp in audio samples
#[derive(Debug, Default)] pub struct SampleCount(AtomicF64);
impl_time_unit!(SampleCount);

View file

@ -0,0 +1,23 @@
use crate::*;
/// Audio sample rate in Hz (samples per second)
#[derive(Debug, Default)] pub struct SampleRate(AtomicF64);
impl_time_unit!(SampleRate);
impl SampleRate {
/// Return the duration of a sample in microseconds (floating)
#[inline] pub fn usec_per_sample (&self) -> f64 {
1_000_000f64 / self.get()
}
/// Return the duration of a sample in microseconds (floating)
#[inline] pub fn sample_per_usec (&self) -> f64 {
self.get() / 1_000_000f64
}
/// Convert a number of samples to microseconds (floating)
#[inline] pub fn samples_to_usec (&self, samples: f64) -> f64 {
self.usec_per_sample() * samples
}
/// Convert a number of microseconds to samples (floating)
#[inline] pub fn usecs_to_sample (&self, usecs: f64) -> f64 {
self.sample_per_usec() * usecs
}
}

118
crates/time/src/timebase.rs Normal file
View file

@ -0,0 +1,118 @@
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<SampleRate>,
b: impl Into<BeatsPerMinute>,
p: impl Into<PulsesPerQuaver>
) -> 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 <E: Iterator<Item=(f64, f64)> + 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) -> Arc<str> {
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).into()
}
/// Format a number of pulses into Beat.Bar starting from 0
#[inline] pub fn format_beats_0_short (&self, pulse: f64) -> Arc<str> {
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).into()
}
/// Format a number of pulses into Beat.Bar.Pulse starting from 1
#[inline] pub fn format_beats_1 (&self, pulse: f64) -> Arc<str> {
let mut string = String::with_capacity(16);
self.format_beats_1_to(&mut string, pulse).expect("failed to format {pulse} into beat");
string.into()
}
/// Format a number of pulses into Beat.Bar.Pulse starting from 1
#[inline] pub fn format_beats_1_to (&self, w: &mut impl std::fmt::Write, pulse: f64) -> Result<(), std::fmt::Error> {
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) };
write!(w, "{}.{}.{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) -> Arc<str> {
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).into()
}
}
impl Default for Timebase {
fn default () -> Self { Self::new(48000f64, 150f64, DEFAULT_PPQ) }
}

59
crates/time/src/unit.rs Normal file
View file

@ -0,0 +1,59 @@
use crate::*;
/// A unit of time, represented as 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.
pub trait TimeUnit: InteriorMutable<f64> {}
/// 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<Self> for $T {
type Output = Self; #[inline] fn $method (self, other: Self) -> Self::Output {
let $a = self.get(); let $b = other.get(); Self($impl.into())
}
}
impl $Op<usize> for $T {
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<f64> for $T {
type Output = Self; #[inline] fn $method (self, other: f64) -> Self::Output {
let $a = self.get(); let $b = other; Self($impl.into())
}
}
}
}
/// Define and implement a unit of time
#[macro_export] macro_rules! impl_time_unit {
($T:ident) => {
impl Gettable<f64> for $T {
fn get (&self) -> f64 { self.0.load(Relaxed) }
}
impl InteriorMutable<f64> for $T {
fn set (&self, value: f64) -> f64 {
let old = self.get();
self.0.store(value, 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});
impl_op!($T, Div, div, |a, b|{a / b});
impl_op!($T, Rem, rem, |a, b|{a % b});
impl From<f64> for $T { fn from (value: f64) -> Self { Self(value.into()) } }
impl From<usize> for $T { fn from (value: usize) -> Self { Self((value as f64).into()) } }
impl From<$T> for f64 { fn from (value: $T) -> Self { value.get() } }
impl From<$T> for usize { fn from (value: $T) -> Self { value.get() as usize } }
impl From<&$T> for f64 { fn from (value: &$T) -> Self { value.get() } }
impl From<&$T> for usize { fn from (value: &$T) -> Self { value.get() as usize } }
impl Clone for $T { fn clone (&self) -> Self { Self(self.get().into()) } }
}
}