mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-10-08 18:56:42 +02:00
stack modes
This commit is contained in:
parent
9e3cc1139d
commit
f321397d6f
11 changed files with 570 additions and 498 deletions
|
|
@ -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
|
||||||
/// Performance optimization.
|
fn get (&self) -> T;
|
||||||
#[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>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_default!(ClockView: {
|
pub trait Mutable<T>: Gettable<T> {
|
||||||
let mut beat = String::with_capacity(16);
|
/// Sets new value, returns old
|
||||||
let _ = write!(beat, "{}", Self::BEAT_EMPTY);
|
fn set (&mut self, value: T) -> T;
|
||||||
let mut time = String::with_capacity(16);
|
}
|
||||||
let _ = write!(time, "{}", Self::TIME_EMPTY);
|
|
||||||
let mut bpm = String::with_capacity(16);
|
pub trait InteriorMutable<T>: Gettable<T> {
|
||||||
let _ = write!(bpm, "{}", Self::BPM_EMPTY);
|
/// Sets new value, returns old
|
||||||
Self {
|
fn set (&self, value: T) -> T;
|
||||||
beat: Memo::new(None, beat),
|
}
|
||||||
time: Memo::new(None, time),
|
|
||||||
bpm: Memo::new(None, bpm),
|
/// Temporal resolutions: sample rate, tempo, MIDI pulses per quaver (beat)
|
||||||
sr: Memo::new(None, String::with_capacity(16)),
|
///
|
||||||
buf: Memo::new(None, String::with_capacity(16)),
|
/// ```
|
||||||
lat: Memo::new(None, String::with_capacity(16)),
|
/// 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 ClockView {
|
impl SampleRate {
|
||||||
pub const BEAT_EMPTY: &'static str = "-.-.--";
|
/// Return the duration of a sample in microseconds (floating)
|
||||||
pub const TIME_EMPTY: &'static str = "-.---s";
|
#[inline] pub fn usec_per_sample (&self) -> f64 {
|
||||||
pub const BPM_EMPTY: &'static str = "---.---";
|
1_000_000f64 / self.get()
|
||||||
pub fn update_clock (cache: &Arc<RwLock<Self>>, clock: &Clock, compact: bool) {
|
}
|
||||||
let rate = clock.timebase.sr.get();
|
/// Return the duration of a sample in microseconds (floating)
|
||||||
let chunk = clock.chunk.load(Relaxed) as f64;
|
#[inline] pub fn sample_per_usec (&self) -> f64 {
|
||||||
let lat = chunk / rate * 1000.;
|
self.get() / 1_000_000f64
|
||||||
let delta = |start: &Moment|clock.global.usec.get() - start.usec.get();
|
}
|
||||||
let mut cache = cache.try_write().unwrap();
|
/// 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(
|
/// A point in time in all time scales (microsecond, sample, MIDI pulse)
|
||||||
Some(chunk), rewrite!(buf, "{chunk}")
|
///
|
||||||
);
|
/// ```
|
||||||
|
/// 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,
|
||||||
|
}
|
||||||
|
|
||||||
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(
|
/// Timestamp in microseconds
|
||||||
Some((compact, rate)), |buf: &mut String,_,_|{
|
///
|
||||||
buf.clear();
|
/// ```
|
||||||
if compact {
|
/// let _ = tek::Microsecond(0.0.into());
|
||||||
write!(buf, "{:.1}kHz", rate / 1000.)
|
/// ```
|
||||||
} else {
|
#[derive(Debug, Default)] pub struct Microsecond (pub AtomicF64);
|
||||||
write!(buf, "{:.0}Hz", rate)
|
|
||||||
}
|
/// 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 {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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))
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
57
src/tek.edn
57
src/tek.edn
|
|
@ -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))
|
||||||
(@f6 (enter Save))
|
(@f3 (project new))
|
||||||
(@f8 (enter Options))
|
(@f4 (enter History))
|
||||||
(@f9 (enter Load))
|
(@f6 (enter Save))
|
||||||
(@f10 (enter Quit))
|
(@f8 (enter Options))
|
||||||
(@f12 (enter Devtools)))
|
(@f9 (enter Load))
|
||||||
|
(@f10 (enter Quit))
|
||||||
|
(@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))
|
||||||
|
|
|
||||||
|
|
@ -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::*;
|
||||||
|
|
|
||||||
180
src/tek_ctrl.rs
180
src/tek_ctrl.rs
|
|
@ -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();
|
||||||
Ok(mode.map(|mode|AppCmd::Enter { mode }))
|
if modes.len() > 1 {
|
||||||
|
let mode = modes.pop();
|
||||||
|
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!()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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))?
|
||||||
|
|
|
||||||
137
src/tek_view.rs
137
src/tek_view.rs
|
|
@ -275,19 +275,21 @@ 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 {
|
||||||
"exact" => Self::boxed(move|state, screen|Exact::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)),
|
||||||
"push" => Self::boxed(move|state, screen|Push::X(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)),
|
||||||
"pull" => Self::boxed(move|state, screen|Pull::X(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)),
|
||||||
"pad" => Self::boxed(move|state, screen|Pad::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)),
|
||||||
"min" => Self::boxed(move|state, screen|Min::W(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)),
|
||||||
"max" => Self::boxed(move|state, screen|Max::W(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!()
|
_ => unreachable!()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -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,12 +316,14 @@ 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 {
|
||||||
"exact" => Self::boxed(move|state, screen|Exact::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)),
|
||||||
"push" => Self::boxed(move|state, screen|Push::XY(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)),
|
||||||
"pull" => Self::boxed(move|state, screen|Pull::XY(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)),
|
||||||
"pad" => Self::boxed(move|state, screen|Pad::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)),
|
||||||
"min" => Self::boxed(move|state, screen|Min::WH(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)),
|
||||||
"max" => Self::boxed(move|state, screen|Max::WH(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!()
|
_ => unreachable!()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
159
src/time_base.rs
159
src/time_base.rs
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
2
tengri
|
|
@ -1 +1 @@
|
||||||
Subproject commit 21d6674ffb81b78eb7efe281c43f0a085cbd94ad
|
Subproject commit 1e8b18134d8ad234e5d0142d706f21f1366668d0
|
||||||
Loading…
Add table
Add a link
Reference in a new issue