stack modes

This commit is contained in:
i do not exist 2026-09-23 13:21:51 +03:00
parent 9e3cc1139d
commit f321397d6f
11 changed files with 570 additions and 498 deletions

View file

@ -1,4 +1,6 @@
use crate::*; use crate::*;
use ::std::sync::Arc;
use ::atomic_float::AtomicF64;
impl App { impl App {
/// Update memoized render of clock values. /// Update memoized render of clock values.
@ -363,79 +365,312 @@ impl_time_unit!(Pulse);
impl_time_unit!(Bpm); impl_time_unit!(Bpm);
impl_time_unit!(LaunchSync); impl_time_unit!(LaunchSync);
/// Contains memoized renders of clock values. pub trait Gettable<T> {
/// Returns current value
fn get (&self) -> T;
}
pub trait Mutable<T>: Gettable<T> {
/// Sets new value, returns old
fn set (&mut self, value: T) -> T;
}
pub trait InteriorMutable<T>: Gettable<T> {
/// Sets new value, returns old
fn set (&self, value: T) -> T;
}
/// Temporal resolutions: sample rate, tempo, MIDI pulses per quaver (beat)
/// ///
/// Performance optimization. /// ```
#[derive(Debug)] pub struct ClockView { /// let _ = tek::Timebase::default();
pub sr: Memo<Option<(bool, f64)>, String>, /// ```
pub buf: Memo<Option<f64>, String>, #[derive(Debug, Clone)] pub struct Timebase {
pub lat: Memo<Option<f64>, String>, /// Audio samples per second
pub bpm: Memo<Option<f64>, String>, pub sr: SampleRate,
pub beat: Memo<Option<f64>, String>, /// MIDI beats per minute
pub time: Memo<Option<f64>, String>, pub bpm: Bpm,
/// MIDI ticks per beat
pub ppq: Ppq,
} }
impl_default!(ClockView: { /// Audio sample rate in Hz (samples per second)
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); #[derive(Debug, Default)] pub struct SampleRate (pub(crate) AtomicF64);
let _ = write!(bpm, "{}", Self::BPM_EMPTY);
/// 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<SampleRate>,
b: impl Into<Bpm>,
p: impl Into<Ppq>
) -> 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 <E: Iterator<Item=(f64, f64)> + 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<str> {
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<str> {
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<str> {
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<str> {
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
}
}
/// 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<Timebase>,
/// 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
///
/// ```
/// 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<Timebase>) -> Self {
Self { usec: 0.into(), sample: 0.into(), pulse: 0.into(), timebase: timebase.clone() }
}
pub fn from_usec (timebase: &Arc<Timebase>, usec: f64) -> Self {
Self { Self {
beat: Memo::new(None, beat), usec: usec.into(),
time: Memo::new(None, time), sample: timebase.sr.usecs_to_sample(usec).into(),
bpm: Memo::new(None, bpm), pulse: timebase.usecs_to_pulse(usec).into(),
sr: Memo::new(None, String::with_capacity(16)), timebase: timebase.clone(),
buf: Memo::new(None, String::with_capacity(16)),
lat: Memo::new(None, String::with_capacity(16)),
}
});
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<RwLock<Self>>, 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)
} }
} }
); pub fn from_sample (timebase: &Arc<Timebase>, 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<Timebase>, 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<str> {
self.timebase.format_beats_1(self.pulse.get()).into()
}
}
if let Some(now) = clock.started.try_read().unwrap().as_ref().map(delta) { impl Microsecond {
let pulse = clock.timebase.usecs_to_pulse(now); #[inline] pub fn format_msu (&self) -> Arc<str> {
let time = now/1000000.; let usecs = self.get() as usize;
let bpm = clock.timebase.bpm.get(); let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000);
cache.beat.update(Some(pulse), |buf, _, _|{ let (minutes, seconds) = (seconds / 60, seconds % 60);
buf.clear(); format!("{minutes}:{seconds:02}:{msecs:03}").into()
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)); /// Iterator that emits subsequent ticks within a range.
} else { ///
cache.beat.update(None, rewrite!(buf, "{}", ClockView::BEAT_EMPTY)); /// ```
cache.time.update(None, rewrite!(buf, "{}", ClockView::TIME_EMPTY)); /// let iter = tek::Ticker::default();
cache.bpm.update(None, rewrite!(buf, "{}", ClockView::BPM_EMPTY)); /// ```
#[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<Self::Item> {
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))
}
} }
} }
} }

