wip: what nuked the arranger

This commit is contained in:
🪞👃🪞 2025-01-11 17:36:02 +01:00
parent ba0ff4af98
commit a92981bb50
11 changed files with 324 additions and 310 deletions

View file

@ -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![],

View file

@ -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 }
}
}
}

View file

@ -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) => {

View file

@ -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
}

View file

@ -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)))))))

View file

@ -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) => {

View file

@ -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) {

View file

@ -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!()
}
}
}

View file

@ -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!()
}
});

View file

@ -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!()
}
}
}

View file

@ -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)?