use crate::*; #[derive(Clone, Debug, PartialEq)] pub enum TransportCommand { Focus(FocusCommand), Clock(ClockCommand), } impl Command for TransportCommand { fn execute (self, state: &mut T) -> Perhaps { use TransportCommand::{Focus, Clock}; use FocusCommand::{Next, Prev}; use ClockCommand::{SetBpm, SetQuant, SetSync}; Ok(match self { Focus(cmd) => cmd.execute(state)?.map(Focus), Clock(SetBpm(bpm)) => Some(Clock(SetBpm(state.bpm().set(bpm)))), Clock(SetQuant(quant)) => Some(Clock(SetQuant(state.quant().set(quant)))), Clock(SetSync(sync)) => Some(Clock(SetSync(state.sync().set(sync)))), _ => return Ok(None) }) } } #[derive(Clone, Debug, PartialEq)] pub enum SequencerCommand { Focus(FocusCommand), Undo, Redo, Clear, Clock(ClockCommand), Phrases(PhrasesCommand), Editor(PhraseCommand), } impl Command for SequencerCommand { fn execute (self, state: &mut SequencerTui) -> Perhaps { use SequencerCommand::*; Ok(match self { Focus(cmd) => cmd.execute(state)?.map(Focus), Phrases(cmd) => cmd.execute(state)?.map(Phrases), Editor(cmd) => cmd.execute(state)?.map(Editor), Clock(cmd) => cmd.execute(state)?.map(Clock), Undo => { todo!() }, Redo => { todo!() }, Clear => { todo!() }, }) } } #[derive(Clone, Debug)] pub enum ArrangerCommand { Focus(FocusCommand), Undo, Redo, Clear, Color(ItemColor), Clock(ClockCommand), Scene(ArrangerSceneCommand), Track(ArrangerTrackCommand), Clip(ArrangerClipCommand), Select(ArrangerSelection), Zoom(usize), Phrases(PhrasesCommand), Editor(PhraseCommand), EditPhrase(Option>>), } impl Command for ArrangerCommand { fn execute (self, state: &mut ArrangerTui) -> Perhaps { use ArrangerCommand::*; Ok(match self { Focus(cmd) => cmd.execute(state)?.map(Focus), Scene(cmd) => cmd.execute(state)?.map(Scene), Track(cmd) => cmd.execute(state)?.map(Track), Clip(cmd) => cmd.execute(state)?.map(Clip), Phrases(cmd) => cmd.execute(state)?.map(Phrases), Editor(cmd) => cmd.execute(state)?.map(Editor), Clock(cmd) => cmd.execute(state)?.map(Clock), Zoom(zoom) => { todo!(); }, Select(selected) => { *state.selected_mut() = selected; None }, EditPhrase(phrase) => { state.edit_phrase(&phrase); None }, _ => { todo!() } }) } } impl Command for ArrangerSceneCommand { fn execute (self, state: &mut ArrangerTui) -> Perhaps { todo!(); Ok(None) } } impl Command for ArrangerTrackCommand { fn execute (self, state: &mut ArrangerTui) -> Perhaps { todo!(); Ok(None) } } impl Command for ArrangerClipCommand { fn execute (self, state: &mut ArrangerTui) -> Perhaps { todo!(); Ok(None) } } #[derive(Clone, PartialEq, Debug)] pub enum PhrasesCommand { Select(usize), Phrase(PhrasePoolCommand), Rename(PhraseRenameCommand), Length(PhraseLengthCommand), Import(FileBrowserCommand), Export(FileBrowserCommand), } impl Command for PhrasesCommand { fn execute (self, state: &mut T) -> Perhaps { use PhrasesCommand::*; Ok(match self { Phrase(command) => command.execute(state)?.map(Phrase), Rename(command) => match command { PhraseRenameCommand::Begin => { let length = state.phrases()[state.phrase_index()].read().unwrap().length; *state.phrases_mode_mut() = Some( PhrasesMode::Length(state.phrase_index(), length, PhraseLengthFocus::Bar) ); None }, _ => command.execute(state)?.map(Rename) }, Length(command) => match command { PhraseLengthCommand::Begin => { let name = state.phrases()[state.phrase_index()].read().unwrap().name.clone(); *state.phrases_mode_mut() = Some( PhrasesMode::Rename(state.phrase_index(), name) ); None }, _ => command.execute(state)?.map(Length) }, Import(command) => match command { FileBrowserCommand::Begin => { *state.phrases_mode_mut() = Some( PhrasesMode::Import(state.phrase_index(), FileBrowser::new(None)?) ); None }, _ => command.execute(state)?.map(Import) }, Export(command) => match command { FileBrowserCommand::Begin => { *state.phrases_mode_mut() = Some( PhrasesMode::Export(state.phrase_index(), FileBrowser::new(None)?) ); None }, _ => command.execute(state)?.map(Export) }, Select(phrase) => { state.set_phrase_index(phrase); None }, }) } } #[derive(Copy, Clone, Debug, PartialEq)] pub enum PhraseLengthCommand { Begin, Cancel, Set(usize), Next, Prev, Inc, Dec, } impl Command for PhraseLengthCommand { fn execute (self, state: &mut T) -> Perhaps { use PhraseLengthFocus::*; use PhraseLengthCommand::*; match state.phrases_mode_mut().clone() { Some(PhrasesMode::Length(phrase, ref mut length, ref mut focus)) => match self { Cancel => { *state.phrases_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 phrase = state.phrases()[phrase].write().unwrap(); let old_length = phrase.length; phrase.length = length; std::mem::drop(phrase); *state.phrases_mode_mut() = None; return Ok(Some(Self::Set(old_length))) }, _ => unreachable!() }, _ => unreachable!() }; Ok(None) } } #[derive(Clone, Debug, PartialEq)] pub enum PhraseRenameCommand { Begin, Cancel, Confirm, Set(String), } impl Command for PhraseRenameCommand { fn execute (self, state: &mut T) -> Perhaps { use PhraseRenameCommand::*; match state.phrases_mode_mut().clone() { Some(PhrasesMode::Rename(phrase, ref mut old_name)) => match self { Set(s) => { state.phrases()[phrase].write().unwrap().name = s.into(); return Ok(Some(Self::Set(old_name.clone()))) }, Confirm => { let old_name = old_name.clone(); *state.phrases_mode_mut() = None; return Ok(Some(Self::Set(old_name))) }, Cancel => { state.phrases()[phrase].write().unwrap().name = old_name.clone(); }, _ => unreachable!() }, _ => unreachable!() }; Ok(None) } } /// Commands supported by [FileBrowser] #[derive(Debug, Clone, PartialEq)] pub enum FileBrowserCommand { Begin, Cancel, Confirm, Select(usize), Chdir(PathBuf), Filter(String), } impl Command for FileBrowserCommand { fn execute (self, state: &mut T) -> Perhaps { use FileBrowserCommand::*; let mode = state.phrases_mode_mut(); match mode { Some(PhrasesMode::Import(index, ref mut browser)) => match self { Cancel => { *mode = None; }, Chdir(cwd) => { *mode = Some(PhrasesMode::Import(*index, FileBrowser::new(Some(cwd))?)); }, Select(index) => { browser.index = index; }, Confirm => { todo!("import midi to phrase"); }, _ => todo!(), _ => unreachable!() }, Some(PhrasesMode::Export(index, ref mut browser)) => match self { Cancel => { *mode = None; }, Chdir(cwd) => { *mode = Some(PhrasesMode::Export(*index, FileBrowser::new(Some(cwd))?)); }, Select(index) => { browser.index = index; }, _ => unreachable!() }, _ => unreachable!(), }; Ok(None) } } #[derive(Clone, PartialEq, Debug)] pub enum PhraseCommand { // TODO: 1-9 seek markers that by default start every 8th of the phrase ToggleDirection, EnterEditMode, ExitEditMode, NoteAppend, NoteSet, NoteCursorSet(Option), NoteLengthSet(usize), NoteScrollSet(usize), TimeCursorSet(Option), TimeScrollSet(usize), TimeZoomSet(usize), } impl Command for PhraseCommand { fn execute (self, state: &mut T) -> Perhaps { use PhraseCommand::*; Ok(match self { ToggleDirection => { todo!() }, EnterEditMode => { state.focus_enter(); None }, ExitEditMode => { state.focus_exit(); None }, NoteAppend => { if state.phrase_editor_entered() { state.put_note(); state.time_cursor_advance(); } None }, NoteSet => { if state.phrase_editor_entered() { state.put_note(); } None }, TimeCursorSet(time) => { state.time_axis().write().unwrap().point_set(time); None }, TimeScrollSet(time) => { state.time_axis().write().unwrap().start_set(time); None }, TimeZoomSet(zoom) => { state.time_axis().write().unwrap().scale_set(zoom); None }, NoteScrollSet(note) => { state.note_axis().write().unwrap().start_set(note); None }, NoteLengthSet(time) => { *state.note_len_mut() = time; None }, NoteCursorSet(note) => { let mut axis = state.note_axis().write().unwrap(); axis.point_set(note); if let Some(point) = axis.point { if point > 73 { axis.point = Some(73); } if point < axis.start { axis.start = (point / 2) * 2; } } None }, _ => unreachable!() }) } }