mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
refactoring time representation into multuple traits
This commit is contained in:
parent
a25548c39c
commit
5a18a2023d
4 changed files with 149 additions and 72 deletions
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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 = (
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue