From 5a18a2023dd7e7e68cf48593bd40e8cf21166458 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 26 Oct 2024 13:38:14 +0300 Subject: [PATCH] refactoring time representation into multuple traits --- crates/tek_core/src/time.rs | 193 +++++++++++++++------- crates/tek_sequencer/src/sequencer.rs | 20 ++- crates/tek_sequencer/src/sequencer_snd.rs | 4 +- crates/tek_sequencer/src/transport.rs | 4 +- 4 files changed, 149 insertions(+), 72 deletions(-) diff --git a/crates/tek_core/src/time.rs b/crates/tek_core/src/time.rs index 8f244e1c..713873b1 100644 --- a/crates/tek_core/src/time.rs +++ b/crates/tek_core/src/time.rs @@ -1,87 +1,158 @@ use crate::*; +pub trait TimeUnit: Mul + Div + From + Sized {} + +impl TimeUnit for T + where T: Mul + Div + From + Sized {} + +/// Trait for struct that defines a sample rate in hertz (samples per second) +pub trait TimeSR { + /// Get the sample rate + fn sr (&self) -> T; + /// Set the sample rate + fn set_sr (&self, sr: T); + /// Return the duration of a sample in microseconds + #[inline] fn usec_per_sample (&self) -> T { T::from(1_000_000f64) / self.sr() } + /// Convert a number of samples to microseconds + #[inline] fn samples_to_usec (&self, samples: T) -> T { samples * self.usec_per_sample() } +} + +/// Trait for struct that defines a tempo in beats per minute +pub trait TimeBPM { + /// Get the tempo + fn bpm (&self) -> T; + /// Set the tempo + fn set_bpm (&self, bpm: T); + /// Return the duration fo a beat in microseconds + #[inline] fn usec_per_beat (&self) -> T { T::from(60_000_000f64) / self.bpm() } + /// Return the number of beats in a second + #[inline] fn beat_per_second (&self) -> T { self.bpm() / T::from(60_000_000f64) } +} + +/// Trait for struct that defines a MIDI resolution in pulses per quaver (beat) +pub trait TimePPQ { + const DEFAULT_PPQ: T; + /// Get the PPQ + fn ppq (&self) -> T; + /// Set the PPQ + fn set_ppq (&self, ppq: T); + /// Return duration of a pulse in microseconds (BPM-dependent) + #[inline] fn usec_per_pulse (&self) -> T where Self: TimeBPM { + self.usec_per_beat() / self.ppq() + } + /// Return number of pulses in a second (BPM-dependent) + #[inline] fn pulses_per_second (&self) -> T where Self: TimeBPM { + self.beat_per_second() * self.ppq() + } + /// Return fraction of a pulse to which a sample corresponds (SR- and BPM-dependent) + #[inline] fn pulses_per_sample (&self) -> T where Self: TimeSR + TimeBPM { + 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, pulses: T) -> T where Self: TimeSR + TimeBPM { + self.pulses_per_sample() * pulses + } + /// Convert a number of samples to a pulse number (SR- and BPM-dependent) + #[inline] fn samples_to_pulse (&self, samples: T) -> T where Self: TimeSR + TimeBPM { + samples / self.pulses_per_sample() + } + /// Return number of samples in a pulse (SR- and BPM-dependent) + #[inline] fn samples_per_pulse (&self) -> T where Self: TimeSR + TimeBPM { + self.sr() / self.pulses_per_second() + } +} + #[derive(Debug)] /// Keeps track of global time units. pub struct Timebase { - /// Frames per second - pub rate: AtomicF64, + /// Samples per second + pub sr: AtomicF64, /// Beats per minute - pub bpm: AtomicF64, + pub bpm: AtomicF64, /// Ticks per beat - pub ppq: AtomicF64, + pub ppq: AtomicF64, } +#[derive(Debug)] +pub struct TransportTime { + /// Current sample sr, tempo, and PPQ. + pub timebase: Arc, + /// Note quantization factor + pub quant: usize, + /// Launch quantization factor + pub sync: usize, + /// Current time in frames + pub frame: usize, + /// Current time in pulses + pub pulse: usize, + /// Current time in microseconds + pub usecs: usize, + /// Pulses per quarter note + pub ppq: usize, +} + +impl TimeSR 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 TimeSR for TransportTime { + #[inline] fn sr (&self) -> f64 { self.timebase.sr() } + #[inline] fn set_sr (&self, sr: f64) { self.timebase.set_sr(sr); } +} + +impl TimeBPM 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 TimeBPM for TransportTime { + #[inline] fn bpm (&self) -> f64 { self.timebase.bpm() } + #[inline] fn set_bpm (&self, bpm: f64) { self.timebase.set_bpm(bpm); } +} + +impl TimePPQ 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 TimePPQ for TransportTime { + const DEFAULT_PPQ: f64 = Timebase::DEFAULT_PPQ; + #[inline] fn ppq (&self) -> f64 { self.timebase.ppq() } + #[inline] fn set_ppq (&self, ppq: f64) { self.timebase.set_ppq(ppq); } +} + +/// Trait defining temporal resolution in different modes. +pub trait TimeBase: TimeSR + TimeBPM + TimePPQ {} + +pub trait TimeFrame { fn frame (&self) -> T; fn set_frame (&self, frame: T); } +pub trait TimePulse { fn pulse (&self) -> T; fn set_pulse (&self, pulse: T); } +pub trait TimeUsec { fn usec (&self) -> T; fn set_usec (&self, usec: T); } + +/// Trait defining a moment in time in different modes. +pub trait TimePoint: TimeFrame + TimePulse + TimeUsec {} + +pub trait TimeSync { fn sync (&self) -> T; fn set_sync (&self, sync: T) -> T; } +pub trait TimeQuant { fn quant (&self) -> T; fn set_quant (&self, quant: T); } + impl Default for Timebase { - fn default () -> Self { - Self { - rate: 48000f64.into(), - bpm: 150f64.into(), - ppq: 96f64.into(), - } - } + fn default () -> Self { Self::new(48000f64, 150f64, 96f64) } } impl Timebase { - pub fn new (rate: f64, bpm: f64, ppq: f64) -> Self { - Self { rate: rate.into(), bpm: bpm.into(), ppq: ppq.into() } + pub fn new ( + sr: impl Into, bpm: impl Into, ppq: impl Into + ) -> Self { + Self { sr: sr.into(), bpm: bpm.into(), ppq: ppq.into() } } - /// Frames per second - #[inline] fn rate (&self) -> f64 { - self.rate.load(Ordering::Relaxed) - } - #[inline] fn usec_per_frame (&self) -> f64 { - 1_000_000f64 / self.rate() - } - #[inline] pub fn frame_to_usec (&self, frame: f64) -> f64 { - frame * self.usec_per_frame() - } - - /// Beats per minute - #[inline] pub fn bpm (&self) -> f64 { - self.bpm.load(Ordering::Relaxed) - } - #[inline] pub fn set_bpm (&self, bpm: f64) { - self.bpm.store(bpm, Ordering::Relaxed) - } - #[inline] fn usec_per_beat (&self) -> f64 { - 60_000_000f64 / self.bpm() - } - #[inline] fn beat_per_second (&self) -> f64 { - self.bpm() / 60000000.0 - } - - /// Pulses per beat - #[inline] pub fn ppq (&self) -> f64 { - self.ppq.load(Ordering::Relaxed) - } - #[inline] pub fn pulse_per_frame (&self) -> f64 { - self.usec_per_pulse() / self.usec_per_frame() - } - #[inline] pub fn usec_per_pulse (&self) -> f64 { - self.usec_per_beat() / self.ppq() - } - #[inline] pub fn pulse_to_frame (&self, pulses: f64) -> f64 { - self.pulse_per_frame() * pulses - } - #[inline] pub fn frame_to_pulse (&self, frames: f64) -> f64 { - frames / self.pulse_per_frame() - } #[inline] pub fn note_to_usec (&self, (num, den): (f64, f64)) -> f64 { 4.0 * self.usec_per_beat() * num / den } - #[inline] pub fn frames_per_pulse (&self) -> f64 { - self.rate() / self.pulses_per_second() - } - #[inline] fn pulses_per_second (&self) -> f64 { - self.beat_per_second() * self.ppq() - } #[inline] pub fn note_to_frame (&self, note: (f64, f64)) -> f64 { self.usec_to_frame(self.note_to_usec(note)) } #[inline] fn usec_to_frame (&self, usec: f64) -> f64 { - usec * self.rate() / 1000.0 + usec * self.sr() / 1000.0 } #[inline] pub fn quantize ( diff --git a/crates/tek_sequencer/src/sequencer.rs b/crates/tek_sequencer/src/sequencer.rs index b8277856..1fb3ea0b 100644 --- a/crates/tek_sequencer/src/sequencer.rs +++ b/crates/tek_sequencer/src/sequencer.rs @@ -113,24 +113,28 @@ pub struct PhrasePlayer { _engine: PhantomData, /// Phrase being played pub phrase: Option>>, - /// Notes currently held at input - pub notes_in: Arc>, - /// Notes currently held at output - pub notes_out: Arc>, + /// Next phrase + pub next_phrase: Option>>, /// Current point in playing phrase pub now: Arc, + /// Frames remaining until switch to next phrase + pub switch_at: Option, /// Play input through output. pub monitoring: bool, /// Write input to sequence. pub recording: bool, /// Overdub input to sequence. pub overdub: bool, + /// Send all notes off + pub reset: bool, // TODO?: after Some(nframes) /// Output from current sequence. pub midi_out: Option>, /// MIDI output buffer pub midi_out_buf: Vec>>, - /// Send all notes off - pub reset: bool, // TODO?: after Some(nframes) + /// Notes currently held at input + pub notes_in: Arc>, + /// Notes currently held at output + pub notes_out: Arc>, } /// Focus layout of sequencer app impl FocusGrid for Sequencer { @@ -326,6 +330,9 @@ impl PhrasePlayer { Self { _engine: Default::default(), phrase: None, + next_phrase: None, + now: Arc::new(0.into()), + switch_at: None, notes_in: Arc::new(RwLock::new([false;128])), notes_out: Arc::new(RwLock::new([false;128])), monitoring: false, @@ -334,7 +341,6 @@ impl PhrasePlayer { midi_out: None, midi_out_buf: vec![Vec::with_capacity(16);16384], reset: true, - now: Arc::new(0.into()), } } pub fn toggle_monitor (&mut self) { self.monitoring = !self.monitoring; } diff --git a/crates/tek_sequencer/src/sequencer_snd.rs b/crates/tek_sequencer/src/sequencer_snd.rs index aece30d6..33906aaa 100644 --- a/crates/tek_sequencer/src/sequencer_snd.rs +++ b/crates/tek_sequencer/src/sequencer_snd.rs @@ -9,7 +9,7 @@ impl Phrase { (frame0, frames, _): (usize, usize, f64), ) { let mut buf = Vec::with_capacity(8); - for (time, tick) in Ticks(timebase.pulse_per_frame()).between_frames( + for (time, tick) in Ticks(timebase.pulses_per_sample()).between_frames( frame0, frame0 + frames ) { let tick = tick % self.length; @@ -84,7 +84,7 @@ impl PhrasePlayer { } if self.recording { phrase.record_event({ - let pulse = timebase.frame_to_pulse( + let pulse = timebase.samples_to_pulse( (frame0 + frame - start_frame) as f64 ); let quantized = ( diff --git a/crates/tek_sequencer/src/transport.rs b/crates/tek_sequencer/src/transport.rs index 13b578de..8bea0dc2 100644 --- a/crates/tek_sequencer/src/transport.rs +++ b/crates/tek_sequencer/src/transport.rs @@ -87,10 +87,10 @@ impl TransportToolbar { self.timebase.ppq() as usize } pub fn pulse (&self) -> usize { - self.timebase.frame_to_pulse(self.frame as f64) as usize + self.timebase.samples_to_pulse(self.frame as f64) as usize } pub fn usecs (&self) -> usize { - self.timebase.frame_to_usec(self.frame as f64) as usize + self.timebase.samples_to_usec(self.frame as f64) as usize } pub fn quant (&self) -> usize { self.quant