From 8b6ab2fd08612697b1a80885e1dde87de1fded9d Mon Sep 17 00:00:00 2001 From: i do not exist Date: Wed, 24 Jun 2026 09:28:36 +0300 Subject: [PATCH] restruct: 40e --- src/app.rs | 11 +- src/app/cli.rs | 4 +- src/device/clock.rs | 287 ++--------------------------------- src/device/clock/moment.rs | 109 +++++++++++++ src/device/clock/ticker.rs | 16 ++ src/device/clock/timebase.rs | 160 +++++++++++++++++++ src/device/editor.rs | 6 +- src/tek.rs | 28 ++-- 8 files changed, 331 insertions(+), 290 deletions(-) create mode 100644 src/device/clock/moment.rs create mode 100644 src/device/clock/ticker.rs create mode 100644 src/device/clock/timebase.rs diff --git a/src/app.rs b/src/app.rs index 1c3738d3..85922694 100644 --- a/src/app.rs +++ b/src/app.rs @@ -82,7 +82,16 @@ impl App { pub fn new_shared ( jack: &Jack<'static>, project: Arrangement, config: Config, mode: impl AsRef ) -> Arc> { - Arc::new(RwLock::new(App::new(jack, ":menu", config, project))) + Arc::new(RwLock::new(App::new(jack, project, config, ":menu"))) + } + + pub fn new_shared_run ( + exit: &Exit, jack: &Jack<'static>, project: Arrangement, config: Config, mode: impl AsRef + ) -> Usually<(Task, Task)> { + let state = Self::new_shared(&jack, project, config, mode); + let keyboard = tui_input(&exit, &state, Duration::from_millis(100))?; + let terminal = tui_output(&exit, &state, Duration::from_millis(10))?; + Ok((keyboard, terminal)) } pub fn confirm (&mut self) -> Perhaps { diff --git a/src/app/cli.rs b/src/app/cli.rs index 99e0b53e..1698d68f 100644 --- a/src/app/cli.rs +++ b/src/app/cli.rs @@ -118,13 +118,13 @@ impl Action { // Run the [Tui] and [Jack] threads with the [App] state. Tui::new(Box::new(std::io::stdout()))?.run(true, &jack.run(move|jack|{ // Between jack init and app's first cycle: - jack.sync_lead(sync_lead, |mut state|{ + jack.sync_lead(*sync_lead, |mut state|{ let clock = app.clock(); clock.playhead.update_from_sample(state.position.frame() as f64); state.position.bbt = Some(clock.bbt()); state.position })?; - jack.sync_follow(sync_follow)?; + jack.sync_follow(*sync_follow)?; // FIXME: They don't work properly. Ok(app) })?)?; diff --git a/src/device/clock.rs b/src/device/clock.rs index a0516c13..9c78dc96 100644 --- a/src/device/clock.rs +++ b/src/device/clock.rs @@ -43,80 +43,6 @@ pub trait HasClock: AsRef + AsMut { #[cfg(feature = "port")] pub click_out: Arc>>, } -/// Temporal resolutions: sample rate, tempo, MIDI pulses per quaver (beat) -/// -/// ``` -/// let _ = tek::Timebase::default(); -/// ``` -#[derive(Debug, Clone)] pub struct Timebase { - /// Audio samples per second - pub sr: SampleRate, - /// MIDI beats per minute - pub bpm: Bpm, - /// MIDI ticks per beat - pub ppq: Ppq, -} - -/// Iterator that emits subsequent ticks within a range. -/// -/// ``` -/// let iter = tek::Ticker::default(); -/// ``` -#[derive(Debug, Default)] pub struct Ticker { - pub spp: f64, - pub sample: usize, - pub start: usize, - pub end: usize, -} - -/// A point in time in all time scales (microsecond, sample, MIDI pulse) -/// -/// ``` -/// let _ = tek::Moment::default(); -/// ``` -#[derive(Debug, Default, Clone)] pub struct Moment { - 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, -} - -/// -/// ``` -/// let _ = tek::Moment2::default(); -/// ``` -#[derive(Debug, Clone, Default)] pub enum Moment2 { - #[default] None, - Zero, - Usec(Microsecond), - Sample(SampleCount), - Pulse(Pulse), -} - -/// MIDI resolution in PPQ (pulses per quarter note) -/// -/// ``` -/// -/// ``` -#[derive(Debug, Default)] pub struct Ppq (pub(crate) AtomicF64); - -/// Timestamp in MIDI pulses -/// -/// ``` -/// -/// ``` -#[derive(Debug, Default)] pub struct Pulse (pub(crate) AtomicF64); - -/// Tempo in beats per minute -/// -/// ``` -/// -/// ``` -#[derive(Debug, Default)] pub struct Bpm (pub(crate) AtomicF64); - /// Quantization setting for launching clips /// /// ``` @@ -131,27 +57,6 @@ pub trait HasClock: AsRef + AsMut { /// ``` #[derive(Debug, Default)] pub struct Quantize (pub(crate) AtomicF64); -/// Timestamp in audio samples -/// -/// ``` -/// -/// ``` -#[derive(Debug, Default)] pub struct SampleCount (pub(crate) AtomicF64); - -/// Audio sample rate in Hz (samples per second) -/// -/// ``` -/// -/// ``` -#[derive(Debug, Default)] pub struct SampleRate (pub(crate) AtomicF64); - -/// Timestamp in microseconds -/// -/// ``` -/// -/// ``` -#[derive(Debug, Default)] pub struct Microsecond (pub(crate) AtomicF64); - /// A unit of time, represented as an atomic 64-bit float. /// /// According to https://stackoverflow.com/a/873367, as per IEEE754, @@ -172,6 +77,8 @@ pub trait TimeUnit: InteriorMutable {} pub time: Memo, String>, } +pub const DEFAULT_PPQ: f64 = 96.0; + /// FIXME: remove this and use PPQ from timebase everywhere: pub const PPQ: usize = 96; @@ -206,8 +113,6 @@ pub const NOTE_NAMES: [&str; 128] = [ "C10", "C#10", "D10", "D#10", "E10", "F10", "F#10", "G10", ]; -pub const DEFAULT_PPQ: f64 = 96.0; - def_command!(ClockCommand: |clock: Clock| { SeekUsec { usec: f64 } => { clock.playhead.update_from_usec(*usec); Ok(None) }, @@ -234,53 +139,6 @@ def_command!(ClockCommand: |clock: Clock| { }), }); -impl Moment { - pub fn zero (timebase: &Arc) -> Self { - Self { usec: 0.into(), sample: 0.into(), pulse: 0.into(), timebase: timebase.clone() } - } - pub fn from_usec (timebase: &Arc, 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, 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, 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 { - self.timebase.format_beats_1(self.pulse.get()).into() - } -} impl LaunchSync { pub fn next (&self) -> f64 { note_duration_next(self.get() as usize) as f64 @@ -289,6 +147,7 @@ impl LaunchSync { note_duration_prev(self.get() as usize) as f64 } } + impl Quantize { pub fn next (&self) -> f64 { note_duration_next(self.get() as usize) as f64 @@ -297,133 +156,6 @@ impl Quantize { note_duration_prev(self.get() as usize) as f64 } } -impl Timebase { - /// Specify sample rate, BPM and PPQ - 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. - #[inline] pub fn pulses_between_samples (&self, start: usize, end: usize) -> Ticker { - Ticker { 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) -> Arc { - 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 { - 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 { - 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 { - 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 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 - } -} -impl Microsecond { - #[inline] pub fn format_msu (&self) -> Arc { - 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() - } -} /// Define and implement a unit of time #[macro_export] macro_rules! impl_time_unit { @@ -467,6 +199,7 @@ impl std::fmt::Debug for Clock { .finish() } } + impl Clock { pub fn new (jack: &Jack<'static>, bpm: Option) -> Usually { let (chunk, transport) = jack.with_client(|c|(c.buffer_size(), c.transport())); @@ -625,6 +358,7 @@ impl Clock { self.timebase().pulses_between_samples(offset, offset + scope.n_frames() as usize) } } + impl Clock { fn _todo_provide_u32 (&self) -> u32 { todo!() @@ -636,11 +370,13 @@ impl Clock { todo!() } } + impl Act for ClockCommand { fn act (&self, state: &mut T) -> Perhaps { self.act(state.clock_mut()) // awesome } } + impl ClockView { pub const BEAT_EMPTY: &'static str = "-.-.--"; pub const TIME_EMPTY: &'static str = "-.---s"; @@ -652,7 +388,7 @@ impl ClockView { let delta = |start: &Moment|clock.global.usec.get() - start.usec.get(); let mut cache = cache.write().unwrap(); cache.buf.update(Some(chunk), rewrite!(buf, "{chunk}")); - cache.lat.update(Some(lat), rewrite!(buf, "{lat:.1}ms")); + cache.lat.update(Some(lat), rewrite!(buf, "{lat:.1}ms")); cache.sr.update(Some((compact, rate)), |buf,_,_|{ buf.clear(); if compact { @@ -678,6 +414,7 @@ impl ClockView { } } } + impl_default!(ClockView: { let mut beat = String::with_capacity(16); let _ = write!(beat, "{}", Self::BEAT_EMPTY); @@ -696,7 +433,7 @@ impl_default!(ClockView: { }); #[cfg(feature = "clock")] impl_has!(Clock: |self: Track|self.sequencer.clock); -impl_default!(Timebase: Self::new(48000f64, 150f64, DEFAULT_PPQ)); +impl_default!(Timebase: Self::new(48000f64, 150f64, DEFAULT_PPQ)); impl_time_unit!(SampleCount); impl_time_unit!(SampleRate); impl_time_unit!(Microsecond); @@ -705,3 +442,7 @@ impl_time_unit!(Ppq); impl_time_unit!(Pulse); impl_time_unit!(Bpm); impl_time_unit!(LaunchSync); + +mod moment; pub use self::moment::*; +mod ticker; pub use self::ticker::*; +mod timebase; pub use self::timebase::*; diff --git a/src/device/clock/moment.rs b/src/device/clock/moment.rs new file mode 100644 index 00000000..bf62a822 --- /dev/null +++ b/src/device/clock/moment.rs @@ -0,0 +1,109 @@ +use crate::*; +use ::std::sync::{Arc, RwLock, atomic::{AtomicUsize, Ordering::*}}; +use ::atomic_float::AtomicF64; +use ::tengri::{draw::*, term::*}; + +/// A point in time in all time scales (microsecond, sample, MIDI pulse) +/// +/// ``` +/// let _ = tek::Moment::default(); +/// ``` +#[derive(Debug, Default, Clone)] pub struct Moment { + 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, +} + +/// +/// ``` +/// let _ = tek::Moment2::default(); +/// ``` +#[derive(Debug, Clone, Default)] pub enum Moment2 { + #[default] None, + Zero, + Usec(Microsecond), + Sample(SampleCount), + Pulse(Pulse), +} + +/// Timestamp in microseconds +/// +/// ``` +/// +/// ``` +#[derive(Debug, Default)] pub struct Microsecond (pub(crate) AtomicF64); + +/// Timestamp in audio samples +/// +/// ``` +/// +/// ``` +#[derive(Debug, Default)] pub struct SampleCount (pub(crate) AtomicF64); + +/// Timestamp in MIDI pulses +/// +/// ``` +/// +/// ``` +#[derive(Debug, Default)] pub struct Pulse (pub(crate) AtomicF64); + +impl Moment { + pub fn zero (timebase: &Arc) -> Self { + Self { usec: 0.into(), sample: 0.into(), pulse: 0.into(), timebase: timebase.clone() } + } + pub fn from_usec (timebase: &Arc, 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, 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, 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 { + self.timebase.format_beats_1(self.pulse.get()).into() + } +} + +impl Microsecond { + #[inline] pub fn format_msu (&self) -> Arc { + 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() + } +} diff --git a/src/device/clock/ticker.rs b/src/device/clock/ticker.rs new file mode 100644 index 00000000..50656d77 --- /dev/null +++ b/src/device/clock/ticker.rs @@ -0,0 +1,16 @@ +use crate::*; +use ::std::sync::{Arc, RwLock, atomic::{AtomicUsize, Ordering::*}}; +use ::atomic_float::AtomicF64; +use ::tengri::{draw::*, term::*}; + +/// Iterator that emits subsequent ticks within a range. +/// +/// ``` +/// let iter = tek::Ticker::default(); +/// ``` +#[derive(Debug, Default)] pub struct Ticker { + pub spp: f64, + pub sample: usize, + pub start: usize, + pub end: usize, +} diff --git a/src/device/clock/timebase.rs b/src/device/clock/timebase.rs new file mode 100644 index 00000000..923e054a --- /dev/null +++ b/src/device/clock/timebase.rs @@ -0,0 +1,160 @@ +use crate::*; +use ::std::sync::{Arc, RwLock, atomic::{AtomicUsize, Ordering::*}}; +use ::atomic_float::AtomicF64; +use ::tengri::{draw::*, term::*}; + +/// Temporal resolutions: sample rate, tempo, MIDI pulses per quaver (beat) +/// +/// ``` +/// let _ = tek::Timebase::default(); +/// ``` +#[derive(Debug, Clone)] pub struct Timebase { + /// Audio samples per second + pub sr: SampleRate, + /// MIDI beats per minute + pub bpm: Bpm, + /// MIDI ticks per beat + pub ppq: Ppq, +} + +/// Audio sample rate in Hz (samples per second) +/// +/// ``` +/// +/// ``` +#[derive(Debug, Default)] pub struct SampleRate (pub(crate) AtomicF64); + +/// Tempo in beats per minute +/// +/// ``` +/// +/// ``` +#[derive(Debug, Default)] pub struct Bpm (pub(crate) AtomicF64); + +/// MIDI resolution in PPQ (pulses per quarter note) +/// +/// ``` +/// +/// ``` +#[derive(Debug, Default)] pub struct Ppq (pub(crate) AtomicF64); + +impl Timebase { + /// Specify sample rate, BPM and PPQ + 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. + #[inline] pub fn pulses_between_samples (&self, start: usize, end: usize) -> Ticker { + Ticker { 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) -> Arc { + 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 { + 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 { + 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 { + 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 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 + } +} diff --git a/src/device/editor.rs b/src/device/editor.rs index d9aa1dda..adf787d2 100644 --- a/src/device/editor.rs +++ b/src/device/editor.rs @@ -317,12 +317,12 @@ impl_has!(Size: |self: MidiEditor| self.size); impl_has!(Size: |self: PianoHorizontal| self.size); impl Draw for MidiEditor { - fn draw(self, to: &mut Tui) -> Usually> { - self.tui().draw(to) + fn draw (self, to: &mut Tui) -> Usually> { + self.mode.tui().draw(to) } } impl Draw for PianoHorizontal { - fn draw(self, to: &mut Tui) -> Usually> { + fn draw (self, to: &mut Tui) -> Usually> { self.tui().draw(to) } } diff --git a/src/tek.rs b/src/tek.rs index 115d37c4..c2697b41 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -1,8 +1,7 @@ #![allow(clippy::unit_arg)] -#![feature( - adt_const_params, associated_type_defaults, anonymous_lifetime_in_impl_trait, closure_lifetime_binder, - impl_trait_in_assoc_type, trait_alias, type_alias_impl_trait, type_changing_struct_update -)] +#![feature(adt_const_params, associated_type_defaults, anonymous_lifetime_in_impl_trait, + closure_lifetime_binder, impl_trait_in_assoc_type, trait_alias, type_alias_impl_trait, + type_changing_struct_update)] /// CLI banner. pub(crate) const HEADER: &'static str = r#" @@ -23,7 +22,7 @@ pub(crate) use ::midly::{Smf, TrackEventKind, MidiMessage, Error as MidiError, n pub extern crate tengri; pub(crate) use tengri::{ - *, lang::*, exit::*, eval::*, keys::*, sing::*, time::*, draw::*, term::*, color::*, + *, lang::*, exit::*, eval::*, keys::*, sing::*, time::*, draw::*, term::*, color::*, task::*, crossterm::event::{Event, KeyEvent}, ratatui::{ self, @@ -88,19 +87,26 @@ pub(crate) use tengri::{ /// Command-line entrypoint. #[cfg(feature = "cli")] pub fn main () -> Usually<()> { + Config::watch(|config|{ Exit::run(|exit|{ Jack::new_run("tek", |jack|{ - let project = Arrangement::new(&jack, &Clock::new(&jack, Some(51.))); - let state = App::new_shared(jack, config, project, ":menu"); - let keyboard = tui_input(&exit, &state, Duration::from_millis(100))?; - let terminal = tui_output(&exit, &state, Duration::from_millis(10))?; - (keyboard, terminal) + App::new_shared_run(&exit, &jack, Arrangement::new( + &jack, + None, + Clock::new(&jack, Some(51.))?, + vec![], + vec![], + vec![], + vec![], + ), config, ":menu") // TODO: Sync I/O timings with main clock, so that things // "accidentally" fall on the beat in overload conditions. }) }) - }) + })?; + + Ok(()) } pub fn swap_value (