stub out some of the edn command readers

This commit is contained in:
🪞👃🪞 2025-01-11 23:35:35 +01:00
parent 1aa0551931
commit 4fb703d05d
8 changed files with 381 additions and 287 deletions

View file

@ -20,6 +20,7 @@ pub(crate) use ::tek_tui::{
*,
tek_input::*,
tek_output::*,
tek_edn::*,
crossterm::event::*,
ratatui::style::{Style, Stylize, Color}
};

View file

@ -152,6 +152,11 @@ pub enum MidiEditCommand {
SetTimeLock(bool),
Show(Option<Arc<RwLock<MidiClip>>>),
}
impl MidiEditCommand {
pub fn from_edn <'a> (head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> 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),

View file

@ -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<String>]) -> Self {
todo!()
}
}
impl<T: HasClips> Command<T> for MidiPoolCommand {
fn execute (self, model: &mut T) -> Perhaps<Self> {
@ -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<String>]) -> Self {
todo!()
}
}
command!(|self:PoolCommand, state: PoolModel|{
use PoolCommand::*;

View file

@ -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<String>]) -> 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<String>]) -> Self {
todo!()
}
}
input_to_command!(SamplerTuiCommand: |state: SamplerTui, input: Event|match state.mode{
Some(SamplerMode::Import(..)) => Self::Import(

View file

@ -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<String>]) -> 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<Arc<RwLock<MidiClip>>>),
Enqueue(usize, usize),
Edit(Option<Arc<RwLock<MidiClip>>>),
SetLoop(usize, usize, bool),
SetColor(usize, usize, ItemPalette),
}
impl ClipCommand {
pub fn from_edn <'a> (head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> 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<String>]) -> 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<String>]) -> 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<Arc<RwLock<MidiClip>>>),
Enqueue(usize, usize),
Edit(Option<Arc<RwLock<MidiClip>>>),
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<ArrangerCommand> {
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 {
@ -386,9 +451,9 @@ fn clip_keymap (state: &Arranger, input: &Event, t: usize, s: usize) -> Option<A
}
fn scene_keymap (state: &Arranger, input: &Event, s: usize) -> Option<ArrangerCommand> {
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 {
@ -398,7 +463,7 @@ fn scene_keymap (state: &Arranger, input: &Event, s: usize) -> Option<ArrangerCo
kpat!(Char('<')) => 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 }),
@ -411,9 +476,9 @@ fn scene_keymap (state: &Arranger, input: &Event, s: usize) -> Option<ArrangerCo
}
fn track_keymap (state: &Arranger, input: &Event, t: usize) -> Option<ArrangerCommand> {
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 {
@ -422,7 +487,7 @@ fn track_keymap (state: &Arranger, input: &Event, t: usize) -> Option<ArrangerCo
kpat!(Char('.')) => 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,

223
time/src/clock.rs Normal file
View file

@ -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<T: HasClock> 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<u32>),
Pause(Option<u32>),
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<String>]) -> Self {
todo!()
}
}
impl<T: HasClock> Command<T> for ClockCommand {
fn execute (self, state: &mut T) -> Perhaps<Self> {
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<Option<Transport>>,
/// Global temporal resolution (shared by [Moment] fields)
pub timebase: Arc<Timebase>,
/// Current global sample and usec (monotonic from JACK clock)
pub global: Arc<Moment>,
/// Global sample and usec at which playback started
pub started: Arc<RwLock<Option<Moment>>>,
/// Playback offset (when playing not from start)
pub offset: Arc<Moment>,
/// Current playhead position
pub playhead: Arc<Moment>,
/// Note quantization factor
pub quant: Arc<Quantize>,
/// Launch quantization factor
pub sync: Arc<LaunchSync>,
/// Size of buffer in samples
pub chunk: Arc<AtomicUsize>,
}
from!(|jack: &Arc<RwLock<JackConnection>>| 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<Timebase> {
&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<u32>) -> 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<u32>) -> 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
}
}
}

View file

@ -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<RwLock<JackConnection>>,

View file

@ -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;
}
#[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<T: HasClock> 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<u32>),
Pause(Option<u32>),
SeekUsec(f64),
SeekSample(f64),
SeekPulse(f64),
SetBpm(f64),
SetQuant(f64),
SetSync(f64),
}
impl<T: HasClock> Command<T> for ClockCommand {
fn execute (self, state: &mut T) -> Perhaps<Self> {
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<Option<Transport>>,
/// Global temporal resolution (shared by [Moment] fields)
pub timebase: Arc<Timebase>,
/// Current global sample and usec (monotonic from JACK clock)
pub global: Arc<Moment>,
/// Global sample and usec at which playback started
pub started: Arc<RwLock<Option<Moment>>>,
/// Playback offset (when playing not from start)
pub offset: Arc<Moment>,
/// Current playhead position
pub playhead: Arc<Moment>,
/// Note quantization factor
pub quant: Arc<Quantize>,
/// Launch quantization factor
pub sync: Arc<LaunchSync>,
/// Size of buffer in samples
pub chunk: Arc<AtomicUsize>,
}
from!(|jack: &Arc<RwLock<JackConnection>>| 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<Timebase> {
&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<u32>) -> Usually<()> {
if let Some(transport) = self.transport.as_ref() {
if let Some(start) = start {
transport.locate(start)?;
}
transport.start()?;
}
#[cfg(test)] #[test] fn test_time () -> Usually<()> {
// TODO!
Ok(())
}
/// Pause, optionally seeking to a given location afterwards
pub fn pause_at (&self, pause: Option<u32>) -> 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::<Vec<_>>();
//println!("{ticks:?}");
//}
//}