tek/src/arranger/arranger_command.rs
2025-01-06 21:10:36 +01:00

247 lines
9.2 KiB
Rust

use crate::*;
use ClockCommand::{Play, Pause};
use ArrangerCommand as Cmd;
#[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<Arc<RwLock<MidiClip>>>),
Enqueue(usize, usize),
Edit(Option<Arc<RwLock<MidiClip>>>),
SetLoop(usize, usize, bool),
SetColor(usize, usize, ItemPalette),
}
//handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self, input.event()));
//input_to_command!(ArrangerCommand: |state: Arranger, input: Event|{KEYS_ARRANGER.handle(state, input)?});
keymap!(KEYS_ARRANGER = |state: Arranger, input: Event| ArrangerCommand {
key(Char('u')) => Cmd::History(-1),
key(Char('U')) => Cmd::History(1),
// TODO: k: toggle on-screen keyboard
ctrl(key(Char('k'))) => { todo!("keyboard") },
// Transport: Play/pause
key(Char(' ')) => Cmd::Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }),
// Transport: Play from start or rewind to start
shift(key(Char(' '))) => Cmd::Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }),
key(Char('e')) => Cmd::Editor(MidiEditCommand::Show(Some(state.pool.phrase().clone()))),
ctrl(key(Char('a'))) => Cmd::Scene(ArrangerSceneCommand::Add),
ctrl(key(Char('t'))) => Cmd::Track(ArrangerTrackCommand::Add),
// Tab: Toggle visibility of phrase pool column
key(Tab) => Cmd::Phrases(PoolCommand::Show(!state.pool.visible)),
}, {
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 {
kpat!(Char('g')) => Some(Cmd::Phrases(PoolCommand::Select(0))),
kpat!(Char('q')) => Some(Cmd::Clip(Clip::Enqueue(t, s))),
kpat!(Char(',')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
kpat!(Char('.')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
kpat!(Char('<')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
kpat!(Char('>')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
kpat!(Char('p')) => Some(Cmd::Clip(Clip::Put(t, s, Some(state.pool.phrase().clone())))),
kpat!(Char('l')) => Some(Cmd::Clip(ArrangerClipCommand::SetLoop(t, s, false))),
kpat!(Delete) => Some(Cmd::Clip(Clip::Put(t, s, None))),
kpat!(Up) => Some(Cmd::Select(
if s > 0 { Selected::Clip(t, s - 1) } else { Selected::Track(t) })),
kpat!(Down) => Some(Cmd::Select(
Selected::Clip(t, (s + 1).min(s_len.saturating_sub(1))))),
kpat!(Left) => Some(Cmd::Select(
if t > 0 { Selected::Clip(t - 1, s) } else { Selected::Scene(s) })),
kpat!(Right) => Some(Cmd::Select(
Selected::Clip((t + 1).min(t_len.saturating_sub(1)), s))),
_ => None
},
Selected::Scene(s) => match input {
kpat!(Char(',')) => Some(Cmd::Scene(Scene::Swap(s, s - 1))),
kpat!(Char('.')) => Some(Cmd::Scene(Scene::Swap(s, s + 1))),
kpat!(Char('<')) => Some(Cmd::Scene(Scene::Swap(s, s - 1))),
kpat!(Char('>')) => Some(Cmd::Scene(Scene::Swap(s, s + 1))),
kpat!(Char('q')) => Some(Cmd::Scene(Scene::Enqueue(s))),
kpat!(Delete) => Some(Cmd::Scene(Scene::Delete(s))),
kpat!(Char('c')) => Some(Cmd::Scene(Scene::SetColor(s, ItemPalette::random()))),
kpat!(Up) => Some(
Cmd::Select(if s > 0 { Selected::Scene(s - 1) } else { Selected::Mix })),
kpat!(Down) => Some(
Cmd::Select(Selected::Scene((s + 1).min(s_len.saturating_sub(1))))),
kpat!(Left) =>
return None,
kpat!(Right) => Some(
Cmd::Select(Selected::Clip(0, s))),
_ => None
},
Selected::Track(t) => match input {
kpat!(Char(',')) => Some(Cmd::Track(Track::Swap(t, t - 1))),
kpat!(Char('.')) => Some(Cmd::Track(Track::Swap(t, t + 1))),
kpat!(Char('<')) => Some(Cmd::Track(Track::Swap(t, t - 1))),
kpat!(Char('>')) => Some(Cmd::Track(Track::Swap(t, t + 1))),
kpat!(Delete) => Some(Cmd::Track(Track::Delete(t))),
kpat!(Char('c')) => Some(Cmd::Track(Track::SetColor(t, ItemPalette::random()))),
kpat!(Up) =>
return None,
kpat!(Down) => Some(
Cmd::Select(Selected::Clip(t, 0))),
kpat!(Left) => Some(
Cmd::Select(if t > 0 { Selected::Track(t - 1) } else { Selected::Mix })),
kpat!(Right) => Some(
Cmd::Select(Selected::Track((t + 1).min(t_len.saturating_sub(1))))),
_ => None
},
Selected::Mix => match input {
kpat!(Delete) => Some(Cmd::Clear),
kpat!(Char('0')) => Some(Cmd::StopAll),
kpat!(Char('c')) => Some(Cmd::Color(ItemPalette::random())),
kpat!(Up) =>
return None,
kpat!(Down) => Some(
Cmd::Select(Selected::Scene(0))),
kpat!(Left) =>
return None,
kpat!(Right) => Some(
Cmd::Select(Selected::Track(0))),
_ => None
},
}
}.or_else(||if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) {
Some(Cmd::Editor(command))
} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) {
Some(Cmd::Phrases(command))
} else {
None
})?);
command!(|self: ArrangerCommand, state: Arranger|match self {
Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?,
Self::Clip(cmd) => cmd.delegate(state, Self::Clip)?,
Self::Scene(cmd) => cmd.delegate(state, Self::Scene)?,
Self::Track(cmd) => cmd.delegate(state, Self::Track)?,
Self::Editor(cmd) => cmd.delegate(&mut state.editor, Self::Editor)?,
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) => {
match cmd {
// autoselect: automatically load selected phrase in editor
PoolCommand::Select(_) => {
let undo = cmd.delegate(&mut state.pool, Self::Phrases)?;
state.editor.set_phrase(Some(state.pool.phrase()));
undo
},
// reload phrase in editor to update color
PoolCommand::Phrase(PhrasePoolCommand::SetColor(index, _)) => {
let undo = cmd.delegate(&mut state.pool, Self::Phrases)?;
state.editor.set_phrase(Some(state.pool.phrase()));
undo
},
_ => cmd.delegate(&mut state.pool, Self::Phrases)?
}
},
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: Arranger|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: Arranger|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: Arranger|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
});