re-enabled space = play! but not pause

This commit is contained in:
🪞👃🪞 2025-01-14 22:25:18 +01:00
parent 0fb7655b53
commit 0ce0a07713
6 changed files with 110 additions and 41 deletions

View file

@ -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 <S: AsRef<str>> (&'a $self, edn: &'a EdnItem<S>) -> 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),* $(,)? }) => { (# $type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => {
impl<'a> EdnProvide<'a, $type> for $State { impl<'a> EdnProvide<'a, $type> for $State {
fn get <S: AsRef<str>> (&'a $self, edn: &'a EdnItem<S>) -> Option<$type> { fn get <S: AsRef<str>> (&'a $self, edn: &'a EdnItem<S>) -> Option<$type> {

View file

@ -15,6 +15,49 @@ pub trait EdnCommand<C>: Command<C> {
/** Implement `EdnCommand` for given `State` and `Command` */ /** Implement `EdnCommand` for given `State` and `Command` */
#[macro_export] macro_rules! edn_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<Self> {
$(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| { $(( ($Command:ty : |$state:ident:$State:ty| { $((
// identifier // identifier
$key:literal [ $key:literal [

View file

@ -108,7 +108,7 @@ from!(|state: (&Clock, &Arc<RwLock<MidiClip>>)|MidiPlayer = {
model.play_clip = Some((Moment::zero(&clock.timebase), Some(clip.clone()))); model.play_clip = Some((Moment::zero(&clock.timebase), Some(clip.clone())));
model model
}); });
has_clock!(|self: MidiPlayer|&self.clock); has_clock!(|self: MidiPlayer|self.clock);
impl HasMidiIns for MidiPlayer { impl HasMidiIns for MidiPlayer {
fn midi_ins (&self) -> &Vec<JackPort<MidiIn>> { &self.midi_ins } fn midi_ins (&self) -> &Vec<JackPort<MidiIn>> { &self.midi_ins }
fn midi_ins_mut (&mut self) -> &mut Vec<JackPort<MidiIn>> { &mut self.midi_ins } fn midi_ins_mut (&mut self) -> &mut Vec<JackPort<MidiIn>> { &mut self.midi_ins }

View file

@ -1,7 +1,7 @@
(@u undo 1) (@u undo 1)
(@shift-u redo 1) (@shift-u redo 1)
(@space play/toggle) (@space clock play 0)
(@shift-space play/start-toggle) (@shift-space clock play 0)
(@e editor show :pool-clip) (@e editor show :pool-clip)
(@ctrl-a scene add) (@ctrl-a scene add)
(@ctrl-t track add) (@ctrl-t track add)

View file

@ -48,7 +48,7 @@ pub use ::tek_tui::{
pub history: Vec<AppCommand>, pub history: Vec<AppCommand>,
} }
has_size!(<TuiOut>|self: App|&self.size); has_size!(<TuiOut>|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_clips!(|self: App|self.pool.as_ref().expect("no clip pool").clips);
//has_editor!(|self: App|self.editor.as_ref().expect("no editor")); //has_editor!(|self: App|self.editor.as_ref().expect("no editor"));
has_jack!(|self: App|&self.jack); has_jack!(|self: App|&self.jack);
@ -427,33 +427,33 @@ handle!(TuiIn: |self: App, input|Ok({
} }
edn_command!(AppCommand: |state: App| { edn_command!(AppCommand: |state: App| {
("stop-all" [] Self::StopAll) ("stop-all" [] Self::StopAll)
("compact" [c: bool] Self::Compact(c)) ("compact" [c: bool] Self::Compact(c))
("color" [c: Color] Self::Color(c.map(ItemPalette::from).unwrap_or_default())) ("color" [c: Color] Self::Color(c.map(ItemPalette::from).unwrap_or_default()))
("undo" [d: usize] Self::History(-(d.unwrap_or(0)as isize))) ("undo" [d: usize] Self::History(-(d.unwrap_or(0)as isize)))
("redo" [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)) ("zoom" [z: usize] Self::Zoom(z))
("select" [t: usize, s: usize] match (t.expect("no track"), s.expect("no scene")) { ("enqueue" [c: Arc<RwLock<MidiClip>>] Self::Enqueue(c))
("select" [t: usize, s: usize] match (t.expect("no track"), s.expect("no scene")) {
(0, 0) => Self::Select(Selection::Mix), (0, 0) => Self::Select(Selection::Mix),
(t, 0) => Self::Select(Selection::Track(t - 1)), (t, 0) => Self::Select(Selection::Track(t - 1)),
(0, s) => Self::Select(Selection::Scene(s - 1)), (0, s) => Self::Select(Selection::Scene(s - 1)),
(t, s) => Self::Select(Selection::Clip(t - 1, s - 1)), (t, s) => Self::Select(Selection::Clip(t - 1, s - 1)),
}) })
("enqueue" [c: Arc<RwLock<MidiClip>>] Self::Enqueue(c))
("clip" [a, ..b] Self::Clip( ("clip" [a, ..b] Self::Clip(ClipCommand::from_edn(state, &a.to_ref(), b)
ClipCommand::from_edn(state, &a.to_ref(), b).expect("invalid command"))) .expect("invalid command")))
("clock" [a, ..b] Self::Clock( ("clock" [a, ..b] Self::Clock(ClockCommand::from_edn(state.clock(), &a.to_ref(), b)
ClockCommand::from_edn(state, &a.to_ref(), b).expect("invalid command"))) .expect("invalid command")))
("editor" [a, ..b] Self::Editor( ("editor" [a, ..b] Self::Editor(MidiEditCommand::from_edn(state.editor.as_ref().expect("no editor"), &a.to_ref(), b)
MidiEditCommand::from_edn(state.editor.as_ref().expect("no editor"), &a.to_ref(), b).expect("invalid command"))) .expect("invalid command")))
("pool" [a, ..b] Self::Pool( ("pool" [a, ..b] Self::Pool(PoolCommand::from_edn(state.pool.as_ref().expect("no pool"), &a.to_ref(), b)
PoolCommand::from_edn(state.pool.as_ref().expect("no pool"), &a.to_ref(), b).expect("invalid command"))) .expect("invalid command")))
("sampler" [a, ..b] Self::Sampler( ("sampler" [a, ..b] Self::Sampler(SamplerCommand::from_edn(state.sampler.as_ref().expect("no sampler"), &a.to_ref(), b)
SamplerCommand::from_edn(state.sampler.as_ref().expect("no sampler"), &a.to_ref(), b).expect("invalid command"))) .expect("invalid command")))
("scene" [a, ..b] Self::Scene( ("scene" [a, ..b] Self::Scene(SceneCommand::from_edn(state, &a.to_ref(), b)
SceneCommand::from_edn(state, &a.to_ref(), b).expect("invalid command"))) .expect("invalid command")))
("track" [a, ..b] Self::Track( ("track" [a, ..b] Self::Track(TrackCommand::from_edn(state, &a.to_ref(), b)
TrackCommand::from_edn(state, &a.to_ref(), b).expect("invalid command"))) .expect("invalid command")))
}); });
command!(|self: AppCommand, state: App|match self { command!(|self: AppCommand, state: App|match self {
Self::Zoom(_) => { println!("\n\rtodo: global zoom"); None }, Self::Zoom(_) => { println!("\n\rtodo: global zoom"); None },
@ -574,7 +574,7 @@ pub trait HasSelection {
/// Device chain /// Device chain
devices: Vec<Box<dyn Device>>, devices: Vec<Box<dyn Device>>,
} }
has_clock!(|self: Track|self.player.clock()); has_clock!(|self: Track|self.player.clock);
has_player!(|self: Track|self.player); has_player!(|self: Track|self.player);
impl Track { impl Track {
const MIN_WIDTH: usize = 9; const MIN_WIDTH: usize = 9;

View file

@ -1,11 +1,13 @@
use crate::*; use crate::*;
pub trait HasClock: Send + Sync { pub trait HasClock: Send + Sync {
fn clock (&self) -> &Clock; fn clock (&self) -> &Clock;
fn clock_mut (&mut self) -> &mut Clock;
} }
#[macro_export] macro_rules! has_clock { #[macro_export] macro_rules! has_clock {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? HasClock for $Struct $(<$($L),*$($T),*>)? { 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), SetQuant(f64),
SetSync(f64), SetSync(f64),
} }
impl<T: HasClock> EdnCommand<T> for ClockCommand { edn_provide!(# u32: |self: Clock| {});
fn from_edn <'a> (state: &T, head: &EdnItem<&str>, tail: &'a [EdnItem<&str>]) -> Option<Self> { edn_provide!(f64: |self: Clock| {});
todo!() 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<T: HasClock> Command<T> for ClockCommand { impl<T: HasClock> Command<T> for ClockCommand {
fn execute (self, state: &mut T) -> Perhaps<Self> { fn execute (self, state: &mut T) -> Perhaps<Self> {
self.execute(state.clock_mut())
}
}
impl Command<Clock> for ClockCommand {
fn execute (self, state: &mut Clock) -> Perhaps<Self> {
use ClockCommand::*; use ClockCommand::*;
match self { match self {
Play(start) => state.clock().play_from(start)?, Play(start) => state.play_from(start)?,
Pause(pause) => state.clock().pause_at(pause)?, Pause(pause) => state.pause_at(pause)?,
SeekUsec(usec) => state.clock().playhead.update_from_usec(usec), SeekUsec(usec) => state.playhead.update_from_usec(usec),
SeekSample(sample) => state.clock().playhead.update_from_sample(sample), SeekSample(sample) => state.playhead.update_from_sample(sample),
SeekPulse(pulse) => state.clock().playhead.update_from_pulse(pulse), SeekPulse(pulse) => state.playhead.update_from_pulse(pulse),
SetBpm(bpm) => return Ok(Some(SetBpm(state.clock().timebase().bpm.set(bpm)))), SetBpm(bpm) => return Ok(Some(SetBpm(state.timebase().bpm.set(bpm)))),
SetQuant(quant) => return Ok(Some(SetQuant(state.clock().quant.set(quant)))), SetQuant(quant) => return Ok(Some(SetQuant(state.quant.set(quant)))),
SetSync(sync) => return Ok(Some(SetSync(state.clock().sync.set(sync)))), SetSync(sync) => return Ok(Some(SetSync(state.sync.set(sync)))),
}; };
Ok(None) Ok(None)
} }