refactoring time representation into multuple traits

This commit is contained in:
🪞👃🪞 2024-10-26 13:38:14 +03:00
parent a25548c39c
commit 5a18a2023d
4 changed files with 149 additions and 72 deletions

View file

@ -1,87 +1,158 @@
use crate::*;
pub trait TimeUnit: Mul<Self, Output=Self> + Div<Self, Output=Self> + From<f64> + Sized {}
impl<T> TimeUnit for T
where T: Mul<Self, Output=Self> + Div<Self, Output=Self> + From<f64> + Sized {}
/// Trait for struct that defines a sample rate in hertz (samples per second)
pub trait TimeSR<T: TimeUnit> {
/// 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<T: TimeUnit> {
/// 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<T: TimeUnit> {
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<T> {
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<T> {
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<T> + TimeBPM<T> {
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<T> + TimeBPM<T> {
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<T> + TimeBPM<T> {
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<T> + TimeBPM<T> {
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<Timebase>,
/// 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<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 TimeSR<f64> for TransportTime {
#[inline] fn sr (&self) -> f64 { self.timebase.sr() }
#[inline] fn set_sr (&self, sr: f64) { self.timebase.set_sr(sr); }
}
impl TimeBPM<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 TimeBPM<f64> for TransportTime {
#[inline] fn bpm (&self) -> f64 { self.timebase.bpm() }
#[inline] fn set_bpm (&self, bpm: f64) { self.timebase.set_bpm(bpm); }
}
impl TimePPQ<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 TimePPQ<f64> 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<T: TimeUnit>: TimeSR<T> + TimeBPM<T> + TimePPQ<T> {}
pub trait TimeFrame<T> { fn frame (&self) -> T; fn set_frame (&self, frame: T); }
pub trait TimePulse<T> { fn pulse (&self) -> T; fn set_pulse (&self, pulse: T); }
pub trait TimeUsec<T> { fn usec (&self) -> T; fn set_usec (&self, usec: T); }
/// Trait defining a moment in time in different modes.
pub trait TimePoint<T>: TimeFrame<T> + TimePulse<T> + TimeUsec<T> {}
pub trait TimeSync<T> { fn sync (&self) -> T; fn set_sync (&self, sync: T) -> T; }
pub trait TimeQuant<T> { 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<AtomicF64>, bpm: impl Into<AtomicF64>, ppq: impl Into<AtomicF64>
) -> 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 (

View file

@ -113,24 +113,28 @@ pub struct PhrasePlayer<E: Engine> {
_engine: PhantomData<E>,
/// Phrase being played
pub phrase: Option<Arc<RwLock<Phrase>>>,
/// Notes currently held at input
pub notes_in: Arc<RwLock<[bool; 128]>>,
/// Notes currently held at output
pub notes_out: Arc<RwLock<[bool; 128]>>,
/// Next phrase
pub next_phrase: Option<Arc<RwLock<Phrase>>>,
/// Current point in playing phrase
pub now: Arc<AtomicUsize>,
/// Frames remaining until switch to next phrase
pub switch_at: Option<AtomicUsize>,
/// 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<Port<MidiOut>>,
/// MIDI output buffer
pub midi_out_buf: Vec<Vec<Vec<u8>>>,
/// Send all notes off
pub reset: bool, // TODO?: after Some(nframes)
/// Notes currently held at input
pub notes_in: Arc<RwLock<[bool; 128]>>,
/// Notes currently held at output
pub notes_out: Arc<RwLock<[bool; 128]>>,
}
/// Focus layout of sequencer app
impl<E: Engine> FocusGrid<SequencerFocus> for Sequencer<E> {
@ -326,6 +330,9 @@ impl<E: Engine> PhrasePlayer<E> {
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<E: Engine> PhrasePlayer<E> {
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; }

View file

@ -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<E: Engine> PhrasePlayer<E> {
}
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 = (

View file

@ -87,10 +87,10 @@ impl<E: Engine> TransportToolbar<E> {
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