From 12faadef44897857980df5cced5efcb4c30ca863 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 14 Jan 2025 19:03:08 +0100 Subject: [PATCH] wip: implementing app command dispatch --- input/src/command.rs | 4 +- input/src/edn_command.rs | 13 +- input/src/edn_input.rs | 2 +- input/src/edn_keymap.rs | 15 +- input/src/handle.rs | 61 --- input/src/input.rs | 64 ++- input/src/lib.rs | 1 - ...ool_keys_clip.edn => keys_clip_length.edn} | 0 ...ool_keys_file.edn => keys_clip_rename.edn} | 0 midi/src/keys_pool.edn | 12 + ...ool_keys_length.edn => keys_pool_file.edn} | 0 midi/src/lib.rs | 4 +- midi/src/midi_edit.rs | 9 +- midi/src/midi_pool.rs | 440 ++++++++++++++++++ midi/src/midi_pool_cmd.rs | 424 ----------------- midi/src/midi_pool_keys.edn | 13 - ...idi_pool_keys_rename.edn => view_pool.edn} | 0 sampler/src/sampler_cmd.rs | 4 +- tek/src/{arranger-keys.edn => keys.edn} | 1 + .../src/keys_clip.edn | 0 .../pool-keys.edn => tek/src/keys_scene.edn | 0 .../{groovebox-keys.edn => keys_track.edn} | 0 tek/src/lib.rs | 71 ++- tek/src/transport-keys.edn | 0 tek/src/transport-view.edn | 0 .../{arranger-view.edn => view_arranger.edn} | 0 ...{groovebox-view.edn => view_groovebox.edn} | 0 ...{sequencer-view.edn => view_sequencer.edn} | 0 ...{sequencer-keys.edn => view_transport.edn} | 0 time/src/clock.rs | 2 +- tui/src/tui_edn_keymap.rs | 9 +- 31 files changed, 598 insertions(+), 551 deletions(-) delete mode 100644 input/src/handle.rs rename midi/src/{midi_pool_keys_clip.edn => keys_clip_length.edn} (100%) rename midi/src/{midi_pool_keys_file.edn => keys_clip_rename.edn} (100%) create mode 100644 midi/src/keys_pool.edn rename midi/src/{midi_pool_keys_length.edn => keys_pool_file.edn} (100%) delete mode 100644 midi/src/midi_pool_cmd.rs delete mode 100644 midi/src/midi_pool_keys.edn rename midi/src/{midi_pool_keys_rename.edn => view_pool.edn} (100%) rename tek/src/{arranger-keys.edn => keys.edn} (99%) rename midi/src/midi_pool_view.edn => tek/src/keys_clip.edn (100%) rename midi/src/pool-keys.edn => tek/src/keys_scene.edn (100%) rename tek/src/{groovebox-keys.edn => keys_track.edn} (100%) delete mode 100644 tek/src/transport-keys.edn delete mode 100644 tek/src/transport-view.edn rename tek/src/{arranger-view.edn => view_arranger.edn} (100%) rename tek/src/{groovebox-view.edn => view_groovebox.edn} (100%) rename tek/src/{sequencer-view.edn => view_sequencer.edn} (100%) rename tek/src/{sequencer-keys.edn => view_transport.edn} (100%) diff --git a/input/src/command.rs b/input/src/command.rs index 9e91b304..dc53c637 100644 --- a/input/src/command.rs +++ b/input/src/command.rs @@ -10,7 +10,9 @@ use crate::*; } pub trait Command: Send + Sync + Sized { fn execute (self, state: &mut S) -> Perhaps; - fn delegate (self, state: &mut S, wrap: impl Fn(Self)->T) -> Perhaps { + fn delegate (self, state: &mut S, wrap: impl Fn(Self)->T) -> Perhaps + where Self: Sized + { Ok(self.execute(state)?.map(wrap)) } } diff --git a/input/src/edn_command.rs b/input/src/edn_command.rs index 373fbe3d..c6830d87 100644 --- a/input/src/edn_command.rs +++ b/input/src/edn_command.rs @@ -1,7 +1,8 @@ use crate::*; /// Turns an EDN item sequence into a command enum variant. pub trait EdnCommand: Command { - fn from_edn <'a> (state: &C, head: &EdnItem<&str>, tail: &'a [EdnItem]) -> Self; + fn from_edn <'a> (state: &C, head: &EdnItem<&str>, tail: &'a [EdnItem<&'a str>]) + -> Option; } /** Implement `EdnCommand` for given `State` and `Command` */ #[macro_export] macro_rules! edn_command { @@ -27,7 +28,11 @@ pub trait EdnCommand: Command { $command:expr ))* }) => { impl EdnCommand<$State> for $Command { - fn from_edn <'a> ($state: &$State, head: &EdnItem<&str>, tail: &'a [EdnItem]) -> Self { + fn from_edn <'a> ( + $state: &$State, + head: &EdnItem<&str>, + tail: &'a [EdnItem<&'a str>] + ) -> Option { $(if let (EdnItem::Key($key), [ // if the identifier matches // bind argument ids $($arg),* @@ -38,9 +43,9 @@ pub trait EdnCommand: Command { $(let $arg: Option<$type> = EdnProvide::<$type>::get($state, $arg);)? )* //$(edn_command!(@bind $state => $arg $(?)? : $type);)* - return $command + return Some($command) })* - panic!("no such command") + None } } }; diff --git a/input/src/edn_input.rs b/input/src/edn_input.rs index f954615a..be91ce30 100644 --- a/input/src/edn_input.rs +++ b/input/src/edn_input.rs @@ -1,7 +1,7 @@ use crate::*; pub trait EdnInput: Input { - fn matches (&self, token: &str) -> bool; + fn matches_edn (&self, token: &str) -> bool; fn get_event > (_: &EdnItem) -> Option { None } diff --git a/input/src/edn_keymap.rs b/input/src/edn_keymap.rs index dd5ca46c..067ef9cf 100644 --- a/input/src/edn_keymap.rs +++ b/input/src/edn_keymap.rs @@ -1,17 +1,17 @@ use crate::*; use EdnItem::*; -pub struct EdnKeymap(pub Vec>); +pub struct EdnKeymap<'a>(pub Vec>); -impl EdnKeymap { +impl<'a> EdnKeymap<'a> { pub fn command , E: EdnCommand, I: EdnInput> ( &self, state: &C, input: &I ) -> Option { for item in self.0.iter() { if let Exp(items) = item { match items.as_slice() { - [Sym(a), b, c @ ..] => if input.matches(a.as_str()) { - return Some(E::from_edn(state, &b.to_ref(), c)) + [Sym(a), b, c @ ..] => if input.matches_edn(a) { + return E::from_edn(state, &b.to_ref(), c) }, _ => unreachable!() } @@ -23,9 +23,10 @@ impl EdnKeymap { } } -impl> From for EdnKeymap { - fn from (source: T) -> Self { - Self(EdnItem::::read_all(source.as_ref()).expect("failed to load keymap")) +impl<'a> From<&'a str> for EdnKeymap<'a> { + fn from (source: &'a str) -> Self { + let items = EdnItem::<&'a str>::read_all(source.as_ref()); + Self(items.expect("failed to load keymap")) } } diff --git a/input/src/handle.rs b/input/src/handle.rs deleted file mode 100644 index 63b87514..00000000 --- a/input/src/handle.rs +++ /dev/null @@ -1,61 +0,0 @@ -use crate::*; -use std::sync::{Mutex, Arc, RwLock}; - -/// Implement the [Handle] trait. -#[macro_export] macro_rules! handle { - (|$self:ident:$Struct:ty,$input:ident|$handler:expr) => { - impl Handle for $Struct { - fn handle (&mut $self, $input: &E) -> Perhaps { - $handler - } - } - }; - ($E:ty: |$self:ident:$Struct:ty,$input:ident|$handler:expr) => { - impl Handle<$E> for $Struct { - fn handle (&mut $self, $input: &$E) -> Perhaps<<$E as Input>::Handled> { - $handler - } - } - } -} - -/// Handle input -pub trait Handle: Send + Sync { - fn handle (&mut self, _input: &E) -> Perhaps { - Ok(None) - } -} -impl> Handle for &mut H { - fn handle (&mut self, context: &E) -> Perhaps { - (*self).handle(context) - } -} -impl> Handle for Option { - fn handle (&mut self, context: &E) -> Perhaps { - if let Some(ref mut handle) = self { - handle.handle(context) - } else { - Ok(None) - } - } -} -impl Handle for Mutex where H: Handle { - fn handle (&mut self, context: &E) -> Perhaps { - self.get_mut().unwrap().handle(context) - } -} -impl Handle for Arc> where H: Handle { - fn handle (&mut self, context: &E) -> Perhaps { - self.lock().unwrap().handle(context) - } -} -impl Handle for RwLock where H: Handle { - fn handle (&mut self, context: &E) -> Perhaps { - self.write().unwrap().handle(context) - } -} -impl Handle for Arc> where H: Handle { - fn handle (&mut self, context: &E) -> Perhaps { - self.write().unwrap().handle(context) - } -} diff --git a/input/src/input.rs b/input/src/input.rs index 6c3c78b8..c8b59e30 100644 --- a/input/src/input.rs +++ b/input/src/input.rs @@ -1,9 +1,12 @@ +use crate::*; +use std::sync::{Mutex, Arc, RwLock}; + /// Event source pub trait Input: Send + Sync + Sized { /// Type of input event type Event; /// Result of handling input - type Handled; + type Handled; // TODO: make this an Option> containing the undo /// Currently handled event fn event (&self) -> &Self::Event; /// Whether component should exit @@ -11,3 +14,62 @@ pub trait Input: Send + Sync + Sized { /// Mark component as done fn done (&self); } + +/// Implement the [Handle] trait. +#[macro_export] macro_rules! handle { + (|$self:ident:$Struct:ty,$input:ident|$handler:expr) => { + impl Handle for $Struct { + fn handle (&mut $self, $input: &E) -> Perhaps { + $handler + } + } + }; + ($E:ty: |$self:ident:$Struct:ty,$input:ident|$handler:expr) => { + impl Handle<$E> for $Struct { + fn handle (&mut $self, $input: &$E) -> Perhaps<<$E as Input>::Handled> { + $handler + } + } + } +} + +/// Handle input +pub trait Handle: Send + Sync { + fn handle (&mut self, _input: &E) -> Perhaps { + Ok(None) + } +} +impl> Handle for &mut H { + fn handle (&mut self, context: &E) -> Perhaps { + (*self).handle(context) + } +} +impl> Handle for Option { + fn handle (&mut self, context: &E) -> Perhaps { + if let Some(ref mut handle) = self { + handle.handle(context) + } else { + Ok(None) + } + } +} +impl Handle for Mutex where H: Handle { + fn handle (&mut self, context: &E) -> Perhaps { + self.get_mut().unwrap().handle(context) + } +} +impl Handle for Arc> where H: Handle { + fn handle (&mut self, context: &E) -> Perhaps { + self.lock().unwrap().handle(context) + } +} +impl Handle for RwLock where H: Handle { + fn handle (&mut self, context: &E) -> Perhaps { + self.write().unwrap().handle(context) + } +} +impl Handle for Arc> where H: Handle { + fn handle (&mut self, context: &E) -> Perhaps { + self.write().unwrap().handle(context) + } +} diff --git a/input/src/lib.rs b/input/src/lib.rs index c3749ebc..45eb82fc 100644 --- a/input/src/lib.rs +++ b/input/src/lib.rs @@ -2,7 +2,6 @@ //mod component; pub use self::component::*; mod input; pub use self::input::*; -mod handle; pub use self::handle::*; mod command; pub use self::command::*; mod event_map; pub use self::event_map::*; diff --git a/midi/src/midi_pool_keys_clip.edn b/midi/src/keys_clip_length.edn similarity index 100% rename from midi/src/midi_pool_keys_clip.edn rename to midi/src/keys_clip_length.edn diff --git a/midi/src/midi_pool_keys_file.edn b/midi/src/keys_clip_rename.edn similarity index 100% rename from midi/src/midi_pool_keys_file.edn rename to midi/src/keys_clip_rename.edn diff --git a/midi/src/keys_pool.edn b/midi/src/keys_pool.edn new file mode 100644 index 00000000..e5c87a0d --- /dev/null +++ b/midi/src/keys_pool.edn @@ -0,0 +1,12 @@ +(:n rename/begin) +(:t length/begin) +(:m import/begin) +(:x export/begin) +(:c clip/color :current :random-color) +(:openbracket select :previous) +(:closebracket select :next) +(:lt swap :current :previous) +(:gt swap :current :next) +(:delete clip/delete :current) +(:shift-A clip/add :after :new-clip) +(:shift-D clip/add :after :cloned-clip) diff --git a/midi/src/midi_pool_keys_length.edn b/midi/src/keys_pool_file.edn similarity index 100% rename from midi/src/midi_pool_keys_length.edn rename to midi/src/keys_pool_file.edn diff --git a/midi/src/lib.rs b/midi/src/lib.rs index d9d9393c..e37aa1d7 100644 --- a/midi/src/lib.rs +++ b/midi/src/lib.rs @@ -9,9 +9,7 @@ mod midi_range; pub use midi_range::*; mod midi_point; pub use midi_point::*; mod midi_view; pub use midi_view::*; -mod midi_pool; pub use midi_pool::*; -mod midi_pool_cmd; pub use midi_pool_cmd::*; - +mod midi_pool; pub use midi_pool::*; mod midi_edit; pub use midi_edit::*; mod piano_h; pub use self::piano_h::*; diff --git a/midi/src/midi_edit.rs b/midi/src/midi_edit.rs index b0e972ed..5011e40a 100644 --- a/midi/src/midi_edit.rs +++ b/midi/src/midi_edit.rs @@ -31,9 +31,8 @@ pub trait HasEditor { } /// Contains state for viewing and editing a clip pub struct MidiEditor { - pub mode: PianoHorizontal, - pub size: Measure, - pub keymap: EdnKeymap, + pub mode: PianoHorizontal, + pub size: Measure, } impl std::fmt::Debug for MidiEditor { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { @@ -47,10 +46,6 @@ impl Default for MidiEditor { Self { mode: PianoHorizontal::new(None), size: Measure::new(), - keymap: EdnKeymap( - EdnItem::::read_all(include_str!("midi_edit_keys.edn")) - .expect("failed to load keymap for MidiEditor") - ) } } } diff --git a/midi/src/midi_pool.rs b/midi/src/midi_pool.rs index 1fafb047..bd5d2c33 100644 --- a/midi/src/midi_pool.rs +++ b/midi/src/midi_pool.rs @@ -1,4 +1,5 @@ use crate::*; +use KeyCode::*; pub type ClipPool = Vec>>; pub trait HasClips { fn clips <'a> (&'a self) -> std::sync::RwLockReadGuard<'a, ClipPool>; @@ -204,3 +205,442 @@ content!(TuiOut: |self: ClipLength| { row!(" ", bars(), ".", beats(), "[", ticks()), } }); +impl PoolCommand { + const KEYS_POOL: &str = include_str!("keys_pool.edn"); + const KEYS_FILE: &str = include_str!("keys_pool_file.edn"); + const KEYS_LENGTH: &str = include_str!("keys_clip_length.edn"); + const KEYS_RENAME: &str = include_str!("keys_clip_rename.edn"); + pub fn from_tui_event (state: &MidiPool, input: &impl EdnInput) -> Usually> { + use EdnItem::*; + let edns: Vec> = EdnItem::read_all(match state.mode() { + Some(PoolMode::Rename(..)) => Self::KEYS_RENAME, + Some(PoolMode::Length(..)) => Self::KEYS_LENGTH, + Some(PoolMode::Import(..)) | Some(PoolMode::Export(..)) => Self::KEYS_FILE, + _ => Self::KEYS_POOL + })?; + for item in edns { + match item { + Exp(e) => match e.as_slice() { + [Sym(key), command, args @ ..] if input.matches_edn(key) => { + return Ok(PoolCommand::from_edn(state, command, args)) + } + _ => {} + }, + _ => panic!("invalid config") + } + } + Ok(None) + } +} +handle!(TuiIn: |self: MidiPool, input|{ + Ok(if let Some(command) = PoolCommand::from_tui_event(self, input)? { + let _undo = command.execute(self)?; + Some(true) + } else { + None + }) +}); +edn_provide!(bool: |self: MidiPool| {}); +edn_provide!(MidiClip: |self: MidiPool| { + ":new-clip" => MidiClip::new( + "Clip", true, 4 * PPQ, None, Some(ItemPalette::random()) + ), + ":cloned-clip" => { + let index = self.clip_index(); + let mut clip = self.clips()[index].read().unwrap().duplicate(); + clip.color = ItemPalette::random_near(clip.color, 0.25); + clip + } +}); +edn_provide!(PathBuf: |self: MidiPool| {}); +edn_provide!(Arc: |self: MidiPool| {}); +edn_provide!(usize: |self: MidiPool| { + ":current" => 0, + ":after" => 0, + ":previous" => 0, + ":next" => 0 +}); +edn_provide!(ItemColor: |self: MidiPool| { + ":random-color" => ItemColor::random() +}); +#[derive(Clone, PartialEq, Debug)] pub enum PoolCommand { + /// Toggle visibility of pool + Show(bool), + /// Select a clip from the clip pool + Select(usize), + /// Rename a clip + Rename(ClipRenameCommand), + /// Change the length of a clip + Length(ClipLengthCommand), + /// Import from file + Import(FileBrowserCommand), + /// Export to file + Export(FileBrowserCommand), + /// Update the contents of the clip pool + Clip(PoolClipCommand), +} +edn_command!(PoolCommand: |state: MidiPool| { + ("show" [a: bool] Self::Show(a.expect("no flag"))) + ("select" [i: usize] Self::Select(i.expect("no index"))) + ("rename" [a, ..b] ClipRenameCommand::from_edn(state, &a.to_ref(), b).map(Self::Rename).expect("invalid command")) + ("length" [a, ..b] ClipLengthCommand::from_edn(state, &a.to_ref(), b).map(Self::Length).expect("invalid command")) + ("import" [a, ..b] FileBrowserCommand::from_edn(state, &a.to_ref(), b).map(Self::Import).expect("invalid command")) + ("export" [a, ..b] FileBrowserCommand::from_edn(state, &a.to_ref(), b).map(Self::Export).expect("invalid command")) + ("clip" [a, ..b] PoolClipCommand::from_edn(state, &a.to_ref(), b).map(Self::Clip).expect("invalid command")) +}); +command!(|self: PoolCommand, state: MidiPool|{ + use PoolCommand::*; + match self { + Rename(ClipRenameCommand::Begin) => { state.begin_clip_rename(); None } + Rename(command) => command.delegate(state, Rename)?, + Length(ClipLengthCommand::Begin) => { state.begin_clip_length(); None }, + Length(command) => command.delegate(state, Length)?, + Import(FileBrowserCommand::Begin) => { state.begin_import()?; None }, + Import(command) => command.delegate(state, Import)?, + Export(FileBrowserCommand::Begin) => { state.begin_export()?; None }, + Export(command) => command.delegate(state, Export)?, + Clip(command) => command.execute(state)?.map(Clip), + Show(visible) => { state.visible = visible; Some(Self::Show(!visible)) }, + Select(clip) => { state.set_clip_index(clip); None }, + } +}); +#[derive(Clone, Debug, PartialEq)] pub enum PoolClipCommand { + Add(usize, MidiClip), + Delete(usize), + Swap(usize, usize), + Import(usize, PathBuf), + Export(usize, PathBuf), + SetName(usize, Arc), + SetLength(usize, usize), + SetColor(usize, ItemColor), +} +edn_command!(PoolClipCommand: |state: MidiPool| { + ("add" [i: usize, c: MidiClip] Self::Add(i.expect("no index"), c.expect("no clip"))) + ("delete" [i: usize] Self::Delete(i.expect("no index"))) + ("swap" [a: usize, b: usize] Self::Swap(a.expect("no index"), b.expect("no index"))) + ("import" [i: usize, p: PathBuf] Self::Import(i.expect("no index"), p.expect("no path"))) + ("export" [i: usize, p: PathBuf] Self::Export(i.expect("no index"), p.expect("no path"))) + ("set-name" [i: usize, n: Arc] Self::SetName(i.expect("no index"), n.expect("no name"))) + ("set-length" [i: usize, l: usize] Self::SetLength(i.expect("no index"), l.expect("no length"))) + ("set-color" [i: usize, c: ItemColor] Self::SetColor(i.expect("no index"), c.expect("no color"))) +}); +impl Command for PoolClipCommand { + fn execute (self, model: &mut T) -> Perhaps { + use PoolClipCommand::*; + Ok(match self { + Add(mut index, clip) => { + let clip = Arc::new(RwLock::new(clip)); + let mut clips = model.clips_mut(); + if index >= clips.len() { + index = clips.len(); + clips.push(clip) + } else { + clips.insert(index, clip); + } + Some(Self::Delete(index)) + }, + Delete(index) => { + let clip = model.clips_mut().remove(index).read().unwrap().clone(); + Some(Self::Add(index, clip)) + }, + Swap(index, other) => { + model.clips_mut().swap(index, other); + Some(Self::Swap(index, other)) + }, + Import(index, path) => { + let bytes = std::fs::read(&path)?; + let smf = Smf::parse(bytes.as_slice())?; + let mut t = 0u32; + let mut events = vec![]; + for track in smf.tracks.iter() { + for event in track.iter() { + t += event.delta.as_int(); + if let TrackEventKind::Midi { channel, message } = event.kind { + events.push((t, channel.as_int(), message)); + } + } + } + let mut clip = MidiClip::new("imported", true, t as usize + 1, None, None); + for event in events.iter() { + clip.notes[event.0 as usize].push(event.2); + } + Self::Add(index, clip).execute(model)? + }, + Export(_index, _path) => { + todo!("export clip to midi file"); + }, + SetName(index, name) => { + let clip = &mut model.clips_mut()[index]; + let old_name = clip.read().unwrap().name.clone(); + clip.write().unwrap().name = name; + Some(Self::SetName(index, old_name)) + }, + SetLength(index, length) => { + let clip = &mut model.clips_mut()[index]; + let old_len = clip.read().unwrap().length; + clip.write().unwrap().length = length; + Some(Self::SetLength(index, old_len)) + }, + SetColor(index, color) => { + let mut color = ItemPalette::from(color); + std::mem::swap(&mut color, &mut model.clips()[index].write().unwrap().color); + Some(Self::SetColor(index, color.base)) + }, + }) + } +} +#[derive(Clone, Debug, PartialEq)] pub enum ClipRenameCommand { + Begin, + Cancel, + Confirm, + Set(Arc), +} +edn_command!(ClipRenameCommand: |state: MidiPool| { + ("begin" [] Self::Begin) + ("cancel" [] Self::Cancel) + ("confirm" [] Self::Confirm) + ("set" [n: Arc] Self::Set(n.expect("no name"))) +}); +command!(|self: ClipRenameCommand, state: MidiPool|{ + use ClipRenameCommand::*; + if let Some( + PoolMode::Rename(clip, ref mut old_name) + ) = state.mode_mut().clone() { + match self { + Set(s) => { + state.clips()[clip].write().unwrap().name = s; + return Ok(Some(Self::Set(old_name.clone().into()))) + }, + Confirm => { + let old_name = old_name.clone(); + *state.mode_mut() = None; + return Ok(Some(Self::Set(old_name))) + }, + Cancel => { + state.clips()[clip].write().unwrap().name = old_name.clone().into(); + return Ok(None) + }, + _ => unreachable!() + } + } else { + unreachable!() + } +}); +#[derive(Copy, Clone, Debug, PartialEq)] pub enum ClipLengthCommand { + Begin, + Cancel, + Set(usize), + Next, + Prev, + Inc, + Dec, +} +edn_command!(ClipLengthCommand: |state: MidiPool| { + ("begin" [] Self::Begin) + ("cancel" [] Self::Cancel) + ("next" [] Self::Next) + ("prev" [] Self::Prev) + ("inc" [] Self::Inc) + ("dec" [] Self::Dec) + ("set" [l: usize] Self::Set(l.expect("no length"))) +}); +command!(|self: ClipLengthCommand, state: MidiPool|{ + use ClipLengthCommand::*; + use ClipLengthFocus::*; + if let Some( + PoolMode::Length(clip, ref mut length, ref mut focus) + ) = state.mode_mut().clone() { + match self { + Cancel => { *state.mode_mut() = None; }, + Prev => { focus.prev() }, + Next => { focus.next() }, + Inc => match focus { + Bar => { *length += 4 * PPQ }, + Beat => { *length += PPQ }, + Tick => { *length += 1 }, + }, + Dec => match focus { + Bar => { *length = length.saturating_sub(4 * PPQ) }, + Beat => { *length = length.saturating_sub(PPQ) }, + Tick => { *length = length.saturating_sub(1) }, + }, + Set(length) => { + let mut old_length = None; + { + let clip = state.clips()[clip].clone();//.write().unwrap(); + old_length = Some(clip.read().unwrap().length); + clip.write().unwrap().length = length; + } + *state.mode_mut() = None; + return Ok(old_length.map(Self::Set)) + }, + _ => unreachable!() + } + } else { + unreachable!(); + } + None +}); +edn_command!(FileBrowserCommand: |state: MidiPool| { + ("begin" [] Self::Begin) + ("cancel" [] Self::Cancel) + ("confirm" [] Self::Confirm) + ("select" [i: usize] Self::Select(i.expect("no index"))) + ("chdir" [p: PathBuf] Self::Chdir(p.expect("no path"))) + ("filter" [f: Arc] Self::Filter(f.expect("no filter"))) +}); +command!(|self: FileBrowserCommand, state: MidiPool|{ + use PoolMode::*; + use FileBrowserCommand::*; + let mode = &mut state.mode; + match mode { + Some(Import(index, ref mut browser)) => match self { + Cancel => { *mode = None; }, + Chdir(cwd) => { *mode = Some(Import(*index, FileBrowser::new(Some(cwd))?)); }, + Select(index) => { browser.index = index; }, + Confirm => if browser.is_file() { + let index = *index; + let path = browser.path(); + *mode = None; + PoolClipCommand::Import(index, path).execute(state)?; + } else if browser.is_dir() { + *mode = Some(Import(*index, browser.chdir()?)); + }, + _ => todo!(), + }, + Some(Export(index, ref mut browser)) => match self { + Cancel => { *mode = None; }, + Chdir(cwd) => { *mode = Some(Export(*index, FileBrowser::new(Some(cwd))?)); }, + Select(index) => { browser.index = index; }, + _ => unreachable!() + }, + _ => unreachable!(), + }; + None +}); +/////////////////////////////////////////////////////////////////////////////////////////////////// +input_to_command!(FileBrowserCommand: |state: MidiPool, input: Event|{ + use FileBrowserCommand::*; + use KeyCode::{Up, Down, Left, Right, Enter, Esc, Backspace, Char}; + if let Some(PoolMode::Import(_index, browser)) = &state.mode { + match input { + kpat!(Up) => Select(browser.index.overflowing_sub(1).0.min(browser.len().saturating_sub(1))), + kpat!(Down) => Select(browser.index.saturating_add(1)% browser.len()), + kpat!(Right) => Chdir(browser.cwd.clone()), + kpat!(Left) => Chdir(browser.cwd.clone()), + + kpat!(Enter) => Confirm, + kpat!(Char(_)) => { todo!() }, + kpat!(Backspace) => { todo!() }, + kpat!(Esc) => Cancel, + _ => return None + } + } else if let Some(PoolMode::Export(_index, browser)) = &state.mode { + match input { + kpat!(Up) => Select(browser.index.overflowing_sub(1).0.min(browser.len())), + kpat!(Down) => Select(browser.index.saturating_add(1) % browser.len()), + kpat!(Right) => Chdir(browser.cwd.clone()), + kpat!(Left) => Chdir(browser.cwd.clone()), + + kpat!(Enter) => Confirm, + kpat!(Char(_)) => { todo!() }, + kpat!(Backspace) => { todo!() }, + kpat!(Esc) => Cancel, + _ => return None + } + } else { + unreachable!() + } +}); +/////////////////////////////////////////////////////////////////////////////////////////////////// +input_to_command!(ClipLengthCommand: |state: MidiPool, input: Event|{ + if let Some(PoolMode::Length(_, length, _)) = state.mode() { + match input { + kpat!(Up) => Self::Inc, + kpat!(Down) => Self::Dec, + kpat!(Right) => Self::Next, + kpat!(Left) => Self::Prev, + kpat!(Enter) => Self::Set(*length), + kpat!(Esc) => Self::Cancel, + _ => return None + } + } else { + unreachable!() + } +}); +/////////////////////////////////////////////////////////////////////////////////////////////////// +impl InputToCommand for ClipRenameCommand { + fn input_to_command (state: &MidiPool, input: &Event) -> Option { + use KeyCode::{Char, Backspace, Enter, Esc}; + if let Some(PoolMode::Rename(_, ref old_name)) = state.mode() { + Some(match input { + kpat!(Char(c)) => { + let mut new_name = old_name.clone().to_string(); + new_name.push(*c); + Self::Set(new_name.into()) + }, + kpat!(Backspace) => { + let mut new_name = old_name.clone().to_string(); + new_name.pop(); + Self::Set(new_name.into()) + }, + kpat!(Enter) => Self::Confirm, + kpat!(Esc) => Self::Cancel, + _ => return None + }) + } else { + unreachable!() + } + } +} +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +//fn to_clips_command (state: &MidiPool, input: &Event) -> Option { + //use KeyCode::{Up, Down, Delete, Char}; + //use PoolCommand as Cmd; + //let index = state.clip_index(); + //let count = state.clips().len(); + //Some(match input { + //kpat!(Char('n')) => Cmd::Rename(ClipRenameCommand::Begin), + //kpat!(Char('t')) => Cmd::Length(ClipLengthCommand::Begin), + //kpat!(Char('m')) => Cmd::Import(FileBrowserCommand::Begin), + //kpat!(Char('x')) => Cmd::Export(FileBrowserCommand::Begin), + //kpat!(Char('c')) => Cmd::Clip(PoolClipCommand::SetColor(index, ItemColor::random())), + //kpat!(Char('[')) | kpat!(Up) => Cmd::Select( + //index.overflowing_sub(1).0.min(state.clips().len() - 1) + //), + //kpat!(Char(']')) | kpat!(Down) => Cmd::Select( + //index.saturating_add(1) % state.clips().len() + //), + //kpat!(Char('<')) => if index > 1 { + //state.set_clip_index(state.clip_index().saturating_sub(1)); + //Cmd::Clip(PoolClipCommand::Swap(index - 1, index)) + //} else { + //return None + //}, + //kpat!(Char('>')) => if index < count.saturating_sub(1) { + //state.set_clip_index(state.clip_index() + 1); + //Cmd::Clip(PoolClipCommand::Swap(index + 1, index)) + //} else { + //return None + //}, + //kpat!(Delete) => if index > 0 { + //state.set_clip_index(index.min(count.saturating_sub(1))); + //Cmd::Clip(PoolClipCommand::Delete(index)) + //} else { + //return None + //}, + //kpat!(Char('a')) | kpat!(Shift-Char('A')) => Cmd::Clip(PoolClipCommand::Add(count, MidiClip::new( + //"Clip", true, 4 * PPQ, None, Some(ItemPalette::random()) + //))), + //kpat!(Char('i')) => Cmd::Clip(PoolClipCommand::Add(index + 1, MidiClip::new( + //"Clip", true, 4 * PPQ, None, Some(ItemPalette::random()) + //))), + //kpat!(Char('d')) | kpat!(Shift-Char('D')) => { + //let mut clip = state.clips()[index].read().unwrap().duplicate(); + //clip.color = ItemPalette::random_near(clip.color, 0.25); + //Cmd::Clip(PoolClipCommand::Add(index + 1, clip)) + //}, + //_ => return None + //}) +//} diff --git a/midi/src/midi_pool_cmd.rs b/midi/src/midi_pool_cmd.rs deleted file mode 100644 index d99641b3..00000000 --- a/midi/src/midi_pool_cmd.rs +++ /dev/null @@ -1,424 +0,0 @@ -use crate::*; -use KeyCode::*; -impl PoolCommand { - const KEYS_POOL: &str = include_str!("midi_pool_keys.edn"); - const KEYS_CLIP: &str = include_str!("midi_pool_keys_clip.edn"); - const KEYS_RENAME: &str = include_str!("midi_pool_keys_rename.edn"); - const KEYS_LENGTH: &str = include_str!("midi_pool_keys_length.edn"); - const KEYS_FILE: &str = include_str!("midi_pool_keys_file.edn"); - pub fn from_tui_event (state: &MidiPool, input: &Event) -> Usually> { - use EdnItem::*; - let edns: Vec> = EdnItem::read_all(match state.mode() { - Some(PoolMode::Rename(..)) => Self::KEYS_RENAME, - Some(PoolMode::Length(..)) => Self::KEYS_LENGTH, - Some(PoolMode::Import(..)) | Some(PoolMode::Export(..)) => Self::KEYS_FILE, - _ => Self::KEYS_CLIP - })?; - for item in edns { - match item { - Exp(e) => match e.as_slice() { - [Sym(key), Key(command), args @ ..] => { - } - _ => panic!("invalid config") - } - _ => panic!("invalid config") - } - } - Ok(None) - } -} -edn_provide!(bool: |self: MidiPool| {}); -edn_provide!(usize: |self: MidiPool| {}); -edn_provide!(MidiClip: |self: MidiPool| {}); -edn_provide!(PathBuf: |self: MidiPool| {}); -edn_provide!(Arc: |self: MidiPool| {}); -edn_provide!(ItemColor: |self: MidiPool| {}); -#[derive(Clone, PartialEq, Debug)] pub enum PoolCommand { - /// Toggle visibility of pool - Show(bool), - /// Select a clip from the clip pool - Select(usize), - /// Rename a clip - Rename(ClipRenameCommand), - /// Change the length of a clip - Length(ClipLengthCommand), - /// Import from file - Import(FileBrowserCommand), - /// Export to file - Export(FileBrowserCommand), - /// Update the contents of the clip pool - Clip(PoolClipCommand), -} -edn_command!(PoolCommand: |state: MidiPool| { - ("show" [a: bool] Self::Show(a.expect("no flag"))) - ("select" [i: usize] Self::Select(i.expect("no index"))) - ("rename" [a, ..b] Self::Rename(ClipRenameCommand::from_edn(state, &a.to_ref(), b))) - ("length" [a, ..b] Self::Length(ClipLengthCommand::from_edn(state, &a.to_ref(), b))) - ("import" [a, ..b] Self::Import(FileBrowserCommand::from_edn(state, &a.to_ref(), b))) - ("export" [a, ..b] Self::Export(FileBrowserCommand::from_edn(state, &a.to_ref(), b))) - ("clip" [a, ..b] Self::Clip(PoolClipCommand::from_edn(state, &a.to_ref(), b))) -}); -command!(|self: PoolCommand, state: MidiPool|{ - use PoolCommand::*; - match self { - Rename(ClipRenameCommand::Begin) => { state.begin_clip_rename(); None } - Rename(command) => command.delegate(state, Rename)?, - Length(ClipLengthCommand::Begin) => { state.begin_clip_length(); None }, - Length(command) => command.delegate(state, Length)?, - Import(FileBrowserCommand::Begin) => { state.begin_import()?; None }, - Import(command) => command.delegate(state, Import)?, - Export(FileBrowserCommand::Begin) => { state.begin_export()?; None }, - Export(command) => command.delegate(state, Export)?, - Clip(command) => command.execute(state)?.map(Clip), - Show(visible) => { state.visible = visible; Some(Self::Show(!visible)) }, - Select(clip) => { state.set_clip_index(clip); None }, - } -}); -#[derive(Clone, Debug, PartialEq)] pub enum PoolClipCommand { - Add(usize, MidiClip), - Delete(usize), - Swap(usize, usize), - Import(usize, PathBuf), - Export(usize, PathBuf), - SetName(usize, Arc), - SetLength(usize, usize), - SetColor(usize, ItemColor), -} -edn_command!(PoolClipCommand: |state: MidiPool| { - ("add" [i: usize, c: MidiClip] Self::Add(i.expect("no index"), c.expect("no clip"))) - ("delete" [i: usize] Self::Delete(i.expect("no index"))) - ("swap" [a: usize, b: usize] Self::Swap(a.expect("no index"), b.expect("no index"))) - ("import" [i: usize, p: PathBuf] Self::Import(i.expect("no index"), p.expect("no path"))) - ("export" [i: usize, p: PathBuf] Self::Export(i.expect("no index"), p.expect("no path"))) - ("set-name" [i: usize, n: Arc] Self::SetName(i.expect("no index"), n.expect("no name"))) - ("set-length" [i: usize, l: usize] Self::SetLength(i.expect("no index"), l.expect("no length"))) - ("set-color" [i: usize, c: ItemColor] Self::SetColor(i.expect("no index"), c.expect("no color"))) -}); -impl Command for PoolClipCommand { - fn execute (self, model: &mut T) -> Perhaps { - use PoolClipCommand::*; - Ok(match self { - Add(mut index, clip) => { - let clip = Arc::new(RwLock::new(clip)); - let mut clips = model.clips_mut(); - if index >= clips.len() { - index = clips.len(); - clips.push(clip) - } else { - clips.insert(index, clip); - } - Some(Self::Delete(index)) - }, - Delete(index) => { - let clip = model.clips_mut().remove(index).read().unwrap().clone(); - Some(Self::Add(index, clip)) - }, - Swap(index, other) => { - model.clips_mut().swap(index, other); - Some(Self::Swap(index, other)) - }, - Import(index, path) => { - let bytes = std::fs::read(&path)?; - let smf = Smf::parse(bytes.as_slice())?; - let mut t = 0u32; - let mut events = vec![]; - for track in smf.tracks.iter() { - for event in track.iter() { - t += event.delta.as_int(); - if let TrackEventKind::Midi { channel, message } = event.kind { - events.push((t, channel.as_int(), message)); - } - } - } - let mut clip = MidiClip::new("imported", true, t as usize + 1, None, None); - for event in events.iter() { - clip.notes[event.0 as usize].push(event.2); - } - Self::Add(index, clip).execute(model)? - }, - Export(_index, _path) => { - todo!("export clip to midi file"); - }, - SetName(index, name) => { - let clip = &mut model.clips_mut()[index]; - let old_name = clip.read().unwrap().name.clone(); - clip.write().unwrap().name = name; - Some(Self::SetName(index, old_name)) - }, - SetLength(index, length) => { - let clip = &mut model.clips_mut()[index]; - let old_len = clip.read().unwrap().length; - clip.write().unwrap().length = length; - Some(Self::SetLength(index, old_len)) - }, - SetColor(index, color) => { - let mut color = ItemPalette::from(color); - std::mem::swap(&mut color, &mut model.clips()[index].write().unwrap().color); - Some(Self::SetColor(index, color.base)) - }, - }) - } -} -#[derive(Clone, Debug, PartialEq)] pub enum ClipRenameCommand { - Begin, - Cancel, - Confirm, - Set(Arc), -} -edn_command!(ClipRenameCommand: |state: MidiPool| { - ("begin" [] Self::Begin) - ("cancel" [] Self::Cancel) - ("confirm" [] Self::Confirm) - ("set" [n: Arc] Self::Set(n.expect("no name"))) -}); -command!(|self: ClipRenameCommand, state: MidiPool|{ - use ClipRenameCommand::*; - if let Some( - PoolMode::Rename(clip, ref mut old_name) - ) = state.mode_mut().clone() { - match self { - Set(s) => { - state.clips()[clip].write().unwrap().name = s; - return Ok(Some(Self::Set(old_name.clone().into()))) - }, - Confirm => { - let old_name = old_name.clone(); - *state.mode_mut() = None; - return Ok(Some(Self::Set(old_name))) - }, - Cancel => { - state.clips()[clip].write().unwrap().name = old_name.clone().into(); - return Ok(None) - }, - _ => unreachable!() - } - } else { - unreachable!() - } -}); -#[derive(Copy, Clone, Debug, PartialEq)] pub enum ClipLengthCommand { - Begin, - Cancel, - Set(usize), - Next, - Prev, - Inc, - Dec, -} -edn_command!(ClipLengthCommand: |state: MidiPool| { - ("begin" [] Self::Begin) - ("cancel" [] Self::Cancel) - ("next" [] Self::Next) - ("prev" [] Self::Prev) - ("inc" [] Self::Inc) - ("dec" [] Self::Dec) - ("set" [l: usize] Self::Set(l.expect("no length"))) -}); -command!(|self: ClipLengthCommand, state: MidiPool|{ - use ClipLengthCommand::*; - use ClipLengthFocus::*; - if let Some( - PoolMode::Length(clip, ref mut length, ref mut focus) - ) = state.mode_mut().clone() { - match self { - Cancel => { *state.mode_mut() = None; }, - Prev => { focus.prev() }, - Next => { focus.next() }, - Inc => match focus { - Bar => { *length += 4 * PPQ }, - Beat => { *length += PPQ }, - Tick => { *length += 1 }, - }, - Dec => match focus { - Bar => { *length = length.saturating_sub(4 * PPQ) }, - Beat => { *length = length.saturating_sub(PPQ) }, - Tick => { *length = length.saturating_sub(1) }, - }, - Set(length) => { - let mut old_length = None; - { - let clip = state.clips()[clip].clone();//.write().unwrap(); - old_length = Some(clip.read().unwrap().length); - clip.write().unwrap().length = length; - } - *state.mode_mut() = None; - return Ok(old_length.map(Self::Set)) - }, - _ => unreachable!() - } - } else { - unreachable!(); - } - None -}); -edn_command!(FileBrowserCommand: |state: MidiPool| { - ("begin" [] Self::Begin) - ("cancel" [] Self::Cancel) - ("confirm" [] Self::Confirm) - ("select" [i: usize] Self::Select(i.expect("no index"))) - ("chdir" [p: PathBuf] Self::Chdir(p.expect("no path"))) - ("filter" [f: Arc] Self::Filter(f.expect("no filter"))) -}); -command!(|self: FileBrowserCommand, state: MidiPool|{ - use PoolMode::*; - use FileBrowserCommand::*; - let mode = &mut state.mode; - match mode { - Some(Import(index, ref mut browser)) => match self { - Cancel => { *mode = None; }, - Chdir(cwd) => { *mode = Some(Import(*index, FileBrowser::new(Some(cwd))?)); }, - Select(index) => { browser.index = index; }, - Confirm => if browser.is_file() { - let index = *index; - let path = browser.path(); - *mode = None; - PoolClipCommand::Import(index, path).execute(state)?; - } else if browser.is_dir() { - *mode = Some(Import(*index, browser.chdir()?)); - }, - _ => todo!(), - }, - Some(Export(index, ref mut browser)) => match self { - Cancel => { *mode = None; }, - Chdir(cwd) => { *mode = Some(Export(*index, FileBrowser::new(Some(cwd))?)); }, - Select(index) => { browser.index = index; }, - _ => unreachable!() - }, - _ => unreachable!(), - }; - None -}); -/////////////////////////////////////////////////////////////////////////////////////////////////// -input_to_command!(PoolCommand: |state: MidiPool, input: Event|match state.mode() { - Some(PoolMode::Rename(..)) => Self::Rename(ClipRenameCommand::input_to_command(state, input)?), - Some(PoolMode::Length(..)) => Self::Length(ClipLengthCommand::input_to_command(state, input)?), - Some(PoolMode::Import(..)) => Self::Import(FileBrowserCommand::input_to_command(state, input)?), - Some(PoolMode::Export(..)) => Self::Export(FileBrowserCommand::input_to_command(state, input)?), - _ => to_clips_command(state, input)? -}); -/////////////////////////////////////////////////////////////////////////////////////////////////// -fn to_clips_command (state: &MidiPool, input: &Event) -> Option { - use KeyCode::{Up, Down, Delete, Char}; - use PoolCommand as Cmd; - let index = state.clip_index(); - let count = state.clips().len(); - Some(match input { - kpat!(Char('n')) => Cmd::Rename(ClipRenameCommand::Begin), - kpat!(Char('t')) => Cmd::Length(ClipLengthCommand::Begin), - kpat!(Char('m')) => Cmd::Import(FileBrowserCommand::Begin), - kpat!(Char('x')) => Cmd::Export(FileBrowserCommand::Begin), - kpat!(Char('c')) => Cmd::Clip(PoolClipCommand::SetColor(index, ItemColor::random())), - kpat!(Char('[')) | kpat!(Up) => Cmd::Select( - index.overflowing_sub(1).0.min(state.clips().len() - 1) - ), - kpat!(Char(']')) | kpat!(Down) => Cmd::Select( - index.saturating_add(1) % state.clips().len() - ), - kpat!(Char('<')) => if index > 1 { - state.set_clip_index(state.clip_index().saturating_sub(1)); - Cmd::Clip(PoolClipCommand::Swap(index - 1, index)) - } else { - return None - }, - kpat!(Char('>')) => if index < count.saturating_sub(1) { - state.set_clip_index(state.clip_index() + 1); - Cmd::Clip(PoolClipCommand::Swap(index + 1, index)) - } else { - return None - }, - kpat!(Delete) => if index > 0 { - state.set_clip_index(index.min(count.saturating_sub(1))); - Cmd::Clip(PoolClipCommand::Delete(index)) - } else { - return None - }, - kpat!(Char('a')) | kpat!(Shift-Char('A')) => Cmd::Clip(PoolClipCommand::Add(count, MidiClip::new( - "Clip", true, 4 * PPQ, None, Some(ItemPalette::random()) - ))), - kpat!(Char('i')) => Cmd::Clip(PoolClipCommand::Add(index + 1, MidiClip::new( - "Clip", true, 4 * PPQ, None, Some(ItemPalette::random()) - ))), - kpat!(Char('d')) | kpat!(Shift-Char('D')) => { - let mut clip = state.clips()[index].read().unwrap().duplicate(); - clip.color = ItemPalette::random_near(clip.color, 0.25); - Cmd::Clip(PoolClipCommand::Add(index + 1, clip)) - }, - _ => return None - }) -} -/////////////////////////////////////////////////////////////////////////////////////////////////// -input_to_command!(FileBrowserCommand: |state: MidiPool, input: Event|{ - use FileBrowserCommand::*; - use KeyCode::{Up, Down, Left, Right, Enter, Esc, Backspace, Char}; - if let Some(PoolMode::Import(_index, browser)) = &state.mode { - match input { - kpat!(Up) => Select(browser.index.overflowing_sub(1).0.min(browser.len().saturating_sub(1))), - kpat!(Down) => Select(browser.index.saturating_add(1)% browser.len()), - kpat!(Right) => Chdir(browser.cwd.clone()), - kpat!(Left) => Chdir(browser.cwd.clone()), - - kpat!(Enter) => Confirm, - kpat!(Char(_)) => { todo!() }, - kpat!(Backspace) => { todo!() }, - kpat!(Esc) => Cancel, - _ => return None - } - } else if let Some(PoolMode::Export(_index, browser)) = &state.mode { - match input { - kpat!(Up) => Select(browser.index.overflowing_sub(1).0.min(browser.len())), - kpat!(Down) => Select(browser.index.saturating_add(1) % browser.len()), - kpat!(Right) => Chdir(browser.cwd.clone()), - kpat!(Left) => Chdir(browser.cwd.clone()), - - kpat!(Enter) => Confirm, - kpat!(Char(_)) => { todo!() }, - kpat!(Backspace) => { todo!() }, - kpat!(Esc) => Cancel, - _ => return None - } - } else { - unreachable!() - } -}); -/////////////////////////////////////////////////////////////////////////////////////////////////// -input_to_command!(ClipLengthCommand: |state: MidiPool, input: Event|{ - if let Some(PoolMode::Length(_, length, _)) = state.mode() { - match input { - kpat!(Up) => Self::Inc, - kpat!(Down) => Self::Dec, - kpat!(Right) => Self::Next, - kpat!(Left) => Self::Prev, - kpat!(Enter) => Self::Set(*length), - kpat!(Esc) => Self::Cancel, - _ => return None - } - } else { - unreachable!() - } -}); -/////////////////////////////////////////////////////////////////////////////////////////////////// -impl InputToCommand for ClipRenameCommand { - fn input_to_command (state: &MidiPool, input: &Event) -> Option { - use KeyCode::{Char, Backspace, Enter, Esc}; - if let Some(PoolMode::Rename(_, ref old_name)) = state.mode() { - Some(match input { - kpat!(Char(c)) => { - let mut new_name = old_name.clone().to_string(); - new_name.push(*c); - Self::Set(new_name.into()) - }, - kpat!(Backspace) => { - let mut new_name = old_name.clone().to_string(); - new_name.pop(); - Self::Set(new_name.into()) - }, - kpat!(Enter) => Self::Confirm, - kpat!(Esc) => Self::Cancel, - _ => return None - }) - } else { - unreachable!() - } - } -} -/////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/midi/src/midi_pool_keys.edn b/midi/src/midi_pool_keys.edn deleted file mode 100644 index cc4a01da..00000000 --- a/midi/src/midi_pool_keys.edn +++ /dev/null @@ -1,13 +0,0 @@ -(:n clip/rename/begin) -(:t clip/length/begin) -(:m clip/import/begin) -(:x clip/export/begin) -(:c clip/color/random) -(:bracket-open clip/select/prev) -(:bracket-close clip/select/next) -(:lt clip/move/prev) -(:gt clip/move/next) -(:del clip/delete) -(:shift-a clip/add) -(:i clip/insert) -(:d clip/duplicate) diff --git a/midi/src/midi_pool_keys_rename.edn b/midi/src/view_pool.edn similarity index 100% rename from midi/src/midi_pool_keys_rename.edn rename to midi/src/view_pool.edn diff --git a/sampler/src/sampler_cmd.rs b/sampler/src/sampler_cmd.rs index fe4bf042..b6e4321a 100644 --- a/sampler/src/sampler_cmd.rs +++ b/sampler/src/sampler_cmd.rs @@ -8,7 +8,7 @@ handle!(TuiIn: |self: SamplerTui, input|SamplerTuiCommand::execute_with_state(se Sample(SamplerCommand), } impl EdnCommand for SamplerTuiCommand { - fn from_edn <'a> (state: &SamplerTui, head: &EdnItem<&str>, tail: &'a [EdnItem]) -> Self { + fn from_edn <'a> (state: &SamplerTui, head: &EdnItem<&str>, tail: &'a [EdnItem<&str>]) -> Option { todo!() } } @@ -24,7 +24,7 @@ impl EdnCommand for SamplerTuiCommand { NoteOff(u7), } impl EdnCommand for SamplerCommand { - fn from_edn <'a> (state: &Sampler, head: &EdnItem<&str>, tail: &'a [EdnItem]) -> Self { + fn from_edn <'a> (state: &Sampler, head: &EdnItem<&str>, tail: &'a [EdnItem<&str>]) -> Option { todo!() } } diff --git a/tek/src/arranger-keys.edn b/tek/src/keys.edn similarity index 99% rename from tek/src/arranger-keys.edn rename to tek/src/keys.edn index 222787c0..e86e1916 100644 --- a/tek/src/arranger-keys.edn +++ b/tek/src/keys.edn @@ -41,3 +41,4 @@ (:q enqueue :clip) (:0 enqueue :stop) + diff --git a/midi/src/midi_pool_view.edn b/tek/src/keys_clip.edn similarity index 100% rename from midi/src/midi_pool_view.edn rename to tek/src/keys_clip.edn diff --git a/midi/src/pool-keys.edn b/tek/src/keys_scene.edn similarity index 100% rename from midi/src/pool-keys.edn rename to tek/src/keys_scene.edn diff --git a/tek/src/groovebox-keys.edn b/tek/src/keys_track.edn similarity index 100% rename from tek/src/groovebox-keys.edn rename to tek/src/keys_track.edn diff --git a/tek/src/lib.rs b/tek/src/lib.rs index 801882b0..fdb10ae0 100644 --- a/tek/src/lib.rs +++ b/tek/src/lib.rs @@ -120,7 +120,7 @@ impl App { //} //}; Ok(Self { - edn: include_str!("./transport-view.edn").to_string(), + edn: include_str!("./view_transport.edn").to_string(), jack: jack.clone(), color: ItemPalette::random(), clock: Clock::new(jack, bpm), @@ -136,7 +136,7 @@ impl App { let clip = MidiClip::new("Clip", true, 384usize, None, Some(ItemColor::random().into())); let clip = Arc::new(RwLock::new(clip)); Ok(Self { - edn: include_str!("./sequencer-view.edn").to_string(), + edn: include_str!("./view_sequencer.edn").to_string(), pool: Some((&clip).into()), editor: Some((&clip).into()), editing: false.into(), @@ -160,7 +160,7 @@ impl App { audio_tos: &[&[PortConnection];2], ) -> Usually { let app = Self { - edn: include_str!("./groovebox-view.edn").to_string(), + edn: include_str!("./view_groovebox.edn").to_string(), sampler: Some(Sampler::new( jack, &"sampler", @@ -187,7 +187,7 @@ impl App { track_width: usize, ) -> Usually { let mut arranger = Self { - edn: include_str!("./arranger-view.edn").to_string(), + edn: include_str!("./view_arranger.edn").to_string(), ..Self::groovebox(jack, bpm, midi_froms, midi_tos, audio_froms, audio_tos)? }; arranger.scenes_add(scenes); @@ -373,8 +373,36 @@ impl App { } Ok(()) } + const KEYS_APP: &str = include_str!("keys.edn"); + const KEYS_CLIP: &str = include_str!("keys_clip.edn"); + const KEYS_TRACK: &str = include_str!("keys_track.edn"); + const KEYS_SCENE: &str = include_str!("keys_scene.edn"); } -handle!(TuiIn: |self: App, input| Ok(None)); +handle!(TuiIn: |self: App, input|{ + use EdnItem::*; + let mut command: Option = None; + let edns: Vec> = EdnItem::read_all(Self::KEYS_APP)?; + for item in edns { + match item { + Exp(e) => match e.as_slice() { + [Sym(key), c, args @ ..] if input.matches_edn(key) => { + command = AppCommand::from_edn(self, c, args); + if command.is_some() { + break + } + } + _ => {} + }, + _ => panic!("invalid config") + } + } + Ok(if let Some(command) = command { + let _undo = command.execute(self)?; + Some(true) + } else { + None + }) +}); #[derive(Clone, Debug)] pub enum AppCommand { Clip(ClipCommand), Clock(ClockCommand), @@ -392,20 +420,28 @@ handle!(TuiIn: |self: App, input| Ok(None)); Zoom(Option), } edn_command!(AppCommand: |state: App| { - ("clip" [a, ..b] Self::Clip(ClipCommand::from_edn(state, &a.to_ref(), b))) - ("clock" [a, ..b] Self::Clock(ClockCommand::from_edn(state, &a.to_ref(), b))) ("color" [c: Color] Self::Color(c.map(ItemPalette::from).unwrap_or_default())) ("compact" [c: bool ] Self::Compact(c)) - ("editor" [a, ..b] Self::Editor(MidiEditCommand::from_edn(state.editor.as_ref().expect("no editor"), &a.to_ref(), b))) ("enqueue" [c: Arc>] Self::Enqueue(c)) ("history" [d: isize] Self::History(d.unwrap_or(0))) - ("pool" [a, ..b] Self::Pool(PoolCommand::from_edn(state.pool.as_ref().expect("no pool"), &a.to_ref(), b))) - ("sampler" [a, ..b] Self::Sampler(SamplerCommand::from_edn(state.sampler.as_ref().expect("no sampler"), &a.to_ref(), b))) - ("scene" [a, ..b] Self::Scene(SceneCommand::from_edn(state, &a.to_ref(), b))) ("select" [s: Selection] Self::Select(s.expect("no selection"))) ("stop-all" [] Self::StopAll) - ("track" [a, ..b] Self::Track(TrackCommand::from_edn(state, &a.to_ref(), b))) ("zoom" [z: usize] Self::Zoom(z)) + + ("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"))) }); command!(|self: AppCommand, state: App|match self { Self::Zoom(_) => { todo!(); }, @@ -950,17 +986,6 @@ content!(TuiOut: |self: Meters<'a>| col!( format!("L/{:>+9.3}", self.0[0]), format!("R/{:>+9.3}", self.0[1]) )); -/// Transport clock app. -pub struct ClockTui { pub jack: Arc>, pub clock: Clock, } -handle!(TuiIn: |self: ClockTui, input|ClockCommand::execute_with_state(self, input.event())); -keymap!(TRANSPORT_KEYS = |state: ClockTui, input: Event| ClockCommand { - key(Char(' ')) => - if state.clock().is_stopped() { ClockCommand::Play(None) } else { ClockCommand::Pause(None) }, - shift(key(Char(' '))) => - if state.clock().is_stopped() { ClockCommand::Play(Some(0)) } else { ClockCommand::Pause(Some(0)) } -}); -has_clock!(|self: ClockTui|&self.clock); -content!(TuiOut:|self: ClockTui|ClockView { compact: false, clock: &self.clock }); pub struct ClockView<'a> { pub compact: bool, pub clock: &'a Clock } content!(TuiOut: |self: ClockView<'a>| Outer(Style::default().fg(TuiTheme::g(255))).enclose(row!( OutputStats::new(self.compact, self.clock), diff --git a/tek/src/transport-keys.edn b/tek/src/transport-keys.edn deleted file mode 100644 index e69de29b..00000000 diff --git a/tek/src/transport-view.edn b/tek/src/transport-view.edn deleted file mode 100644 index e69de29b..00000000 diff --git a/tek/src/arranger-view.edn b/tek/src/view_arranger.edn similarity index 100% rename from tek/src/arranger-view.edn rename to tek/src/view_arranger.edn diff --git a/tek/src/groovebox-view.edn b/tek/src/view_groovebox.edn similarity index 100% rename from tek/src/groovebox-view.edn rename to tek/src/view_groovebox.edn diff --git a/tek/src/sequencer-view.edn b/tek/src/view_sequencer.edn similarity index 100% rename from tek/src/sequencer-view.edn rename to tek/src/view_sequencer.edn diff --git a/tek/src/sequencer-keys.edn b/tek/src/view_transport.edn similarity index 100% rename from tek/src/sequencer-keys.edn rename to tek/src/view_transport.edn diff --git a/time/src/clock.rs b/time/src/clock.rs index 826d0b0e..7e3b9724 100644 --- a/time/src/clock.rs +++ b/time/src/clock.rs @@ -21,7 +21,7 @@ pub enum ClockCommand { SetSync(f64), } impl EdnCommand for ClockCommand { - fn from_edn <'a> (state: &T, head: &EdnItem<&str>, tail: &'a [EdnItem]) -> Self { + fn from_edn <'a> (state: &T, head: &EdnItem<&str>, tail: &'a [EdnItem<&str>]) -> Option { todo!() } } diff --git a/tui/src/tui_edn_keymap.rs b/tui/src/tui_edn_keymap.rs index 071f32ee..05678878 100644 --- a/tui/src/tui_edn_keymap.rs +++ b/tui/src/tui_edn_keymap.rs @@ -1,7 +1,12 @@ use crate::*; + impl EdnInput for TuiIn { - fn matches (&self, token: &str) -> bool { - false + fn matches_edn (&self, token: &str) -> bool { + if let Some(event) = parse_key_spec(token.to_string(), KeyModifiers::NONE) { + &event == self.event() + } else { + false + } } fn get_event > (item: &EdnItem) -> Option { match item { EdnItem::Sym(s) => parse_key_spec(s.as_ref().to_string(), KeyModifiers::NONE), _ => None }