use crate::*; use ClockCommand::{Play, Pause}; use ArrangerCommand as Cmd; #[derive(Clone, Debug)] pub enum ArrangerCommand { History(isize), Color(ItemPalette), Clock(ClockCommand), Scene(ArrangerSceneCommand), Track(ArrangerTrackCommand), Clip(ArrangerClipCommand), Select(ArrangerSelection), Zoom(usize), Phrases(PoolCommand), Editor(MidiEditCommand), StopAll, Clear, } #[derive(Clone, Debug)] pub enum ArrangerTrackCommand { Add, Delete(usize), Stop(usize), Swap(usize, usize), SetSize(usize), SetZoom(usize), SetColor(usize, ItemPalette), } #[derive(Clone, Debug)] pub enum ArrangerSceneCommand { Enqueue(usize), Add, Delete(usize), Swap(usize, usize), SetSize(usize), SetZoom(usize), SetColor(usize, ItemPalette), } #[derive(Clone, Debug)] pub enum ArrangerClipCommand { Get(usize, usize), Put(usize, usize, Option>>), Enqueue(usize, usize), Edit(Option>>), SetLoop(usize, usize, bool), SetColor(usize, usize, ItemPalette), } //handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self, input.event())); //input_to_command!(ArrangerCommand: |state: Arranger, input: Event|{KEYS_ARRANGER.handle(state, input)?}); keymap!(KEYS_ARRANGER = |state: Arranger, input: Event| ArrangerCommand { key(Char('u')) => Cmd::History(-1), key(Char('U')) => Cmd::History(1), // TODO: k: toggle on-screen keyboard ctrl(key(Char('k'))) => { todo!("keyboard") }, // Transport: Play/pause key(Char(' ')) => Cmd::Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }), // Transport: Play from start or rewind to start 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(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 { kpat!(Char('g')) => Some(Cmd::Phrases(PoolCommand::Select(0))), kpat!(Char('q')) => Some(Cmd::Clip(Clip::Enqueue(t, s))), kpat!(Char(',')) => Some(Cmd::Clip(Clip::Put(t, s, None))), kpat!(Char('.')) => Some(Cmd::Clip(Clip::Put(t, s, None))), kpat!(Char('<')) => Some(Cmd::Clip(Clip::Put(t, s, None))), kpat!(Char('>')) => Some(Cmd::Clip(Clip::Put(t, s, None))), kpat!(Char('p')) => Some(Cmd::Clip(Clip::Put(t, s, Some(state.pool.phrase().clone())))), kpat!(Char('l')) => Some(Cmd::Clip(ArrangerClipCommand::SetLoop(t, s, false))), kpat!(Delete) => Some(Cmd::Clip(Clip::Put(t, s, None))), kpat!(Up) => Some(Cmd::Select( if s > 0 { Selected::Clip(t, s - 1) } else { Selected::Track(t) })), kpat!(Down) => Some(Cmd::Select( Selected::Clip(t, (s + 1).min(s_len.saturating_sub(1))))), kpat!(Left) => Some(Cmd::Select( if t > 0 { Selected::Clip(t - 1, s) } else { Selected::Scene(s) })), kpat!(Right) => Some(Cmd::Select( Selected::Clip((t + 1).min(t_len.saturating_sub(1)), s))), _ => None }, Selected::Scene(s) => match input { kpat!(Char(',')) => Some(Cmd::Scene(Scene::Swap(s, s - 1))), kpat!(Char('.')) => Some(Cmd::Scene(Scene::Swap(s, s + 1))), kpat!(Char('<')) => Some(Cmd::Scene(Scene::Swap(s, s - 1))), kpat!(Char('>')) => Some(Cmd::Scene(Scene::Swap(s, s + 1))), kpat!(Char('q')) => Some(Cmd::Scene(Scene::Enqueue(s))), kpat!(Delete) => Some(Cmd::Scene(Scene::Delete(s))), kpat!(Char('c')) => Some(Cmd::Scene(Scene::SetColor(s, ItemPalette::random()))), kpat!(Up) => Some( Cmd::Select(if s > 0 { Selected::Scene(s - 1) } else { Selected::Mix })), kpat!(Down) => Some( Cmd::Select(Selected::Scene((s + 1).min(s_len.saturating_sub(1))))), kpat!(Left) => return None, kpat!(Right) => Some( Cmd::Select(Selected::Clip(0, s))), _ => None }, Selected::Track(t) => match input { kpat!(Char(',')) => Some(Cmd::Track(Track::Swap(t, t - 1))), kpat!(Char('.')) => Some(Cmd::Track(Track::Swap(t, t + 1))), kpat!(Char('<')) => Some(Cmd::Track(Track::Swap(t, t - 1))), kpat!(Char('>')) => Some(Cmd::Track(Track::Swap(t, t + 1))), kpat!(Delete) => Some(Cmd::Track(Track::Delete(t))), kpat!(Char('c')) => Some(Cmd::Track(Track::SetColor(t, ItemPalette::random()))), kpat!(Up) => return None, kpat!(Down) => Some( Cmd::Select(Selected::Clip(t, 0))), kpat!(Left) => Some( Cmd::Select(if t > 0 { Selected::Track(t - 1) } else { Selected::Mix })), kpat!(Right) => Some( Cmd::Select(Selected::Track((t + 1).min(t_len.saturating_sub(1))))), _ => None }, Selected::Mix => match input { kpat!(Delete) => Some(Cmd::Clear), kpat!(Char('0')) => Some(Cmd::StopAll), kpat!(Char('c')) => Some(Cmd::Color(ItemPalette::random())), kpat!(Up) => return None, kpat!(Down) => Some( Cmd::Select(Selected::Scene(0))), kpat!(Left) => return None, kpat!(Right) => Some( Cmd::Select(Selected::Track(0))), _ => 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: Arranger|match self { Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?, Self::Clip(cmd) => cmd.delegate(state, Self::Clip)?, Self::Scene(cmd) => cmd.delegate(state, Self::Scene)?, Self::Track(cmd) => cmd.delegate(state, Self::Track)?, Self::Editor(cmd) => cmd.delegate(&mut state.editor, Self::Editor)?, Self::Zoom(_) => { todo!(); }, Self::Select(selected) => { *state.selected_mut() = selected; None }, Self::Color(palette) => { let old = state.color; state.color = palette; Some(Self::Color(old)) }, Self::Phrases(cmd) => { match cmd { // autoselect: automatically load selected phrase in editor PoolCommand::Select(_) => { let undo = cmd.delegate(&mut state.pool, Self::Phrases)?; state.editor.set_phrase(Some(state.pool.phrase())); undo }, // reload phrase in editor to update color PoolCommand::Phrase(PhrasePoolCommand::SetColor(index, _)) => { let undo = cmd.delegate(&mut state.pool, Self::Phrases)?; state.editor.set_phrase(Some(state.pool.phrase())); undo }, _ => cmd.delegate(&mut state.pool, Self::Phrases)? } }, Self::History(_) => { todo!() }, Self::StopAll => { for track in 0..state.tracks.len() { state.tracks[track].player.enqueue_next(None); } None }, Self::Clear => { todo!() }, }); command!(|self: ArrangerTrackCommand, state: Arranger|match self { Self::SetColor(index, color) => { let old = state.tracks[index].color; state.tracks[index].color = color; Some(Self::SetColor(index, old)) }, Self::Stop(track) => { state.tracks[track].player.enqueue_next(None); None }, _ => None }); command!(|self: ArrangerSceneCommand, state: Arranger|match self { Self::Delete(index) => { state.scene_del(index); None }, Self::SetColor(index, color) => { let old = state.scenes[index].color; state.scenes[index].color = color; Some(Self::SetColor(index, old)) }, Self::Enqueue(scene) => { for track in 0..state.tracks.len() { state.tracks[track].player.enqueue_next(state.scenes[scene].clips[track].as_ref()); } None }, _ => None }); command!(|self: ArrangerClipCommand, state: Arranger|match self { Self::Get(track, scene) => { todo!() }, Self::Put(track, scene, phrase) => { let old = state.scenes[scene].clips[track].clone(); state.scenes[scene].clips[track] = phrase; Some(Self::Put(track, scene, old)) }, Self::Enqueue(track, scene) => { state.tracks[track].player.enqueue_next(state.scenes[scene].clips[track].as_ref()); None }, _ => None });