mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
start porting some sequencer keybinds
This commit is contained in:
parent
8472805142
commit
de77daf565
6 changed files with 248 additions and 223 deletions
|
|
@ -7,8 +7,6 @@ pub(crate) mod midi_out; pub(crate) use midi_out::*;
|
|||
pub(crate) mod midi_phrase; pub(crate) use midi_phrase::*;
|
||||
pub(crate) mod midi_play; pub(crate) use midi_play::*;
|
||||
pub(crate) mod midi_rec; pub(crate) use midi_rec::*;
|
||||
pub(crate) mod midi_scene; pub(crate) use midi_scene::*;
|
||||
pub(crate) mod midi_track; pub(crate) use midi_track::*;
|
||||
|
||||
/// Add "all notes off" to the start of a buffer.
|
||||
pub fn all_notes_off (output: &mut [Vec<Vec<u8>>]) {
|
||||
|
|
|
|||
|
|
@ -1,96 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
pub trait HasScenes<S: ArrangerSceneApi> {
|
||||
fn scenes (&self) -> &Vec<S>;
|
||||
fn scenes_mut (&mut self) -> &mut Vec<S>;
|
||||
fn scene_add (&mut self, name: Option<&str>, color: Option<ItemPalette>) -> Usually<&mut S>;
|
||||
fn scene_del (&mut self, index: usize) {
|
||||
self.scenes_mut().remove(index);
|
||||
}
|
||||
fn scene_default_name (&self) -> String {
|
||||
format!("Scene {}", self.scenes().len() + 1)
|
||||
}
|
||||
fn selected_scene (&self) -> Option<&S> {
|
||||
None
|
||||
}
|
||||
fn selected_scene_mut (&mut self) -> Option<&mut S> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ArrangerSceneCommand {
|
||||
Add,
|
||||
Delete(usize),
|
||||
RandomColor,
|
||||
Play(usize),
|
||||
Swap(usize, usize),
|
||||
SetSize(usize),
|
||||
SetZoom(usize),
|
||||
}
|
||||
|
||||
//impl<T: ArrangerApi> Command<T> for ArrangerSceneCommand {
|
||||
//fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||
//match self {
|
||||
//Self::Delete(index) => { state.scene_del(index); },
|
||||
//_ => todo!()
|
||||
//}
|
||||
//Ok(None)
|
||||
//}
|
||||
//}
|
||||
|
||||
pub trait ArrangerSceneApi: Sized {
|
||||
fn name (&self) -> &Arc<RwLock<String>>;
|
||||
fn clips (&self) -> &Vec<Option<Arc<RwLock<Phrase>>>>;
|
||||
fn color (&self) -> ItemPalette;
|
||||
|
||||
fn ppqs (scenes: &[Self], factor: usize) -> Vec<(usize, usize)> {
|
||||
let mut total = 0;
|
||||
if factor == 0 {
|
||||
scenes.iter().map(|scene|{
|
||||
let pulses = scene.pulses().max(PPQ);
|
||||
total = total + pulses;
|
||||
(pulses, total - pulses)
|
||||
}).collect()
|
||||
} else {
|
||||
(0..=scenes.len()).map(|i|{
|
||||
(factor*PPQ, factor*PPQ*i)
|
||||
}).collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn longest_name (scenes: &[Self]) -> usize {
|
||||
scenes.iter().map(|s|s.name().read().unwrap().len()).fold(0, usize::max)
|
||||
}
|
||||
|
||||
/// Returns the pulse length of the longest phrase in the scene
|
||||
fn pulses (&self) -> usize {
|
||||
self.clips().iter().fold(0, |a, p|{
|
||||
a.max(p.as_ref().map(|q|q.read().unwrap().length).unwrap_or(0))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns true if all phrases in the scene are
|
||||
/// currently playing on the given collection of tracks.
|
||||
fn is_playing <T: ArrangerTrackApi> (&self, tracks: &[T]) -> bool {
|
||||
self.clips().iter().any(|clip|clip.is_some()) && self.clips().iter().enumerate()
|
||||
.all(|(track_index, clip)|match clip {
|
||||
Some(clip) => tracks
|
||||
.get(track_index)
|
||||
.map(|track|{
|
||||
if let Some((_, Some(phrase))) = track.player().play_phrase() {
|
||||
*phrase.read().unwrap() == *clip.read().unwrap()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.unwrap_or(false),
|
||||
None => true
|
||||
})
|
||||
}
|
||||
|
||||
fn clip (&self, index: usize) -> Option<&Arc<RwLock<Phrase>>> {
|
||||
match self.clips().get(index) { Some(Some(clip)) => Some(clip), _ => None }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
pub trait HasTracks<T: ArrangerTrackApi>: Send + Sync {
|
||||
fn tracks (&self) -> &Vec<T>;
|
||||
fn tracks_mut (&mut self) -> &mut Vec<T>;
|
||||
}
|
||||
|
||||
impl<T: ArrangerTrackApi> HasTracks<T> for Vec<T> {
|
||||
fn tracks (&self) -> &Vec<T> {
|
||||
self
|
||||
}
|
||||
fn tracks_mut (&mut self) -> &mut Vec<T> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ArrangerTracksApi<T: ArrangerTrackApi>: HasTracks<T> {
|
||||
fn track_add (&mut self, name: Option<&str>, color: Option<ItemPalette>)-> Usually<&mut T>;
|
||||
fn track_del (&mut self, index: usize);
|
||||
fn track_default_name (&self) -> String {
|
||||
format!("Track {}", self.tracks().len() + 1)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ArrangerTrackCommand {
|
||||
Add,
|
||||
Delete(usize),
|
||||
RandomColor,
|
||||
Stop,
|
||||
Swap(usize, usize),
|
||||
SetSize(usize),
|
||||
SetZoom(usize),
|
||||
}
|
||||
|
||||
pub trait ArrangerTrackApi: HasPlayer + Send + Sync + Sized {
|
||||
/// Name of track
|
||||
fn name (&self) -> &Arc<RwLock<String>>;
|
||||
/// Preferred width of track column
|
||||
fn width (&self) -> usize;
|
||||
/// Preferred width of track column
|
||||
fn width_mut (&mut self) -> &mut usize;
|
||||
/// Identifying color of track
|
||||
fn color (&self) -> ItemPalette;
|
||||
|
||||
fn longest_name (tracks: &[Self]) -> usize {
|
||||
tracks.iter().map(|s|s.name().read().unwrap().len()).fold(0, usize::max)
|
||||
}
|
||||
|
||||
const MIN_WIDTH: usize = 3;
|
||||
|
||||
fn width_inc (&mut self) {
|
||||
*self.width_mut() += 1;
|
||||
}
|
||||
|
||||
fn width_dec (&mut self) {
|
||||
if self.width() > Self::MIN_WIDTH {
|
||||
*self.width_mut() -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Hosts the JACK callback for a collection of tracks
|
||||
pub struct TracksAudio<'a, T: ArrangerTrackApi, H: HasTracks<T>>(
|
||||
// Track collection
|
||||
pub &'a mut H,
|
||||
/// Note buffer
|
||||
pub &'a mut Vec<u8>,
|
||||
/// Note chunk buffer
|
||||
pub &'a mut Vec<Vec<Vec<u8>>>,
|
||||
/// Marker
|
||||
pub PhantomData<T>,
|
||||
);
|
||||
|
||||
impl<'a, T: ArrangerTrackApi, H: HasTracks<T>> Audio for TracksAudio<'a, T, H> {
|
||||
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||
let model = &mut self.0;
|
||||
let note_buffer = &mut self.1;
|
||||
let output_buffer = &mut self.2;
|
||||
for track in model.tracks_mut().iter_mut() {
|
||||
if PlayerAudio(track.player_mut(), note_buffer, output_buffer).process(client, scope) == Control::Quit {
|
||||
return Control::Quit
|
||||
}
|
||||
}
|
||||
Control::Continue
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
use crate::*;
|
||||
use ClockCommand::{Play, Pause};
|
||||
use KeyCode::{Char, Down, Right, Delete};
|
||||
/// Root view for standalone `tek_arranger`
|
||||
pub struct ArrangerTui {
|
||||
pub jack: Arc<RwLock<JackClient>>,
|
||||
|
|
@ -42,10 +44,6 @@ from_jack!(|jack| ArrangerTui Self {
|
|||
note_buf: vec![],
|
||||
perf: PerfModel::default(),
|
||||
});
|
||||
has_clock!(|self: ArrangerTui|&self.clock);
|
||||
has_phrases!(|self: ArrangerTui|self.phrases.phrases);
|
||||
has_editor!(|self: ArrangerTui|self.editor);
|
||||
handle!(<Tui>|self: ArrangerTui, input|ArrangerCommand::execute_with_state(self, input));
|
||||
render!(<Tui>|self: ArrangerTui|{
|
||||
let arranger = ||lay!(|add|{
|
||||
let color = self.color;
|
||||
|
|
@ -112,10 +110,15 @@ audio!(|self: ArrangerTui, client, scope|{
|
|||
self.perf.update(t0, scope);
|
||||
return Control::Continue
|
||||
});
|
||||
has_clock!(|self: ArrangerTui|&self.clock);
|
||||
has_phrases!(|self: ArrangerTui|self.phrases.phrases);
|
||||
has_editor!(|self: ArrangerTui|self.editor);
|
||||
handle!(<Tui>|self: ArrangerTui, input|ArrangerCommand::execute_with_state(self, input));
|
||||
#[derive(Clone, Debug)] pub enum ArrangerCommand {
|
||||
Undo,
|
||||
Redo,
|
||||
Clear,
|
||||
StopAll,
|
||||
Color(ItemPalette),
|
||||
Clock(ClockCommand),
|
||||
Scene(ArrangerSceneCommand),
|
||||
|
|
@ -126,35 +129,60 @@ audio!(|self: ArrangerTui, client, scope|{
|
|||
Phrases(PhrasesCommand),
|
||||
Editor(PhraseCommand),
|
||||
}
|
||||
input_to_command!(ArrangerCommand: <Tui>|state:ArrangerTui,input|to_arranger_command(state, input)?);
|
||||
fn to_arranger_command (state: &ArrangerTui, input: &TuiInput) -> Option<ArrangerCommand> {
|
||||
use ArrangerSelection::*;
|
||||
use ArrangerCommand as Cmd;
|
||||
use KeyCode::Char;
|
||||
input_to_command!(ArrangerCommand: <Tui>|state:ArrangerTui,input|{
|
||||
use ArrangerSelection as Selected;
|
||||
use ArrangerCommand::*;
|
||||
// WSAD navigation, Q launches, E edits, PgUp/Down pool, Arrows editor
|
||||
Some(match input.event() {
|
||||
key_pat!(Char('e')) => Cmd::Editor(PhraseCommand::Show(Some(state.phrases.phrase().clone()))),
|
||||
key_pat!(Char('l')) => Cmd::Clip(ArrangerClipCommand::SetLoop(false)),
|
||||
key_pat!(Char('+')) => Cmd::Zoom(0), // TODO
|
||||
key_pat!(Char('=')) => Cmd::Zoom(0), // TODO
|
||||
key_pat!(Char('_')) => Cmd::Zoom(0), // TODO
|
||||
key_pat!(Char('-')) => Cmd::Zoom(0), // TODO
|
||||
key_pat!(Char('`')) => { todo!("toggle state mode") },
|
||||
key_pat!(Ctrl-Char('a')) => Cmd::Scene(ArrangerSceneCommand::Add),
|
||||
key_pat!(Ctrl-Char('t')) => Cmd::Track(ArrangerTrackCommand::Add),
|
||||
match input.event() {
|
||||
// Transport: Play/pause
|
||||
key_pat!(Char(' ')) =>
|
||||
Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }),
|
||||
// Transport: Play from start or rewind to start
|
||||
key_pat!(Shift-Char(' ')) =>
|
||||
Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }),
|
||||
// TODO: u: undo
|
||||
key_pat!(Char('u')) =>
|
||||
{ todo!("undo") },
|
||||
// TODO: Shift-U: redo
|
||||
key_pat!(Char('U')) =>
|
||||
{ todo!("redo") },
|
||||
// TODO: k: toggle on-screen keyboard
|
||||
key_pat!(Ctrl-Char('k')) =>
|
||||
{ todo!("keyboard") },
|
||||
key_pat!(Char('e')) =>
|
||||
Editor(PhraseCommand::Show(Some(state.phrases.phrase().clone()))),
|
||||
key_pat!(Char('l')) =>
|
||||
Clip(ArrangerClipCommand::SetLoop(false)),
|
||||
key_pat!(Ctrl-Char('a')) =>
|
||||
Scene(ArrangerSceneCommand::Add),
|
||||
key_pat!(Ctrl-Char('t')) =>
|
||||
Track(ArrangerTrackCommand::Add),
|
||||
key_pat!(Char('0')) => match state.selected() {
|
||||
Selected::Mix => StopAll,
|
||||
Selected::Track(t) => return None,
|
||||
Selected::Scene(s) => return None,
|
||||
Selected::Clip(t, s) => return None,
|
||||
},
|
||||
key_pat!(Char('q')) => match state.selected() {
|
||||
Selected::Mix => return None,
|
||||
Selected::Track(t) => return None,
|
||||
Selected::Scene(s) => return None,
|
||||
Selected::Clip(t, s) => return None,
|
||||
},
|
||||
_ => match state.selected() {
|
||||
Mix => to_arranger_mix_command(input)?,
|
||||
Track(t) => to_arranger_track_command(input, t)?,
|
||||
Scene(s) => to_arranger_scene_command(input, s)?,
|
||||
Clip(t, s) => to_arranger_clip_command(input, t, s)?,
|
||||
Selected::Mix => to_arranger_mix_command(input)?,
|
||||
Selected::Track(t) => to_arranger_track_command(input, t)?,
|
||||
Selected::Scene(s) => to_arranger_scene_command(input, s)?,
|
||||
Selected::Clip(t, s) => to_arranger_clip_command(input, t, s)?,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
fn to_arranger_mix_command (input: &TuiInput) -> Option<ArrangerCommand> {
|
||||
use KeyCode::{Char, Down, Right, Delete};
|
||||
use ArrangerCommand as Cmd;
|
||||
use ArrangerSelection as Select;
|
||||
Some(match input.event() {
|
||||
// 0: Enqueue phrase 0 (stop all)
|
||||
key_pat!(Char('0')) => Cmd::StopAll,
|
||||
key_pat!(Char('s')) => Cmd::Select(Select::Scene(0)),
|
||||
key_pat!(Char('d')) => Cmd::Select(Select::Track(0)),
|
||||
key_pat!(Char(',')) => Cmd::Zoom(0),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,99 @@
|
|||
use crate::*;
|
||||
|
||||
pub trait HasScenes<S: ArrangerSceneApi> {
|
||||
fn scenes (&self) -> &Vec<S>;
|
||||
fn scenes_mut (&mut self) -> &mut Vec<S>;
|
||||
fn scene_add (&mut self, name: Option<&str>, color: Option<ItemPalette>) -> Usually<&mut S>;
|
||||
fn scene_del (&mut self, index: usize) {
|
||||
self.scenes_mut().remove(index);
|
||||
}
|
||||
fn scene_default_name (&self) -> String {
|
||||
format!("Scene {}", self.scenes().len() + 1)
|
||||
}
|
||||
fn selected_scene (&self) -> Option<&S> {
|
||||
None
|
||||
}
|
||||
fn selected_scene_mut (&mut self) -> Option<&mut S> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ArrangerSceneCommand {
|
||||
Add,
|
||||
Delete(usize),
|
||||
RandomColor,
|
||||
Play(usize),
|
||||
Swap(usize, usize),
|
||||
SetSize(usize),
|
||||
SetZoom(usize),
|
||||
}
|
||||
|
||||
//impl<T: ArrangerApi> Command<T> for ArrangerSceneCommand {
|
||||
//fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||
//match self {
|
||||
//Self::Delete(index) => { state.scene_del(index); },
|
||||
//_ => todo!()
|
||||
//}
|
||||
//Ok(None)
|
||||
//}
|
||||
//}
|
||||
|
||||
pub trait ArrangerSceneApi: Sized {
|
||||
fn name (&self) -> &Arc<RwLock<String>>;
|
||||
fn clips (&self) -> &Vec<Option<Arc<RwLock<Phrase>>>>;
|
||||
fn color (&self) -> ItemPalette;
|
||||
|
||||
fn ppqs (scenes: &[Self], factor: usize) -> Vec<(usize, usize)> {
|
||||
let mut total = 0;
|
||||
if factor == 0 {
|
||||
scenes.iter().map(|scene|{
|
||||
let pulses = scene.pulses().max(PPQ);
|
||||
total = total + pulses;
|
||||
(pulses, total - pulses)
|
||||
}).collect()
|
||||
} else {
|
||||
(0..=scenes.len()).map(|i|{
|
||||
(factor*PPQ, factor*PPQ*i)
|
||||
}).collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn longest_name (scenes: &[Self]) -> usize {
|
||||
scenes.iter().map(|s|s.name().read().unwrap().len()).fold(0, usize::max)
|
||||
}
|
||||
|
||||
/// Returns the pulse length of the longest phrase in the scene
|
||||
fn pulses (&self) -> usize {
|
||||
self.clips().iter().fold(0, |a, p|{
|
||||
a.max(p.as_ref().map(|q|q.read().unwrap().length).unwrap_or(0))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns true if all phrases in the scene are
|
||||
/// currently playing on the given collection of tracks.
|
||||
fn is_playing <T: ArrangerTrackApi> (&self, tracks: &[T]) -> bool {
|
||||
self.clips().iter().any(|clip|clip.is_some()) && self.clips().iter().enumerate()
|
||||
.all(|(track_index, clip)|match clip {
|
||||
Some(clip) => tracks
|
||||
.get(track_index)
|
||||
.map(|track|{
|
||||
if let Some((_, Some(phrase))) = track.player().play_phrase() {
|
||||
*phrase.read().unwrap() == *clip.read().unwrap()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.unwrap_or(false),
|
||||
None => true
|
||||
})
|
||||
}
|
||||
|
||||
fn clip (&self, index: usize) -> Option<&Arc<RwLock<Phrase>>> {
|
||||
match self.clips().get(index) { Some(Some(clip)) => Some(clip), _ => None }
|
||||
}
|
||||
|
||||
}
|
||||
pub fn to_arranger_scene_command (input: &TuiInput, s: usize) -> Option<ArrangerCommand> {
|
||||
use KeyCode::{Char, Up, Down, Right, Enter, Delete};
|
||||
use ArrangerCommand as Cmd;
|
||||
|
|
@ -18,6 +113,7 @@ pub fn to_arranger_scene_command (input: &TuiInput, s: usize) -> Option<Arranger
|
|||
_ => return None
|
||||
})
|
||||
}
|
||||
|
||||
impl HasScenes<ArrangerScene> for ArrangerTui {
|
||||
fn scenes (&self) -> &Vec<ArrangerScene> {
|
||||
&self.scenes
|
||||
|
|
|
|||
|
|
@ -1,18 +1,104 @@
|
|||
use crate::*;
|
||||
use KeyCode::{Char, Down, Left, Right, Delete};
|
||||
|
||||
pub trait HasTracks<T: ArrangerTrackApi>: Send + Sync {
|
||||
fn tracks (&self) -> &Vec<T>;
|
||||
fn tracks_mut (&mut self) -> &mut Vec<T>;
|
||||
}
|
||||
|
||||
impl<T: ArrangerTrackApi> HasTracks<T> for Vec<T> {
|
||||
fn tracks (&self) -> &Vec<T> {
|
||||
self
|
||||
}
|
||||
fn tracks_mut (&mut self) -> &mut Vec<T> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ArrangerTracksApi<T: ArrangerTrackApi>: HasTracks<T> {
|
||||
fn track_add (&mut self, name: Option<&str>, color: Option<ItemPalette>)-> Usually<&mut T>;
|
||||
fn track_del (&mut self, index: usize);
|
||||
fn track_default_name (&self) -> String {
|
||||
format!("Track {}", self.tracks().len() + 1)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ArrangerTrackCommand {
|
||||
Add,
|
||||
Delete(usize),
|
||||
RandomColor,
|
||||
Stop,
|
||||
Swap(usize, usize),
|
||||
SetSize(usize),
|
||||
SetZoom(usize),
|
||||
}
|
||||
|
||||
pub trait ArrangerTrackApi: HasPlayer + Send + Sync + Sized {
|
||||
/// Name of track
|
||||
fn name (&self) -> &Arc<RwLock<String>>;
|
||||
/// Preferred width of track column
|
||||
fn width (&self) -> usize;
|
||||
/// Preferred width of track column
|
||||
fn width_mut (&mut self) -> &mut usize;
|
||||
/// Identifying color of track
|
||||
fn color (&self) -> ItemPalette;
|
||||
|
||||
fn longest_name (tracks: &[Self]) -> usize {
|
||||
tracks.iter().map(|s|s.name().read().unwrap().len()).fold(0, usize::max)
|
||||
}
|
||||
|
||||
const MIN_WIDTH: usize = 3;
|
||||
|
||||
fn width_inc (&mut self) {
|
||||
*self.width_mut() += 1;
|
||||
}
|
||||
|
||||
fn width_dec (&mut self) {
|
||||
if self.width() > Self::MIN_WIDTH {
|
||||
*self.width_mut() -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Hosts the JACK callback for a collection of tracks
|
||||
pub struct TracksAudio<'a, T: ArrangerTrackApi, H: HasTracks<T>>(
|
||||
// Track collection
|
||||
pub &'a mut H,
|
||||
/// Note buffer
|
||||
pub &'a mut Vec<u8>,
|
||||
/// Note chunk buffer
|
||||
pub &'a mut Vec<Vec<Vec<u8>>>,
|
||||
/// Marker
|
||||
pub PhantomData<T>,
|
||||
);
|
||||
|
||||
impl<'a, T: ArrangerTrackApi, H: HasTracks<T>> Audio for TracksAudio<'a, T, H> {
|
||||
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||
let model = &mut self.0;
|
||||
let note_buffer = &mut self.1;
|
||||
let output_buffer = &mut self.2;
|
||||
for track in model.tracks_mut().iter_mut() {
|
||||
if PlayerAudio(track.player_mut(), note_buffer, output_buffer).process(client, scope) == Control::Quit {
|
||||
return Control::Quit
|
||||
}
|
||||
}
|
||||
Control::Continue
|
||||
}
|
||||
}
|
||||
pub fn to_arranger_track_command (input: &TuiInput, t: usize) -> Option<ArrangerCommand> {
|
||||
use KeyCode::{Char, Down, Left, Right, Delete};
|
||||
use ArrangerCommand as Cmd;
|
||||
use ArrangerSelection as Select;
|
||||
use ArrangerTrackCommand as Track;
|
||||
use ArrangerCommand::*;
|
||||
use ArrangerSelection as Selected;
|
||||
use ArrangerTrackCommand as Tracks;
|
||||
Some(match input.event() {
|
||||
key_pat!(Char('s')) => Cmd::Select(Select::Clip(t, 0)),
|
||||
key_pat!(Char('a')) => Cmd::Select(if t > 0 { Select::Track(t - 1) } else { Select::Mix }),
|
||||
key_pat!(Char('d')) => Cmd::Select(Select::Track(t + 1)),
|
||||
key_pat!(Char(',')) => Cmd::Track(Track::Swap(t, t - 1)),
|
||||
key_pat!(Char('.')) => Cmd::Track(Track::Swap(t, t + 1)),
|
||||
key_pat!(Char('<')) => Cmd::Track(Track::Swap(t, t - 1)),
|
||||
key_pat!(Char('>')) => Cmd::Track(Track::Swap(t, t + 1)),
|
||||
key_pat!(Delete) => Cmd::Track(Track::Delete(t)),
|
||||
key_pat!(Char('s')) => Select(Selected::Clip(t, 0)),
|
||||
key_pat!(Char('a')) => Select(if t > 0 { Selected::Track(t - 1) } else { Selected::Mix }),
|
||||
key_pat!(Char('d')) => Select(Selected::Track(t + 1)),
|
||||
key_pat!(Char(',')) => Track(Tracks::Swap(t, t - 1)),
|
||||
key_pat!(Char('.')) => Track(Tracks::Swap(t, t + 1)),
|
||||
key_pat!(Char('<')) => Track(Tracks::Swap(t, t - 1)),
|
||||
key_pat!(Char('>')) => Track(Tracks::Swap(t, t + 1)),
|
||||
key_pat!(Delete) => Track(Tracks::Delete(t)),
|
||||
//key_pat!(Char('c')) => Cmd::Track(Track::Color(t, ItemPalette::random())),
|
||||
_ => return None
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue