mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
428 lines
16 KiB
Rust
428 lines
16 KiB
Rust
use crate::*;
|
|
|
|
#[derive(Clone, PartialEq)]
|
|
enum SequencerCommand {
|
|
FocusNext,
|
|
FocusPrev,
|
|
FocusUp,
|
|
FocusDown,
|
|
FocusLeft,
|
|
FocusRight,
|
|
Transport(TransportCommand),
|
|
Phrases(PhrasePoolCommand),
|
|
Editor(PhraseEditorCommand),
|
|
}
|
|
#[derive(Clone, PartialEq)]
|
|
pub enum PhrasePoolCommand {
|
|
Previous,
|
|
Next,
|
|
MoveUp,
|
|
MoveDown,
|
|
Delete,
|
|
Append,
|
|
Insert,
|
|
Duplicate,
|
|
RandomColor,
|
|
Edit,
|
|
Rename(PhraseRenameCommand),
|
|
Length(PhraseLengthCommand),
|
|
}
|
|
#[derive(Clone, PartialEq)]
|
|
pub enum PhraseRenameCommand {
|
|
Begin,
|
|
Backspace,
|
|
Append(char),
|
|
Set(String),
|
|
Confirm,
|
|
Cancel,
|
|
}
|
|
#[derive(Clone, PartialEq)]
|
|
pub enum PhraseLengthCommand {
|
|
Begin,
|
|
Next,
|
|
Previous,
|
|
Increment,
|
|
Decrement,
|
|
Set(usize),
|
|
Confirm,
|
|
Cancel,
|
|
}
|
|
#[derive(Clone, PartialEq)]
|
|
pub enum PhraseEditorCommand {
|
|
// TODO: 1-9 seek markers that by default start every 8th of the phrase
|
|
ToggleDirection,
|
|
EnterEditMode,
|
|
ExitEditMode,
|
|
NoteLengthDecrement,
|
|
NoteLengthIncrement,
|
|
TimeZoomIn,
|
|
TimeZoomOut,
|
|
NoteAppend,
|
|
NoteSet,
|
|
NotePageUp,
|
|
NotePageDown,
|
|
GoUp,
|
|
GoDown,
|
|
GoLeft,
|
|
GoRight,
|
|
}
|
|
impl HandleKey<SequencerCommand> for Sequencer<Tui> {
|
|
fn match_input (&self, from: &TuiInput) -> Option<SequencerCommand> {
|
|
match from.event() {
|
|
key!(KeyCode::Tab) => Some(SequencerCommand::FocusNext),
|
|
key!(Shift-KeyCode::Tab) => Some(SequencerCommand::FocusPrev),
|
|
key!(KeyCode::BackTab) => Some(SequencerCommand::FocusPrev),
|
|
key!(Shift-KeyCode::BackTab) => Some(SequencerCommand::FocusPrev),
|
|
key!(KeyCode::Up) => Some(SequencerCommand::FocusUp),
|
|
key!(KeyCode::Down) => Some(SequencerCommand::FocusDown),
|
|
key!(KeyCode::Left) => Some(SequencerCommand::FocusLeft),
|
|
key!(KeyCode::Right) => Some(SequencerCommand::FocusRight),
|
|
key!(KeyCode::Char(' ')) => Some(SequencerCommand::Transport(
|
|
TransportCommand::TogglePlay)), // FIXME go through transport
|
|
_ => match self.focused() {
|
|
SequencerFocus::Transport => self.transport.as_ref()
|
|
.map(|t|t.read().unwrap().match_input(from).map(SequencerCommand::Transport))
|
|
.flatten(),
|
|
SequencerFocus::PhrasePool => self.phrases.read().unwrap()
|
|
.match_input(from)
|
|
.map(SequencerCommand::Phrases),
|
|
SequencerFocus::PhraseEditor => self.editor
|
|
.match_input(from)
|
|
.map(SequencerCommand::Editor),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
impl HandleKey<PhrasePoolCommand> for PhrasePool<Tui> {
|
|
fn match_input (&self, from: &TuiInput) -> Option<PhrasePoolCommand> {
|
|
match from.event() {
|
|
key!(KeyCode::Up) => Some(PhrasePoolCommand::Previous),
|
|
key!(KeyCode::Down) => Some(PhrasePoolCommand::Next),
|
|
key!(KeyCode::Char(',')) => Some(PhrasePoolCommand::MoveUp),
|
|
key!(KeyCode::Char('.')) => Some(PhrasePoolCommand::MoveDown),
|
|
key!(KeyCode::Delete) => Some(PhrasePoolCommand::Delete),
|
|
key!(KeyCode::Char('a')) => Some(PhrasePoolCommand::Append),
|
|
key!(KeyCode::Char('i')) => Some(PhrasePoolCommand::Insert),
|
|
key!(KeyCode::Char('d')) => Some(PhrasePoolCommand::Duplicate),
|
|
key!(KeyCode::Char('c')) => Some(PhrasePoolCommand::RandomColor),
|
|
key!(KeyCode::Char('n')) => Some(PhrasePoolCommand::Rename(PhraseRenameCommand::Begin)),
|
|
key!(KeyCode::Char('t')) => Some(PhrasePoolCommand::Length(PhraseLengthCommand::Begin)),
|
|
_ => match self.mode {
|
|
Some(PhrasePoolMode::Rename(..)) => HandleKey::<PhraseRenameCommand>
|
|
::match_input(self, from).map(PhrasePoolCommand::Rename),
|
|
Some(PhrasePoolMode::Length(..)) => HandleKey::<PhraseLengthCommand>
|
|
::match_input(self, from).map(PhrasePoolCommand::Length),
|
|
_ => None
|
|
}
|
|
}
|
|
}
|
|
}
|
|
impl HandleKey<PhraseRenameCommand> for PhrasePool<Tui> {
|
|
fn match_input (&self, from: &TuiInput) -> Option<PhraseRenameCommand> {
|
|
match from.event() {
|
|
key!(KeyCode::Backspace) => Some(PhraseRenameCommand::Backspace),
|
|
key!(KeyCode::Enter) => Some(PhraseRenameCommand::Confirm),
|
|
key!(KeyCode::Esc) => Some(PhraseRenameCommand::Cancel),
|
|
key!(KeyCode::Char(c)) => Some(PhraseRenameCommand::Append(*c)),
|
|
_ => None
|
|
}
|
|
}
|
|
}
|
|
impl HandleKey<PhraseLengthCommand> for PhrasePool<Tui> {
|
|
fn match_input (&self, from: &TuiInput) -> Option<PhraseLengthCommand> {
|
|
match from.event() {
|
|
key!(KeyCode::Up) => Some(PhraseLengthCommand::Increment),
|
|
key!(KeyCode::Down) => Some(PhraseLengthCommand::Decrement),
|
|
key!(KeyCode::Right) => Some(PhraseLengthCommand::Next),
|
|
key!(KeyCode::Left) => Some(PhraseLengthCommand::Previous),
|
|
key!(KeyCode::Enter) => Some(PhraseLengthCommand::Confirm),
|
|
key!(KeyCode::Esc) => Some(PhraseLengthCommand::Cancel),
|
|
_ => None
|
|
}
|
|
}
|
|
}
|
|
impl HandleKey<PhraseEditorCommand> for PhraseEditor<Tui> {
|
|
fn match_input (&self, from: &TuiInput) -> Option<PhraseEditorCommand> {
|
|
match from.event() {
|
|
key!(KeyCode::Char('`')) => Some(PhraseEditorCommand::ToggleDirection),
|
|
key!(KeyCode::Enter) => Some(PhraseEditorCommand::EnterEditMode),
|
|
key!(KeyCode::Esc) => Some(PhraseEditorCommand::ExitEditMode),
|
|
key!(KeyCode::Char('[')) => Some(PhraseEditorCommand::NoteLengthDecrement),
|
|
key!(KeyCode::Char(']')) => Some(PhraseEditorCommand::NoteLengthIncrement),
|
|
key!(KeyCode::Char('a')) => Some(PhraseEditorCommand::NoteAppend),
|
|
key!(KeyCode::Char('s')) => Some(PhraseEditorCommand::NoteSet),
|
|
key!(KeyCode::Char('-')) => Some(PhraseEditorCommand::TimeZoomOut),
|
|
key!(KeyCode::Char('_')) => Some(PhraseEditorCommand::TimeZoomOut),
|
|
key!(KeyCode::Char('=')) => Some(PhraseEditorCommand::TimeZoomIn),
|
|
key!(KeyCode::Char('+')) => Some(PhraseEditorCommand::TimeZoomIn),
|
|
key!(KeyCode::PageUp) => Some(PhraseEditorCommand::NotePageUp),
|
|
key!(KeyCode::PageDown) => Some(PhraseEditorCommand::NotePageDown),
|
|
key!(KeyCode::Up) => Some(PhraseEditorCommand::GoUp),
|
|
key!(KeyCode::Down) => Some(PhraseEditorCommand::GoDown),
|
|
key!(KeyCode::Left) => Some(PhraseEditorCommand::GoLeft),
|
|
key!(KeyCode::Right) => Some(PhraseEditorCommand::GoRight),
|
|
_ => None
|
|
}
|
|
}
|
|
}
|
|
/// Handle top-level events in standalone sequencer.
|
|
impl Handle<Tui> for Sequencer<Tui> {
|
|
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
|
if let Some(command) = self.match_input(from) {
|
|
let _undo = command.run(self)?;
|
|
return Ok(Some(true))
|
|
}
|
|
Ok(None)
|
|
}
|
|
}
|
|
impl Handle<Tui> for PhrasePool<Tui> {
|
|
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
|
if let Some(command) = HandleKey::<PhrasePoolCommand>::match_input(self, from) {
|
|
let _undo = command.run(self)?;
|
|
return Ok(Some(true))
|
|
}
|
|
Ok(None)
|
|
}
|
|
}
|
|
impl Handle<Tui> for PhraseEditor<Tui> {
|
|
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
|
if let Some(command) = self.match_input(from) {
|
|
let _undo = command.run(self)?;
|
|
return Ok(Some(true))
|
|
}
|
|
Ok(None)
|
|
}
|
|
}
|
|
impl<E: Engine> Command<Sequencer<E>> for SequencerCommand {
|
|
fn run (&self, state: &mut Sequencer<E>) -> Perhaps<Self> {
|
|
match self {
|
|
Self::FocusNext => { state.focus_next(); },
|
|
Self::FocusPrev => { state.focus_prev(); },
|
|
Self::FocusUp => { state.focus_up(); },
|
|
Self::FocusDown => { state.focus_down(); },
|
|
Self::FocusLeft => { state.focus_left(); },
|
|
Self::FocusRight => { state.focus_right(); },
|
|
Self::Transport(command) => if let Some(ref transport) = state.transport {
|
|
return command
|
|
.run(&mut*transport.write().unwrap())
|
|
.map(|x|x.map(SequencerCommand::Transport))
|
|
},
|
|
Self::Phrases(command) => {
|
|
return command
|
|
.run(&mut*state.phrases.write().unwrap())
|
|
.map(|x|x.map(SequencerCommand::Phrases))
|
|
},
|
|
Self::Editor(command) => {
|
|
return command
|
|
.run(&mut state.editor)
|
|
.map(|x|x.map(SequencerCommand::Editor))
|
|
},
|
|
}
|
|
Ok(None)
|
|
}
|
|
}
|
|
impl<E: Engine> Command<PhrasePool<E>> for PhrasePoolCommand {
|
|
fn run (&self, state: &mut PhrasePool<E>) -> Perhaps<Self> {
|
|
match self {
|
|
Self::Previous => {
|
|
state.select_prev()
|
|
},
|
|
Self::Next => {
|
|
state.select_next()
|
|
},
|
|
Self::Delete => {
|
|
state.delete_selected()
|
|
},
|
|
Self::Append => {
|
|
state.append_new(None, None)
|
|
},
|
|
Self::Insert => {
|
|
state.insert_new(None, None)
|
|
},
|
|
Self::Duplicate => {
|
|
state.insert_dup()
|
|
},
|
|
Self::Edit => {
|
|
todo!();
|
|
}
|
|
Self::RandomColor => {
|
|
state.randomize_color()
|
|
},
|
|
Self::MoveUp => {
|
|
state.move_up()
|
|
},
|
|
Self::MoveDown => {
|
|
state.move_down()
|
|
},
|
|
Self::Rename(PhraseRenameCommand::Begin) => {
|
|
state.begin_rename()
|
|
},
|
|
Self::Rename(_) => {
|
|
unreachable!()
|
|
},
|
|
Self::Length(PhraseLengthCommand::Begin) => {
|
|
state.begin_length()
|
|
},
|
|
Self::Length(_) => {
|
|
unreachable!()
|
|
},
|
|
}
|
|
Ok(None)
|
|
}
|
|
}
|
|
impl<E: Engine> Command<PhrasePool<E>> for PhraseRenameCommand {
|
|
fn run (&self, state: &mut PhrasePool<E>) -> Perhaps<Self> {
|
|
if let Some(PhrasePoolMode::Rename(phrase, ref mut old_name)) = state.mode {
|
|
match self {
|
|
Self::Begin => {
|
|
unreachable!();
|
|
},
|
|
Self::Backspace => {
|
|
let mut phrase = state.phrases[phrase].write().unwrap();
|
|
let old_name = phrase.name.clone();
|
|
phrase.name.pop();
|
|
return Ok(Some(Self::Set(old_name)))
|
|
},
|
|
Self::Append(c) => {
|
|
let mut phrase = state.phrases[phrase].write().unwrap();
|
|
let old_name = phrase.name.clone();
|
|
phrase.name.push(*c);
|
|
return Ok(Some(Self::Set(old_name)))
|
|
},
|
|
Self::Set(s) => {
|
|
let mut phrase = state.phrases[phrase].write().unwrap();
|
|
phrase.name = s.into();
|
|
return Ok(Some(Self::Set(old_name.clone())))
|
|
},
|
|
Self::Confirm => {
|
|
let old_name = old_name.clone();
|
|
state.mode = None;
|
|
return Ok(Some(Self::Set(old_name)))
|
|
},
|
|
Self::Cancel => {
|
|
let mut phrase = state.phrases[phrase].write().unwrap();
|
|
phrase.name = old_name.clone();
|
|
}
|
|
};
|
|
Ok(None)
|
|
} else if *self == Self::Begin {
|
|
todo!()
|
|
} else {
|
|
unreachable!()
|
|
}
|
|
}
|
|
}
|
|
impl<E: Engine> Command<PhrasePool<E>> for PhraseLengthCommand {
|
|
fn run (&self, state: &mut PhrasePool<E>) -> Perhaps<Self> {
|
|
if let Some(PhrasePoolMode::Length(phrase, ref mut length, ref mut focus)) = state.mode {
|
|
match self {
|
|
Self::Begin => {
|
|
unreachable!();
|
|
},
|
|
Self::Previous => {
|
|
focus.prev()
|
|
},
|
|
Self::Next => {
|
|
focus.next()
|
|
},
|
|
Self::Increment => match focus {
|
|
PhraseLengthFocus::Bar => {
|
|
*length += 4 * PPQ
|
|
},
|
|
PhraseLengthFocus::Beat => {
|
|
*length += PPQ
|
|
},
|
|
PhraseLengthFocus::Tick => {
|
|
*length += 1
|
|
},
|
|
},
|
|
Self::Decrement => match focus {
|
|
PhraseLengthFocus::Bar => {
|
|
*length = length.saturating_sub(4 * PPQ)
|
|
},
|
|
PhraseLengthFocus::Beat => {
|
|
*length = length.saturating_sub(PPQ)
|
|
},
|
|
PhraseLengthFocus::Tick => {
|
|
*length = length.saturating_sub(1)
|
|
},
|
|
},
|
|
Self::Cancel => {
|
|
state.mode = None;
|
|
},
|
|
Self::Confirm => {
|
|
return Self::Set(*length).run(state)
|
|
},
|
|
Self::Set(length) => {
|
|
let mut phrase = state.phrases[phrase].write().unwrap();
|
|
let old_length = phrase.length;
|
|
phrase.length = *length;
|
|
state.mode = None;
|
|
return Ok(Some(Self::Set(old_length)))
|
|
},
|
|
}
|
|
Ok(None)
|
|
} else if *self == Self::Begin {
|
|
todo!()
|
|
} else {
|
|
unreachable!()
|
|
}
|
|
}
|
|
}
|
|
impl<E: Engine> Command<PhraseEditor<E>> for PhraseEditorCommand {
|
|
fn run (&self, state: &mut PhraseEditor<E>) -> Perhaps<Self> {
|
|
match self {
|
|
Self::ToggleDirection => {
|
|
state.mode = !state.mode;
|
|
},
|
|
Self::EnterEditMode => {
|
|
state.entered = true;
|
|
},
|
|
Self::ExitEditMode => {
|
|
state.entered = false;
|
|
},
|
|
Self::TimeZoomOut => {
|
|
state.time_zoom_out()
|
|
},
|
|
Self::TimeZoomIn => {
|
|
state.time_zoom_in()
|
|
},
|
|
Self::NoteLengthDecrement => {
|
|
state.note_length_dec()
|
|
},
|
|
Self::NoteLengthIncrement => {
|
|
state.note_length_inc()
|
|
},
|
|
Self::NotePageUp => {
|
|
state.note_page_up()
|
|
},
|
|
Self::NotePageDown => {
|
|
state.note_page_down()
|
|
},
|
|
Self::NoteAppend => if state.entered {
|
|
state.put();
|
|
state.time_cursor_advance();
|
|
},
|
|
Self::NoteSet => if state.entered {
|
|
state.put();
|
|
},
|
|
Self::GoUp => match state.entered {
|
|
true => state.note_cursor_inc(),
|
|
false => state.note_scroll_inc(),
|
|
},
|
|
Self::GoDown => match state.entered {
|
|
true => state.note_cursor_dec(),
|
|
false => state.note_scroll_dec(),
|
|
},
|
|
Self::GoLeft => match state.entered {
|
|
true => state.time_cursor_dec(),
|
|
false => state.time_scroll_dec(),
|
|
},
|
|
Self::GoRight => match state.entered {
|
|
true => state.time_cursor_inc(),
|
|
false => state.time_scroll_inc(),
|
|
},
|
|
}
|
|
Ok(None)
|
|
}
|
|
}
|