diff --git a/src/arranger/arranger_command.rs b/src/arranger/arranger_command.rs index 4cb2a2fa..2669fc54 100644 --- a/src/arranger/arranger_command.rs +++ b/src/arranger/arranger_command.rs @@ -45,121 +45,114 @@ pub enum ArrangerClipCommand { SetColor(usize, usize, ItemPalette), } -input_to_command!(ArrangerCommand: |state: ArrangerTui, input: Event|match input { - key_pat!(Char('u')) => Self::History(-1), - key_pat!(Char('U')) => Self::History(1), +use ArrangerCommand as Cmd; +keymap!(KEYS_ARRANGER = |state: ArrangerTui, input: Event| ArrangerCommand { + key(Char('u')) => Cmd::History(-1), + key(Char('U')) => Cmd::History(1), // TODO: k: toggle on-screen keyboard - key_pat!(Ctrl-Char('k')) => { todo!("keyboard") }, + ctrl(key(Char('k'))) => { todo!("keyboard") }, // Transport: Play/pause - key_pat!(Char(' ')) => - Self::Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }), + key(Char(' ')) => Cmd::Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }), // Transport: Play from start or rewind to start - key_pat!(Shift-Char(' ')) => - Self::Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }), - key_pat!(Char('e')) => - Self::Editor(MidiEditCommand::Show(Some(state.pool.phrase().clone()))), - key_pat!(Ctrl-Left) => - Self::Scene(ArrangerSceneCommand::Add), - key_pat!(Ctrl-Char('t')) => - Self::Track(ArrangerTrackCommand::Add), + shift(key(Char(' '))) => Cmd::Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }), + key(Char('e')) => Cmd::Editor(MidiEditCommand::Show(Some(state.pool.phrase().clone()))), + ctrl(key(Char('a'))) => Cmd::Scene(ArrangerSceneCommand::Add), + ctrl(key(Char('t'))) => Cmd::Track(ArrangerTrackCommand::Add), // Tab: Toggle visibility of phrase pool column - key_pat!(Tab) => - Self::Phrases(PoolCommand::Show(!state.pool.visible)), - _ => { - use ArrangerCommand as Cmd; - use ArrangerSelection as Selected; - use ArrangerSceneCommand as Scene; - use ArrangerTrackCommand as Track; - use ArrangerClipCommand as Clip; - let t_len = state.tracks.len(); - let s_len = state.scenes.len(); - match state.selected() { - Selected::Clip(t, s) => match input { - key_pat!(Char('g')) => Some(Cmd::Phrases(PoolCommand::Select(0))), - key_pat!(Char('q')) => Some(Cmd::Clip(Clip::Enqueue(t, s))), - key_pat!(Char(',')) => Some(Cmd::Clip(Clip::Put(t, s, None))), - key_pat!(Char('.')) => Some(Cmd::Clip(Clip::Put(t, s, None))), - key_pat!(Char('<')) => Some(Cmd::Clip(Clip::Put(t, s, None))), - key_pat!(Char('>')) => Some(Cmd::Clip(Clip::Put(t, s, None))), - key_pat!(Char('p')) => Some(Cmd::Clip(Clip::Put(t, s, Some(state.pool.phrase().clone())))), - key_pat!(Char('l')) => Some(Cmd::Clip(ArrangerClipCommand::SetLoop(t, s, false))), - key_pat!(Delete) => Some(Cmd::Clip(Clip::Put(t, s, None))), + key(Tab) => Cmd::Phrases(PoolCommand::Show(!state.pool.visible)), +}, { + use ArrangerSelection as Selected; + use ArrangerSceneCommand as Scene; + use ArrangerTrackCommand as Track; + use ArrangerClipCommand as Clip; + let t_len = state.tracks.len(); + let s_len = state.scenes.len(); + match state.selected() { + Selected::Clip(t, s) => match input { + key_pat!(Char('g')) => Some(Cmd::Phrases(PoolCommand::Select(0))), + key_pat!(Char('q')) => Some(Cmd::Clip(Clip::Enqueue(t, s))), + key_pat!(Char(',')) => Some(Cmd::Clip(Clip::Put(t, s, None))), + key_pat!(Char('.')) => Some(Cmd::Clip(Clip::Put(t, s, None))), + key_pat!(Char('<')) => Some(Cmd::Clip(Clip::Put(t, s, None))), + key_pat!(Char('>')) => Some(Cmd::Clip(Clip::Put(t, s, None))), + key_pat!(Char('p')) => Some(Cmd::Clip(Clip::Put(t, s, Some(state.pool.phrase().clone())))), + key_pat!(Char('l')) => Some(Cmd::Clip(ArrangerClipCommand::SetLoop(t, s, false))), + key_pat!(Delete) => Some(Cmd::Clip(Clip::Put(t, s, None))), - key_pat!(Up) => Some(Cmd::Select( - if s > 0 { Selected::Clip(t, s - 1) } else { Selected::Track(t) })), - key_pat!(Down) => Some(Cmd::Select( - Selected::Clip(t, (s + 1).min(s_len.saturating_sub(1))))), - key_pat!(Left) => Some(Cmd::Select( - if t > 0 { Selected::Clip(t - 1, s) } else { Selected::Scene(s) })), - key_pat!(Right) => Some(Cmd::Select( - Selected::Clip((t + 1).min(t_len.saturating_sub(1)), s))), + key_pat!(Up) => Some(Cmd::Select( + if s > 0 { Selected::Clip(t, s - 1) } else { Selected::Track(t) })), + key_pat!(Down) => Some(Cmd::Select( + Selected::Clip(t, (s + 1).min(s_len.saturating_sub(1))))), + key_pat!(Left) => Some(Cmd::Select( + if t > 0 { Selected::Clip(t - 1, s) } else { Selected::Scene(s) })), + key_pat!(Right) => Some(Cmd::Select( + Selected::Clip((t + 1).min(t_len.saturating_sub(1)), s))), - _ => None - }, - Selected::Scene(s) => match input { - key_pat!(Char(',')) => Some(Cmd::Scene(Scene::Swap(s, s - 1))), - key_pat!(Char('.')) => Some(Cmd::Scene(Scene::Swap(s, s + 1))), - key_pat!(Char('<')) => Some(Cmd::Scene(Scene::Swap(s, s - 1))), - key_pat!(Char('>')) => Some(Cmd::Scene(Scene::Swap(s, s + 1))), - key_pat!(Char('q')) => Some(Cmd::Scene(Scene::Enqueue(s))), - key_pat!(Delete) => Some(Cmd::Scene(Scene::Delete(s))), - key_pat!(Char('c')) => Some(Cmd::Scene(Scene::SetColor(s, ItemPalette::random()))), + _ => None + }, + Selected::Scene(s) => match input { + key_pat!(Char(',')) => Some(Cmd::Scene(Scene::Swap(s, s - 1))), + key_pat!(Char('.')) => Some(Cmd::Scene(Scene::Swap(s, s + 1))), + key_pat!(Char('<')) => Some(Cmd::Scene(Scene::Swap(s, s - 1))), + key_pat!(Char('>')) => Some(Cmd::Scene(Scene::Swap(s, s + 1))), + key_pat!(Char('q')) => Some(Cmd::Scene(Scene::Enqueue(s))), + key_pat!(Delete) => Some(Cmd::Scene(Scene::Delete(s))), + key_pat!(Char('c')) => Some(Cmd::Scene(Scene::SetColor(s, ItemPalette::random()))), - key_pat!(Up) => Some( - Cmd::Select(if s > 0 { Selected::Scene(s - 1) } else { Selected::Mix })), - key_pat!(Down) => Some( - Cmd::Select(Selected::Scene((s + 1).min(s_len.saturating_sub(1))))), - key_pat!(Left) => - return None, - key_pat!(Right) => Some( - Cmd::Select(Selected::Clip(0, s))), + key_pat!(Up) => Some( + Cmd::Select(if s > 0 { Selected::Scene(s - 1) } else { Selected::Mix })), + key_pat!(Down) => Some( + Cmd::Select(Selected::Scene((s + 1).min(s_len.saturating_sub(1))))), + key_pat!(Left) => + return None, + key_pat!(Right) => Some( + Cmd::Select(Selected::Clip(0, s))), - _ => None - }, - Selected::Track(t) => match input { - key_pat!(Char(',')) => Some(Cmd::Track(Track::Swap(t, t - 1))), - key_pat!(Char('.')) => Some(Cmd::Track(Track::Swap(t, t + 1))), - key_pat!(Char('<')) => Some(Cmd::Track(Track::Swap(t, t - 1))), - key_pat!(Char('>')) => Some(Cmd::Track(Track::Swap(t, t + 1))), - key_pat!(Delete) => Some(Cmd::Track(Track::Delete(t))), - key_pat!(Char('c')) => Some(Cmd::Track(Track::SetColor(t, ItemPalette::random()))), + _ => None + }, + Selected::Track(t) => match input { + key_pat!(Char(',')) => Some(Cmd::Track(Track::Swap(t, t - 1))), + key_pat!(Char('.')) => Some(Cmd::Track(Track::Swap(t, t + 1))), + key_pat!(Char('<')) => Some(Cmd::Track(Track::Swap(t, t - 1))), + key_pat!(Char('>')) => Some(Cmd::Track(Track::Swap(t, t + 1))), + key_pat!(Delete) => Some(Cmd::Track(Track::Delete(t))), + key_pat!(Char('c')) => Some(Cmd::Track(Track::SetColor(t, ItemPalette::random()))), - key_pat!(Up) => - return None, - key_pat!(Down) => Some( - Cmd::Select(Selected::Clip(t, 0))), - key_pat!(Left) => Some( - Cmd::Select(if t > 0 { Selected::Track(t - 1) } else { Selected::Mix })), - key_pat!(Right) => Some( - Cmd::Select(Selected::Track((t + 1).min(t_len.saturating_sub(1))))), + key_pat!(Up) => + return None, + key_pat!(Down) => Some( + Cmd::Select(Selected::Clip(t, 0))), + key_pat!(Left) => Some( + Cmd::Select(if t > 0 { Selected::Track(t - 1) } else { Selected::Mix })), + key_pat!(Right) => Some( + Cmd::Select(Selected::Track((t + 1).min(t_len.saturating_sub(1))))), - _ => None - }, - Selected::Mix => match input { - key_pat!(Delete) => Some(Cmd::Clear), - key_pat!(Char('0')) => Some(Cmd::StopAll), - key_pat!(Char('c')) => Some(Cmd::Color(ItemPalette::random())), + _ => None + }, + Selected::Mix => match input { + key_pat!(Delete) => Some(Cmd::Clear), + key_pat!(Char('0')) => Some(Cmd::StopAll), + key_pat!(Char('c')) => Some(Cmd::Color(ItemPalette::random())), - key_pat!(Up) => - return None, - key_pat!(Down) => Some( - Cmd::Select(Selected::Scene(0))), - key_pat!(Left) => - return None, - key_pat!(Right) => Some( - Cmd::Select(Selected::Track(0))), + key_pat!(Up) => + return None, + key_pat!(Down) => Some( + Cmd::Select(Selected::Scene(0))), + key_pat!(Left) => + return None, + key_pat!(Right) => Some( + Cmd::Select(Selected::Track(0))), - _ => None - }, - } - }.or_else(||if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) { - Some(Self::Editor(command)) - } else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) { - Some(Self::Phrases(command)) - } else { - None - })? -}); + _ => None + }, + } +}.or_else(||if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) { + Some(Cmd::Editor(command)) +} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) { + Some(Cmd::Phrases(command)) +} else { + None +})?); command!(|self: ArrangerCommand, state: ArrangerTui|match self { Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?, diff --git a/src/clock/clock_tui.rs b/src/clock/clock_tui.rs index 3bde3068..e323477d 100644 --- a/src/clock/clock_tui.rs +++ b/src/clock/clock_tui.rs @@ -10,7 +10,38 @@ pub struct TransportTui { } has_clock!(|self: TransportTui|&self.clock); audio!(|self: TransportTui, client, scope|ClockAudio(self).process(client, scope)); -handle!(|self: TransportTui, input|TransportCommand::execute_with_state(self, input.event())); +handle!(|self: TransportTui, input|ClockCommand::execute_with_state(self, input.event())); +keymap!(TRANSPORT_KEYS = |state: TransportTui, input: Event| ClockCommand { + key(Char(' ')) => + if state.clock().is_stopped() { Play(None) } else { Pause(None) }, + shift(key(Char(' '))) => + if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) } +}); +// TODO: +//keymap!(TRANSPORT_BPM_KEYS = |state: Clock, input: Event| ClockCommand { + //key(Char(',')) => SetBpm(state.bpm().get() - 1.0), + //key(Char('.')) => SetBpm(state.bpm().get() + 1.0), + //key(Char('<')) => SetBpm(state.bpm().get() - 0.001), + //key(Char('>')) => SetBpm(state.bpm().get() + 0.001), +//}); +//keymap!(TRANSPORT_QUANT_KEYS = |state: Clock, input: Event| ClockCommand { + //key(Char(',')) => SetQuant(state.quant.prev()), + //key(Char('.')) => SetQuant(state.quant.next()), + //key(Char('<')) => SetQuant(state.quant.prev()), + //key(Char('>')) => SetQuant(state.quant.next()), +//}); +//keymap!(TRANSPORT_SYNC_KEYS = |sync: Clock, input: Event | ClockCommand { + //key(Char(',')) => SetSync(state.sync.prev()), + //key(Char('.')) => SetSync(state.sync.next()), + //key(Char('<')) => SetSync(state.sync.prev()), + //key(Char('>')) => SetSync(state.sync.next()), +//}); +//keymap!(TRANSPORT_SEEK_KEYS = |state: Clock, input: Event| ClockCommand { + //key(Char(',')) => todo!("transport seek bar"), + //key(Char('.')) => todo!("transport seek bar"), + //key(Char('<')) => todo!("transport seek beat"), + //key(Char('>')) => todo!("transport seek beat"), +//}); render!(Tui: (self: TransportTui) => TransportView { compact: false, clock: &self.clock @@ -105,71 +136,3 @@ render!(Tui: (self: OutputStats) => Tui::either(self.compact, Bsp::e(Tui::fg(TuiTheme::g(255), format!("{:.3}ms", self.latency)), " latency"), ))); -#[derive(Clone, Debug, PartialEq)] -pub enum TransportCommand { - Clock(ClockCommand), -} - -command!(|self:TransportCommand,state:TransportTui|match self { - //Self::Focus(cmd) => cmd.execute(state)?.map(Self::Focus), - Self::Clock(cmd) => cmd.execute(state)?.map(Self::Clock), - _ => unreachable!(), -}); -impl InputToCommand for TransportCommand { - fn input_to_command (state: &TransportTui, input: &Event) -> Option { - use TransportCommand::*; - Some(match input { - key_pat!(Char(' ')) => Clock(if state.clock().is_stopped() { - Play(None) - } else { - Pause(None) - }), - key_pat!(Shift-Char(' ')) => Clock(if state.clock().is_stopped() { - Play(Some(0)) - } else { - Pause(Some(0)) - }), - _ => return None - }) - } -} -fn to_bpm_command (input: &TuiIn, bpm: f64) -> Option { - use TransportCommand::*; - Some(match input.event() { - key_pat!(Char(',')) => Clock(SetBpm(bpm - 1.0)), - key_pat!(Char('.')) => Clock(SetBpm(bpm + 1.0)), - key_pat!(Char('<')) => Clock(SetBpm(bpm - 0.001)), - key_pat!(Char('>')) => Clock(SetBpm(bpm + 0.001)), - _ => return None, - }) -} -fn to_quant_command (input: &TuiIn, quant: &Quantize) -> Option { - use TransportCommand::*; - Some(match input.event() { - key_pat!(Char(',')) => Clock(SetQuant(quant.prev())), - key_pat!(Char('.')) => Clock(SetQuant(quant.next())), - key_pat!(Char('<')) => Clock(SetQuant(quant.prev())), - key_pat!(Char('>')) => Clock(SetQuant(quant.next())), - _ => return None, - }) -} -fn to_sync_command (input: &TuiIn, sync: &LaunchSync) -> Option { - use TransportCommand::*; - Some(match input.event() { - key_pat!(Char(',')) => Clock(SetSync(sync.prev())), - key_pat!(Char('.')) => Clock(SetSync(sync.next())), - key_pat!(Char('<')) => Clock(SetSync(sync.prev())), - key_pat!(Char('>')) => Clock(SetSync(sync.next())), - _ => return None, - }) -} -fn to_seek_command (input: &TuiIn) -> Option { - use TransportCommand::*; - Some(match input.event() { - key_pat!(Char(',')) => todo!("transport seek bar"), - key_pat!(Char('.')) => todo!("transport seek bar"), - key_pat!(Char('<')) => todo!("transport seek beat"), - key_pat!(Char('>')) => todo!("transport seek beat"), - _ => return None, - }) -} diff --git a/src/command.rs b/src/command.rs index 742ec027..480f01ac 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,12 +1,50 @@ use crate::*; -#[macro_export] macro_rules! command { - (|$self:ident:$Command:ty,$state:ident:$State:ty|$handler:expr) => { - impl Command<$State> for $Command { - fn execute ($self, $state: &mut $State) -> Perhaps { - Ok($handler) +#[macro_export] macro_rules! keymap { + ( + $KEYS:ident = |$state:ident: $State:ty, $input:ident: $Input:ty| $Command:ty + { $($key:expr => $handler:expr),* $(,)? } $(,)? + ) => { + pub const $KEYS: EventMap<'static, $State, $Input, $Command> = EventMap { + fallback: None, + bindings: &[ $(($key, &|$state|Some($handler)),)* ] + }; + input_to_command!($Command: |state: $State, input: $Input|{ + $KEYS.handle(state, input)? + }); + }; + ( + $KEYS:ident = |$state:ident: $State:ty, $input:ident: $Input:ty| $Command:ty + { $($key:expr => $handler:expr),* $(,)? }, + $default:expr + ) => { + pub const $KEYS: EventMap<'static, $State, $Input, $Command> = EventMap { + fallback: Some(&|$state, $input|Some($default)), + bindings: &[ $(($key, &|$state|Some($handler)),)* ] + }; + input_to_command!($Command: |state: $State, input: $Input|{ + $KEYS.handle(state, input)? + }); + }; +} + +pub struct EventMap<'a, S, I: PartialEq, C> { + pub bindings: &'a [(I, &'a dyn Fn(&S) -> Option)], + pub fallback: Option<&'a dyn Fn(&S, &I) -> Option> +} + +impl<'a, S, I: PartialEq, C> EventMap<'a, S, I, C> { + pub fn handle (&self, state: &S, input: &I) -> Option { + for (binding, handler) in self.bindings.iter() { + if input == binding { + return handler(state) } } + if let Some(fallback) = self.fallback { + fallback(state, input) + } else { + None + } } } @@ -39,69 +77,12 @@ pub trait InputToCommand: Command + Sized { } } -pub type KeyMapping = [(E, &'static dyn Fn(&T)->U);N]; - -pub struct EventMap<'a, S, I: PartialEq, C> { - pub bindings: &'a [(I, &'a dyn Fn(&S) -> Option)], - pub fallback: Option<&'a dyn Fn(&S, &I) -> Option> -} - -impl<'a, S, I: PartialEq, C> EventMap<'a, S, I, C> { - pub fn handle (&self, state: &S, input: &I) -> Option { - for (binding, handler) in self.bindings.iter() { - if input == binding { - return handler(state) +#[macro_export] macro_rules! command { + (|$self:ident:$Command:ty,$state:ident:$State:ty|$handler:expr) => { + impl Command<$State> for $Command { + fn execute ($self, $state: &mut $State) -> Perhaps { + Ok($handler) } } - if let Some(fallback) = self.fallback { - fallback(state, input) - } else { - None - } } } - -#[macro_export] macro_rules! event_map { - ($events:expr) => { - EventMap { bindings: $events, fallback: None } - }; - ($events:expr, $default: expr) => { - EventMap { bindings: $events, fallback: Some($default) } - }; -} - -#[macro_export] macro_rules! event_map_input_to_command { - ($Input:ty: $Model:ty: $Command:ty: $EventMap:expr) => { - input_to_command!($Command: |state: $Model, input: $Input|{ - event_map!($EventMap).handle(state, input)? - }); - } -} - -#[macro_export] macro_rules! keymap { - ( - $KEYS:ident: |$state:ident: $State:ty, $input:ident: $Input:ty| $Command:ty - { $($key:expr => $handler:expr),* $(,)? } $(,)? - ) => { - pub const $KEYS: EventMap<'static, $State, $Input, $Command> = EventMap { - fallback: None, - bindings: &[ $(($key, &|$state|Some($handler)),)* ] - }; - input_to_command!($Command: |state: $State, input: $Input|{ - $KEYS.handle(state, input)? - }); - }; - ( - $KEYS:ident: |$state:ident: $State:ty, $input:ident: $Input:ty| $Command:ty - { $($key:expr => $handler:expr),* $(,)? }, - $default:expr - ) => { - pub const $KEYS: EventMap<'static, $State, $Input, $Command> = EventMap { - fallback: Some(&|$state, $input|$default), - bindings: &[ $(($key, &|$state|Some($handler)),)* ] - }; - input_to_command!($Command: |state: $State, input: $Input|{ - $KEYS.handle(state, input)? - }); - }; -} diff --git a/src/groovebox.rs b/src/groovebox.rs index 40ee7e26..97ed72fd 100644 --- a/src/groovebox.rs +++ b/src/groovebox.rs @@ -41,14 +41,16 @@ impl Groovebox { player, sampler, _jack: jack.clone(), + pool: crate::pool::PoolModel::from(&phrase), editor: crate::midi::MidiEditor::from(&phrase), + + compact: true, + status: true, size: Measure::new(), midi_buf: vec![vec![];65536], note_buf: vec![], perf: PerfModel::default(), - status: true, - compact: true, }) } } @@ -144,7 +146,7 @@ pub enum GrooveboxCommand { } handle!(|self: Groovebox, input|GrooveboxCommand::execute_with_state(self, input.event())); -keymap!(KEYS_GROOVEBOX: |state: Groovebox, input: Event| GrooveboxCommand { +keymap!(KEYS_GROOVEBOX = |state: Groovebox, input: Event| GrooveboxCommand { // Tab: Toggle compact mode key(Tab) => Cmd::Compact(!state.compact), // q: Enqueue currently edited phrase @@ -179,13 +181,13 @@ keymap!(KEYS_GROOVEBOX: |state: Groovebox, input: Event| GrooveboxCommand { } else { return None }, -}, Some(if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) { +}, if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) { Cmd::Editor(command) } else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) { Cmd::Pool(command) } else { return None -})); +}); command!(|self: GrooveboxCommand, state: Groovebox|match self { Self::Enqueue(phrase) => { diff --git a/src/midi/midi_editor.rs b/src/midi/midi_editor.rs index 0ee905f7..82b89ba8 100644 --- a/src/midi/midi_editor.rs +++ b/src/midi/midi_editor.rs @@ -149,7 +149,7 @@ pub enum MidiEditCommand { handle!(|self: MidiEditor, input|MidiEditCommand::execute_with_state(self, input.event())); -keymap!(KEYS_MIDI_EDITOR: |s: MidiEditor, _input: Event| MidiEditCommand { +keymap!(KEYS_MIDI_EDITOR = |s: MidiEditor, _input: Event| MidiEditCommand { key(Up) => SetNoteCursor(s.note_point() + 1), key(Char('w')) => SetNoteCursor(s.note_point() + 1), key(Down) => SetNoteCursor(s.note_point().saturating_sub(1)), diff --git a/src/sequencer.rs b/src/sequencer.rs index 498e0185..e0656c6c 100644 --- a/src/sequencer.rs +++ b/src/sequencer.rs @@ -109,29 +109,25 @@ handle!(|self:SequencerTui,input|SequencerCommand::execute_with_state(self, Editor(MidiEditCommand), Enqueue(Option>>), } -input_to_command!(SequencerCommand: |state: SequencerTui, input: Event|match input { +keymap!(KEYS_SEQUENCER = |state: SequencerTui, input: Event| SequencerCommand { // TODO: k: toggle on-screen keyboard - key_pat!(Ctrl-Char('k')) => { todo!("keyboard") }, + ctrl(key(Char('k'))) => { todo!("keyboard") }, // Transport: Play/pause - key_pat!(Char(' ')) => Cmd::Clock( - if state.clock().is_stopped() { Play(None) } else { Pause(None) } - ), + key(Char(' ')) => Cmd::Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }), // Transport: Play from start or rewind to start - key_pat!(Shift-Char(' ')) => Cmd::Clock( - if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) } - ), + shift(key(Char(' '))) => Cmd::Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }), // u: undo - key_pat!(Char('u')) => Cmd::History(-1), + key(Char('u')) => Cmd::History(-1), // Shift-U: redo - key_pat!(Char('U')) => Cmd::History( 1), + key(Char('U')) => Cmd::History( 1), // Tab: Toggle compact mode - key_pat!(Tab) => Cmd::Compact(!state.compact), + key(Tab) => Cmd::Compact(!state.compact), // q: Enqueue currently edited phrase - key_pat!(Char('q')) => Cmd::Enqueue(Some(state.pool.phrase().clone())), + key(Char('q')) => Cmd::Enqueue(Some(state.pool.phrase().clone())), // 0: Enqueue phrase 0 (stop all) - key_pat!(Char('0')) => Cmd::Enqueue(Some(state.phrases()[0].clone())), + key(Char('0')) => Cmd::Enqueue(Some(state.phrases()[0].clone())), // e: Toggle between editing currently playing or other phrase - key_pat!(Char('e')) => if let Some((_, Some(playing))) = state.player.play_phrase() { + key(Char('e')) => if let Some((_, Some(playing))) = state.player.play_phrase() { let editing = state.editor.phrase().as_ref().map(|p|p.read().unwrap().clone()); let selected = state.pool.phrase().clone(); Cmd::Editor(Show(Some(if Some(selected.read().unwrap().clone()) != editing { @@ -141,16 +137,13 @@ input_to_command!(SequencerCommand: |state: SequencerTui, input: Event|match inp }))) } else { return None - }, - // For the rest, use the default keybindings of the components. - // The ones defined above supersede them. - _ => if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) { - Cmd::Editor(command) - } else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) { - Cmd::Pool(command) - } else { - return None } +}, if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) { + Cmd::Editor(command) +} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) { + Cmd::Pool(command) +} else { + return None }); command!(|self: SequencerCommand, state: SequencerTui|match self { Self::Enqueue(phrase) => {