View file

@ -1,8 +1,38 @@
(mode Menu (name Main Menu) (info Welcome to Tek!) (keys Y Confirm) (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)))))) (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.) (mode Transport (name Transport) (info JACK transport controller.)
(keys Clock Global) (keys Clock Global)
(view Transport)) (view Transport))
(mode Arranger (name Arranger) (info Launch grid.) (mode Arranger (name Arranger) (info Launch grid.)
(keys Global Clock Tracks Scenes Clip Launch Color History) (keys Global Clock Tracks Scenes Clip Launch Color History)
(view (bar/n 2 StatusBar (bar/s 8 (bar/e 16 (align/nw Transport) (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 TracksDevicesCells
(bar/s 2 TracksNamesCells TracksInputsCells)))))) (bar/s 2 TracksNamesCells TracksInputsCells))))))
(bar/w 20 (text) (bar/e 16 ScenesNames ScenesClips)))))) (bar/w 20 (text) (bar/e 16 ScenesNames ScenesClips))))))
(mode Sequencer (info MIDI sequencer.) (mode Sequencer (info MIDI sequencer.)
(keys Global Clock I IFine Y Page EditorView EditorAdd EditorDel) (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)))))) (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.) (mode Sampler (info Sample player.)
(keys SamplerDirs SamplerRec SamplerPlay) (keys SamplerDirs SamplerRec SamplerPlay)
(view (bsp/s (exact/h 1 Transport) (bsp/n (exact/h 1 StatusBar) (fill/xy SampleGrid))))) (view (bsp/s (exact/h 1 Transport) (bsp/n (exact/h 1 StatusBar) (fill/xy SampleGrid)))))
(mode Groovebox (info Sequencer with sampler.) (mode Groovebox (info Sequencer with sampler.)
(keys Global Clock Sampler I IFine Y Page EditorView EditorAdd EditorDel) (keys Global Clock Sampler I IFine Y Page EditorView EditorAdd EditorDel)
(view (bsp/w MetersOut (bsp/e MetersIn (bsp/w GrooveMeta GrooveEditor))))) (view (bsp/w MetersOut (bsp/e MetersIn (bsp/w GrooveMeta GrooveEditor)))))
@ -32,20 +65,22 @@
(view GrooveEditor (bsp/n GrooveSample GrooveSequence)) (view GrooveEditor (bsp/n GrooveSample GrooveSequence))
(view GrooveSample (exact/h SampleHeight (bsp/e (fill/y (exact/w 20 (align/nw SampleStatus))) SampleViewer))) (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 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 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)) (keys Global (@escape leave)
(@f2 (project new)) (@f1 (enter Help))
(@f3 (enter History)) (@f2 (enter Rename))
(@f3 (project new))
(@f4 (enter History))
(@f6 (enter Save)) (@f6 (enter Save))
(@f8 (enter Options)) (@f8 (enter Options))
(@f9 (enter Load)) (@f9 (enter Load))
(@f10 (enter Quit)) (@f10 (enter Quit))
(@f12 (enter Devtools))) (@f12 (enter Devtools)))
(keys Back (@escape cancel))
(keys Confirm (@enter confirm)) (keys Confirm (@enter confirm))
(keys Page (@pgup page/up) (@pgdn page/down)) (keys Page (@pgup page/up) (@pgdn page/down))
(keys Delete (@delete delete) (@backspace delete/back)) (keys Delete (@delete delete) (@backspace delete/back))

View file

