diff --git a/src/proj_clock.rs b/src/clock.rs similarity index 54% rename from src/proj_clock.rs rename to src/clock.rs index 56ea1c9e..a7ad9b03 100644 --- a/src/proj_clock.rs +++ b/src/clock.rs @@ -1,4 +1,6 @@ use crate::*; +use ::std::sync::Arc; +use ::atomic_float::AtomicF64; impl App { /// Update memoized render of clock values. @@ -363,79 +365,312 @@ impl_time_unit!(Pulse); impl_time_unit!(Bpm); impl_time_unit!(LaunchSync); -/// Contains memoized renders of clock values. -/// -/// Performance optimization. -#[derive(Debug)] pub struct ClockView { - pub sr: Memo, String>, - pub buf: Memo, String>, - pub lat: Memo, String>, - pub bpm: Memo, String>, - pub beat: Memo, String>, - pub time: Memo, String>, +pub trait Gettable { + /// Returns current value + fn get (&self) -> T; } -impl_default!(ClockView: { - let mut beat = String::with_capacity(16); - let _ = write!(beat, "{}", Self::BEAT_EMPTY); - let mut time = String::with_capacity(16); - let _ = write!(time, "{}", Self::TIME_EMPTY); - let mut bpm = String::with_capacity(16); - let _ = write!(bpm, "{}", Self::BPM_EMPTY); - Self { - beat: Memo::new(None, beat), - time: Memo::new(None, time), - bpm: Memo::new(None, bpm), - sr: Memo::new(None, String::with_capacity(16)), - buf: Memo::new(None, String::with_capacity(16)), - lat: Memo::new(None, String::with_capacity(16)), +pub trait Mutable: Gettable { + /// Sets new value, returns old + fn set (&mut self, value: T) -> T; +} + +pub trait InteriorMutable: Gettable { + /// Sets new value, returns old + fn set (&self, value: T) -> T; +} + +/// 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 ClockView { - pub const BEAT_EMPTY: &'static str = "-.-.--"; - pub const TIME_EMPTY: &'static str = "-.---s"; - pub const BPM_EMPTY: &'static str = "---.---"; - pub fn update_clock (cache: &Arc>, clock: &Clock, compact: bool) { - let rate = clock.timebase.sr.get(); - let chunk = clock.chunk.load(Relaxed) as f64; - let lat = chunk / rate * 1000.; - let delta = |start: &Moment|clock.global.usec.get() - start.usec.get(); - let mut cache = cache.try_write().unwrap(); +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 + } +} - cache.buf.update( - Some(chunk), rewrite!(buf, "{chunk}") - ); +/// 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, +} - cache.lat.update( - Some(lat), rewrite!(buf, "{lat:.1}ms") - ); +/// +/// ``` +/// let _ = tek::Moment2::default(); +/// ``` +#[derive(Debug, Clone, Default)] pub enum Moment2 { + #[default] None, + Zero, + Usec(Microsecond), + Sample(SampleCount), + Pulse(Pulse), +} - cache.sr.update( - Some((compact, rate)), |buf: &mut String,_,_|{ - buf.clear(); - if compact { - write!(buf, "{:.1}kHz", rate / 1000.) - } else { - write!(buf, "{:.0}Hz", rate) - } +/// Timestamp in microseconds +/// +/// ``` +/// let _ = tek::Microsecond(0.0.into()); +/// ``` +#[derive(Debug, Default)] pub struct Microsecond (pub AtomicF64); + +/// Timestamp in audio samples +/// +/// ``` +/// let _ = tek::SampleCount(0.0.into()); +/// ``` +#[derive(Debug, Default)] pub struct SampleCount (pub AtomicF64); + +/// Timestamp in MIDI pulses +/// +/// ``` +/// let _ = tek::Pulse(0.0.into()); +/// ``` +#[derive(Debug, Default)] pub struct Pulse (pub 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() + } +} + +/// 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, +} + +impl Iterator for Ticker { + type Item = (usize, usize); + fn next (&mut self) -> Option { + loop { + if self.sample > self.end { return None } + let spp = self.spp; + let sample = self.sample as f64; + let start = self.start; + let end = self.end; + self.sample += 1; + //println!("{spp} {sample} {start} {end}"); + let jitter = sample.rem_euclid(spp); // ramps + let next_jitter = (sample + 1.0).rem_euclid(spp); + if jitter > next_jitter { // at crossing: + let time = (sample as usize) % (end as usize-start as usize); + let tick = (sample / spp) as usize; + return Some((time, tick)) } - ); - - if let Some(now) = clock.started.try_read().unwrap().as_ref().map(delta) { - let pulse = clock.timebase.usecs_to_pulse(now); - let time = now/1000000.; - let bpm = clock.timebase.bpm.get(); - cache.beat.update(Some(pulse), |buf, _, _|{ - buf.clear(); - clock.timebase.format_beats_1_to(buf, pulse) - }); - cache.time.update(Some(time), rewrite!(buf, "{:.3}s", time)); - cache.bpm.update(Some(bpm), rewrite!(buf, "{:.3}", bpm)); - } else { - cache.beat.update(None, rewrite!(buf, "{}", ClockView::BEAT_EMPTY)); - cache.time.update(None, rewrite!(buf, "{}", ClockView::TIME_EMPTY)); - cache.bpm.update(None, rewrite!(buf, "{}", ClockView::BPM_EMPTY)); } } } diff --git a/src/tek.edn b/src/tek.edn index 3fd26f6f..0079dcdc 100644 --- a/src/tek.edn +++ b/src/tek.edn @@ -1,8 +1,38 @@ (mode Menu (name Main Menu) (info Welcome to Tek!) (keys Y Confirm) (view (bar/s 2 Transport (bar/n 2 StatusBar (align/c (bar/s 2 (align/x Logo) Menu)))))) + +(mode Help + (view (bg (g 32) (fg (g 64) (fill/xy + (bg (g 48) (fg (g 224) (align/c (expand/wh 2 2 (text Nobody Will Help! )))))))))) + +(mode Quit + (view (bg (g 32) (fg (g 64) (fill/xy + (bg (g 48) (fg (g 224) (align/c (expand/wh 2 2 (text Are you sure? )))))))))) + +(mode Save + (view (bg (g 32) (fg (g 64) (fill/xy + (bg (g 48) (fg (g 224) (align/c (expand/wh 2 2 (text Select save path )))))))))) + +(mode Load + (view (bg (g 32) (fg (g 64) (fill/xy + (bg (g 48) (fg (g 224) (align/c (expand/wh 2 2 (text Select load path )))))))))) + +(mode History + (view (bg (g 32) (fg (g 64) (fill/xy + (bg (g 48) (fg (g 224) (align/c (expand/wh 2 2 (text Undo/redo history )))))))))) + +(mode Rename + (view (bg (g 32) (fg (g 64) (fill/xy + (bg (g 48) (fg (g 224) (align/c (expand/wh 2 2 (text Rename item )))))))))) + +(mode Options + (view (bg (g 32) (fg (g 64) (fill/xy + (bg (g 48) (fg (g 224) (align/c (expand/wh 2 2 (text Options )))))))))) + (mode Transport (name Transport) (info JACK transport controller.) (keys Clock Global) (view Transport)) + (mode Arranger (name Arranger) (info Launch grid.) (keys Global Clock Tracks Scenes Clip Launch Color History) (view (bar/n 2 StatusBar (bar/s 8 (bar/e 16 (align/nw Transport) @@ -14,12 +44,15 @@ (bar/s 2 TracksDevicesCells (bar/s 2 TracksNamesCells TracksInputsCells)))))) (bar/w 20 (text) (bar/e 16 ScenesNames ScenesClips)))))) + (mode Sequencer (info MIDI sequencer.) (keys Global Clock I IFine Y Page EditorView EditorAdd EditorDel) (view (bsp/s (exact/h 1 Transport) (bsp/n (exact/h 1 StatusBar) (fill/xy (bsp/a (fill/xy (align/e Pool)) Editor)))))) + (mode Sampler (info Sample player.) (keys SamplerDirs SamplerRec SamplerPlay) (view (bsp/s (exact/h 1 Transport) (bsp/n (exact/h 1 StatusBar) (fill/xy SampleGrid))))) + (mode Groovebox (info Sequencer with sampler.) (keys Global Clock Sampler I IFine Y Page EditorView EditorAdd EditorDel) (view (bsp/w MetersOut (bsp/e MetersIn (bsp/w GrooveMeta GrooveEditor))))) @@ -32,20 +65,22 @@ (view GrooveEditor (bsp/n GrooveSample GrooveSequence)) (view GrooveSample (exact/h SampleHeight (bsp/e (fill/y (exact/w 20 (align/nw SampleStatus))) SampleViewer))) (view GrooveSequence (bsp/e (fill/y (align/n (bsp/s Status/v EditorStatus))) (bsp/e SamplesKeys Editor))) -(view StatusBar (bar/e 40 (bg (g 10) (full/x (pad/x 1 (bar/s 1 (text Selected) Selected)))) - (bg (g 20) (full/x (pad/x 1 (bar/s 1 (text Mode) ModeList)))))) (view Transport (bg (g 0) (pad/x 1 (bar/e 4 PlayPause (bar/e 16 Bpm (bar/e 16 Beat Time)))))) +(view StatusBar (bar/e 24 (bg (g 50) (full/x (pad/x 1 (bar/s 1 (text Selected) Selected)))) + (bar/e 24 (bg (g 40) (full/x (pad/x 1 (bar/s 1 (text Mode) ModeList)))) + (bg (g 30) (full/x (pad/x 1 (bar/s 1 (text History) HistoryCount))))))) -(keys Global (@f1 (enter Help)) - (@f2 (project new)) - (@f3 (enter History)) - (@f6 (enter Save)) - (@f8 (enter Options)) - (@f9 (enter Load)) - (@f10 (enter Quit)) - (@f12 (enter Devtools))) +(keys Global (@escape leave) + (@f1 (enter Help)) + (@f2 (enter Rename)) + (@f3 (project new)) + (@f4 (enter History)) + (@f6 (enter Save)) + (@f8 (enter Options)) + (@f9 (enter Load)) + (@f10 (enter Quit)) + (@f12 (enter Devtools))) -(keys Back (@escape cancel)) (keys Confirm (@enter confirm)) (keys Page (@pgup page/up) (@pgdn page/down)) (keys Delete (@delete delete) (@backspace delete/back)) diff --git a/src/tek.rs b/src/tek.rs index 0bdb0ad1..71b67820 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -2,6 +2,8 @@ mod deps; pub use self::deps::*; +mod clock; pub use self::clock::*; + mod midi_octave; pub use self::midi_octave::*; mod midi_piano; pub use self::midi_piano::*; mod midi_point; pub use self::midi_point::*; @@ -20,10 +22,6 @@ mod midi_play; pub use self::midi_play::*; mod midi_rec; pub use self::midi_rec::*; mod midi_seq; pub use self::midi_seq::*; -mod time_base; pub use self::time_base::*; -mod time_moment; pub use self::time_moment::*; -mod time_ticker; pub use self::time_ticker::*; - /// Define a type alias for iterators of sized items (columns). macro_rules! def_sizes_iter { ($Type:ident => $($Item:ty),+) => { @@ -33,7 +31,6 @@ macro_rules! def_sizes_iter { } mod proj; pub use self::proj::*; -mod proj_clock; pub use self::proj_clock::*; mod proj_device; pub use self::proj_device::*; mod proj_mixer; pub use self::proj_mixer::*; mod proj_port; pub use self::proj_port::*; diff --git a/src/tek_ctrl.rs b/src/tek_ctrl.rs index 799f2a56..b1f3590a 100644 --- a/src/tek_ctrl.rs +++ b/src/tek_ctrl.rs @@ -23,13 +23,23 @@ impl_from!(AppCmd: |x: TracksCmd| AppCmd::Tracks { command: x }); } /// Enter a contextual mode. #[command(Enter = "enter")] pub fn cmd_mode_enter (&mut self, mode: Arc) -> Perhaps { - self.modes_mut().push(mode); - Ok(Some(AppCmd::Leave)) + let mut modes = self.modes_mut(); + Ok(if modes.last() != Some(&mode) { + modes.push(mode); + Some(AppCmd::Leave) + } else { + None + }) } /// Leave a contextual mode. #[command(Leave = "leave")] fn cmd_mode_leave (&mut self) -> Perhaps { - let mode = self.modes_mut().pop(); - Ok(mode.map(|mode|AppCmd::Enter { mode })) + let mut modes = self.modes_mut(); + if modes.len() > 1 { + let mode = modes.pop(); + Ok(mode.map(|mode|AppCmd::Enter { mode })) + } else { + Ok(None) + } } /// Navigate along a contextual axis. #[command(Axis = "axis")] pub fn cmd_axis (&mut self, command: AxisCmd) -> Perhaps { @@ -607,6 +617,87 @@ impl MidiClipControl for T where T: HasClip + + Namespace> + Namespace> {} +#[tek_proc::commands(MidiClipCmd)] pub trait MidiClipControl: HasClip + + Namespace> + Namespace> +{ + /// Create a clip at the current position if it does not already exist. + #[command(Upsert = "upsert")] fn clip_upsert ( + &mut self + ) -> Perhaps { + Ok(self.clip_mut().map(|mut clip|if clip.is_none() { + *clip = Some(Default::default()); + None // TODO undo + } else { + None + }).flatten()) + } + #[command(SetColor = "color")] fn clip_set_color ( + &mut self, color: Option + ) -> Perhaps { + Ok(self.clip_slot().map_inner_mut(|clip|MidiClipCmd::SetColor { + color: Some(clip.set_color(color)) + })) + } + /// Toggle looping for the active clip + #[command(SetLoop = "loop")] fn clip_toggle_loop ( + &mut self, looping: Option + ) -> Perhaps { + Ok(self.clip_slot().map_inner_mut(|clip|MidiClipCmd::SetLoop { + looping: Some(clip.toggle_loop(looping)) + })) + } +} + +impl BrowseControl for T where T: + Namespace + Namespace + Namespace> {} +#[tek_proc::commands(BrowseCmd = "browse")] pub trait BrowseControl: + Namespace + Namespace + Namespace> +{ + /// Toggle visibility of browser + #[command(Show = "show")] fn show (&mut self) -> Perhaps { + todo!() + } + /// Set current directory + #[command(Chdir = "chdir")] fn chdir (&mut self, _path: PathBuf) -> Perhaps { + todo!() + } + /// Set current filter + #[command(SetSearch = "set-search")] fn set_search (&mut self, _filter: Arc) -> Perhaps { + todo!() + } + /// Set selected item + #[command(SetCursor = "set-cursor")] fn set_cursor (&mut self, _index: usize) -> Perhaps { + todo!() + } +} + +#[tek_proc::commands(AxisCmd)] impl App { + /// Increment the given data axis. + #[command(Inc = "inc")] pub fn inc (&mut self, axis: ControlAxis) -> Perhaps { + Ok(match (&self.dialog, axis) { + (Dialog::None, _) => todo!(), + (Dialog::Menu(_, _), ControlAxis::Y) => { + AppCmd::Dialog { dialog: self.dialog.menu_next() }.dispatch(self)?; + None + }, + _ => todo!() + }) + } + /// Decrement the given data axis. + #[command(Dec = "dec")] pub fn dec (&mut self, axis: ControlAxis) -> Perhaps { + Ok(match (&self.dialog, axis) { + (Dialog::None, _) => None, + (Dialog::Menu(_, _), ControlAxis::Y) => { + AppCmd::Dialog { dialog: self.dialog.menu_prev() }.dispatch(self)?; + None + }, + _ => todo!() + }) + } +} + //impl //+ Namespace @@ -822,84 +913,3 @@ impl MidiClipControl for T where T: HasClip - + Namespace> + Namespace> {} -#[tek_proc::commands(MidiClipCmd)] pub trait MidiClipControl: HasClip - + Namespace> + Namespace> -{ - /// Create a clip at the current position if it does not already exist. - #[command(Upsert = "upsert")] fn clip_upsert ( - &mut self - ) -> Perhaps { - Ok(self.clip_mut().map(|mut clip|if clip.is_none() { - *clip = Some(Default::default()); - None // TODO undo - } else { - None - }).flatten()) - } - #[command(SetColor = "color")] fn clip_set_color ( - &mut self, color: Option - ) -> Perhaps { - Ok(self.clip_slot().map_inner_mut(|clip|MidiClipCmd::SetColor { - color: Some(clip.set_color(color)) - })) - } - /// Toggle looping for the active clip - #[command(SetLoop = "loop")] fn clip_toggle_loop ( - &mut self, looping: Option - ) -> Perhaps { - Ok(self.clip_slot().map_inner_mut(|clip|MidiClipCmd::SetLoop { - looping: Some(clip.toggle_loop(looping)) - })) - } -} - -impl BrowseControl for T where T: - Namespace + Namespace + Namespace> {} -#[tek_proc::commands(BrowseCmd = "browse")] pub trait BrowseControl: - Namespace + Namespace + Namespace> -{ - /// Toggle visibility of browser - #[command(Show = "show")] fn show (&mut self) -> Perhaps { - todo!() - } - /// Set current directory - #[command(Chdir = "chdir")] fn chdir (&mut self, _path: PathBuf) -> Perhaps { - todo!() - } - /// Set current filter - #[command(SetSearch = "set-search")] fn set_search (&mut self, _filter: Arc) -> Perhaps { - todo!() - } - /// Set selected item - #[command(SetCursor = "set-cursor")] fn set_cursor (&mut self, _index: usize) -> Perhaps { - todo!() - } -} - -#[tek_proc::commands(AxisCmd)] impl App { - /// Increment the given data axis. - #[command(Inc = "inc")] pub fn inc (&mut self, axis: ControlAxis) -> Perhaps { - Ok(match (&self.dialog, axis) { - (Dialog::None, _) => todo!(), - (Dialog::Menu(_, _), ControlAxis::Y) => { - AppCmd::Dialog { dialog: self.dialog.menu_next() }.dispatch(self)?; - None - }, - _ => todo!() - }) - } - /// Decrement the given data axis. - #[command(Dec = "dec")] pub fn dec (&mut self, axis: ControlAxis) -> Perhaps { - Ok(match (&self.dialog, axis) { - (Dialog::None, _) => None, - (Dialog::Menu(_, _), ControlAxis::Y) => { - AppCmd::Dialog { dialog: self.dialog.menu_prev() }.dispatch(self)?; - None - }, - _ => todo!() - }) - } -} diff --git a/src/tek_mode.rs b/src/tek_mode.rs index 0cfb6ad2..85cdd27c 100644 --- a/src/tek_mode.rs +++ b/src/tek_mode.rs @@ -9,10 +9,7 @@ use crate::*; /// assert_eq!(modes.len(), 1); /// ``` #[derive(Default, Debug, Clone)] -pub struct Modes(pub Arc, - Arc ->>>); +pub struct Modes(pub Arc, Arc>>>); impl Modes { diff --git a/src/tek_pool.rs b/src/tek_pool.rs index 8272455d..593524c4 100644 --- a/src/tek_pool.rs +++ b/src/tek_pool.rs @@ -1,9 +1,15 @@ use crate::*; #[derive(Default, Debug)] pub struct Pool(pub Arc>>>); + pub type SamplePool = Pool; + pub type ClipPool = Pool; -impl Clone for Pool { fn clone (&self) -> Self { Pool(self.0.clone()) } } + +impl Clone for Pool { + fn clone (&self) -> Self { Pool(self.0.clone()) } +} + impl Pool { pub fn slot (&self, index: usize) -> Slot { let mut state = self.0.write(); @@ -67,7 +73,9 @@ impl From>> for Pool { } #[derive(Default, Debug)] pub struct Slot(pub Arc>>>>); + pub type SampleSlot = Slot; + pub type ClipSlot = Slot; impl Clone for Slot { @@ -95,10 +103,12 @@ impl Slot { value } } + pub trait SlotExt { fn map_inner (&self, cb: impl FnOnce(&T)->U) -> Option; fn map_inner_mut (&self, cb: impl FnOnce(&mut T)->U) -> Option; } + impl SlotExt for Slot { fn map_inner (&self, cb: impl FnOnce(&T)->U) -> Option { self.read().as_ref().map(|x|cb(&*x.read())) @@ -107,6 +117,7 @@ impl SlotExt for Slot { self.read().as_ref().map(|x|cb(&mut*x.write())) } } + impl SlotExt for Option> { fn map_inner (&self, cb: impl FnOnce(&T)->U) -> Option { self.as_ref().map(|x|x.map_inner(cb))? diff --git a/src/tek_view.rs b/src/tek_view.rs index 8f4e6473..2edf2177 100644 --- a/src/tek_view.rs +++ b/src/tek_view.rs @@ -275,19 +275,21 @@ impl View { } }, - "exact" | "min" | "max" | "push" | "pull" | "pad" => { + "exact" | "min" | "max" | "push" | "pull" | "pad" | "shrink" | "expand" => { match head.split('/').skip(1).next() { Some("w") | Some("x") => { let value = Arc::from(arg!(expr, head, 1, "value")); let value = move|state: &App|state.namespace(&value); let thunk = Self::compile(arg!(expr, head, 2, "content"))?; match ns { - "exact" => Self::boxed(move|state, screen|Exact::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), - "push" => Self::boxed(move|state, screen|Push::X(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), - "pull" => Self::boxed(move|state, screen|Pull::X(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), - "pad" => Self::boxed(move|state, screen|Pad::X(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), - "min" => Self::boxed(move|state, screen|Min::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), - "max" => Self::boxed(move|state, screen|Max::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), + "shrink" => Self::boxed(move|state, screen|Shrink::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), + "expand" => Self::boxed(move|state, screen|Expand::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), + "exact" => Self::boxed(move|state, screen|Exact::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), + "push" => Self::boxed(move|state, screen|Push::X(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), + "pull" => Self::boxed(move|state, screen|Pull::X(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), + "pad" => Self::boxed(move|state, screen|Pad::X(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), + "min" => Self::boxed(move|state, screen|Min::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), + "max" => Self::boxed(move|state, screen|Max::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), _ => unreachable!() } }, @@ -296,6 +298,8 @@ impl View { let value = move|state: &App|state.namespace(&value); let thunk = Self::compile(arg!(expr, head, 2, "content"))?; match ns { + "shrink" => Self::boxed(move|state, screen|Shrink::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), + "expand" => Self::boxed(move|state, screen|Expand::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), "exact" => Self::boxed(move|state, screen|Exact::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), "push" => Self::boxed(move|state, screen|Push::Y(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), "pull" => Self::boxed(move|state, screen|Pull::Y(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), @@ -312,12 +316,14 @@ impl View { let value2 = move|state: &App|state.namespace(&value2); let thunk = Self::compile(arg!(expr, head, 3, "content"))?; match ns { - "exact" => Self::boxed(move|state, screen|Exact::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)), - "push" => Self::boxed(move|state, screen|Push::XY(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)), - "pull" => Self::boxed(move|state, screen|Pull::XY(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)), - "pad" => Self::boxed(move|state, screen|Pad::XY(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)), - "min" => Self::boxed(move|state, screen|Min::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)), - "max" => Self::boxed(move|state, screen|Max::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)), + "shrink" => Self::boxed(move|state, screen|Shrink::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)), + "expand" => Self::boxed(move|state, screen|Expand::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)), + "exact" => Self::boxed(move|state, screen|Exact::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)), + "push" => Self::boxed(move|state, screen|Push::XY(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)), + "pull" => Self::boxed(move|state, screen|Pull::XY(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)), + "pad" => Self::boxed(move|state, screen|Pad::XY(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)), + "min" => Self::boxed(move|state, screen|Min::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)), + "max" => Self::boxed(move|state, screen|Max::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)), _ => unreachable!() } }, @@ -385,7 +391,8 @@ impl View { Some("Editor") => draw!("TODO editor"), Some("MetersIn") => draw!(|___|bg(Rgb(30, 30, 30), "Input Meters".align_s().full_h())), Some("MetersOut") => draw!(|___|bg(Rgb(30, 30, 30), "Output Meters".align_s().full_h())), - Some("ModeList") => draw!(|app|app.modes.read().get(0)), // TODO all + Some("ModeList") => draw!(|app|view_modes(app)), + Some("HistoryCount") => draw!(|app|app.history.len()), Some("ScenesClips") => draw!(|app|view_scenes_clips(app)), Some("ScenesNames") => draw!(|app|app.view_scenes_names()), Some("Sessions") => draw!(|___|view_sessions()), @@ -922,6 +929,26 @@ impl ScenesView for Project { } } +fn view_modes <'a, S: HasModeStack> (state: &'a S) -> impl Draw + 'a { + draw(move|to: &mut Tui|{ + let XYWH(x, y, w, h) = to.area(); + let mut x_next = x; + let mut w_used = 0u16; + for item in state.modes().iter() { + let area = XYWH(x_next, y, w.minus(w_used), h); + if let Some(used) = to.draw(area, item)? { + let used_w = used.w() + 1; + x_next = x_next.plus(used_w); + w_used = w_used.plus(used_w); + if w_used >= w { + break; + } + } + } + Ok(Some(XYWH(x, y, w_used, h))) + }) +} + /// Draw clips per scene fn view_scenes_clips (state: &S) -> impl Draw { let select = state.selection(); @@ -1536,19 +1563,81 @@ impl Memo { } } -pub trait Gettable { - /// Returns current value - fn get (&self) -> T; +/// Contains memoized renders of clock values. +/// +/// Performance optimization. +#[derive(Debug)] pub struct ClockView { + pub sr: Memo, String>, + pub buf: Memo, String>, + pub lat: Memo, String>, + pub bpm: Memo, String>, + pub beat: Memo, String>, + pub time: Memo, String>, } -pub trait Mutable: Gettable { - /// Sets new value, returns old - fn set (&mut self, value: T) -> T; -} +impl_default!(ClockView: { + let mut beat = String::with_capacity(16); + let _ = write!(beat, "{}", Self::BEAT_EMPTY); + let mut time = String::with_capacity(16); + let _ = write!(time, "{}", Self::TIME_EMPTY); + let mut bpm = String::with_capacity(16); + let _ = write!(bpm, "{}", Self::BPM_EMPTY); + Self { + beat: Memo::new(None, beat), + time: Memo::new(None, time), + bpm: Memo::new(None, bpm), + sr: Memo::new(None, String::with_capacity(16)), + buf: Memo::new(None, String::with_capacity(16)), + lat: Memo::new(None, String::with_capacity(16)), + } +}); -pub trait InteriorMutable: Gettable { - /// Sets new value, returns old - fn set (&self, value: T) -> T; +impl ClockView { + pub const BEAT_EMPTY: &'static str = "-.-.--"; + pub const TIME_EMPTY: &'static str = "-.---s"; + pub const BPM_EMPTY: &'static str = "---.---"; + pub fn update_clock (cache: &Arc>, clock: &Clock, compact: bool) { + let rate = clock.timebase.sr.get(); + let chunk = clock.chunk.load(Relaxed) as f64; + let lat = chunk / rate * 1000.; + let delta = |start: &Moment|clock.global.usec.get() - start.usec.get(); + let mut cache = cache.try_write().unwrap(); + + cache.buf.update( + Some(chunk), rewrite!(buf, "{chunk}") + ); + + cache.lat.update( + Some(lat), rewrite!(buf, "{lat:.1}ms") + ); + + cache.sr.update( + Some((compact, rate)), |buf: &mut String,_,_|{ + buf.clear(); + if compact { + write!(buf, "{:.1}kHz", rate / 1000.) + } else { + write!(buf, "{:.0}Hz", rate) + } + } + ); + + if let Some(now) = clock.started.try_read().unwrap().as_ref().map(delta) { + let pulse = clock.timebase.usecs_to_pulse(now); + let time = now/1000000.; + let bpm = clock.timebase.bpm.get(); + cache.beat.update(Some(pulse), |buf, _, _|{ + buf.clear(); + clock.timebase.format_beats_1_to(buf, pulse) + }); + cache.time.update(Some(time), rewrite!(buf, "{:.3}s", time)); + cache.bpm.update(Some(bpm), rewrite!(buf, "{:.3}", bpm)); + } else { + cache.beat.update(None, rewrite!(buf, "{}", ClockView::BEAT_EMPTY)); + cache.time.update(None, rewrite!(buf, "{}", ClockView::TIME_EMPTY)); + cache.bpm.update(None, rewrite!(buf, "{}", ClockView::BPM_EMPTY)); + } + } } #[cfg(test)] mod test_view { diff --git a/src/time_base.rs b/src/time_base.rs deleted file mode 100644 index 15e3cb87..00000000 --- a/src/time_base.rs +++ /dev/null @@ -1,159 +0,0 @@ -use crate::*; -use ::std::sync::Arc; -use ::atomic_float::AtomicF64; - -/// 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/time_moment.rs b/src/time_moment.rs deleted file mode 100644 index d0e9eac2..00000000 --- a/src/time_moment.rs +++ /dev/null @@ -1,108 +0,0 @@ -use crate::*; -use ::std::sync::Arc; -use ::atomic_float::AtomicF64; - -/// 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/time_ticker.rs b/src/time_ticker.rs deleted file mode 100644 index dc4347d5..00000000 --- a/src/time_ticker.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::*; - -/// 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, -} - -impl Iterator for Ticker { - type Item = (usize, usize); - fn next (&mut self) -> Option { - loop { - if self.sample > self.end { return None } - let spp = self.spp; - let sample = self.sample as f64; - let start = self.start; - let end = self.end; - self.sample += 1; - //println!("{spp} {sample} {start} {end}"); - let jitter = sample.rem_euclid(spp); // ramps - let next_jitter = (sample + 1.0).rem_euclid(spp); - if jitter > next_jitter { // at crossing: - let time = (sample as usize) % (end as usize-start as usize); - let tick = (sample / spp) as usize; - return Some((time, tick)) - } - } - } -} diff --git a/tengri b/tengri index 21d6674f..1e8b1813 160000 --- a/tengri +++ b/tengri @@ -1 +1 @@ -Subproject commit 21d6674ffb81b78eb7efe281c43f0a085cbd94ad +Subproject commit 1e8b18134d8ad234e5d0142d706f21f1366668d0