mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +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, .. } =>
|
||||
engine.run(&jack.activate_with(|jack|Ok({
|
||||
let clip = default_clip();
|
||||
let clock = default_clock(jack);
|
||||
let mut app = Arranger {
|
||||
jack: jack.clone(),
|
||||
midi_ins: vec![JackPort::<MidiIn>::new(jack, format!("M/{name}"), &midi_froms)?,],
|
||||
midi_outs: vec![JackPort::<MidiOut>::new(jack, format!("{name}/M"), &midi_tos)?, ],
|
||||
clock,
|
||||
pool: (&clip).into(),
|
||||
editor: (&clip).into(),
|
||||
pool: PoolModel::default(),//(&clip).into(),
|
||||
editor: MidiEditor::default(),//(&clip).into(),
|
||||
selected: ArrangerSelection::Clip(0, 0),
|
||||
scenes: vec![],
|
||||
tracks: vec![],
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
use crate::*;
|
||||
|
||||
pub trait HasMidiClip {
|
||||
fn clip (&self) -> &Arc<RwLock<MidiClip>>;
|
||||
fn clip (&self) -> Option<Arc<RwLock<MidiClip>>>;
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! has_clip {
|
||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||
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::*;
|
||||
|
||||
pub trait HasPhrases {
|
||||
fn clips (&self) -> &Vec<Arc<RwLock<MidiClip>>>;
|
||||
fn clips_mut (&mut self) -> &mut Vec<Arc<RwLock<MidiClip>>>;
|
||||
pub type ClipPool = 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 {
|
||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasPhrases for $Struct $(<$($L),*$($T),*>)? {
|
||||
fn clips (&$self) -> &Vec<Arc<RwLock<MidiClip>>> { &$cb }
|
||||
fn clips_mut (&mut $self) -> &mut Vec<Arc<RwLock<MidiClip>>> { &mut$cb }
|
||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasClips for $Struct $(<$($L),*$($T),*>)? {
|
||||
fn clips <'a> (&'a $self) -> std::sync::RwLockReadGuard<'a, ClipPool> {
|
||||
$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),
|
||||
}
|
||||
|
||||
impl<T: HasPhrases> Command<T> for MidiPoolCommand {
|
||||
impl<T: HasClips> Command<T> for MidiPoolCommand {
|
||||
fn execute (self, model: &mut T) -> Perhaps<Self> {
|
||||
use MidiPoolCommand::*;
|
||||
Ok(match self {
|
||||
Add(mut index, clip) => {
|
||||
let clip = Arc::new(RwLock::new(clip));
|
||||
let clips = model.clips_mut();
|
||||
let mut clips = model.clips_mut();
|
||||
if index >= clips.len() {
|
||||
index = clips.len();
|
||||
clips.push(clip)
|
||||
|
|
@ -72,15 +78,15 @@ impl<T: HasPhrases> Command<T> for MidiPoolCommand {
|
|||
todo!("export clip to midi file");
|
||||
},
|
||||
SetName(index, name) => {
|
||||
let mut clip = model.clips()[index].write().unwrap();
|
||||
let old_name = clip.name.clone();
|
||||
clip.name = name;
|
||||
let clip = &mut model.clips_mut()[index];
|
||||
let old_name = clip.read().unwrap().name.clone();
|
||||
clip.write().unwrap().name = name;
|
||||
Some(Self::SetName(index, old_name))
|
||||
},
|
||||
SetLength(index, length) => {
|
||||
let mut clip = model.clips()[index].write().unwrap();
|
||||
let old_len = clip.length;
|
||||
clip.length = length;
|
||||
let clip = &mut model.clips_mut()[index];
|
||||
let old_len = clip.read().unwrap().length;
|
||||
clip.write().unwrap().length = length;
|
||||
Some(Self::SetLength(index, old_len))
|
||||
},
|
||||
SetColor(index, color) => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use tek::*;
|
|||
|
||||
struct ExamplePhrases(Vec<Arc<RwLock<Phrase>>>);
|
||||
|
||||
impl HasPhrases for ExamplePhrases {
|
||||
impl HasClips for ExamplePhrases {
|
||||
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
||||
&self.0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
(bsp/s :toolbar
|
||||
(bsp/w :pool (fill/x (align/c
|
||||
(fill/x (align/c
|
||||
(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> + '_ {
|
||||
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(
|
||||
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())))))
|
||||
|
|
@ -353,7 +353,11 @@ impl Arranger {
|
|||
let cells = Map::new(scenes, move|(_, scene, y1, y2), s| {
|
||||
let h = (y2 - y1) as u16;
|
||||
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 cell = phat_sel_3(
|
||||
//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) }),
|
||||
// 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.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'))) => return None,//Cmd::Scene(ArrangerSceneCommand::Add),
|
||||
ctrl(key(Char('t'))) => Cmd::Track(ArrangerTrackCommand::Add),
|
||||
// Tab: Toggle visibility of clip pool column
|
||||
key(Tab) => Cmd::Phrases(PoolCommand::Show(!state.pool.visible)),
|
||||
|
|
@ -697,13 +702,14 @@ keymap!(KEYS_ARRANGER = |state: Arranger, input: Event| ArrangerCommand {
|
|||
Selected::Scene(s) => scene_keymap(state, input, s),
|
||||
Selected::Track(t) => track_keymap(state, input, t),
|
||||
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!(Up) => return None,
|
||||
kpat!(Down) => Some( Cmd::Select(Selected::Scene(0))),
|
||||
kpat!(Left) => return None,
|
||||
kpat!(Right) => Some( Cmd::Select(Selected::Track(0))),
|
||||
|
||||
_ => None
|
||||
|
|
@ -725,23 +731,30 @@ fn clip_keymap (state: &Arranger, input: &Event, t: usize, s: usize) -> Option<A
|
|||
let t_len = state.tracks.len();
|
||||
let s_len = state.scenes.len();
|
||||
Some(match input {
|
||||
|
||||
kpat!(Char('g')) => Cmd::Phrases(PoolCommand::Select(0)),
|
||||
kpat!(Char('q')) => Cmd::Clip(Clip::Enqueue(t, s)),
|
||||
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!(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!(Up) =>
|
||||
Cmd::Select(if s > 0 { Selected::Clip(t, s - 1) } else { Selected::Track(t) }),
|
||||
kpat!(Down) =>
|
||||
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!(Right) =>
|
||||
Cmd::Select(Selected::Clip((t + 1).min(t_len.saturating_sub(1)), s)),
|
||||
|
||||
kpat!(Up) => Cmd::Select(if s > 0 { Selected::Clip(t, s - 1) } else { Selected::Track(t) }),
|
||||
kpat!(Down) => 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!(Right) => Cmd::Select(Selected::Clip((t + 1).min(t_len.saturating_sub(1)), s)),
|
||||
|
||||
_ => return None
|
||||
})
|
||||
}
|
||||
|
|
@ -753,6 +766,7 @@ fn scene_keymap (state: &Arranger, input: &Event, s: usize) -> Option<ArrangerCo
|
|||
let t_len = state.tracks.len();
|
||||
let s_len = state.scenes.len();
|
||||
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)),
|
||||
|
|
@ -761,14 +775,10 @@ fn scene_keymap (state: &Arranger, input: &Event, s: usize) -> Option<ArrangerCo
|
|||
kpat!(Delete) => Cmd::Scene(Scene::Delete(s)),
|
||||
kpat!(Char('c')) => Cmd::Scene(Scene::SetColor(s, ItemPalette::random())),
|
||||
|
||||
kpat!(Up) =>
|
||||
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!(Left) =>
|
||||
return None,
|
||||
kpat!(Right) =>
|
||||
Cmd::Select(Selected::Clip(0, s)),
|
||||
kpat!(Up) => 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!(Left) => return None,
|
||||
kpat!(Right) => Cmd::Select(Selected::Clip(0, s)),
|
||||
|
||||
_ => return None
|
||||
})
|
||||
|
|
@ -781,6 +791,7 @@ fn track_keymap (state: &Arranger, input: &Event, t: usize) -> Option<ArrangerCo
|
|||
let t_len = state.tracks.len();
|
||||
let s_len = state.scenes.len();
|
||||
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)),
|
||||
|
|
@ -788,14 +799,10 @@ fn track_keymap (state: &Arranger, input: &Event, t: usize) -> Option<ArrangerCo
|
|||
kpat!(Delete) => Cmd::Track(Track::Delete(t)),
|
||||
kpat!(Char('c')) => Cmd::Track(Track::SetColor(t, ItemPalette::random())),
|
||||
|
||||
kpat!(Up) =>
|
||||
return None,
|
||||
kpat!(Down) =>
|
||||
Cmd::Select(Selected::Clip(t, 0)),
|
||||
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)))),
|
||||
kpat!(Up) => return None,
|
||||
kpat!(Down) => Cmd::Select(Selected::Clip(t, 0)),
|
||||
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
|
||||
})
|
||||
|
|
@ -825,19 +832,20 @@ command!(|self: ArrangerCommand, state: Arranger|match self {
|
|||
// autoselect: automatically load selected clip in editor
|
||||
PoolCommand::Select(_) => {
|
||||
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
|
||||
},
|
||||
// reload clip in editor to update color
|
||||
PoolCommand::Phrase(MidiPoolCommand::SetColor(index, _)) => {
|
||||
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
|
||||
},
|
||||
_ => cmd.delegate(&mut state.pool, Self::Phrases)?
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
command!(|self: ArrangerClipCommand, state: Arranger|match self {
|
||||
Self::Get(track, scene) => { todo!() },
|
||||
Self::Put(track, scene, clip) => {
|
||||
|
|
|
|||
|
|
@ -151,13 +151,13 @@ command!(|self: GrooveboxCommand, state: Groovebox|match self {
|
|||
// autoselect: automatically load selected clip in editor
|
||||
PoolCommand::Select(_) => {
|
||||
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
|
||||
},
|
||||
// update color in all places simultaneously
|
||||
PoolCommand::Phrase(SetColor(index, _)) => {
|
||||
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
|
||||
},
|
||||
_ => cmd.delegate(&mut state.pool, Self::Pool)?
|
||||
|
|
@ -181,7 +181,7 @@ keymap!(<'a> KEYS_GROOVEBOX = |state: Groovebox, input: Event| GrooveboxCommand
|
|||
// Tab: Toggle compact mode
|
||||
key(Tab) => Cmd::Compact(!state.compact),
|
||||
// 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)
|
||||
key(Char('0')) => Cmd::Enqueue(Some(state.pool.clips()[0].clone())),
|
||||
// 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)
|
||||
),
|
||||
// e: Toggle between editing currently playing or other 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 selected = state.pool.clip().clone();
|
||||
Cmd::Editor(Show(Some(if Some(selected.read().unwrap().clone()) != editing {
|
||||
selected
|
||||
} else {
|
||||
playing.clone()
|
||||
})))
|
||||
} else {
|
||||
return None
|
||||
},
|
||||
//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 selected = state.pool.clip().clone().map(|s|s.read().unwrap().clone());
|
||||
//Cmd::Editor(Show(if selected != editing {
|
||||
//selected
|
||||
//} else {
|
||||
//Some(playing.clone())
|
||||
//}))
|
||||
//} else {
|
||||
//return None
|
||||
//},
|
||||
}, if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) {
|
||||
Cmd::Editor(command)
|
||||
} 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::*;
|
||||
mod clip_rename; pub use self::clip_rename::*;
|
||||
use crate::*;
|
||||
use super::*;
|
||||
use PhraseLengthFocus::*;
|
||||
use PhraseLengthCommand::*;
|
||||
use KeyCode::{Up, Down, Left, Right, Enter, Esc};
|
||||
|
||||
use super::*;
|
||||
|
||||
|
|
@ -7,7 +10,7 @@ use super::*;
|
|||
pub struct PoolModel {
|
||||
pub(crate) visible: bool,
|
||||
/// Collection of clips
|
||||
pub(crate) clips: Vec<Arc<RwLock<MidiClip>>>,
|
||||
pub(crate) clips: Arc<RwLock<Vec<Arc<RwLock<MidiClip>>>>>,
|
||||
/// Selected clip
|
||||
pub(crate) clip: AtomicUsize,
|
||||
/// Mode switch
|
||||
|
|
@ -17,15 +20,34 @@ pub struct PoolModel {
|
|||
/// Scroll offset
|
||||
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);
|
||||
render!(TuiOut: (self: PoolView<'a>) => {
|
||||
let Self(compact, model) = self;
|
||||
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 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_offset = i as u16 * item_height;
|
||||
let selected = i == model.clip_index();
|
||||
|
|
@ -183,26 +205,8 @@ fn to_clips_command (state: &PoolModel, input: &Event) -> Option<PoolCommand> {
|
|||
_ => 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_clip!(|self: PoolModel|self.clips[self.clip_index()]);
|
||||
has_clip!(|self: PoolModel|self.clips().get(self.clip_index()).map(|c|c.clone()));
|
||||
impl PoolModel {
|
||||
pub(crate) fn clip_index (&self) -> usize {
|
||||
self.clip.load(Relaxed)
|
||||
|
|
@ -288,3 +292,204 @@ input_to_command!(FileBrowserCommand: |state: PoolModel, input: Event|{
|
|||
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
|
||||
key(Tab) => Cmd::Compact(!state.compact),
|
||||
// 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)
|
||||
key(Char('0')) => Cmd::Enqueue(Some(state.clips()[0].clone())),
|
||||
// e: Toggle between editing currently playing or other 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 selected = state.pool.clip().clone();
|
||||
Cmd::Editor(Show(Some(if Some(selected.read().unwrap().clone()) != editing {
|
||||
selected
|
||||
} else {
|
||||
playing.clone()
|
||||
})))
|
||||
} else {
|
||||
return None
|
||||
}
|
||||
//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 selected = state.pool.clip().clone();
|
||||
//Cmd::Editor(Show(Some(if Some(selected.read().unwrap().clone()) != editing {
|
||||
//selected
|
||||
//} else {
|
||||
//playing.clone()
|
||||
//})))
|
||||
//} else {
|
||||
//return None
|
||||
//}
|
||||
}, if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) {
|
||||
Cmd::Editor(command)
|
||||
} 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
|
||||
PoolCommand::Select(_) => {
|
||||
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
|
||||
},
|
||||
// update color in all places simultaneously
|
||||
PoolCommand::Phrase(SetColor(index, _)) => {
|
||||
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
|
||||
},
|
||||
_ => cmd.delegate(&mut state.pool, Self::Pool)?
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue