From a92981bb50a82637b0b1d25515ff41ae3595e0ed Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 11 Jan 2025 17:36:02 +0100 Subject: [PATCH] wip: what nuked the arranger --- cli/tek.rs | 5 +- midi/src/midi_clip.rs | 4 +- midi/src/midi_pool.rs | 34 ++-- tek/examples/midi_import.rs.fixme | 2 +- tek/src/arranger.edn | 2 +- tek/src/arranger.rs | 74 +++++---- tek/src/groovebox.rs | 28 ++-- tek/src/pool.rs | 253 +++++++++++++++++++++++++++--- tek/src/pool/clip_length.rs | 144 ----------------- tek/src/pool/clip_rename.rs | 60 ------- tek/src/sequencer.rs | 28 ++-- 11 files changed, 324 insertions(+), 310 deletions(-) delete mode 100644 tek/src/pool/clip_length.rs delete mode 100644 tek/src/pool/clip_rename.rs diff --git a/cli/tek.rs b/cli/tek.rs index 05e4d51f..4f6baefc 100644 --- a/cli/tek.rs +++ b/cli/tek.rs @@ -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::::new(jack, format!("M/{name}"), &midi_froms)?,], midi_outs: vec![JackPort::::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![], diff --git a/midi/src/midi_clip.rs b/midi/src/midi_clip.rs index 93b3b7cc..29c3f6a4 100644 --- a/midi/src/midi_clip.rs +++ b/midi/src/midi_clip.rs @@ -1,13 +1,13 @@ use crate::*; pub trait HasMidiClip { - fn clip (&self) -> &Arc>; + fn clip (&self) -> Option>>; } #[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> { &$cb } + fn clip (&$self) -> Option>> { $cb } } } } diff --git a/midi/src/midi_pool.rs b/midi/src/midi_pool.rs index 9e1e3c88..664133ac 100644 --- a/midi/src/midi_pool.rs +++ b/midi/src/midi_pool.rs @@ -1,15 +1,21 @@ use crate::*; -pub trait HasPhrases { - fn clips (&self) -> &Vec>>; - fn clips_mut (&mut self) -> &mut Vec>>; +pub type ClipPool = Vec>>; + +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>> { &$cb } - fn clips_mut (&mut $self) -> &mut Vec>> { &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 Command for MidiPoolCommand { +impl Command for MidiPoolCommand { fn execute (self, model: &mut T) -> Perhaps { 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 Command 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) => { diff --git a/tek/examples/midi_import.rs.fixme b/tek/examples/midi_import.rs.fixme index 92ce4cb7..abaabfe3 100644 --- a/tek/examples/midi_import.rs.fixme +++ b/tek/examples/midi_import.rs.fixme @@ -2,7 +2,7 @@ use tek::*; struct ExamplePhrases(Vec>>); -impl HasPhrases for ExamplePhrases { +impl HasClips for ExamplePhrases { fn phrases (&self) -> &Vec>> { &self.0 } diff --git a/tek/src/arranger.edn b/tek/src/arranger.edn index 9edea7de..351737e6 100644 --- a/tek/src/arranger.edn +++ b/tek/src/arranger.edn @@ -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))))))) diff --git a/tek/src/arranger.rs b/tek/src/arranger.rs index d9a8218e..3fe9b93d 100644 --- a/tek/src/arranger.rs +++ b/tek/src/arranger.rs @@ -310,7 +310,7 @@ impl Arranger { fn scene_row (&self, tracks_w: u16) -> impl Content + '_ { 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 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 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 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 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 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) => { diff --git a/tek/src/groovebox.rs b/tek/src/groovebox.rs index 918f3c42..48a0a6a9 100644 --- a/tek/src/groovebox.rs +++ b/tek/src/groovebox.rs @@ -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) { diff --git a/tek/src/pool.rs b/tek/src/pool.rs index 6f8685f4..98237709 100644 --- a/tek/src/pool.rs +++ b/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>>, + pub(crate) clips: Arc>>>>, /// 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>|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 { _ => 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>|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, +} + +impl PhraseLength { + pub fn new (pulses: usize, focus: Option) -> 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 { + format!("{}", self.bars()).into() + } + pub fn beats_string (&self) -> Arc { + format!("{}", self.beats()).into() + } + pub fn ticks_string (&self) -> Arc { + 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), +} + +impl Command for PhraseRenameCommand { + fn execute (self, state: &mut PoolModel) -> Perhaps { + 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 for PhraseRenameCommand { + fn input_to_command (state: &PoolModel, input: &Event) -> Option { + 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!() + } + } +} diff --git a/tek/src/pool/clip_length.rs b/tek/src/pool/clip_length.rs deleted file mode 100644 index db24596b..00000000 --- a/tek/src/pool/clip_length.rs +++ /dev/null @@ -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, -} - -impl PhraseLength { - pub fn new (pulses: usize, focus: Option) -> 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 { - format!("{}", self.bars()).into() - } - pub fn beats_string (&self) -> Arc { - format!("{}", self.beats()).into() - } - pub fn ticks_string (&self) -> Arc { - 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!() - } -}); diff --git a/tek/src/pool/clip_rename.rs b/tek/src/pool/clip_rename.rs deleted file mode 100644 index ff552f42..00000000 --- a/tek/src/pool/clip_rename.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::*; -use super::*; - -#[derive(Clone, Debug, PartialEq)] -pub enum PhraseRenameCommand { - Begin, - Cancel, - Confirm, - Set(Arc), -} - -impl Command for PhraseRenameCommand { - fn execute (self, state: &mut PoolModel) -> Perhaps { - 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 for PhraseRenameCommand { - fn input_to_command (state: &PoolModel, input: &Event) -> Option { - 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!() - } - } -} diff --git a/tek/src/sequencer.rs b/tek/src/sequencer.rs index d9771296..17b4ca2a 100644 --- a/tek/src/sequencer.rs +++ b/tek/src/sequencer.rs @@ -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)?