diff --git a/edn/src/edn_provide.rs b/edn/src/edn_provide.rs index 87a85fb7..8cd14c52 100644 --- a/edn/src/edn_provide.rs +++ b/edn/src/edn_provide.rs @@ -17,7 +17,19 @@ use crate::*; } } }; - // Provide a value that may also be a numeric literal in the EDN + // Provide a value that may also be a numeric literal in the EDN, to a generic implementation. + (# $type:ty:|$self:ident:<$T:ident:$Trait:path>|{ $($pat:pat => $expr:expr),* $(,)? }) => { + impl<'a, $T: $Trait> EdnProvide<'a, $type> for $T { + fn get > (&'a $self, edn: &'a EdnItem) -> Option<$type> { + Some(match edn.to_ref() { + $(EdnItem::Sym($pat) => $expr,)* + EdnItem::Num(n) => n as $type, + _ => return None + }) + } + } + }; + // Provide a value that may also be a numeric literal in the EDN, to a concrete implementation. (# $type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { impl<'a> EdnProvide<'a, $type> for $State { fn get > (&'a $self, edn: &'a EdnItem) -> Option<$type> { diff --git a/input/src/edn_input.rs b/input/src/edn_input.rs index a5284f0e..1ae78e6b 100644 --- a/input/src/edn_input.rs +++ b/input/src/edn_input.rs @@ -15,6 +15,49 @@ pub trait EdnCommand: Command { /** Implement `EdnCommand` for given `State` and `Command` */ #[macro_export] macro_rules! edn_command { + ($Command:ty : |$state:ident:<$T:ident: $Trait:path>| { $(( + // identifier + $key:literal [ + // named parameters + $( + // argument name + $arg:ident + // if type is not provided defaults to EdnItem + $( + // type:name separator + : + // argument type + $type:ty + )? + ),* + // rest of parameters + $(, ..$rest:ident)? + ] + // bound command: + $command:expr + ))* }) => { + impl<$T: $Trait> EdnCommand<$T> for $Command { + fn from_edn <'a> ( + $state: &$T, + head: &EdnItem<&str>, + tail: &'a [EdnItem<&'a str>] + ) -> Option { + $(if let (EdnItem::Key($key), [ // if the identifier matches + // bind argument ids + $($arg),* + // bind rest parameters + $(, $rest @ ..)? + ]) = (head, tail) { + $( + $(let $arg: Option<$type> = EdnProvide::<$type>::get($state, $arg);)? + )* + //$(edn_command!(@bind $state => $arg $(?)? : $type);)* + return Some($command) + })* + None + } + } + }; ($Command:ty : |$state:ident:$State:ty| { $(( // identifier $key:literal [ diff --git a/midi/src/midi_player.rs b/midi/src/midi_player.rs index 4723f7c8..70a03086 100644 --- a/midi/src/midi_player.rs +++ b/midi/src/midi_player.rs @@ -108,7 +108,7 @@ from!(|state: (&Clock, &Arc>)|MidiPlayer = { model.play_clip = Some((Moment::zero(&clock.timebase), Some(clip.clone()))); model }); -has_clock!(|self: MidiPlayer|&self.clock); +has_clock!(|self: MidiPlayer|self.clock); impl HasMidiIns for MidiPlayer { fn midi_ins (&self) -> &Vec> { &self.midi_ins } fn midi_ins_mut (&mut self) -> &mut Vec> { &mut self.midi_ins } diff --git a/tek/src/keys.edn b/tek/src/keys.edn index 512c4f49..f60fc308 100644 --- a/tek/src/keys.edn +++ b/tek/src/keys.edn @@ -1,7 +1,7 @@ (@u undo 1) (@shift-u redo 1) -(@space play/toggle) -(@shift-space play/start-toggle) +(@space clock play 0) +(@shift-space clock play 0) (@e editor show :pool-clip) (@ctrl-a scene add) (@ctrl-t track add) diff --git a/tek/src/lib.rs b/tek/src/lib.rs index 0dd56c2a..bb406053 100644 --- a/tek/src/lib.rs +++ b/tek/src/lib.rs @@ -48,7 +48,7 @@ pub use ::tek_tui::{ pub history: Vec, } has_size!(|self: App|&self.size); -has_clock!(|self: App|&self.clock); +has_clock!(|self: App|self.clock); has_clips!(|self: App|self.pool.as_ref().expect("no clip pool").clips); //has_editor!(|self: App|self.editor.as_ref().expect("no editor")); has_jack!(|self: App|&self.jack); @@ -427,33 +427,33 @@ handle!(TuiIn: |self: App, input|Ok({ } edn_command!(AppCommand: |state: App| { ("stop-all" [] Self::StopAll) - ("compact" [c: bool] Self::Compact(c)) - ("color" [c: Color] Self::Color(c.map(ItemPalette::from).unwrap_or_default())) - ("undo" [d: usize] Self::History(-(d.unwrap_or(0)as isize))) - ("redo" [d: usize] Self::History(d.unwrap_or(0) as isize)) - ("zoom" [z: usize] Self::Zoom(z)) - ("select" [t: usize, s: usize] match (t.expect("no track"), s.expect("no scene")) { + ("compact" [c: bool] Self::Compact(c)) + ("color" [c: Color] Self::Color(c.map(ItemPalette::from).unwrap_or_default())) + ("undo" [d: usize] Self::History(-(d.unwrap_or(0)as isize))) + ("redo" [d: usize] Self::History(d.unwrap_or(0) as isize)) + ("zoom" [z: usize] Self::Zoom(z)) + ("enqueue" [c: Arc>] Self::Enqueue(c)) + ("select" [t: usize, s: usize] match (t.expect("no track"), s.expect("no scene")) { (0, 0) => Self::Select(Selection::Mix), (t, 0) => Self::Select(Selection::Track(t - 1)), (0, s) => Self::Select(Selection::Scene(s - 1)), (t, s) => Self::Select(Selection::Clip(t - 1, s - 1)), }) - ("enqueue" [c: Arc>] Self::Enqueue(c)) - ("clip" [a, ..b] Self::Clip( - ClipCommand::from_edn(state, &a.to_ref(), b).expect("invalid command"))) - ("clock" [a, ..b] Self::Clock( - ClockCommand::from_edn(state, &a.to_ref(), b).expect("invalid command"))) - ("editor" [a, ..b] Self::Editor( - MidiEditCommand::from_edn(state.editor.as_ref().expect("no editor"), &a.to_ref(), b).expect("invalid command"))) - ("pool" [a, ..b] Self::Pool( - PoolCommand::from_edn(state.pool.as_ref().expect("no pool"), &a.to_ref(), b).expect("invalid command"))) - ("sampler" [a, ..b] Self::Sampler( - SamplerCommand::from_edn(state.sampler.as_ref().expect("no sampler"), &a.to_ref(), b).expect("invalid command"))) - ("scene" [a, ..b] Self::Scene( - SceneCommand::from_edn(state, &a.to_ref(), b).expect("invalid command"))) - ("track" [a, ..b] Self::Track( - TrackCommand::from_edn(state, &a.to_ref(), b).expect("invalid command"))) + ("clip" [a, ..b] Self::Clip(ClipCommand::from_edn(state, &a.to_ref(), b) + .expect("invalid command"))) + ("clock" [a, ..b] Self::Clock(ClockCommand::from_edn(state.clock(), &a.to_ref(), b) + .expect("invalid command"))) + ("editor" [a, ..b] Self::Editor(MidiEditCommand::from_edn(state.editor.as_ref().expect("no editor"), &a.to_ref(), b) + .expect("invalid command"))) + ("pool" [a, ..b] Self::Pool(PoolCommand::from_edn(state.pool.as_ref().expect("no pool"), &a.to_ref(), b) + .expect("invalid command"))) + ("sampler" [a, ..b] Self::Sampler(SamplerCommand::from_edn(state.sampler.as_ref().expect("no sampler"), &a.to_ref(), b) + .expect("invalid command"))) + ("scene" [a, ..b] Self::Scene(SceneCommand::from_edn(state, &a.to_ref(), b) + .expect("invalid command"))) + ("track" [a, ..b] Self::Track(TrackCommand::from_edn(state, &a.to_ref(), b) + .expect("invalid command"))) }); command!(|self: AppCommand, state: App|match self { Self::Zoom(_) => { println!("\n\rtodo: global zoom"); None }, @@ -574,7 +574,7 @@ pub trait HasSelection { /// Device chain devices: Vec>, } -has_clock!(|self: Track|self.player.clock()); +has_clock!(|self: Track|self.player.clock); has_player!(|self: Track|self.player); impl Track { const MIN_WIDTH: usize = 9; diff --git a/time/src/clock.rs b/time/src/clock.rs index 7e3b9724..6f863bc2 100644 --- a/time/src/clock.rs +++ b/time/src/clock.rs @@ -1,11 +1,13 @@ use crate::*; pub trait HasClock: Send + Sync { fn clock (&self) -> &Clock; + fn clock_mut (&mut self) -> &mut 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 } + fn clock (&$self) -> &Clock { &$cb } + fn clock_mut (&mut $self) -> &mut Clock { &mut $cb } } } } @@ -20,23 +22,35 @@ pub enum ClockCommand { SetQuant(f64), SetSync(f64), } -impl EdnCommand for ClockCommand { - fn from_edn <'a> (state: &T, head: &EdnItem<&str>, tail: &'a [EdnItem<&str>]) -> Option { - todo!() - } -} +edn_provide!(# u32: |self: Clock| {}); +edn_provide!(f64: |self: Clock| {}); +edn_command!(ClockCommand: |state: Clock| { + ("play" [t: u32] Self::Play(t)) + ("pause" [t: u32] Self::Pause(t)) + ("seek/usec" [t: f64] Self::SeekUsec(t.expect("no usec"))) + ("seek/pulse" [t: f64] Self::SeekPulse(t.expect("no pulse"))) + ("seek/sample" [t: f64] Self::SeekSample(t.expect("no sample"))) + ("set/bpm" [t: f64] Self::SetBpm(t.expect("no bpm"))) + ("set/sync" [t: f64] Self::SetSync(t.expect("no sync"))) + ("set/quant" [t: f64] Self::SetQuant(t.expect("no quant"))) +}); impl Command for ClockCommand { fn execute (self, state: &mut T) -> Perhaps { + self.execute(state.clock_mut()) + } +} +impl Command for ClockCommand { + fn execute (self, state: &mut Clock) -> 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)))), + Play(start) => state.play_from(start)?, + Pause(pause) => state.pause_at(pause)?, + SeekUsec(usec) => state.playhead.update_from_usec(usec), + SeekSample(sample) => state.playhead.update_from_sample(sample), + SeekPulse(pulse) => state.playhead.update_from_pulse(pulse), + SetBpm(bpm) => return Ok(Some(SetBpm(state.timebase().bpm.set(bpm)))), + SetQuant(quant) => return Ok(Some(SetQuant(state.quant.set(quant)))), + SetSync(sync) => return Ok(Some(SetSync(state.sync.set(sync)))), }; Ok(None) }