mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 20:26:42 +01:00
356 lines
12 KiB
Rust
356 lines
12 KiB
Rust
use crate::*;
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub enum TransportCommand {
|
|
Focus(FocusCommand),
|
|
Clock(ClockCommand),
|
|
}
|
|
|
|
impl<T: TransportControl> Command<T> for TransportCommand {
|
|
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
|
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<SequencerTui> for SequencerCommand {
|
|
fn execute (self, state: &mut SequencerTui) -> Perhaps<Self> {
|
|
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<Arc<RwLock<Phrase>>>),
|
|
}
|
|
|
|
impl Command<ArrangerTui> for ArrangerCommand {
|
|
fn execute (self, state: &mut ArrangerTui) -> Perhaps<Self> {
|
|
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<ArrangerTui> for ArrangerSceneCommand {
|
|
fn execute (self, state: &mut ArrangerTui) -> Perhaps<Self> {
|
|
todo!();
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
impl Command<ArrangerTui> for ArrangerTrackCommand {
|
|
fn execute (self, state: &mut ArrangerTui) -> Perhaps<Self> {
|
|
todo!();
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
impl Command<ArrangerTui> for ArrangerClipCommand {
|
|
fn execute (self, state: &mut ArrangerTui) -> Perhaps<Self> {
|
|
todo!();
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Debug)]
|
|
pub enum PhrasesCommand {
|
|
Select(usize),
|
|
Phrase(PhrasePoolCommand),
|
|
Rename(PhraseRenameCommand),
|
|
Length(PhraseLengthCommand),
|
|
Import(FileBrowserCommand),
|
|
Export(FileBrowserCommand),
|
|
}
|
|
|
|
impl<T: PhrasesControl> Command<T> for PhrasesCommand {
|
|
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
|
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<T: PhrasesControl> Command<T> for PhraseLengthCommand {
|
|
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
|
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<T: PhrasesControl> Command<T> for PhraseRenameCommand {
|
|
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
|
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<T: PhrasesControl> Command<T> for FileBrowserCommand {
|
|
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
|
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<usize>),
|
|
NoteLengthSet(usize),
|
|
NoteScrollSet(usize),
|
|
TimeCursorSet(Option<usize>),
|
|
TimeScrollSet(usize),
|
|
TimeZoomSet(usize),
|
|
}
|
|
|
|
impl<T: PhraseEditorControl + HasEnter> Command<T> for PhraseCommand {
|
|
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
|
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!()
|
|
})
|
|
}
|
|
}
|