@ -2,6 +2,8 @@
mod deps; pub use self::deps::*; mod deps; pub use self::deps::*;
mod clock; pub use self::clock::*;
mod midi_octave; pub use self::midi_octave::*; mod midi_octave; pub use self::midi_octave::*;
mod midi_piano; pub use self::midi_piano::*; mod midi_piano; pub use self::midi_piano::*;
mod midi_point; pub use self::midi_point::*; 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_rec; pub use self::midi_rec::*;
mod midi_seq; pub use self::midi_seq::*; 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). /// Define a type alias for iterators of sized items (columns).
macro_rules! def_sizes_iter { macro_rules! def_sizes_iter {
($Type:ident => $($Item:ty),+) => { ($Type:ident => $($Item:ty),+) => {
@ -33,7 +31,6 @@ macro_rules! def_sizes_iter {
} }
mod proj; pub use self::proj::*; mod proj; pub use self::proj::*;
mod proj_clock; pub use self::proj_clock::*;
mod proj_device; pub use self::proj_device::*; mod proj_device; pub use self::proj_device::*;
mod proj_mixer; pub use self::proj_mixer::*; mod proj_mixer; pub use self::proj_mixer::*;
mod proj_port; pub use self::proj_port::*; mod proj_port; pub use self::proj_port::*;

View file

@ -23,13 +23,23 @@ impl_from!(AppCmd: |x: TracksCmd| AppCmd::Tracks { command: x });
} }
/// Enter a contextual mode. /// Enter a contextual mode.
#[command(Enter = "enter")] pub fn cmd_mode_enter (&mut self, mode: Arc<str>) -> Perhaps<AppCmd> { #[command(Enter = "enter")] pub fn cmd_mode_enter (&mut self, mode: Arc<str>) -> Perhaps<AppCmd> {
self.modes_mut().push(mode); let mut modes = self.modes_mut();
Ok(Some(AppCmd::Leave)) Ok(if modes.last() != Some(&mode) {
modes.push(mode);
Some(AppCmd::Leave)
} else {
None
})
} }
/// Leave a contextual mode. /// Leave a contextual mode.
#[command(Leave = "leave")] fn cmd_mode_leave (&mut self) -> Perhaps<AppCmd> { #[command(Leave = "leave")] fn cmd_mode_leave (&mut self) -> Perhaps<AppCmd> {
let mode = self.modes_mut().pop(); let mut modes = self.modes_mut();
if modes.len() > 1 {
let mode = modes.pop();
Ok(mode.map(|mode|AppCmd::Enter { mode })) Ok(mode.map(|mode|AppCmd::Enter { mode }))
} else {
Ok(None)
}
} }
/// Navigate along a contextual axis. /// Navigate along a contextual axis.
#[command(Axis = "axis")] pub fn cmd_axis (&mut self, command: AxisCmd) -> Perhaps<AppCmd> { #[command(Axis = "axis")] pub fn cmd_axis (&mut self, command: AxisCmd) -> Perhaps<AppCmd> {
@ -607,6 +617,87 @@ impl<T: HasClip + HasEditor
// TODO: 1-9 seek markers that by default start every 8th of the clip // TODO: 1-9 seek markers that by default start every 8th of the clip
} }
impl<T> MidiClipControl for T where T: HasClip
+ Namespace<Option<bool>> + Namespace<Option<ItemTheme>> {}
#[tek_proc::commands(MidiClipCmd)] pub trait MidiClipControl: HasClip
+ Namespace<Option<bool>> + Namespace<Option<ItemTheme>>
{
/// Create a clip at the current position if it does not already exist.
#[command(Upsert = "upsert")] fn clip_upsert (
&mut self
) -> Perhaps<MidiClipCmd> {
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<ItemTheme>
) -> Perhaps<MidiClipCmd> {
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<bool>
) -> Perhaps<MidiClipCmd> {
Ok(self.clip_slot().map_inner_mut(|clip|MidiClipCmd::SetLoop {
looping: Some(clip.toggle_loop(looping))
}))
}
}
impl<T> BrowseControl for T where T:
Namespace<usize> + Namespace<PathBuf> + Namespace<Arc<str>> {}
#[tek_proc::commands(BrowseCmd = "browse")] pub trait BrowseControl:
Namespace<usize> + Namespace<PathBuf> + Namespace<Arc<str>>
{
/// Toggle visibility of browser
#[command(Show = "show")] fn show (&mut self) -> Perhaps<BrowseCmd> {
todo!()
}
/// Set current directory
#[command(Chdir = "chdir")] fn chdir (&mut self, _path: PathBuf) -> Perhaps<BrowseCmd> {
todo!()
}
/// Set current filter
#[command(SetSearch = "set-search")] fn set_search (&mut self, _filter: Arc<str>) -> Perhaps<BrowseCmd> {
todo!()
}
/// Set selected item
#[command(SetCursor = "set-cursor")] fn set_cursor (&mut self, _index: usize) -> Perhaps<BrowseCmd> {
todo!()
}
}
#[tek_proc::commands(AxisCmd)] impl App {
/// Increment the given data axis.
#[command(Inc = "inc")] pub fn inc (&mut self, axis: ControlAxis) -> Perhaps<AxisCmd> {
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<AxisCmd> {
Ok(match (&self.dialog, axis) {
(Dialog::None, _) => None,
(Dialog::Menu(_, _), ControlAxis::Y) => {
AppCmd::Dialog { dialog: self.dialog.menu_prev() }.dispatch(self)?;
None
},
_ => todo!()
})
}
}
//impl<T: HasPool //impl<T: HasPool
//+ Namespace<bool> //+ Namespace<bool>
//+ Namespace<usize> //+ Namespace<usize>
@ -822,84 +913,3 @@ impl<T: HasClip + HasEditor
////}) ////})
////} ////}
//} //}
impl<T> MidiClipControl for T where T: HasClip
+ Namespace<Option<bool>> + Namespace<Option<ItemTheme>> {}
#[tek_proc::commands(MidiClipCmd)] pub trait MidiClipControl: HasClip
+ Namespace<Option<bool>> + Namespace<Option<ItemTheme>>
{
/// Create a clip at the current position if it does not already exist.
#[command(Upsert = "upsert")] fn clip_upsert (
&mut self
) -> Perhaps<MidiClipCmd> {
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<ItemTheme>
) -> Perhaps<MidiClipCmd> {
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<bool>
) -> Perhaps<MidiClipCmd> {
Ok(self.clip_slot().map_inner_mut(|clip|MidiClipCmd::SetLoop {
looping: Some(clip.toggle_loop(looping))
}))
}
}
impl<T> BrowseControl for T where T:
Namespace<usize> + Namespace<PathBuf> + Namespace<Arc<str>> {}
#[tek_proc::commands(BrowseCmd = "browse")] pub trait BrowseControl:
Namespace<usize> + Namespace<PathBuf> + Namespace<Arc<str>>
{
/// Toggle visibility of browser
#[command(Show = "show")] fn show (&mut self) -> Perhaps<BrowseCmd> {
todo!()
}
/// Set current directory
#[command(Chdir = "chdir")] fn chdir (&mut self, _path: PathBuf) -> Perhaps<BrowseCmd> {
todo!()
}
/// Set current filter
#[command(SetSearch = "set-search")] fn set_search (&mut self, _filter: Arc<str>) -> Perhaps<BrowseCmd> {
todo!()
}
/// Set selected item
#[command(SetCursor = "set-cursor")] fn set_cursor (&mut self, _index: usize) -> Perhaps<BrowseCmd> {
todo!()
}
}
#[tek_proc::commands(AxisCmd)] impl App {
/// Increment the given data axis.
#[command(Inc = "inc")] pub fn inc (&mut self, axis: ControlAxis) -> Perhaps<AxisCmd> {
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<AxisCmd> {
Ok(match (&self.dialog, axis) {
(Dialog::None, _) => None,
(Dialog::Menu(_, _), ControlAxis::Y) => {
AppCmd::Dialog { dialog: self.dialog.menu_prev() }.dispatch(self)?;
None
},
_ => todo!()
})
}
}

