From 4fb703d05d19f7237703d17ff6dc6c32002520a2 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 11 Jan 2025 23:35:35 +0100 Subject: [PATCH] stub out some of the edn command readers --- midi/src/lib.rs | 3 +- midi/src/midi_editor.rs | 5 + midi/src/midi_pool.rs | 10 ++ sampler/src/sampler.rs | 10 ++ tek/src/control.rs | 177 ++++++++++++++++++++---------- time/src/clock.rs | 223 ++++++++++++++++++++++++++++++++++++++ time/src/clock_tui.rs | 5 +- time/src/lib.rs | 235 ++-------------------------------------- 8 files changed, 381 insertions(+), 287 deletions(-) create mode 100644 time/src/clock.rs diff --git a/midi/src/lib.rs b/midi/src/lib.rs index b3d0171a..edb2e276 100644 --- a/midi/src/lib.rs +++ b/midi/src/lib.rs @@ -19,7 +19,8 @@ pub(crate) use ::tek_jack::{*, jack::*}; pub(crate) use ::tek_tui::{ *, tek_input::*, - tek_output::*, + tek_output::*, + tek_edn::*, crossterm::event::*, ratatui::style::{Style, Stylize, Color} }; diff --git a/midi/src/midi_editor.rs b/midi/src/midi_editor.rs index 79fb6c63..e511820c 100644 --- a/midi/src/midi_editor.rs +++ b/midi/src/midi_editor.rs @@ -152,6 +152,11 @@ pub enum MidiEditCommand { SetTimeLock(bool), Show(Option>>), } +impl MidiEditCommand { + pub fn from_edn <'a> (head: &EdnItem<&str>, tail: &'a [EdnItem]) -> Self { + todo!() + } +} handle!(TuiIn: |self: MidiEditor, input|MidiEditCommand::execute_with_state(self, input.event())); keymap!(KEYS_MIDI_EDITOR = |s: MidiEditor, _input: Event| MidiEditCommand { key(Up) => SetNoteCursor(s.note_point() + 1), diff --git a/midi/src/midi_pool.rs b/midi/src/midi_pool.rs index 596fbe85..ee09e252 100644 --- a/midi/src/midi_pool.rs +++ b/midi/src/midi_pool.rs @@ -36,6 +36,11 @@ pub enum MidiPoolCommand { SetLength(usize, usize), SetColor(usize, ItemColor), } +impl MidiPoolCommand { + pub fn from_edn <'a> (head: &EdnItem<&str>, tail: &'a [EdnItem]) -> Self { + todo!() + } +} impl Command for MidiPoolCommand { fn execute (self, model: &mut T) -> Perhaps { @@ -191,6 +196,11 @@ pub enum PoolCommand { /// Export to file Export(FileBrowserCommand), } +impl PoolCommand { + pub fn from_edn <'a> (head: &EdnItem<&str>, tail: &'a [EdnItem]) -> Self { + todo!() + } +} command!(|self:PoolCommand, state: PoolModel|{ use PoolCommand::*; diff --git a/sampler/src/sampler.rs b/sampler/src/sampler.rs index e1c29ddc..b1a4c439 100644 --- a/sampler/src/sampler.rs +++ b/sampler/src/sampler.rs @@ -663,6 +663,11 @@ handle!(TuiIn: |self: SamplerTui, input|SamplerTuiCommand::execute_with_state(se Select(usize), Sample(SamplerCommand), } +impl SamplerTuiCommand { + pub fn from_edn <'a> (head: &EdnItem<&str>, tail: &'a [EdnItem]) -> Self { + todo!() + } +} #[derive(Clone, Debug)] pub enum SamplerCommand { RecordBegin(u7), @@ -674,6 +679,11 @@ handle!(TuiIn: |self: SamplerTui, input|SamplerTuiCommand::execute_with_state(se NoteOn(u7, u7), NoteOff(u7), } +impl SamplerCommand { + pub fn from_edn <'a> (head: &EdnItem<&str>, tail: &'a [EdnItem]) -> Self { + todo!() + } +} input_to_command!(SamplerTuiCommand: |state: SamplerTui, input: Event|match state.mode{ Some(SamplerMode::Import(..)) => Self::Import( diff --git a/tek/src/control.rs b/tek/src/control.rs index 86bbb1b2..73aab79a 100644 --- a/tek/src/control.rs +++ b/tek/src/control.rs @@ -1,4 +1,5 @@ use crate::*; +use EdnItem::*; use ClockCommand::{Play, Pause}; use KeyCode::{Tab, Char}; use SequencerCommand as SeqCmd; @@ -15,7 +16,7 @@ handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self, #[derive(Clone, Debug)] pub enum AppCommand { Clear, - Clip(ArrangerClipCommand), + Clip(ClipCommand), Clock(ClockCommand), Color(ItemPalette), Compact(bool), @@ -24,12 +25,101 @@ handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self, History(isize), Pool(PoolCommand), Sampler(SamplerCommand), - Scene(ArrangerSceneCommand), + Scene(SceneCommand), Select(ArrangerSelection), StopAll, - Track(ArrangerTrackCommand), + Track(TrackCommand), Zoom(usize), } +impl AppCommand { + pub fn from_edn <'a> (head: &EdnItem<&str>, tail: &'a [EdnItem]) -> Self { + match (head, tail) { + (Key("clear"), [ ]) => Self::Clear, + (Key("clip"), [a, b @ ..]) => Self::Clip(ClipCommand::from_edn(&a.to_ref(), b)), + (Key("clock"), [a, b @ ..]) => Self::Clock(ClockCommand::from_edn(&a.to_ref(), b)), + (Key("color"), [a ]) => Self::Color(ItemPalette::default()), + (Key("compact"), [a ]) => Self::Compact(true), + (Key("editor"), [a, b @ ..]) => Self::Editor(MidiEditCommand::from_edn(&a.to_ref(), b)), + (Key("enqueue"), [a ]) => Self::Enqueue(None), + (Key("history"), [a ]) => Self::History(0), + (Key("pool"), [a, b @ ..]) => Self::Pool(PoolCommand::from_edn(&a.to_ref(), b)), + (Key("sampler"), [a, b @ ..]) => Self::Sampler(SamplerCommand::from_edn(&a.to_ref(), b)), + (Key("scene"), [a, b @ ..]) => Self::Scene(SceneCommand::from_edn(&a.to_ref(), b)), + (Key("select"), [a ]) => Self::Select(ArrangerSelection::Mix), + (Key("stop-all"), [ ]) => Self::StopAll, + (Key("track"), [a, b @ ..]) => Self::Track(TrackCommand::from_edn(&a.to_ref(), b)), + (Key("zoom"), [a ]) => Self::Zoom(0), + _ => panic!(), + } + } +} +#[derive(Clone, Debug)] pub enum ClipCommand { + Get(usize, usize), + Put(usize, usize, Option>>), + Enqueue(usize, usize), + Edit(Option>>), + SetLoop(usize, usize, bool), + SetColor(usize, usize, ItemPalette), +} +impl ClipCommand { + pub fn from_edn <'a> (head: &EdnItem<&str>, tail: &'a [EdnItem]) -> Self { + match (head, tail) { + (Key("get"), [a, b ]) => Self::Get(0, 0), + (Key("put"), [a, b, c ]) => Self::Put(0, 0, None), + (Key("enqueue"), [a, b ]) => Self::Enqueue(0, 0), + (Key("edit"), [a ]) => Self::Edit(None), + (Key("loop"), [a, b, c ]) => Self::SetLoop(0, 0, true), + (Key("color"), [a, b, c ]) => Self::SetColor(0, 0, ItemPalette::random()), + _ => panic!(), + } + } +} +#[derive(Clone, Debug)] pub enum SceneCommand { + Add, + Del(usize), + Swap(usize, usize), + SetSize(usize), + SetZoom(usize), + SetColor(usize, ItemPalette), + Enqueue(usize), +} +impl SceneCommand { + pub fn from_edn <'a> (head: &EdnItem<&str>, tail: &'a [EdnItem]) -> Self { + match (head, tail) { + (Key("add"), [ ]) => Self::Add, + (Key("del"), [a ]) => Self::Del(0), + (Key("swap"), [a, b ]) => Self::Swap(0, 0), + (Key("size"), [a ]) => Self::SetSize(0), + (Key("zoom"), [a, ]) => Self::SetZoom(0), + (Key("color"), [a, b, ]) => Self::SetColor(0, ItemPalette::random()), + (Key("enqueue"), [a, ]) => Self::Enqueue(0), + _ => panic!(), + } + } +} +#[derive(Clone, Debug)] pub enum TrackCommand { + Add, + Del(usize), + Stop(usize), + Swap(usize, usize), + SetSize(usize), + SetZoom(usize), + SetColor(usize, ItemPalette), +} +impl TrackCommand { + pub fn from_edn <'a> (head: &EdnItem<&str>, tail: &'a [EdnItem]) -> Self { + match (head, tail) { + (Key("add"), [ ]) => Self::Add, + (Key("del"), [a ]) => Self::Del(0), + (Key("stop"), [a ]) => Self::Stop(0), + (Key("swap"), [a, b ]) => Self::Swap(0, 0), + (Key("size"), [a ]) => Self::SetSize(0), + (Key("zoom"), [a, ]) => Self::SetZoom(0), + (Key("color"), [a, b, ]) => Self::SetColor(0, ItemPalette::random()), + _ => panic!(), + } + } +} #[derive(Clone, Debug)] pub enum SequencerCommand { Compact(bool), History(isize), @@ -51,9 +141,9 @@ handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self, History(isize), Color(ItemPalette), Clock(ClockCommand), - Scene(ArrangerSceneCommand), - Track(ArrangerTrackCommand), - Clip(ArrangerClipCommand), + Scene(SceneCommand), + Track(TrackCommand), + Clip(ClipCommand), Select(ArrangerSelection), Zoom(usize), Pool(PoolCommand), @@ -61,32 +151,7 @@ handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self, StopAll, Clear, } -#[derive(Clone, Debug)] pub enum ArrangerClipCommand { - Get(usize, usize), - Put(usize, usize, Option>>), - Enqueue(usize, usize), - Edit(Option>>), - SetLoop(usize, usize, bool), - SetColor(usize, usize, ItemPalette), -} -#[derive(Clone, Debug)] pub enum ArrangerSceneCommand { - Add, - Delete(usize), - Swap(usize, usize), - SetSize(usize), - SetZoom(usize), - SetColor(usize, ItemPalette), - Enqueue(usize), -} -#[derive(Clone, Debug)] pub enum ArrangerTrackCommand { - Add, - Delete(usize), - Stop(usize), - Swap(usize, usize), - SetSize(usize), - SetZoom(usize), - SetColor(usize, ItemPalette), -} + command!(|self: SequencerCommand, state: Sequencer|match self { Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?, @@ -183,9 +248,9 @@ command!(|self: ArrangerCommand, state: Arranger|match self { } }, }); -command!(|self: ArrangerSceneCommand, state: Arranger|match self { +command!(|self: SceneCommand, state: Arranger|match self { Self::Add => { state.scene_add(None, None)?; None } - Self::Delete(index) => { state.scene_del(index); None }, + Self::Del(index) => { state.scene_del(index); None }, Self::SetColor(index, color) => { let old = state.scenes[index].color; state.scenes[index].color = color; @@ -199,9 +264,9 @@ command!(|self: ArrangerSceneCommand, state: Arranger|match self { }, _ => None }); -command!(|self: ArrangerTrackCommand, state: Arranger|match self { +command!(|self: TrackCommand, state: Arranger|match self { Self::Add => { state.track_add(None, None)?; None }, - Self::Delete(index) => { state.track_del(index); None }, + Self::Del(index) => { state.track_del(index); None }, Self::Stop(track) => { state.tracks[track].player.enqueue_next(None); None }, Self::SetColor(index, color) => { let old = state.tracks[index].color; @@ -210,7 +275,7 @@ command!(|self: ArrangerTrackCommand, state: Arranger|match self { }, _ => None }); -command!(|self: ArrangerClipCommand, state: Arranger|match self { +command!(|self: ClipCommand, state: Arranger|match self { Self::Get(track, scene) => { todo!() }, Self::Put(track, scene, clip) => { let old = state.scenes[scene].clips[track].clone(); @@ -311,16 +376,16 @@ keymap!(KEYS_ARRANGER = |state: Arranger, input: Event| ArrangerCommand { // Transport: Play from start or rewind to start shift(key(Char(' '))) => ArrCmd::Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }), key(Char('e')) => ArrCmd::Editor(MidiEditCommand::Show(state.pool.clip().clone())), - ctrl(key(Char('a'))) => ArrCmd::Scene(ArrangerSceneCommand::Add), - ctrl(key(Char('A'))) => return None,//ArrCmd::Scene(ArrangerSceneCommand::Add), - ctrl(key(Char('t'))) => ArrCmd::Track(ArrangerTrackCommand::Add), + ctrl(key(Char('a'))) => ArrCmd::Scene(SceneCommand::Add), + ctrl(key(Char('A'))) => return None,//ArrCmd::Scene(SceneCommand::Add), + ctrl(key(Char('t'))) => ArrCmd::Track(TrackCommand::Add), // Tab: Toggle visibility of clip pool column key(Tab) => ArrCmd::Pool(PoolCommand::Show(!state.pool.visible)), }, { use ArrangerSelection as Selected; - use ArrangerSceneCommand as Scene; - use ArrangerTrackCommand as Track; - use ArrangerClipCommand as Clip; + use SceneCommand as Scene; + use TrackCommand as Track; + use ClipCommand as Clip; let t_len = state.tracks.len(); let s_len = state.scenes.len(); match state.selected { @@ -351,9 +416,9 @@ keymap!(KEYS_ARRANGER = |state: Arranger, input: Event| ArrangerCommand { fn clip_keymap (state: &Arranger, input: &Event, t: usize, s: usize) -> Option { use ArrangerSelection as Selected; - use ArrangerSceneCommand as Scene; - use ArrangerTrackCommand as Track; - use ArrangerClipCommand as Clip; + use SceneCommand as Scene; + use TrackCommand as Track; + use ClipCommand as Clip; let t_len = state.tracks.len(); let s_len = state.scenes.len(); Some(match input { @@ -385,10 +450,10 @@ fn clip_keymap (state: &Arranger, input: &Event, t: usize, s: usize) -> Option Option { - use ArrangerSelection as Selected; - use ArrangerSceneCommand as Scene; - use ArrangerTrackCommand as Track; - use ArrangerClipCommand as Clip; + use ArrangerSelection as Selected; + use SceneCommand as Scene; + use TrackCommand as Track; + use ClipCommand as Clip; let t_len = state.tracks.len(); let s_len = state.scenes.len(); Some(match input { @@ -398,7 +463,7 @@ fn scene_keymap (state: &Arranger, input: &Event, s: usize) -> Option ArrCmd::Scene(Scene::Swap(s, s - 1)), kpat!(Char('>')) => ArrCmd::Scene(Scene::Swap(s, s + 1)), kpat!(Char('q')) => ArrCmd::Scene(Scene::Enqueue(s)), - kpat!(Delete) => ArrCmd::Scene(Scene::Delete(s)), + kpat!(Delete) => ArrCmd::Scene(Scene::Del(s)), kpat!(Char('c')) => ArrCmd::Scene(Scene::SetColor(s, ItemPalette::random())), kpat!(Up) => ArrCmd::Select(if s > 0 { Selected::Scene(s - 1) } else { Selected::Mix }), @@ -410,10 +475,10 @@ fn scene_keymap (state: &Arranger, input: &Event, s: usize) -> Option Option { - use ArrangerSelection as Selected; - use ArrangerSceneCommand as Scene; - use ArrangerTrackCommand as Track; - use ArrangerClipCommand as Clip; + use ArrangerSelection as Selected; + use SceneCommand as Scene; + use TrackCommand as Track; + use ClipCommand as Clip; let t_len = state.tracks.len(); let s_len = state.scenes.len(); Some(match input { @@ -422,7 +487,7 @@ fn track_keymap (state: &Arranger, input: &Event, t: usize) -> Option ArrCmd::Track(Track::Swap(t, t + 1)), kpat!(Char('<')) => ArrCmd::Track(Track::Swap(t, t - 1)), kpat!(Char('>')) => ArrCmd::Track(Track::Swap(t, t + 1)), - kpat!(Delete) => ArrCmd::Track(Track::Delete(t)), + kpat!(Delete) => ArrCmd::Track(Track::Del(t)), kpat!(Char('c')) => ArrCmd::Track(Track::SetColor(t, ItemPalette::random())), kpat!(Up) => return None, diff --git a/time/src/clock.rs b/time/src/clock.rs new file mode 100644 index 00000000..6f5865da --- /dev/null +++ b/time/src/clock.rs @@ -0,0 +1,223 @@ +use crate::*; +use EdnItem::*; + +pub trait HasClock: Send + Sync { + fn clock (&self) -> &Clock; +} + +#[macro_export] macro_rules! has_clock { + (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { + impl $(<$($L),*$($T $(: $U)?),*>)? HasClock for $Struct $(<$($L),*$($T),*>)? { + fn clock (&$self) -> &Clock { $cb } + } + } +} + +/// Hosts the JACK callback for updating the temporal pointer and playback status. +pub struct ClockAudio<'a, T: HasClock>(pub &'a mut T); + +impl Audio for ClockAudio<'_, T> { + #[inline] fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { + self.0.clock().update_from_scope(scope).unwrap(); + Control::Continue + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum ClockCommand { + Play(Option), + Pause(Option), + SeekUsec(f64), + SeekSample(f64), + SeekPulse(f64), + SetBpm(f64), + SetQuant(f64), + SetSync(f64), +} +impl ClockCommand { + pub fn from_edn <'a> (head: &EdnItem<&str>, tail: &'a [EdnItem]) -> Self { + todo!() + } +} + +impl Command for ClockCommand { + fn execute (self, state: &mut T) -> Perhaps { + use ClockCommand::*; + match self { + Play(start) => state.clock().play_from(start)?, + Pause(pause) => state.clock().pause_at(pause)?, + SeekUsec(usec) => state.clock().playhead.update_from_usec(usec), + SeekSample(sample) => state.clock().playhead.update_from_sample(sample), + SeekPulse(pulse) => state.clock().playhead.update_from_pulse(pulse), + SetBpm(bpm) => return Ok(Some(SetBpm(state.clock().timebase().bpm.set(bpm)))), + SetQuant(quant) => return Ok(Some(SetQuant(state.clock().quant.set(quant)))), + SetSync(sync) => return Ok(Some(SetSync(state.clock().sync.set(sync)))), + }; + Ok(None) + } +} + +#[derive(Clone, Default)] +pub struct Clock { + /// JACK transport handle. + pub transport: Arc>, + /// Global temporal resolution (shared by [Moment] fields) + pub timebase: Arc, + /// Current global sample and usec (monotonic from JACK clock) + pub global: Arc, + /// Global sample and usec at which playback started + pub started: Arc>>, + /// Playback offset (when playing not from start) + pub offset: Arc, + /// Current playhead position + pub playhead: Arc, + /// Note quantization factor + pub quant: Arc, + /// Launch quantization factor + pub sync: Arc, + /// Size of buffer in samples + pub chunk: Arc, +} + +from!(|jack: &Arc>| Clock = { + let jack = jack.read().unwrap(); + let chunk = jack.client().buffer_size(); + let transport = jack.client().transport(); + let timebase = Arc::new(Timebase::default()); + Self { + quant: Arc::new(24.into()), + sync: Arc::new(384.into()), + transport: Arc::new(Some(transport)), + chunk: Arc::new((chunk as usize).into()), + global: Arc::new(Moment::zero(&timebase)), + playhead: Arc::new(Moment::zero(&timebase)), + offset: Arc::new(Moment::zero(&timebase)), + started: RwLock::new(None).into(), + timebase, + } +}); + +impl std::fmt::Debug for Clock { + fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + f.debug_struct("Clock") + .field("timebase", &self.timebase) + .field("chunk", &self.chunk) + .field("quant", &self.quant) + .field("sync", &self.sync) + .field("global", &self.global) + .field("playhead", &self.playhead) + .field("started", &self.started) + .finish() + } +} + +impl Clock { + pub fn timebase (&self) -> &Arc { + &self.timebase + } + /// Current sample rate + pub fn sr (&self) -> &SampleRate { + &self.timebase.sr + } + /// Current tempo + pub fn bpm (&self) -> &BeatsPerMinute { + &self.timebase.bpm + } + /// Current MIDI resolution + pub fn ppq (&self) -> &PulsesPerQuaver { + &self.timebase.ppq + } + /// Next pulse that matches launch sync (for phrase switchover) + pub fn next_launch_pulse (&self) -> usize { + let sync = self.sync.get() as usize; + let pulse = self.playhead.pulse.get() as usize; + if pulse % sync == 0 { + pulse + } else { + (pulse / sync + 1) * sync + } + } + /// Start playing, optionally seeking to a given location beforehand + pub fn play_from (&self, start: Option) -> Usually<()> { + if let Some(transport) = self.transport.as_ref() { + if let Some(start) = start { + transport.locate(start)?; + } + transport.start()?; + } + Ok(()) + } + /// Pause, optionally seeking to a given location afterwards + pub fn pause_at (&self, pause: Option) -> Usually<()> { + if let Some(transport) = self.transport.as_ref() { + transport.stop()?; + if let Some(pause) = pause { + transport.locate(pause)?; + } + } + Ok(()) + } + /// Is currently paused? + pub fn is_stopped (&self) -> bool { + self.started.read().unwrap().is_none() + } + /// Is currently playing? + pub fn is_rolling (&self) -> bool { + self.started.read().unwrap().is_some() + } + /// Update chunk size + pub fn set_chunk (&self, n_frames: usize) { + self.chunk.store(n_frames, Relaxed); + } + pub fn update_from_scope (&self, scope: &ProcessScope) -> Usually<()> { + // Store buffer length + self.set_chunk(scope.n_frames() as usize); + + // Store reported global frame and usec + let CycleTimes { current_frames, current_usecs, .. } = scope.cycle_times()?; + self.global.sample.set(current_frames as f64); + self.global.usec.set(current_usecs as f64); + + let mut started = self.started.write().unwrap(); + + // If transport has just started or just stopped, + // update starting point: + if let Some(transport) = self.transport.as_ref() { + match (transport.query_state()?, started.as_ref()) { + (TransportState::Rolling, None) => { + let moment = Moment::zero(&self.timebase); + moment.sample.set(current_frames as f64); + moment.usec.set(current_usecs as f64); + *started = Some(moment); + }, + (TransportState::Stopped, Some(_)) => { + *started = None; + }, + _ => {} + }; + } + + self.playhead.update_from_sample(started.as_ref() + .map(|started|current_frames as f64 - started.sample.get()) + .unwrap_or(0.)); + + Ok(()) + } + + pub fn bbt (&self) -> PositionBBT { + let pulse = self.playhead.pulse.get() as i32; + let ppq = self.timebase.ppq.get() as i32; + let bpm = self.timebase.bpm.get(); + let bar = (pulse / ppq) / 4; + PositionBBT { + bar: 1 + bar, + beat: 1 + (pulse / ppq) % 4, + tick: (pulse % ppq), + bar_start_tick: (bar * 4 * ppq) as f64, + beat_type: 4., + beats_per_bar: 4., + beats_per_minute: bpm, + ticks_per_beat: ppq as f64 + } + } +} diff --git a/time/src/clock_tui.rs b/time/src/clock_tui.rs index c5ad06bd..d89df929 100644 --- a/time/src/clock_tui.rs +++ b/time/src/clock_tui.rs @@ -1,7 +1,6 @@ use crate::*; -use ClockCommand::{Play, Pause, SetBpm, SetQuant, SetSync}; -//use FocusCommand::{Next, Prev}; -use KeyCode::{Enter, Left, Right, Char}; +use KeyCode::*; +use ClockCommand::{Play, Pause}; /// Transport clock app. pub struct TransportTui { pub jack: Arc>, diff --git a/time/src/lib.rs b/time/src/lib.rs index bb6b87e9..e0297cb6 100644 --- a/time/src/lib.rs +++ b/time/src/lib.rs @@ -1,3 +1,4 @@ +mod clock; pub use self::clock::*; mod clock_tui; pub use self::clock_tui::*; mod microsecond; pub use self::microsecond::*; mod moment; pub use self::moment::*; @@ -15,234 +16,14 @@ pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem}; pub use ::atomic_float; pub(crate) use atomic_float::*; pub(crate) use ::tek_tui::{ *, - tek_input::*, tek_output::*, - crossterm::event::KeyCode, - ratatui::prelude::* + tek_input::*, + tek_edn::*, + ratatui::prelude::*, + crossterm::event::*, }; -pub trait HasClock: Send + Sync { - fn clock (&self) -> &Clock; +#[cfg(test)] #[test] fn test_time () -> Usually<()> { + // TODO! + Ok(()) } - -#[macro_export] macro_rules! has_clock { - (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? HasClock for $Struct $(<$($L),*$($T),*>)? { - fn clock (&$self) -> &Clock { $cb } - } - } -} - -/// Hosts the JACK callback for updating the temporal pointer and playback status. -pub struct ClockAudio<'a, T: HasClock>(pub &'a mut T); - -impl Audio for ClockAudio<'_, T> { - #[inline] fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { - self.0.clock().update_from_scope(scope).unwrap(); - Control::Continue - } -} - -#[derive(Clone, Debug, PartialEq)] -pub enum ClockCommand { - Play(Option), - Pause(Option), - SeekUsec(f64), - SeekSample(f64), - SeekPulse(f64), - SetBpm(f64), - SetQuant(f64), - SetSync(f64), -} - -impl Command for ClockCommand { - fn execute (self, state: &mut T) -> Perhaps { - use ClockCommand::*; - match self { - Play(start) => state.clock().play_from(start)?, - Pause(pause) => state.clock().pause_at(pause)?, - SeekUsec(usec) => state.clock().playhead.update_from_usec(usec), - SeekSample(sample) => state.clock().playhead.update_from_sample(sample), - SeekPulse(pulse) => state.clock().playhead.update_from_pulse(pulse), - SetBpm(bpm) => return Ok(Some(SetBpm(state.clock().timebase().bpm.set(bpm)))), - SetQuant(quant) => return Ok(Some(SetQuant(state.clock().quant.set(quant)))), - SetSync(sync) => return Ok(Some(SetSync(state.clock().sync.set(sync)))), - }; - Ok(None) - } -} - -#[derive(Clone, Default)] -pub struct Clock { - /// JACK transport handle. - pub transport: Arc>, - /// Global temporal resolution (shared by [Moment] fields) - pub timebase: Arc, - /// Current global sample and usec (monotonic from JACK clock) - pub global: Arc, - /// Global sample and usec at which playback started - pub started: Arc>>, - /// Playback offset (when playing not from start) - pub offset: Arc, - /// Current playhead position - pub playhead: Arc, - /// Note quantization factor - pub quant: Arc, - /// Launch quantization factor - pub sync: Arc, - /// Size of buffer in samples - pub chunk: Arc, -} - -from!(|jack: &Arc>| Clock = { - let jack = jack.read().unwrap(); - let chunk = jack.client().buffer_size(); - let transport = jack.client().transport(); - let timebase = Arc::new(Timebase::default()); - Self { - quant: Arc::new(24.into()), - sync: Arc::new(384.into()), - transport: Arc::new(Some(transport)), - chunk: Arc::new((chunk as usize).into()), - global: Arc::new(Moment::zero(&timebase)), - playhead: Arc::new(Moment::zero(&timebase)), - offset: Arc::new(Moment::zero(&timebase)), - started: RwLock::new(None).into(), - timebase, - } -}); - -impl std::fmt::Debug for Clock { - fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - f.debug_struct("Clock") - .field("timebase", &self.timebase) - .field("chunk", &self.chunk) - .field("quant", &self.quant) - .field("sync", &self.sync) - .field("global", &self.global) - .field("playhead", &self.playhead) - .field("started", &self.started) - .finish() - } -} - -impl Clock { - pub fn timebase (&self) -> &Arc { - &self.timebase - } - /// Current sample rate - pub fn sr (&self) -> &SampleRate { - &self.timebase.sr - } - /// Current tempo - pub fn bpm (&self) -> &BeatsPerMinute { - &self.timebase.bpm - } - /// Current MIDI resolution - pub fn ppq (&self) -> &PulsesPerQuaver { - &self.timebase.ppq - } - /// Next pulse that matches launch sync (for phrase switchover) - pub fn next_launch_pulse (&self) -> usize { - let sync = self.sync.get() as usize; - let pulse = self.playhead.pulse.get() as usize; - if pulse % sync == 0 { - pulse - } else { - (pulse / sync + 1) * sync - } - } - /// Start playing, optionally seeking to a given location beforehand - pub fn play_from (&self, start: Option) -> Usually<()> { - if let Some(transport) = self.transport.as_ref() { - if let Some(start) = start { - transport.locate(start)?; - } - transport.start()?; - } - Ok(()) - } - /// Pause, optionally seeking to a given location afterwards - pub fn pause_at (&self, pause: Option) -> Usually<()> { - if let Some(transport) = self.transport.as_ref() { - transport.stop()?; - if let Some(pause) = pause { - transport.locate(pause)?; - } - } - Ok(()) - } - /// Is currently paused? - pub fn is_stopped (&self) -> bool { - self.started.read().unwrap().is_none() - } - /// Is currently playing? - pub fn is_rolling (&self) -> bool { - self.started.read().unwrap().is_some() - } - /// Update chunk size - pub fn set_chunk (&self, n_frames: usize) { - self.chunk.store(n_frames, Relaxed); - } - pub fn update_from_scope (&self, scope: &ProcessScope) -> Usually<()> { - // Store buffer length - self.set_chunk(scope.n_frames() as usize); - - // Store reported global frame and usec - let CycleTimes { current_frames, current_usecs, .. } = scope.cycle_times()?; - self.global.sample.set(current_frames as f64); - self.global.usec.set(current_usecs as f64); - - let mut started = self.started.write().unwrap(); - - // If transport has just started or just stopped, - // update starting point: - if let Some(transport) = self.transport.as_ref() { - match (transport.query_state()?, started.as_ref()) { - (TransportState::Rolling, None) => { - let moment = Moment::zero(&self.timebase); - moment.sample.set(current_frames as f64); - moment.usec.set(current_usecs as f64); - *started = Some(moment); - }, - (TransportState::Stopped, Some(_)) => { - *started = None; - }, - _ => {} - }; - } - - self.playhead.update_from_sample(started.as_ref() - .map(|started|current_frames as f64 - started.sample.get()) - .unwrap_or(0.)); - - Ok(()) - } - - pub fn bbt (&self) -> PositionBBT { - let pulse = self.playhead.pulse.get() as i32; - let ppq = self.timebase.ppq.get() as i32; - let bpm = self.timebase.bpm.get(); - let bar = (pulse / ppq) / 4; - PositionBBT { - bar: 1 + bar, - beat: 1 + (pulse / ppq) % 4, - tick: (pulse % ppq), - bar_start_tick: (bar * 4 * ppq) as f64, - beat_type: 4., - beats_per_bar: 4., - beats_per_minute: bpm, - ticks_per_beat: ppq as f64 - } - } -} - -//#[cfg(test)] -//mod test { - //use super::*; - //#[test] - //fn test_samples_to_ticks () { - //let ticks = Ticks(12.3).between_samples(0, 100).collect::>(); - //println!("{ticks:?}"); - //} -//}