use crate::*; use ClockCommand::{Play, Pause}; #[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), } input_to_command!(ArrangerCommand: |state: ArrangerTui, input|match input.event() { key_pat!(Char('u')) => Self::History(-1), key_pat!(Char('U')) => Self::History(1), // TODO: k: toggle on-screen keyboard key_pat!(Ctrl-Char('k')) => { todo!("keyboard") }, // Transport: Play/pause key_pat!(Char(' ')) => Self::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.phrases.phrase().clone()))), key_pat!(Ctrl-Left) => Self::Scene(ArrangerSceneCommand::Add), key_pat!(Ctrl-Char('t')) => Self::Track(ArrangerTrackCommand::Add), // Tab: Toggle visibility of phrase pool column key_pat!(Tab) => Self::Phrases(PoolCommand::Show(!state.phrases.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.event() { 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.phrases.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))), _ => None }, Selected::Scene(s) => match input.event() { 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))), _ => None }, Selected::Track(t) => match input.event() { 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))))), _ => None }, Selected::Mix => match input.event() { 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))), _ => 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.phrases, input) { Some(Self::Phrases(command)) } else { None })? }); fn to_arrangement_command (state: &ArrangerTui, input: &TuiIn) -> Option { 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.event() { 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.phrases.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))), _ => None }, Selected::Scene(s) => match input.event() { 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) => None, key_pat!(Right) => Some( Cmd::Select(Selected::Clip(0, s))), _ => None }, Selected::Track(t) => match input.event() { 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) => 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.event() { 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) => None, key_pat!(Down) => Some( Cmd::Select(Selected::Scene(0))), key_pat!(Left) => None, key_pat!(Right) => Some( Cmd::Select(Selected::Track(0))), _ => None }, } } command!(|self: ArrangerCommand, state: ArrangerTui|match self { Self::Scene(cmd) => cmd.execute(state)?.map(Self::Scene), Self::Track(cmd) => cmd.execute(state)?.map(Self::Track), Self::Clip(cmd) => cmd.execute(state)?.map(Self::Clip), Self::Editor(cmd) => cmd.execute(&mut state.editor)?.map(Self::Editor), Self::Clock(cmd) => cmd.execute(state)?.map(Self::Clock), 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) => { let mut default = |cmd: PoolCommand|{ cmd.execute(&mut state.phrases).map(|x|x.map(Self::Phrases)) }; match cmd { // autoselect: automatically load selected phrase in editor PoolCommand::Select(_) => { let undo = default(cmd)?; state.editor.set_phrase(Some(state.phrases.phrase())); undo }, // reload phrase in editor to update color PoolCommand::Phrase(PhrasePoolCommand::SetColor(index, _)) => { let undo = default(cmd)?; state.editor.set_phrase(Some(state.phrases.phrase())); undo }, _ => default(cmd)? } }, 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: ArrangerTui|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: ArrangerTui|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: ArrangerTui|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 });