mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-04-28 05:20:14 +02:00
340 lines
14 KiB
Rust
340 lines
14 KiB
Rust
use crate::*;
|
|
use ClockCommand::{Play, Pause};
|
|
use KeyCode::{Char, Delete, Tab, Up, Down, Left, Right};
|
|
|
|
#[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(PhraseCommand),
|
|
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<Arc<RwLock<MidiClip>>>),
|
|
Enqueue(usize, usize),
|
|
Edit(Option<Arc<RwLock<MidiClip>>>),
|
|
SetLoop(usize, usize, bool),
|
|
SetColor(usize, usize, ItemPalette),
|
|
}
|
|
|
|
input_to_command!(ArrangerCommand: <Tui>|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(PhraseCommand::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) = PhraseCommand::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: &TuiInput) -> Option<ArrangerCommand> {
|
|
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
|
|
});
|