From 4df15d6bac5fb7d26ab95b5dd06f4a33b75e912c Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 2 Nov 2024 01:03:23 +0200 Subject: [PATCH] make TimeUnit a trait --- crates/tek_core/src/time.rs | 411 +++++++++++----------- crates/tek_sequencer/src/arranger_tui.rs | 4 +- crates/tek_sequencer/src/sequencer.rs | 2 +- crates/tek_sequencer/src/sequencer_snd.rs | 4 +- crates/tek_sequencer/src/transport.rs | 25 +- crates/tek_sequencer/src/transport_cmd.rs | 6 +- crates/tek_sequencer/src/transport_tui.rs | 6 +- 7 files changed, 238 insertions(+), 220 deletions(-) diff --git a/crates/tek_core/src/time.rs b/crates/tek_core/src/time.rs index 84c1a2af..d5e74607 100644 --- a/crates/tek_core/src/time.rs +++ b/crates/tek_core/src/time.rs @@ -3,81 +3,212 @@ use std::iter::Iterator; pub const DEFAULT_PPQ: f64 = 96.0; -/// The unit of time, an atomic 64-bit float. +/// 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. -#[derive(Debug, Default)] -pub struct TimeUnit(AtomicF64); -impl TimeUnit { - pub fn get (&self) -> f64 { self.0.load(Ordering::Relaxed) } - pub fn set (&self, value: f64) { self.0.store(value, Ordering::Relaxed) } +pub trait TimeUnit { + fn get (&self) -> f64;// { self.0.load(Ordering::Relaxed) } + fn set (&self, value: f64);// { self.0.store(value, Ordering::Relaxed) } } -/// Temporal resolutions: sample rate, tempo, MIDI pulses per quaver (beat) -#[derive(Debug, Clone)] -pub struct Timebase { - /// Audio samples per second - pub sr: TimeUnit, - /// MIDI beats per minute - pub bpm: TimeUnit, - /// MIDI ticks per beat - pub ppq: TimeUnit, -} -/// A point in time in all time scales (microsecond, sample, MIDI pulse) -#[derive(Debug, Default, Clone)] -pub struct Instant { - pub timebase: Arc, - /// Current time in microseconds - pub usec: TimeUnit, - /// Current time in audio samples - pub sample: TimeUnit, - /// Current time in MIDI pulses - pub pulse: TimeUnit, -} -impl From for TimeUnit { fn from (value: f64) -> Self { Self(value.into()) } } -impl From for TimeUnit { fn from (value: usize) -> Self { Self((value as f64).into()) } } -impl Into for TimeUnit { fn into (self) -> f64 { self.get() } } -impl Into for TimeUnit { fn into (self) -> usize { self.get() as usize } } -impl Into for &TimeUnit { fn into (self) -> f64 { self.get() } } -impl Into for &TimeUnit { fn into (self) -> usize { self.get() as usize } } -impl Clone for TimeUnit { fn clone (&self) -> Self { Self(self.get().into()) } } +/// Implement arithmetic for a unit of time macro_rules! impl_op { - ($Op:ident, $method:ident, |$a:ident,$b:ident|{$impl:expr}) => { - impl $Op for TimeUnit { + ($T:ident, $Op:ident, $method:ident, |$a:ident,$b:ident|{$impl:expr}) => { + impl $Op 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 for TimeUnit { + impl $Op 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 for TimeUnit { + impl $Op for $T { type Output = Self; #[inline] fn $method (self, other: f64) -> Self::Output { let $a = self.get(); let $b = other; Self($impl.into()) } } } } -impl_op!(Add, add, |a, b|{a + b}); -impl_op!(Sub, sub, |a, b|{a - b}); -impl_op!(Mul, mul, |a, b|{a * b}); -impl_op!(Div, div, |a, b|{a / b}); -impl_op!(Rem, rem, |a, b|{a % b}); +/// Define and implement a unit of time +macro_rules! time_unit { + ($T:ident) => { + #[derive(Debug, Default)] pub struct $T(AtomicF64); + impl TimeUnit for $T { + fn get (&self) -> f64 { self.0.load(Ordering::Relaxed) } + fn set (&self, value: f64) { self.0.store(value, Ordering::Relaxed) } + } + 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 for $T { fn from (value: f64) -> Self { Self(value.into()) } } + impl From for $T { fn from (value: usize) -> Self { Self((value as f64).into()) } } + impl Into for $T { fn into (self) -> f64 { self.get() } } + impl Into for $T { fn into (self) -> usize { self.get() as usize } } + impl Into for &$T { fn into (self) -> f64 { self.get() } } + impl Into for &$T { fn into (self) -> usize { self.get() as usize } } + impl Clone for $T { fn clone (&self) -> Self { Self(self.get().into()) } } + } +} + +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 { samples * self.usec_per_sample() } + /// Convert a number of microseconds to samples (floating) + #[inline] pub fn usecs_to_sample (&self, usecs: f64) -> f64 { usecs * self.sample_per_usec() } +} + +time_unit!(BeatsPerMinute); + +time_unit!(PulsesPerQuaver); + +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}") + } +} + +time_unit!(SampleCount); + +time_unit!(Pulse); + +time_unit!(LaunchSync); + +time_unit!(Quantize); + +/// 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 { + 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. - pub fn pulses_between_samples (&self, start: usize, end: usize) -> TicksIterator { + #[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) } +} +/// A point in time in all time scales (microsecond, sample, MIDI pulse) +#[derive(Debug, Default, Clone)] +pub struct Instant { + pub timebase: Arc, + /// Current time in microseconds + pub usec: Microsecond, + /// Current time in audio samples + pub sample: SampleCount, + /// Current time in MIDI pulses + pub pulse: Pulse, } -impl Default for Timebase { fn default () -> Self { Self::new(48000f64, 150f64, DEFAULT_PPQ) } } impl Instant { pub fn zero (timebase: &Arc) -> Self { Self { usec: 0.into(), sample: 0.into(), pulse: 0.into(), timebase: timebase.clone() } @@ -85,7 +216,7 @@ impl Instant { pub fn from_usec (timebase: &Arc, usec: f64) -> Self { Self { usec: usec.into(), - sample: timebase.usecs_to_sample(usec).into(), + sample: timebase.sr.usecs_to_sample(usec).into(), pulse: timebase.usecs_to_pulse(usec).into(), timebase: timebase.clone(), } @@ -93,7 +224,7 @@ impl Instant { pub fn from_sample (timebase: &Arc, sample: f64) -> Self { Self { sample: sample.into(), - usec: timebase.samples_to_usec(sample).into(), + usec: timebase.sr.samples_to_usec(sample).into(), pulse: timebase.samples_to_pulse(sample).into(), timebase: timebase.clone(), } @@ -106,27 +237,54 @@ impl Instant { timebase: timebase.clone(), } } - pub fn update_from_usec (&self, usec: f64) { + #[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.usecs_to_sample(usec)); + self.sample.set(self.timebase.sr.usecs_to_sample(usec)); } - pub fn update_from_sample (&self, sample: f64) { - self.usec.set(self.timebase.samples_to_usec(sample)); + #[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); } - pub fn update_from_pulse (&self, pulse: f64) { + #[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)); } - pub fn format_beat (&self) -> String { - self.format_beats_1(self.pulse().get()) + #[inline] pub fn format_beat (&self) -> String { + self.timebase.format_beats_1(self.pulse.get()) + } +} +/// A timer with starting point, current time, and quantization +pub struct Timer { + pub timebase: Arc, + /// Starting point in global time + pub started: Option, + /// Current moment in global time + pub current: Instant, + /// Note quantization factor + pub quant: Quantize, + /// Launch quantization factor + pub sync: LaunchSync, + /// Playback state + pub playing: RwLock>, +} +/// Something that defines launch quantization +impl Timer { + #[inline] pub fn next_launch_pulse (&self) -> usize { + let sync = self.sync.get() as usize; + let pulse = self.current.pulse.get() as usize; + if pulse % sync == 0 { pulse } else { (pulse / sync + 1) * sync } } } /// Iterator that emits subsequent ticks within a range. -pub struct TicksIterator { spp: f64, sample: usize, start: usize, end: usize, } +pub struct TicksIterator { + spp: f64, + sample: usize, + start: usize, + end: usize, +} impl Iterator for TicksIterator { type Item = (usize, usize); fn next (&mut self) -> Option { @@ -148,146 +306,7 @@ impl Iterator for TicksIterator { } } } -/// Something that defines a sample rate in hertz (samples per second) -pub trait SampleRate { - /// Get the sample rate - fn sr (&self) -> &TimeUnit; - /// Return the duration of a sample in microseconds (floating) - #[inline] fn usec_per_sample (&self) -> f64 { 1_000_000f64 / self.sr().get() } - /// Return the duration of a sample in microseconds (floating) - #[inline] fn sample_per_usec (&self) -> f64 { self.sr().get() / 1_000_000f64 } - /// Convert a number of samples to microseconds (floating) - #[inline] fn samples_to_usec (&self, samples: f64) -> f64 { samples * self.usec_per_sample() } - /// Convert a number of microseconds to samples (floating) - #[inline] fn usecs_to_sample (&self, usecs: f64) -> f64 { usecs * self.sample_per_usec() } -} -impl SampleRate for Timebase { #[inline] fn sr (&self) -> &TimeUnit { &self.sr } } -impl SampleRate for Instant { #[inline] fn sr (&self) -> &TimeUnit { self.timebase.sr() } } -/// Something that defines a tempo in BPM (beats per minute) -/// and a MIDI resolution in pulses per beat (PPQ, pulses per quaver) -pub trait MIDITime { - /// Get the tempo - fn bpm (&self) -> &TimeUnit; - // Get the PPQ - fn ppq (&self) -> &TimeUnit; - /// Return the duration fo a beat in microseconds - #[inline] fn usec_per_beat (&self) -> f64 { 60_000_000f64 / self.bpm().get() } - /// Return the number of beats in a second - #[inline] fn beat_per_second (&self) -> f64 { self.bpm().get() / 60f64 } - /// Return the number of microseconds corresponding to a note of the given duration - #[inline] 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] fn pulse_per_usec (&self) -> f64 { self.ppq().get() / self.usec_per_beat() } - /// Return duration of a pulse in microseconds (BPM-dependent) - #[inline] 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] 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] fn pulses_to_usec (&self, pulse: f64) -> f64 { pulse / self.usec_per_pulse() } - /// Return number of pulses in a second (BPM-dependent) - #[inline] 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] fn pulses_per_sample (&self) -> f64 where Self: SampleRate { - self.usec_per_pulse() / self.usec_per_sample() - } - /// Return number of samples in a pulse (SR- and BPM-dependent) - #[inline] fn samples_per_pulse (&self) -> f64 where Self: SampleRate { - self.sr().get() / self.pulses_per_second() - } - /// Convert a number of pulses to a sample number (SR- and BPM-dependent) - #[inline] fn pulses_to_sample (&self, p: f64) -> f64 where Self: SampleRate { - self.pulses_per_sample() * p - } - /// Convert a number of samples to a pulse number (SR- and BPM-dependent) - #[inline] fn samples_to_pulse (&self, s: f64) -> f64 where Self: SampleRate { - s / self.pulses_per_sample() - } - /// Return the number of samples corresponding to a note of the given duration - #[inline] fn note_to_samples (&self, note: (f64, f64)) -> f64 where Self: SampleRate { - self.usec_to_sample(self.note_to_usec(note)) - } - /// Return the number of samples corresponding to the given number of microseconds - #[inline] fn usec_to_sample (&self, usec: f64) -> f64 where Self: SampleRate { - usec * self.sr().get() / 1000f64 - } - /// Return the quantized position of a moment in time given a step - #[inline] 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] 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] 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] 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] 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] 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 MIDITime for Timebase { - #[inline] fn bpm (&self) -> &TimeUnit { &self.bpm } - #[inline] fn ppq (&self) -> &TimeUnit { &self.ppq } -} -impl MIDITime for Instant { - #[inline] fn bpm (&self) -> &TimeUnit { &self.timebase.bpm() } - #[inline] fn ppq (&self) -> &TimeUnit { &self.timebase.ppq() } -} -/// Something that refers to a point in time in samples -pub trait SamplePosition { fn sample (&self) -> &TimeUnit; } -impl SamplePosition for Instant { #[inline] fn sample (&self) -> &TimeUnit { &self.sample } } -/// Something that refers to a point in time in MIDI pulses -pub trait PulsePosition { fn pulse (&self) -> &TimeUnit; } -impl PulsePosition for Instant { #[inline] fn pulse (&self) -> &TimeUnit { &self.pulse } } -/// Something that refers to a point in time in microseconds -pub trait UsecPosition { - fn usec (&self) -> &TimeUnit; - #[inline] fn format_current_usec (&self) -> String { - let usecs: usize = self.usec().get() as usize; - let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000); - let (minutes, seconds) = (seconds / 60, seconds % 60); - format!("{minutes}:{seconds:02}:{msecs:03}") - } -} -impl UsecPosition for Instant { #[inline] fn usec (&self) -> &TimeUnit { &self.usec } } -/// Something that defines launch quantization -pub trait LaunchSync { - fn sync (&self) -> &TimeUnit; - #[inline] fn next_launch_pulse (&self) -> usize where Self: PulsePosition { - let sync = self.sync().get() as usize; - let pulse = self.pulse().get() as usize; - if pulse % sync == 0 { pulse } else { (pulse / sync + 1) * sync } - } -} -/// Something that defines note quantization -pub trait Quantize { fn quant (&self) -> &TimeUnit; } + /// (pulses, name), assuming 96 PPQ pub const NOTE_DURATIONS: [(usize, &str);26] = [ (1, "1/384"), diff --git a/crates/tek_sequencer/src/arranger_tui.rs b/crates/tek_sequencer/src/arranger_tui.rs index aeddd9bd..1551a705 100644 --- a/crates/tek_sequencer/src/arranger_tui.rs +++ b/crates/tek_sequencer/src/arranger_tui.rs @@ -202,8 +202,8 @@ impl<'a> Content for VerticalArranger<'a, Tui> { // beats until switchover let until_next = player.next_phrase.as_ref() .map(|(t, _)|{ - let target = t.pulse().get(); - let current = clock.current.pulse().get(); + let target = t.pulse.get(); + let current = clock.current.pulse.get(); if target > current { let remaining = target - current; format!("▎-{:>}", clock.timebase().format_beats_0_short(remaining)) diff --git a/crates/tek_sequencer/src/sequencer.rs b/crates/tek_sequencer/src/sequencer.rs index 9a02b147..49fb05a1 100644 --- a/crates/tek_sequencer/src/sequencer.rs +++ b/crates/tek_sequencer/src/sequencer.rs @@ -418,7 +418,7 @@ impl PhrasePlayer { } pub fn pulses_since_start (&self) -> Option { if let Some((started, Some(_))) = self.phrase.as_ref() { - Some(self.clock.current.pulse().get() - started.pulse().get()) + Some(self.clock.current.pulse.get() - started.pulse.get()) } else { None } diff --git a/crates/tek_sequencer/src/sequencer_snd.rs b/crates/tek_sequencer/src/sequencer_snd.rs index 11ea8b60..dd6f381d 100644 --- a/crates/tek_sequencer/src/sequencer_snd.rs +++ b/crates/tek_sequencer/src/sequencer_snd.rs @@ -106,7 +106,7 @@ impl PhrasePlayer { // If it's time for the next enqueued phrase, handle it here: if next { if let Some((start_at, phrase)) = &self.next_phrase { - let start = start_at.sample().get() as usize; + let start = start_at.sample.get() as usize; // If it's time to switch to the next phrase: if start <= sample0 { // Samples elapsed since phrase was supposed to start @@ -127,7 +127,7 @@ impl PhrasePlayer { let sample0 = scope.last_frame_time() as usize; if let (true, Some((started, phrase))) = (self.is_rolling(), &self.phrase) { let start = started.sample.get() as usize; - let quant = self.clock.quant().get(); + let quant = self.clock.quant.get(); // For highlighting keys and note repeat let mut notes_in = self.notes_in.write().unwrap(); // Record from each input diff --git a/crates/tek_sequencer/src/transport.rs b/crates/tek_sequencer/src/transport.rs index 855cbeb7..9c1afcec 100644 --- a/crates/tek_sequencer/src/transport.rs +++ b/crates/tek_sequencer/src/transport.rs @@ -4,23 +4,22 @@ pub struct TransportTime { /// Current moment in time pub current: Instant, /// Note quantization factor - pub quant: TimeUnit, + pub quant: Quantize, /// Launch quantization factor - pub sync: TimeUnit, + pub sync: LaunchSync, /// Playback state pub playing: RwLock>, } impl TransportTime { - pub fn timebase (&self) -> &Arc { &self.current.timebase } -} -impl PulsePosition for TransportTime { - #[inline] fn pulse (&self) -> &TimeUnit { self.current.pulse() } -} -impl Quantize for TransportTime { - #[inline] fn quant (&self) -> &TimeUnit { &self.quant } -} -impl LaunchSync for TransportTime { - #[inline] fn sync (&self) -> &TimeUnit { &self.sync } + #[inline] pub fn timebase (&self) -> &Arc { &self.current.timebase } + #[inline] pub fn pulse (&self) -> f64 { self.current.pulse.get() } + #[inline] pub fn quant (&self) -> f64 { self.quant.get() } + #[inline] pub fn sync (&self) -> f64 { self.sync.get() } + #[inline] pub fn next_launch_pulse (&self) -> usize { + let sync = self.sync.get() as usize; + let pulse = self.current.pulse.get() as usize; + if pulse % sync == 0 { pulse } else { (pulse / sync + 1) * sync } + } } /// Stores and displays time-related state. pub struct TransportToolbar { @@ -63,7 +62,7 @@ impl TransportToolbar { Arc::new(TransportTime { playing: Some(TransportState::Stopped).into(), quant: 24.into(), - sync: (timebase.ppq().get() * 4.).into(), + sync: (timebase.ppq.get() * 4.).into(), current: Instant::default(), }) } diff --git a/crates/tek_sequencer/src/transport_cmd.rs b/crates/tek_sequencer/src/transport_cmd.rs index 04f2b2bf..8b22a0a7 100644 --- a/crates/tek_sequencer/src/transport_cmd.rs +++ b/crates/tek_sequencer/src/transport_cmd.rs @@ -24,7 +24,7 @@ impl TransportToolbar { Ok(Some(true)) } fn handle_bpm (&mut self, from: &TuiInput) -> Perhaps { - let bpm = self.clock.timebase().bpm().get(); + let bpm = self.clock.timebase().bpm.get(); match from.event() { key!(KeyCode::Char(',')) => { self.clock.timebase().bpm.set(bpm - 1.0); }, key!(KeyCode::Char('.')) => { self.clock.timebase().bpm.set(bpm + 1.0); }, @@ -35,7 +35,7 @@ impl TransportToolbar { Ok(Some(true)) } fn handle_quant (&mut self, from: &TuiInput) -> Perhaps { - let quant = self.clock.quant().get() as usize; + let quant = self.clock.quant.get() as usize; match from.event() { key!(KeyCode::Char(',')) => { self.clock.quant.set(prev_note_length(quant) as f64); }, key!(KeyCode::Char('.')) => { self.clock.quant.set(next_note_length(quant) as f64); }, @@ -44,7 +44,7 @@ impl TransportToolbar { return Ok(Some(true)) } fn handle_sync (&mut self, from: &TuiInput) -> Perhaps { - let sync = self.clock.sync().get() as usize; + let sync = self.clock.sync.get() as usize; match from.event() { key!(KeyCode::Char(',')) => { self.clock.quant.set(prev_note_length(sync) as f64); }, key!(KeyCode::Char('.')) => { self.clock.quant.set(next_note_length(sync) as f64); }, diff --git a/crates/tek_sequencer/src/transport_tui.rs b/crates/tek_sequencer/src/transport_tui.rs index 6ff6518d..2da303e1 100644 --- a/crates/tek_sequencer/src/transport_tui.rs +++ b/crates/tek_sequencer/src/transport_tui.rs @@ -15,20 +15,20 @@ impl Content for TransportToolbar { row!( self.focus.wrap(self.focused, TransportToolbarFocus::Bpm, &Outset::X(1u16, { - let bpm = self.clock.timebase().bpm().get(); + let bpm = self.clock.timebase().bpm.get(); row! { "BPM ", format!("{}.{:03}", bpm as usize, (bpm * 1000.0) % 1000.0) } })), //let quant = self.focus.wrap(self.focused, TransportToolbarFocus::Quant, &Outset::X(1u16, row! { //"QUANT ", ppq_to_name(self.quant as usize) //})), self.focus.wrap(self.focused, TransportToolbarFocus::Sync, &Outset::X(1u16, row! { - "SYNC ", pulses_to_name(self.clock.sync().get() as usize) + "SYNC ", pulses_to_name(self.clock.sync.get() as usize) })) ).align_w().fill_x(), self.focus.wrap(self.focused, TransportToolbarFocus::Clock, &{ let time1 = self.clock.current.format_beat(); - let time2 = self.clock.current.format_current_usec(); + let time2 = self.clock.current.usec.format_msu(); row!("B" ,time1.as_str(), " T", time2.as_str()).outset_x(1) }).align_e().fill_x(),