View file

@ -9,10 +9,7 @@ use crate::*;
/// assert_eq!(modes.len(), 1); /// assert_eq!(modes.len(), 1);
/// ``` /// ```
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
pub struct Modes(pub Arc<RwLock<BTreeMap< pub struct Modes(pub Arc<RwLock<BTreeMap<Arc<str>, Arc<Mode>>>>);
Arc<str>,
Arc<Mode>
>>>);
impl Modes { impl Modes {

View file

@ -1,9 +1,15 @@
use crate::*; use crate::*;
#[derive(Default, Debug)] pub struct Pool<T>(pub Arc<RwLock<Vec<Slot<T>>>>); #[derive(Default, Debug)] pub struct Pool<T>(pub Arc<RwLock<Vec<Slot<T>>>>);
pub type SamplePool = Pool<Sample>; pub type SamplePool = Pool<Sample>;
pub type ClipPool = Pool<MidiClip>; pub type ClipPool = Pool<MidiClip>;
impl<T> Clone for Pool<T> { fn clone (&self) -> Self { Pool(self.0.clone()) } }
impl<T> Clone for Pool<T> {
fn clone (&self) -> Self { Pool(self.0.clone()) }
}
impl<T> Pool<T> { impl<T> Pool<T> {
pub fn slot (&self, index: usize) -> Slot<T> { pub fn slot (&self, index: usize) -> Slot<T> {
let mut state = self.0.write(); let mut state = self.0.write();
@ -67,7 +73,9 @@ impl<T> From<Vec<Option<T>>> for Pool<T> {
} }
#[derive(Default, Debug)] pub struct Slot<T>(pub Arc<RwLock<Option<Arc<RwLock<T>>>>>); #[derive(Default, Debug)] pub struct Slot<T>(pub Arc<RwLock<Option<Arc<RwLock<T>>>>>);
pub type SampleSlot = Slot<Sample>; pub type SampleSlot = Slot<Sample>;
pub type ClipSlot = Slot<MidiClip>; pub type ClipSlot = Slot<MidiClip>;
impl<T> Clone for Slot<T> { impl<T> Clone for Slot<T> {
@ -95,10 +103,12 @@ impl<T> Slot<T> {
value value
} }
} }
pub trait SlotExt<T> { pub trait SlotExt<T> {
fn map_inner <U> (&self, cb: impl FnOnce(&T)->U) -> Option<U>; fn map_inner <U> (&self, cb: impl FnOnce(&T)->U) -> Option<U>;
fn map_inner_mut <U> (&self, cb: impl FnOnce(&mut T)->U) -> Option<U>; fn map_inner_mut <U> (&self, cb: impl FnOnce(&mut T)->U) -> Option<U>;
} }
impl<T> SlotExt<T> for Slot<T> { impl<T> SlotExt<T> for Slot<T> {
fn map_inner <U> (&self, cb: impl FnOnce(&T)->U) -> Option<U> { fn map_inner <U> (&self, cb: impl FnOnce(&T)->U) -> Option<U> {
self.read().as_ref().map(|x|cb(&*x.read())) self.read().as_ref().map(|x|cb(&*x.read()))
@ -107,6 +117,7 @@ impl<T> SlotExt<T> for Slot<T> {
self.read().as_ref().map(|x|cb(&mut*x.write())) self.read().as_ref().map(|x|cb(&mut*x.write()))
} }
} }
impl<T> SlotExt<T> for Option<Slot<T>> { impl<T> SlotExt<T> for Option<Slot<T>> {
fn map_inner <U> (&self, cb: impl FnOnce(&T)->U) -> Option<U> { fn map_inner <U> (&self, cb: impl FnOnce(&T)->U) -> Option<U> {
self.as_ref().map(|x|x.map_inner(cb))? self.as_ref().map(|x|x.map_inner(cb))?

View file

@ -275,13 +275,15 @@ impl View<Tui, App> {
} }
}, },
"exact" | "min" | "max" | "push" | "pull" | "pad" => { "exact" | "min" | "max" | "push" | "pull" | "pad" | "shrink" | "expand" => {
match head.split('/').skip(1).next() { match head.split('/').skip(1).next() {
Some("w") | Some("x") => { Some("w") | Some("x") => {
let value = Arc::from(arg!(expr, head, 1, "value")); let value = Arc::from(arg!(expr, head, 1, "value"));
let value = move|state: &App|state.namespace(&value); let value = move|state: &App|state.namespace(&value);
let thunk = Self::compile(arg!(expr, head, 2, "content"))?; let thunk = Self::compile(arg!(expr, head, 2, "content"))?;
match ns { match ns {
"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)), "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)), "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)), "pull" => Self::boxed(move|state, screen|Pull::X(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
@ -296,6 +298,8 @@ impl View<Tui, App> {
let value = move|state: &App|state.namespace(&value); let value = move|state: &App|state.namespace(&value);
let thunk = Self::compile(arg!(expr, head, 2, "content"))?; let thunk = Self::compile(arg!(expr, head, 2, "content"))?;
match ns { 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)), "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)), "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)), "pull" => Self::boxed(move|state, screen|Pull::Y(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
@ -312,6 +316,8 @@ impl View<Tui, App> {
let value2 = move|state: &App|state.namespace(&value2); let value2 = move|state: &App|state.namespace(&value2);
let thunk = Self::compile(arg!(expr, head, 3, "content"))?; let thunk = Self::compile(arg!(expr, head, 3, "content"))?;
match ns { match ns {
"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)), "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)), "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)), "pull" => Self::boxed(move|state, screen|Pull::XY(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
@ -385,7 +391,8 @@ impl View<Tui, App> {
Some("Editor") => draw!("TODO editor"), Some("Editor") => draw!("TODO editor"),
Some("MetersIn") => draw!(|___|bg(Rgb(30, 30, 30), "Input Meters".align_s().full_h())), 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("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("ScenesClips") => draw!(|app|view_scenes_clips(app)),
Some("ScenesNames") => draw!(|app|app.view_scenes_names()), Some("ScenesNames") => draw!(|app|app.view_scenes_names()),
Some("Sessions") => draw!(|___|view_sessions()), Some("Sessions") => draw!(|___|view_sessions()),
@ -922,6 +929,26 @@ impl ScenesView for Project {
} }
} }
fn view_modes <'a, S: HasModeStack> (state: &'a S) -> impl Draw<Tui> + '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 /// Draw clips per scene
fn view_scenes_clips <S: TracksView + ScenesView> (state: &S) -> impl Draw<Tui> { fn view_scenes_clips <S: TracksView + ScenesView> (state: &S) -> impl Draw<Tui> {
let select = state.selection(); let select = state.selection();
@ -1536,19 +1563,81 @@ impl<T: PartialEq, U> Memo<T, U> {
} }
} }
pub trait Gettable<T> { /// Contains memoized renders of clock values.
/// Returns current value ///
fn get (&self) -> T; /// Performance optimization.
#[derive(Debug)] pub struct ClockView {
pub sr: Memo<Option<(bool, f64)>, String>,
pub buf: Memo<Option<f64>, String>,
pub lat: Memo<Option<f64>, String>,
pub bpm: Memo<Option<f64>, String>,
pub beat: Memo<Option<f64>, String>,
pub time: Memo<Option<f64>, String>,
} }
pub trait Mutable<T>: Gettable<T> { impl_default!(ClockView: {
/// Sets new value, returns old let mut beat = String::with_capacity(16);
fn set (&mut self, value: T) -> T; 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<T>: Gettable<T> { impl ClockView {
/// Sets new value, returns old pub const BEAT_EMPTY: &'static str = "-.-.--";
fn set (&self, value: T) -> T; pub const TIME_EMPTY: &'static str = "-.---s";
pub const BPM_EMPTY: &'static str = "---.---";
pub fn update_clock (cache: &Arc<RwLock<Self>>, 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 { #[cfg(test)] mod test_view {

View file

@ -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<SampleRate>,
b: impl Into<Bpm>,
p: impl Into<Ppq>
) -> 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 <E: Iterator<Item=(f64, f64)> + 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<str> {
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<str> {
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<str> {
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<str> {
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
}
}

View file

@ -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<Timebase>,
/// 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<Timebase>) -> Self {
Self { usec: 0.into(), sample: 0.into(), pulse: 0.into(), timebase: timebase.clone() }
}
pub fn from_usec (timebase: &Arc<Timebase>, 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<Timebase>, 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<Timebase>, 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<str> {
self.timebase.format_beats_1(self.pulse.get()).into()
}
}
impl Microsecond {
#[inline] pub fn format_msu (&self) -> Arc<str> {
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()
}
}

View file

@ -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<Self::Item> {
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))
}
}
}
}

2
tengri

@ -1 +1 @@
Subproject commit 21d6674ffb81b78eb7efe281c43f0a085cbd94ad Subproject commit 1e8b18134d8ad234e5d0142d706f21f1366668d0