mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
wip: what nuked the arranger
This commit is contained in:
parent
ba0ff4af98
commit
a92981bb50
11 changed files with 324 additions and 310 deletions
|
|
@ -170,15 +170,14 @@ pub fn main () -> Usually<()> {
|
||||||
}))?)?,
|
}))?)?,
|
||||||
TekMode::Arranger { scenes, tracks, track_width, .. } =>
|
TekMode::Arranger { scenes, tracks, track_width, .. } =>
|
||||||
engine.run(&jack.activate_with(|jack|Ok({
|
engine.run(&jack.activate_with(|jack|Ok({
|
||||||
let clip = default_clip();
|
|
||||||
let clock = default_clock(jack);
|
let clock = default_clock(jack);
|
||||||
let mut app = Arranger {
|
let mut app = Arranger {
|
||||||
jack: jack.clone(),
|
jack: jack.clone(),
|
||||||
midi_ins: vec![JackPort::<MidiIn>::new(jack, format!("M/{name}"), &midi_froms)?,],
|
midi_ins: vec![JackPort::<MidiIn>::new(jack, format!("M/{name}"), &midi_froms)?,],
|
||||||
midi_outs: vec![JackPort::<MidiOut>::new(jack, format!("{name}/M"), &midi_tos)?, ],
|
midi_outs: vec![JackPort::<MidiOut>::new(jack, format!("{name}/M"), &midi_tos)?, ],
|
||||||
clock,
|
clock,
|
||||||
pool: (&clip).into(),
|
pool: PoolModel::default(),//(&clip).into(),
|
||||||
editor: (&clip).into(),
|
editor: MidiEditor::default(),//(&clip).into(),
|
||||||
selected: ArrangerSelection::Clip(0, 0),
|
selected: ArrangerSelection::Clip(0, 0),
|
||||||
scenes: vec![],
|
scenes: vec![],
|
||||||
tracks: vec![],
|
tracks: vec![],
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
pub trait HasMidiClip {
|
pub trait HasMidiClip {
|
||||||
fn clip (&self) -> &Arc<RwLock<MidiClip>>;
|
fn clip (&self) -> Option<Arc<RwLock<MidiClip>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export] macro_rules! has_clip {
|
#[macro_export] macro_rules! has_clip {
|
||||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasMidiClip for $Struct $(<$($L),*$($T),*>)? {
|
impl $(<$($L),*$($T $(: $U)?),*>)? HasMidiClip for $Struct $(<$($L),*$($T),*>)? {
|
||||||
fn clip (&$self) -> &Arc<RwLock<MidiClip>> { &$cb }
|
fn clip (&$self) -> Option<Arc<RwLock<MidiClip>>> { $cb }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,21 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
pub trait HasPhrases {
|
pub type ClipPool = Vec<Arc<RwLock<MidiClip>>>;
|
||||||
fn clips (&self) -> &Vec<Arc<RwLock<MidiClip>>>;
|
|
||||||
fn clips_mut (&mut self) -> &mut Vec<Arc<RwLock<MidiClip>>>;
|
pub trait HasClips {
|
||||||
|
fn clips <'a> (&'a self) -> std::sync::RwLockReadGuard<'a, ClipPool>;
|
||||||
|
fn clips_mut <'a> (&'a self) -> std::sync::RwLockWriteGuard<'a, ClipPool>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export] macro_rules! has_clips {
|
#[macro_export] macro_rules! has_clips {
|
||||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasPhrases for $Struct $(<$($L),*$($T),*>)? {
|
impl $(<$($L),*$($T $(: $U)?),*>)? HasClips for $Struct $(<$($L),*$($T),*>)? {
|
||||||
fn clips (&$self) -> &Vec<Arc<RwLock<MidiClip>>> { &$cb }
|
fn clips <'a> (&'a $self) -> std::sync::RwLockReadGuard<'a, ClipPool> {
|
||||||
fn clips_mut (&mut $self) -> &mut Vec<Arc<RwLock<MidiClip>>> { &mut$cb }
|
$cb.read().unwrap()
|
||||||
|
}
|
||||||
|
fn clips_mut <'a> (&'a $self) -> std::sync::RwLockWriteGuard<'a, ClipPool> {
|
||||||
|
$cb.write().unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -26,13 +32,13 @@ pub enum MidiPoolCommand {
|
||||||
SetColor(usize, ItemColor),
|
SetColor(usize, ItemColor),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: HasPhrases> Command<T> for MidiPoolCommand {
|
impl<T: HasClips> Command<T> for MidiPoolCommand {
|
||||||
fn execute (self, model: &mut T) -> Perhaps<Self> {
|
fn execute (self, model: &mut T) -> Perhaps<Self> {
|
||||||
use MidiPoolCommand::*;
|
use MidiPoolCommand::*;
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
Add(mut index, clip) => {
|
Add(mut index, clip) => {
|
||||||
let clip = Arc::new(RwLock::new(clip));
|
let clip = Arc::new(RwLock::new(clip));
|
||||||
let clips = model.clips_mut();
|
let mut clips = model.clips_mut();
|
||||||
if index >= clips.len() {
|
if index >= clips.len() {
|
||||||
index = clips.len();
|
index = clips.len();
|
||||||
clips.push(clip)
|
clips.push(clip)
|
||||||
|
|
@ -72,15 +78,15 @@ impl<T: HasPhrases> Command<T> for MidiPoolCommand {
|
||||||
todo!("export clip to midi file");
|
todo!("export clip to midi file");
|
||||||
},
|
},
|
||||||
SetName(index, name) => {
|
SetName(index, name) => {
|
||||||
let mut clip = model.clips()[index].write().unwrap();
|
let clip = &mut model.clips_mut()[index];
|
||||||
let old_name = clip.name.clone();
|
let old_name = clip.read().unwrap().name.clone();
|
||||||
clip.name = name;
|
clip.write().unwrap().name = name;
|
||||||
Some(Self::SetName(index, old_name))
|
Some(Self::SetName(index, old_name))
|
||||||
},
|
},
|
||||||
SetLength(index, length) => {
|
SetLength(index, length) => {
|
||||||
let mut clip = model.clips()[index].write().unwrap();
|
let clip = &mut model.clips_mut()[index];
|
||||||
let old_len = clip.length;
|
let old_len = clip.read().unwrap().length;
|
||||||
clip.length = length;
|
clip.write().unwrap().length = length;
|
||||||
Some(Self::SetLength(index, old_len))
|
Some(Self::SetLength(index, old_len))
|
||||||
},
|
},
|
||||||
SetColor(index, color) => {
|
SetColor(index, color) => {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use tek::*;
|
||||||
|
|
||||||
struct ExamplePhrases(Vec<Arc<RwLock<Phrase>>>);
|
struct ExamplePhrases(Vec<Arc<RwLock<Phrase>>>);
|
||||||
|
|
||||||
impl HasPhrases for ExamplePhrases {
|
impl HasClips for ExamplePhrases {
|
||||||
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
(bsp/s :toolbar
|
(bsp/s :toolbar
|
||||||
(bsp/w :pool (fill/x (align/c
|
(fill/x (align/c
|
||||||
(bsp/s :outputs (bsp/s :inputs (bsp/s :tracks :scenes)))))))
|
(bsp/s :outputs (bsp/s :inputs (bsp/s :tracks :scenes)))))))
|
||||||
|
|
|
||||||
|
|
@ -310,7 +310,7 @@ impl Arranger {
|
||||||
|
|
||||||
fn scene_row (&self, tracks_w: u16) -> impl Content<TuiOut> + '_ {
|
fn scene_row (&self, tracks_w: u16) -> impl Content<TuiOut> + '_ {
|
||||||
let h = (self.size.h() as u16).saturating_sub(8).max(8);
|
let h = (self.size.h() as u16).saturating_sub(8).max(8);
|
||||||
let border = |x|Skinny(Style::default().fg(Color::Rgb(0,0,0)).bg(Color::Reset)).enclose2(x);
|
let border = |x|x;//Skinny(Style::default().fg(Color::Rgb(0,0,0)).bg(Color::Reset)).enclose2(x);
|
||||||
Bsp::e(
|
Bsp::e(
|
||||||
Tui::bg(Color::Reset, Fixed::xy(self.sidebar_w() as u16, h, self.scene_headers())),
|
Tui::bg(Color::Reset, Fixed::xy(self.sidebar_w() as u16, h, self.scene_headers())),
|
||||||
Tui::bg(Color::Reset, Fill::x(Align::c(Fixed::xy(tracks_w, h, border(self.scene_cells())))))
|
Tui::bg(Color::Reset, Fill::x(Align::c(Fixed::xy(tracks_w, h, border(self.scene_cells())))))
|
||||||
|
|
@ -353,7 +353,11 @@ impl Arranger {
|
||||||
let cells = Map::new(scenes, move|(_, scene, y1, y2), s| {
|
let cells = Map::new(scenes, move|(_, scene, y1, y2), s| {
|
||||||
let h = (y2 - y1) as u16;
|
let h = (y2 - y1) as u16;
|
||||||
let color = scene.color();
|
let color = scene.color();
|
||||||
let name = "⏹ ";
|
let name = if let Some(c) = &scene.clips[t] {
|
||||||
|
c.read().unwrap().name.to_string()
|
||||||
|
} else {
|
||||||
|
"⏹ ".to_string()
|
||||||
|
};
|
||||||
let last = last_color.read().unwrap().clone();
|
let last = last_color.read().unwrap().clone();
|
||||||
//let cell = phat_sel_3(
|
//let cell = phat_sel_3(
|
||||||
//selected_track == Some(i) && selected_scene == Some(j),
|
//selected_track == Some(i) && selected_scene == Some(j),
|
||||||
|
|
@ -680,8 +684,9 @@ keymap!(KEYS_ARRANGER = |state: Arranger, input: Event| ArrangerCommand {
|
||||||
key(Char(' ')) => Cmd::Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }),
|
key(Char(' ')) => Cmd::Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }),
|
||||||
// Transport: Play from start or rewind to start
|
// 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)) }),
|
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.clip().clone()))),
|
key(Char('e')) => Cmd::Editor(MidiEditCommand::Show(state.pool.clip().clone())),
|
||||||
ctrl(key(Char('a'))) => Cmd::Scene(ArrangerSceneCommand::Add),
|
ctrl(key(Char('a'))) => Cmd::Scene(ArrangerSceneCommand::Add),
|
||||||
|
ctrl(key(Char('A'))) => return None,//Cmd::Scene(ArrangerSceneCommand::Add),
|
||||||
ctrl(key(Char('t'))) => Cmd::Track(ArrangerTrackCommand::Add),
|
ctrl(key(Char('t'))) => Cmd::Track(ArrangerTrackCommand::Add),
|
||||||
// Tab: Toggle visibility of clip pool column
|
// Tab: Toggle visibility of clip pool column
|
||||||
key(Tab) => Cmd::Phrases(PoolCommand::Show(!state.pool.visible)),
|
key(Tab) => Cmd::Phrases(PoolCommand::Show(!state.pool.visible)),
|
||||||
|
|
@ -697,6 +702,7 @@ keymap!(KEYS_ARRANGER = |state: Arranger, input: Event| ArrangerCommand {
|
||||||
Selected::Scene(s) => scene_keymap(state, input, s),
|
Selected::Scene(s) => scene_keymap(state, input, s),
|
||||||
Selected::Track(t) => track_keymap(state, input, t),
|
Selected::Track(t) => track_keymap(state, input, t),
|
||||||
Selected::Mix => match input {
|
Selected::Mix => match input {
|
||||||
|
|
||||||
kpat!(Delete) => Some(Cmd::Clear),
|
kpat!(Delete) => Some(Cmd::Clear),
|
||||||
kpat!(Char('0')) => Some(Cmd::StopAll),
|
kpat!(Char('0')) => Some(Cmd::StopAll),
|
||||||
kpat!(Char('c')) => Some(Cmd::Color(ItemPalette::random())),
|
kpat!(Char('c')) => Some(Cmd::Color(ItemPalette::random())),
|
||||||
|
|
@ -725,23 +731,30 @@ fn clip_keymap (state: &Arranger, input: &Event, t: usize, s: usize) -> Option<A
|
||||||
let t_len = state.tracks.len();
|
let t_len = state.tracks.len();
|
||||||
let s_len = state.scenes.len();
|
let s_len = state.scenes.len();
|
||||||
Some(match input {
|
Some(match input {
|
||||||
|
|
||||||
kpat!(Char('g')) => Cmd::Phrases(PoolCommand::Select(0)),
|
kpat!(Char('g')) => Cmd::Phrases(PoolCommand::Select(0)),
|
||||||
kpat!(Char('q')) => Cmd::Clip(Clip::Enqueue(t, s)),
|
kpat!(Char('q')) => Cmd::Clip(Clip::Enqueue(t, s)),
|
||||||
kpat!(Char('l')) => Cmd::Clip(Clip::SetLoop(t, s, false)),
|
kpat!(Char('l')) => Cmd::Clip(Clip::SetLoop(t, s, false)),
|
||||||
|
|
||||||
|
kpat!(Enter) => if state.scenes[s].clips[t].is_none() {
|
||||||
|
// FIXME: get this clip from the pool (autoregister via intmut)
|
||||||
|
let clip = Arc::new(RwLock::new(MidiClip::default()));
|
||||||
|
Cmd::Clip(Clip::Put(t, s, Some(clip)))
|
||||||
|
} else {
|
||||||
|
return None
|
||||||
|
},
|
||||||
kpat!(Delete) => Cmd::Clip(Clip::Put(t, s, None)),
|
kpat!(Delete) => Cmd::Clip(Clip::Put(t, s, None)),
|
||||||
kpat!(Char('p')) => Cmd::Clip(Clip::Put(t, s, Some(state.pool.clip().clone()))),
|
kpat!(Char('p')) => Cmd::Clip(Clip::Put(t, s, state.pool.clip().clone())),
|
||||||
kpat!(Char(',')) => Cmd::Clip(Clip::Put(t, s, None)),
|
kpat!(Char(',')) => Cmd::Clip(Clip::Put(t, s, None)),
|
||||||
kpat!(Char('.')) => Cmd::Clip(Clip::Put(t, s, None)),
|
kpat!(Char('.')) => Cmd::Clip(Clip::Put(t, s, None)),
|
||||||
kpat!(Char('<')) => Cmd::Clip(Clip::Put(t, s, None)),
|
kpat!(Char('<')) => Cmd::Clip(Clip::Put(t, s, None)),
|
||||||
kpat!(Char('>')) => Cmd::Clip(Clip::Put(t, s, None)),
|
kpat!(Char('>')) => Cmd::Clip(Clip::Put(t, s, None)),
|
||||||
kpat!(Up) =>
|
|
||||||
Cmd::Select(if s > 0 { Selected::Clip(t, s - 1) } else { Selected::Track(t) }),
|
kpat!(Up) => Cmd::Select(if s > 0 { Selected::Clip(t, s - 1) } else { Selected::Track(t) }),
|
||||||
kpat!(Down) =>
|
kpat!(Down) => Cmd::Select(Selected::Clip(t, (s + 1).min(s_len.saturating_sub(1)))),
|
||||||
Cmd::Select(Selected::Clip(t, (s + 1).min(s_len.saturating_sub(1)))),
|
kpat!(Left) => Cmd::Select(if t > 0 { Selected::Clip(t - 1, s) } else { Selected::Scene(s) }),
|
||||||
kpat!(Left) =>
|
kpat!(Right) => Cmd::Select(Selected::Clip((t + 1).min(t_len.saturating_sub(1)), s)),
|
||||||
Cmd::Select(if t > 0 { Selected::Clip(t - 1, s) } else { Selected::Scene(s) }),
|
|
||||||
kpat!(Right) =>
|
|
||||||
Cmd::Select(Selected::Clip((t + 1).min(t_len.saturating_sub(1)), s)),
|
|
||||||
_ => return None
|
_ => return None
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -753,6 +766,7 @@ fn scene_keymap (state: &Arranger, input: &Event, s: usize) -> Option<ArrangerCo
|
||||||
let t_len = state.tracks.len();
|
let t_len = state.tracks.len();
|
||||||
let s_len = state.scenes.len();
|
let s_len = state.scenes.len();
|
||||||
Some(match input {
|
Some(match input {
|
||||||
|
|
||||||
kpat!(Char(',')) => Cmd::Scene(Scene::Swap(s, s - 1)),
|
kpat!(Char(',')) => Cmd::Scene(Scene::Swap(s, s - 1)),
|
||||||
kpat!(Char('.')) => Cmd::Scene(Scene::Swap(s, s + 1)),
|
kpat!(Char('.')) => Cmd::Scene(Scene::Swap(s, s + 1)),
|
||||||
kpat!(Char('<')) => Cmd::Scene(Scene::Swap(s, s - 1)),
|
kpat!(Char('<')) => Cmd::Scene(Scene::Swap(s, s - 1)),
|
||||||
|
|
@ -761,14 +775,10 @@ fn scene_keymap (state: &Arranger, input: &Event, s: usize) -> Option<ArrangerCo
|
||||||
kpat!(Delete) => Cmd::Scene(Scene::Delete(s)),
|
kpat!(Delete) => Cmd::Scene(Scene::Delete(s)),
|
||||||
kpat!(Char('c')) => Cmd::Scene(Scene::SetColor(s, ItemPalette::random())),
|
kpat!(Char('c')) => Cmd::Scene(Scene::SetColor(s, ItemPalette::random())),
|
||||||
|
|
||||||
kpat!(Up) =>
|
kpat!(Up) => Cmd::Select(if s > 0 { Selected::Scene(s - 1) } else { Selected::Mix }),
|
||||||
Cmd::Select(if s > 0 { Selected::Scene(s - 1) } else { Selected::Mix }),
|
kpat!(Down) => Cmd::Select(Selected::Scene((s + 1).min(s_len.saturating_sub(1)))),
|
||||||
kpat!(Down) =>
|
kpat!(Left) => return None,
|
||||||
Cmd::Select(Selected::Scene((s + 1).min(s_len.saturating_sub(1)))),
|
kpat!(Right) => Cmd::Select(Selected::Clip(0, s)),
|
||||||
kpat!(Left) =>
|
|
||||||
return None,
|
|
||||||
kpat!(Right) =>
|
|
||||||
Cmd::Select(Selected::Clip(0, s)),
|
|
||||||
|
|
||||||
_ => return None
|
_ => return None
|
||||||
})
|
})
|
||||||
|
|
@ -781,6 +791,7 @@ fn track_keymap (state: &Arranger, input: &Event, t: usize) -> Option<ArrangerCo
|
||||||
let t_len = state.tracks.len();
|
let t_len = state.tracks.len();
|
||||||
let s_len = state.scenes.len();
|
let s_len = state.scenes.len();
|
||||||
Some(match input {
|
Some(match input {
|
||||||
|
|
||||||
kpat!(Char(',')) => Cmd::Track(Track::Swap(t, t - 1)),
|
kpat!(Char(',')) => Cmd::Track(Track::Swap(t, t - 1)),
|
||||||
kpat!(Char('.')) => Cmd::Track(Track::Swap(t, t + 1)),
|
kpat!(Char('.')) => Cmd::Track(Track::Swap(t, t + 1)),
|
||||||
kpat!(Char('<')) => Cmd::Track(Track::Swap(t, t - 1)),
|
kpat!(Char('<')) => Cmd::Track(Track::Swap(t, t - 1)),
|
||||||
|
|
@ -788,14 +799,10 @@ fn track_keymap (state: &Arranger, input: &Event, t: usize) -> Option<ArrangerCo
|
||||||
kpat!(Delete) => Cmd::Track(Track::Delete(t)),
|
kpat!(Delete) => Cmd::Track(Track::Delete(t)),
|
||||||
kpat!(Char('c')) => Cmd::Track(Track::SetColor(t, ItemPalette::random())),
|
kpat!(Char('c')) => Cmd::Track(Track::SetColor(t, ItemPalette::random())),
|
||||||
|
|
||||||
kpat!(Up) =>
|
kpat!(Up) => return None,
|
||||||
return None,
|
kpat!(Down) => Cmd::Select(Selected::Clip(t, 0)),
|
||||||
kpat!(Down) =>
|
kpat!(Left) => Cmd::Select(if t > 0 { Selected::Track(t - 1) } else { Selected::Mix }),
|
||||||
Cmd::Select(Selected::Clip(t, 0)),
|
kpat!(Right) => Cmd::Select(Selected::Track((t + 1).min(t_len.saturating_sub(1)))),
|
||||||
kpat!(Left) =>
|
|
||||||
Cmd::Select(if t > 0 { Selected::Track(t - 1) } else { Selected::Mix }),
|
|
||||||
kpat!(Right) =>
|
|
||||||
Cmd::Select(Selected::Track((t + 1).min(t_len.saturating_sub(1)))),
|
|
||||||
|
|
||||||
_ => return None
|
_ => return None
|
||||||
})
|
})
|
||||||
|
|
@ -825,19 +832,20 @@ command!(|self: ArrangerCommand, state: Arranger|match self {
|
||||||
// autoselect: automatically load selected clip in editor
|
// autoselect: automatically load selected clip in editor
|
||||||
PoolCommand::Select(_) => {
|
PoolCommand::Select(_) => {
|
||||||
let undo = cmd.delegate(&mut state.pool, Self::Phrases)?;
|
let undo = cmd.delegate(&mut state.pool, Self::Phrases)?;
|
||||||
state.editor.set_clip(Some(state.pool.clip()));
|
state.editor.set_clip(state.pool.clip().as_ref());
|
||||||
undo
|
undo
|
||||||
},
|
},
|
||||||
// reload clip in editor to update color
|
// reload clip in editor to update color
|
||||||
PoolCommand::Phrase(MidiPoolCommand::SetColor(index, _)) => {
|
PoolCommand::Phrase(MidiPoolCommand::SetColor(index, _)) => {
|
||||||
let undo = cmd.delegate(&mut state.pool, Self::Phrases)?;
|
let undo = cmd.delegate(&mut state.pool, Self::Phrases)?;
|
||||||
state.editor.set_clip(Some(state.pool.clip()));
|
state.editor.set_clip(state.pool.clip().as_ref());
|
||||||
undo
|
undo
|
||||||
},
|
},
|
||||||
_ => cmd.delegate(&mut state.pool, Self::Phrases)?
|
_ => cmd.delegate(&mut state.pool, Self::Phrases)?
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
command!(|self: ArrangerClipCommand, state: Arranger|match self {
|
command!(|self: ArrangerClipCommand, state: Arranger|match self {
|
||||||
Self::Get(track, scene) => { todo!() },
|
Self::Get(track, scene) => { todo!() },
|
||||||
Self::Put(track, scene, clip) => {
|
Self::Put(track, scene, clip) => {
|
||||||
|
|
|
||||||
|
|
@ -151,13 +151,13 @@ command!(|self: GrooveboxCommand, state: Groovebox|match self {
|
||||||
// autoselect: automatically load selected clip in editor
|
// autoselect: automatically load selected clip in editor
|
||||||
PoolCommand::Select(_) => {
|
PoolCommand::Select(_) => {
|
||||||
let undo = cmd.delegate(&mut state.pool, Self::Pool)?;
|
let undo = cmd.delegate(&mut state.pool, Self::Pool)?;
|
||||||
state.editor.set_clip(Some(state.pool.clip()));
|
state.editor.set_clip(state.pool.clip().as_ref());
|
||||||
undo
|
undo
|
||||||
},
|
},
|
||||||
// update color in all places simultaneously
|
// update color in all places simultaneously
|
||||||
PoolCommand::Phrase(SetColor(index, _)) => {
|
PoolCommand::Phrase(SetColor(index, _)) => {
|
||||||
let undo = cmd.delegate(&mut state.pool, Self::Pool)?;
|
let undo = cmd.delegate(&mut state.pool, Self::Pool)?;
|
||||||
state.editor.set_clip(Some(state.pool.clip()));
|
state.editor.set_clip(state.pool.clip().as_ref());
|
||||||
undo
|
undo
|
||||||
},
|
},
|
||||||
_ => cmd.delegate(&mut state.pool, Self::Pool)?
|
_ => cmd.delegate(&mut state.pool, Self::Pool)?
|
||||||
|
|
@ -181,7 +181,7 @@ keymap!(<'a> KEYS_GROOVEBOX = |state: Groovebox, input: Event| GrooveboxCommand
|
||||||
// Tab: Toggle compact mode
|
// Tab: Toggle compact mode
|
||||||
key(Tab) => Cmd::Compact(!state.compact),
|
key(Tab) => Cmd::Compact(!state.compact),
|
||||||
// q: Enqueue currently edited clip
|
// q: Enqueue currently edited clip
|
||||||
key(Char('q')) => Cmd::Enqueue(Some(state.pool.clip().clone())),
|
key(Char('q')) => Cmd::Enqueue(state.pool.clip().clone()),
|
||||||
// 0: Enqueue clip 0 (stop all)
|
// 0: Enqueue clip 0 (stop all)
|
||||||
key(Char('0')) => Cmd::Enqueue(Some(state.pool.clips()[0].clone())),
|
key(Char('0')) => Cmd::Enqueue(Some(state.pool.clips()[0].clone())),
|
||||||
// TODO: k: toggle on-screen keyboard
|
// TODO: k: toggle on-screen keyboard
|
||||||
|
|
@ -201,17 +201,17 @@ keymap!(<'a> KEYS_GROOVEBOX = |state: Groovebox, input: Event| GrooveboxCommand
|
||||||
SamplerCommand::SetSample(u7::from(state.editor.note_point() as u8), None)
|
SamplerCommand::SetSample(u7::from(state.editor.note_point() as u8), None)
|
||||||
),
|
),
|
||||||
// e: Toggle between editing currently playing or other clip
|
// e: Toggle between editing currently playing or other clip
|
||||||
shift(key(Char('e'))) => if let Some((_, Some(playing))) = state.player.play_clip() {
|
//shift(key(Char('e'))) => if let Some((_, Some(playing))) = state.player.play_clip() {
|
||||||
let editing = state.editor.clip().as_ref().map(|p|p.read().unwrap().clone());
|
//let editing = state.editor.clip().as_ref().map(|p|p.read().unwrap().clone());
|
||||||
let selected = state.pool.clip().clone();
|
//let selected = state.pool.clip().clone().map(|s|s.read().unwrap().clone());
|
||||||
Cmd::Editor(Show(Some(if Some(selected.read().unwrap().clone()) != editing {
|
//Cmd::Editor(Show(if selected != editing {
|
||||||
selected
|
//selected
|
||||||
} else {
|
//} else {
|
||||||
playing.clone()
|
//Some(playing.clone())
|
||||||
})))
|
//}))
|
||||||
} else {
|
//} else {
|
||||||
return None
|
//return None
|
||||||
},
|
//},
|
||||||
}, if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) {
|
}, if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) {
|
||||||
Cmd::Editor(command)
|
Cmd::Editor(command)
|
||||||
} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) {
|
} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) {
|
||||||
|
|
|
||||||
253
tek/src/pool.rs
253
tek/src/pool.rs
|
|
@ -1,5 +1,8 @@
|
||||||
mod clip_length; pub use self::clip_length::*;
|
use crate::*;
|
||||||
mod clip_rename; pub use self::clip_rename::*;
|
use super::*;
|
||||||
|
use PhraseLengthFocus::*;
|
||||||
|
use PhraseLengthCommand::*;
|
||||||
|
use KeyCode::{Up, Down, Left, Right, Enter, Esc};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
@ -7,7 +10,7 @@ use super::*;
|
||||||
pub struct PoolModel {
|
pub struct PoolModel {
|
||||||
pub(crate) visible: bool,
|
pub(crate) visible: bool,
|
||||||
/// Collection of clips
|
/// Collection of clips
|
||||||
pub(crate) clips: Vec<Arc<RwLock<MidiClip>>>,
|
pub(crate) clips: Arc<RwLock<Vec<Arc<RwLock<MidiClip>>>>>,
|
||||||
/// Selected clip
|
/// Selected clip
|
||||||
pub(crate) clip: AtomicUsize,
|
pub(crate) clip: AtomicUsize,
|
||||||
/// Mode switch
|
/// Mode switch
|
||||||
|
|
@ -17,15 +20,34 @@ pub struct PoolModel {
|
||||||
/// Scroll offset
|
/// Scroll offset
|
||||||
scroll: usize,
|
scroll: usize,
|
||||||
}
|
}
|
||||||
|
impl Default for PoolModel {
|
||||||
|
fn default () -> Self {
|
||||||
|
Self {
|
||||||
|
visible: true,
|
||||||
|
clips: Arc::from(RwLock::from(vec![])),
|
||||||
|
clip: 0.into(),
|
||||||
|
scroll: 0,
|
||||||
|
mode: None,
|
||||||
|
size: Measure::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
from!(|clip:&Arc<RwLock<MidiClip>>|PoolModel = {
|
||||||
|
let mut model = Self::default();
|
||||||
|
model.clips.write().unwrap().push(clip.clone());
|
||||||
|
model.clip.store(1, Relaxed);
|
||||||
|
model
|
||||||
|
});
|
||||||
|
|
||||||
pub struct PoolView<'a>(pub bool, pub &'a PoolModel);
|
pub struct PoolView<'a>(pub bool, pub &'a PoolModel);
|
||||||
render!(TuiOut: (self: PoolView<'a>) => {
|
render!(TuiOut: (self: PoolView<'a>) => {
|
||||||
let Self(compact, model) = self;
|
let Self(compact, model) = self;
|
||||||
let PoolModel { clips, mode, .. } = self.1;
|
let PoolModel { clips, mode, .. } = self.1;
|
||||||
let color = self.1.clip().read().unwrap().color;
|
let color = self.1.clip().map(|c|c.read().unwrap().color).unwrap_or_else(||TuiTheme::g(32).into());
|
||||||
let on_bg = |x|x;//Bsp::b(Repeat(" "), Tui::bg(color.darkest.rgb, x));
|
let on_bg = |x|x;//Bsp::b(Repeat(" "), Tui::bg(color.darkest.rgb, x));
|
||||||
let border = |x|x;//Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb)).enclose(x);
|
let border = |x|x;//Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb)).enclose(x);
|
||||||
Tui::bg(Color::Green, Fixed::y(clips.len() as u16, on_bg(border(Map::new(||model.clips().iter(), move|clip, i|{
|
let iter = | |model.clips().clone().into_iter();
|
||||||
|
Tui::bg(Color::Reset, Fixed::y(clips.read().unwrap().len() as u16, on_bg(border(Map::new(iter, move|clip, i|{
|
||||||
let item_height = 1;
|
let item_height = 1;
|
||||||
let item_offset = i as u16 * item_height;
|
let item_offset = i as u16 * item_height;
|
||||||
let selected = i == model.clip_index();
|
let selected = i == model.clip_index();
|
||||||
|
|
@ -183,26 +205,8 @@ fn to_clips_command (state: &PoolModel, input: &Event) -> Option<PoolCommand> {
|
||||||
_ => return None
|
_ => return None
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
impl Default for PoolModel {
|
|
||||||
fn default () -> Self {
|
|
||||||
Self {
|
|
||||||
visible: true,
|
|
||||||
clips: vec![RwLock::new(MidiClip::default()).into()],
|
|
||||||
clip: 0.into(),
|
|
||||||
scroll: 0,
|
|
||||||
mode: None,
|
|
||||||
size: Measure::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
from!(|clip:&Arc<RwLock<MidiClip>>|PoolModel = {
|
|
||||||
let mut model = Self::default();
|
|
||||||
model.clips.push(clip.clone());
|
|
||||||
model.clip.store(1, Relaxed);
|
|
||||||
model
|
|
||||||
});
|
|
||||||
has_clips!(|self: PoolModel|self.clips);
|
has_clips!(|self: PoolModel|self.clips);
|
||||||
has_clip!(|self: PoolModel|self.clips[self.clip_index()]);
|
has_clip!(|self: PoolModel|self.clips().get(self.clip_index()).map(|c|c.clone()));
|
||||||
impl PoolModel {
|
impl PoolModel {
|
||||||
pub(crate) fn clip_index (&self) -> usize {
|
pub(crate) fn clip_index (&self) -> usize {
|
||||||
self.clip.load(Relaxed)
|
self.clip.load(Relaxed)
|
||||||
|
|
@ -288,3 +292,204 @@ input_to_command!(FileBrowserCommand: |state: PoolModel, input: Event|{
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Displays and edits clip length.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PhraseLength {
|
||||||
|
/// Pulses per beat (quaver)
|
||||||
|
pub ppq: usize,
|
||||||
|
/// Beats per bar
|
||||||
|
pub bpb: usize,
|
||||||
|
/// Length of clip in pulses
|
||||||
|
pub pulses: usize,
|
||||||
|
/// Selected subdivision
|
||||||
|
pub focus: Option<PhraseLengthFocus>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PhraseLength {
|
||||||
|
pub fn new (pulses: usize, focus: Option<PhraseLengthFocus>) -> Self {
|
||||||
|
Self { ppq: PPQ, bpb: 4, pulses, focus }
|
||||||
|
}
|
||||||
|
pub fn bars (&self) -> usize {
|
||||||
|
self.pulses / (self.bpb * self.ppq)
|
||||||
|
}
|
||||||
|
pub fn beats (&self) -> usize {
|
||||||
|
(self.pulses % (self.bpb * self.ppq)) / self.ppq
|
||||||
|
}
|
||||||
|
pub fn ticks (&self) -> usize {
|
||||||
|
self.pulses % self.ppq
|
||||||
|
}
|
||||||
|
pub fn bars_string (&self) -> Arc<str> {
|
||||||
|
format!("{}", self.bars()).into()
|
||||||
|
}
|
||||||
|
pub fn beats_string (&self) -> Arc<str> {
|
||||||
|
format!("{}", self.beats()).into()
|
||||||
|
}
|
||||||
|
pub fn ticks_string (&self) -> Arc<str> {
|
||||||
|
format!("{:>02}", self.ticks()).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Focused field of `PhraseLength`
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub enum PhraseLengthFocus {
|
||||||
|
/// Editing the number of bars
|
||||||
|
Bar,
|
||||||
|
/// Editing the number of beats
|
||||||
|
Beat,
|
||||||
|
/// Editing the number of ticks
|
||||||
|
Tick,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PhraseLengthFocus {
|
||||||
|
pub fn next (&mut self) {
|
||||||
|
*self = match self {
|
||||||
|
Self::Bar => Self::Beat,
|
||||||
|
Self::Beat => Self::Tick,
|
||||||
|
Self::Tick => Self::Bar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn prev (&mut self) {
|
||||||
|
*self = match self {
|
||||||
|
Self::Bar => Self::Tick,
|
||||||
|
Self::Beat => Self::Bar,
|
||||||
|
Self::Tick => Self::Beat,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render!(TuiOut: (self: PhraseLength) => {
|
||||||
|
let bars = ||self.bars_string();
|
||||||
|
let beats = ||self.beats_string();
|
||||||
|
let ticks = ||self.ticks_string();
|
||||||
|
match self.focus {
|
||||||
|
None =>
|
||||||
|
row!(" ", bars(), ".", beats(), ".", ticks()),
|
||||||
|
Some(PhraseLengthFocus::Bar) =>
|
||||||
|
row!("[", bars(), "]", beats(), ".", ticks()),
|
||||||
|
Some(PhraseLengthFocus::Beat) =>
|
||||||
|
row!(" ", bars(), "[", beats(), "]", ticks()),
|
||||||
|
Some(PhraseLengthFocus::Tick) =>
|
||||||
|
row!(" ", bars(), ".", beats(), "[", ticks()),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
|
pub enum PhraseLengthCommand {
|
||||||
|
Begin,
|
||||||
|
Cancel,
|
||||||
|
Set(usize),
|
||||||
|
Next,
|
||||||
|
Prev,
|
||||||
|
Inc,
|
||||||
|
Dec,
|
||||||
|
}
|
||||||
|
|
||||||
|
command!(|self:PhraseLengthCommand,state:PoolModel|{
|
||||||
|
match state.clips_mode_mut().clone() {
|
||||||
|
Some(PoolMode::Length(clip, ref mut length, ref mut focus)) => match self {
|
||||||
|
Cancel => { *state.clips_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 old_length = None;
|
||||||
|
{
|
||||||
|
let mut clip = state.clips()[clip].clone();//.write().unwrap();
|
||||||
|
old_length = Some(clip.read().unwrap().length);
|
||||||
|
clip.write().unwrap().length = length;
|
||||||
|
}
|
||||||
|
*state.clips_mode_mut() = None;
|
||||||
|
return Ok(old_length.map(Self::Set))
|
||||||
|
},
|
||||||
|
_ => unreachable!()
|
||||||
|
},
|
||||||
|
_ => unreachable!()
|
||||||
|
};
|
||||||
|
None
|
||||||
|
});
|
||||||
|
|
||||||
|
input_to_command!(PhraseLengthCommand: |state: PoolModel, input: Event|{
|
||||||
|
if let Some(PoolMode::Length(_, length, _)) = state.clips_mode() {
|
||||||
|
match input {
|
||||||
|
kpat!(Up) => Self::Inc,
|
||||||
|
kpat!(Down) => Self::Dec,
|
||||||
|
kpat!(Right) => Self::Next,
|
||||||
|
kpat!(Left) => Self::Prev,
|
||||||
|
kpat!(Enter) => Self::Set(*length),
|
||||||
|
kpat!(Esc) => Self::Cancel,
|
||||||
|
_ => return None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
use crate::*;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum PhraseRenameCommand {
|
||||||
|
Begin,
|
||||||
|
Cancel,
|
||||||
|
Confirm,
|
||||||
|
Set(Arc<str>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command<PoolModel> for PhraseRenameCommand {
|
||||||
|
fn execute (self, state: &mut PoolModel) -> Perhaps<Self> {
|
||||||
|
use PhraseRenameCommand::*;
|
||||||
|
match state.clips_mode_mut().clone() {
|
||||||
|
Some(PoolMode::Rename(clip, ref mut old_name)) => match self {
|
||||||
|
Set(s) => {
|
||||||
|
state.clips()[clip].write().unwrap().name = s;
|
||||||
|
return Ok(Some(Self::Set(old_name.clone().into())))
|
||||||
|
},
|
||||||
|
Confirm => {
|
||||||
|
let old_name = old_name.clone();
|
||||||
|
*state.clips_mode_mut() = None;
|
||||||
|
return Ok(Some(Self::Set(old_name)))
|
||||||
|
},
|
||||||
|
Cancel => {
|
||||||
|
state.clips()[clip].write().unwrap().name = old_name.clone().into();
|
||||||
|
},
|
||||||
|
_ => unreachable!()
|
||||||
|
},
|
||||||
|
_ => unreachable!()
|
||||||
|
};
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputToCommand<Event, PoolModel> for PhraseRenameCommand {
|
||||||
|
fn input_to_command (state: &PoolModel, input: &Event) -> Option<Self> {
|
||||||
|
use KeyCode::{Char, Backspace, Enter, Esc};
|
||||||
|
if let Some(PoolMode::Rename(_, ref old_name)) = state.clips_mode() {
|
||||||
|
Some(match input {
|
||||||
|
kpat!(Char(c)) => {
|
||||||
|
let mut new_name = old_name.clone().to_string();
|
||||||
|
new_name.push(*c);
|
||||||
|
Self::Set(new_name.into())
|
||||||
|
},
|
||||||
|
kpat!(Backspace) => {
|
||||||
|
let mut new_name = old_name.clone().to_string();
|
||||||
|
new_name.pop();
|
||||||
|
Self::Set(new_name.into())
|
||||||
|
},
|
||||||
|
kpat!(Enter) => Self::Confirm,
|
||||||
|
kpat!(Esc) => Self::Cancel,
|
||||||
|
_ => return None
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,144 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
use super::*;
|
|
||||||
use PhraseLengthFocus::*;
|
|
||||||
use PhraseLengthCommand::*;
|
|
||||||
use KeyCode::{Up, Down, Left, Right, Enter, Esc};
|
|
||||||
|
|
||||||
/// Displays and edits clip length.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct PhraseLength {
|
|
||||||
/// Pulses per beat (quaver)
|
|
||||||
pub ppq: usize,
|
|
||||||
/// Beats per bar
|
|
||||||
pub bpb: usize,
|
|
||||||
/// Length of clip in pulses
|
|
||||||
pub pulses: usize,
|
|
||||||
/// Selected subdivision
|
|
||||||
pub focus: Option<PhraseLengthFocus>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PhraseLength {
|
|
||||||
pub fn new (pulses: usize, focus: Option<PhraseLengthFocus>) -> Self {
|
|
||||||
Self { ppq: PPQ, bpb: 4, pulses, focus }
|
|
||||||
}
|
|
||||||
pub fn bars (&self) -> usize {
|
|
||||||
self.pulses / (self.bpb * self.ppq)
|
|
||||||
}
|
|
||||||
pub fn beats (&self) -> usize {
|
|
||||||
(self.pulses % (self.bpb * self.ppq)) / self.ppq
|
|
||||||
}
|
|
||||||
pub fn ticks (&self) -> usize {
|
|
||||||
self.pulses % self.ppq
|
|
||||||
}
|
|
||||||
pub fn bars_string (&self) -> Arc<str> {
|
|
||||||
format!("{}", self.bars()).into()
|
|
||||||
}
|
|
||||||
pub fn beats_string (&self) -> Arc<str> {
|
|
||||||
format!("{}", self.beats()).into()
|
|
||||||
}
|
|
||||||
pub fn ticks_string (&self) -> Arc<str> {
|
|
||||||
format!("{:>02}", self.ticks()).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Focused field of `PhraseLength`
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub enum PhraseLengthFocus {
|
|
||||||
/// Editing the number of bars
|
|
||||||
Bar,
|
|
||||||
/// Editing the number of beats
|
|
||||||
Beat,
|
|
||||||
/// Editing the number of ticks
|
|
||||||
Tick,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PhraseLengthFocus {
|
|
||||||
pub fn next (&mut self) {
|
|
||||||
*self = match self {
|
|
||||||
Self::Bar => Self::Beat,
|
|
||||||
Self::Beat => Self::Tick,
|
|
||||||
Self::Tick => Self::Bar,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn prev (&mut self) {
|
|
||||||
*self = match self {
|
|
||||||
Self::Bar => Self::Tick,
|
|
||||||
Self::Beat => Self::Bar,
|
|
||||||
Self::Tick => Self::Beat,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render!(TuiOut: (self: PhraseLength) => {
|
|
||||||
let bars = ||self.bars_string();
|
|
||||||
let beats = ||self.beats_string();
|
|
||||||
let ticks = ||self.ticks_string();
|
|
||||||
match self.focus {
|
|
||||||
None =>
|
|
||||||
row!(" ", bars(), ".", beats(), ".", ticks()),
|
|
||||||
Some(PhraseLengthFocus::Bar) =>
|
|
||||||
row!("[", bars(), "]", beats(), ".", ticks()),
|
|
||||||
Some(PhraseLengthFocus::Beat) =>
|
|
||||||
row!(" ", bars(), "[", beats(), "]", ticks()),
|
|
||||||
Some(PhraseLengthFocus::Tick) =>
|
|
||||||
row!(" ", bars(), ".", beats(), "[", ticks()),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
||||||
pub enum PhraseLengthCommand {
|
|
||||||
Begin,
|
|
||||||
Cancel,
|
|
||||||
Set(usize),
|
|
||||||
Next,
|
|
||||||
Prev,
|
|
||||||
Inc,
|
|
||||||
Dec,
|
|
||||||
}
|
|
||||||
|
|
||||||
command!(|self:PhraseLengthCommand,state:PoolModel|{
|
|
||||||
match state.clips_mode_mut().clone() {
|
|
||||||
Some(PoolMode::Length(clip, ref mut length, ref mut focus)) => match self {
|
|
||||||
Cancel => { *state.clips_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 clip = state.clips()[clip].write().unwrap();
|
|
||||||
let old_length = clip.length;
|
|
||||||
clip.length = length;
|
|
||||||
std::mem::drop(clip);
|
|
||||||
*state.clips_mode_mut() = None;
|
|
||||||
return Ok(Some(Self::Set(old_length)))
|
|
||||||
},
|
|
||||||
_ => unreachable!()
|
|
||||||
},
|
|
||||||
_ => unreachable!()
|
|
||||||
};
|
|
||||||
None
|
|
||||||
});
|
|
||||||
|
|
||||||
input_to_command!(PhraseLengthCommand: |state: PoolModel, input: Event|{
|
|
||||||
if let Some(PoolMode::Length(_, length, _)) = state.clips_mode() {
|
|
||||||
match input {
|
|
||||||
kpat!(Up) => Self::Inc,
|
|
||||||
kpat!(Down) => Self::Dec,
|
|
||||||
kpat!(Right) => Self::Next,
|
|
||||||
kpat!(Left) => Self::Prev,
|
|
||||||
kpat!(Enter) => Self::Set(*length),
|
|
||||||
kpat!(Esc) => Self::Cancel,
|
|
||||||
_ => return None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum PhraseRenameCommand {
|
|
||||||
Begin,
|
|
||||||
Cancel,
|
|
||||||
Confirm,
|
|
||||||
Set(Arc<str>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Command<PoolModel> for PhraseRenameCommand {
|
|
||||||
fn execute (self, state: &mut PoolModel) -> Perhaps<Self> {
|
|
||||||
use PhraseRenameCommand::*;
|
|
||||||
match state.clips_mode_mut().clone() {
|
|
||||||
Some(PoolMode::Rename(clip, ref mut old_name)) => match self {
|
|
||||||
Set(s) => {
|
|
||||||
state.clips()[clip].write().unwrap().name = s;
|
|
||||||
return Ok(Some(Self::Set(old_name.clone().into())))
|
|
||||||
},
|
|
||||||
Confirm => {
|
|
||||||
let old_name = old_name.clone();
|
|
||||||
*state.clips_mode_mut() = None;
|
|
||||||
return Ok(Some(Self::Set(old_name)))
|
|
||||||
},
|
|
||||||
Cancel => {
|
|
||||||
state.clips()[clip].write().unwrap().name = old_name.clone().into();
|
|
||||||
},
|
|
||||||
_ => unreachable!()
|
|
||||||
},
|
|
||||||
_ => unreachable!()
|
|
||||||
};
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InputToCommand<Event, PoolModel> for PhraseRenameCommand {
|
|
||||||
fn input_to_command (state: &PoolModel, input: &Event) -> Option<Self> {
|
|
||||||
use KeyCode::{Char, Backspace, Enter, Esc};
|
|
||||||
if let Some(PoolMode::Rename(_, ref old_name)) = state.clips_mode() {
|
|
||||||
Some(match input {
|
|
||||||
kpat!(Char(c)) => {
|
|
||||||
let mut new_name = old_name.clone().to_string();
|
|
||||||
new_name.push(*c);
|
|
||||||
Self::Set(new_name.into())
|
|
||||||
},
|
|
||||||
kpat!(Backspace) => {
|
|
||||||
let mut new_name = old_name.clone().to_string();
|
|
||||||
new_name.pop();
|
|
||||||
Self::Set(new_name.into())
|
|
||||||
},
|
|
||||||
kpat!(Enter) => Self::Confirm,
|
|
||||||
kpat!(Esc) => Self::Cancel,
|
|
||||||
_ => return None
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -125,21 +125,21 @@ keymap!(KEYS_SEQUENCER = |state: Sequencer, input: Event| SequencerCommand {
|
||||||
// Tab: Toggle compact mode
|
// Tab: Toggle compact mode
|
||||||
key(Tab) => Cmd::Compact(!state.compact),
|
key(Tab) => Cmd::Compact(!state.compact),
|
||||||
// q: Enqueue currently edited clip
|
// q: Enqueue currently edited clip
|
||||||
key(Char('q')) => Cmd::Enqueue(Some(state.pool.clip().clone())),
|
key(Char('q')) => Cmd::Enqueue(state.pool.clip().clone()),
|
||||||
// 0: Enqueue clip 0 (stop all)
|
// 0: Enqueue clip 0 (stop all)
|
||||||
key(Char('0')) => Cmd::Enqueue(Some(state.clips()[0].clone())),
|
key(Char('0')) => Cmd::Enqueue(Some(state.clips()[0].clone())),
|
||||||
// e: Toggle between editing currently playing or other clip
|
// e: Toggle between editing currently playing or other clip
|
||||||
key(Char('e')) => if let Some((_, Some(playing))) = state.player.play_clip() {
|
//key(Char('e')) => if let Some((_, Some(playing))) = state.player.play_clip() {
|
||||||
let editing = state.editor.clip().as_ref().map(|p|p.read().unwrap().clone());
|
//let editing = state.editor.clip().as_ref().map(|p|p.read().unwrap().clone());
|
||||||
let selected = state.pool.clip().clone();
|
//let selected = state.pool.clip().clone();
|
||||||
Cmd::Editor(Show(Some(if Some(selected.read().unwrap().clone()) != editing {
|
//Cmd::Editor(Show(Some(if Some(selected.read().unwrap().clone()) != editing {
|
||||||
selected
|
//selected
|
||||||
} else {
|
//} else {
|
||||||
playing.clone()
|
//playing.clone()
|
||||||
})))
|
//})))
|
||||||
} else {
|
//} else {
|
||||||
return None
|
//return None
|
||||||
}
|
//}
|
||||||
}, if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) {
|
}, if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) {
|
||||||
Cmd::Editor(command)
|
Cmd::Editor(command)
|
||||||
} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) {
|
} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) {
|
||||||
|
|
@ -156,13 +156,13 @@ command!(|self: SequencerCommand, state: Sequencer|match self {
|
||||||
// autoselect: automatically load selected clip in editor
|
// autoselect: automatically load selected clip in editor
|
||||||
PoolCommand::Select(_) => {
|
PoolCommand::Select(_) => {
|
||||||
let undo = cmd.delegate(&mut state.pool, Self::Pool)?;
|
let undo = cmd.delegate(&mut state.pool, Self::Pool)?;
|
||||||
state.editor.set_clip(Some(state.pool.clip()));
|
state.editor.set_clip(state.pool.clip().as_ref());
|
||||||
undo
|
undo
|
||||||
},
|
},
|
||||||
// update color in all places simultaneously
|
// update color in all places simultaneously
|
||||||
PoolCommand::Phrase(SetColor(index, _)) => {
|
PoolCommand::Phrase(SetColor(index, _)) => {
|
||||||
let undo = cmd.delegate(&mut state.pool, Self::Pool)?;
|
let undo = cmd.delegate(&mut state.pool, Self::Pool)?;
|
||||||
state.editor.set_clip(Some(state.pool.clip()));
|
state.editor.set_clip(state.pool.clip().as_ref());
|
||||||
undo
|
undo
|
||||||
},
|
},
|
||||||
_ => cmd.delegate(&mut state.pool, Self::Pool)?
|
_ => cmd.delegate(&mut state.pool, Self::Pool)?
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue