From 2a5af2c753a0c1799c974dbe101c1814ec270744 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 16 Jan 2025 17:22:20 +0100 Subject: [PATCH] autocreate on tab --- midi/src/midi_pool.rs | 88 +++++++++++++++++++++---------------------- tek/src/lib.rs | 26 +++++++++++-- 2 files changed, 65 insertions(+), 49 deletions(-) diff --git a/midi/src/midi_pool.rs b/midi/src/midi_pool.rs index fc1b2f19..4ff52fb7 100644 --- a/midi/src/midi_pool.rs +++ b/midi/src/midi_pool.rs @@ -1,8 +1,7 @@ use crate::*; -use KeyCode::*; pub type ClipPool = Vec>>; pub trait HasClips { - fn clips <'a> (&'a self) -> std::sync::RwLockReadGuard<'a, ClipPool>; + fn clips <'a> (&'a self) -> std::sync::RwLockReadGuard<'a, ClipPool>; fn clips_mut <'a> (&'a self) -> std::sync::RwLockWriteGuard<'a, ClipPool>; fn add_clip (&self) -> (usize, Arc>) { let clip = Arc::new(RwLock::new(MidiClip::new("Clip", true, 384, None, None))); @@ -13,7 +12,7 @@ pub trait HasClips { #[macro_export] macro_rules! has_clips { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { impl $(<$($L),*$($T $(: $U)?),*>)? HasClips for $Struct $(<$($L),*$($T),*>)? { - fn clips <'a> (&'a $self) -> std::sync::RwLockReadGuard<'a, ClipPool> { + fn clips <'a> (&'a $self) -> std::sync::RwLockReadGuard<'a, ClipPool> { $cb.read().unwrap() } fn clips_mut <'a> (&'a $self) -> std::sync::RwLockWriteGuard<'a, ClipPool> { @@ -63,26 +62,11 @@ from!(|clip:&Arc>|MidiPool = { has_clips!(|self: MidiPool|self.clips); has_clip!(|self: MidiPool|self.clips().get(self.clip_index()).map(|c|c.clone())); impl MidiPool { - pub(crate) fn clip_index (&self) -> usize { - self.clip.load(Relaxed) - } - pub(crate) fn set_clip_index (&self, value: usize) { - self.clip.store(value, Relaxed); - } - pub(crate) fn mode (&self) -> &Option { - &self.mode - } - pub(crate) fn mode_mut (&mut self) -> &mut Option { - &mut self.mode - } - pub fn file_picker (&self) -> Option<&FileBrowser> { - match self.mode { - Some(PoolMode::Import(_, ref file_picker)) => Some(file_picker), - Some(PoolMode::Export(_, ref file_picker)) => Some(file_picker), - _ => None - } - } - pub fn begin_clip_length (&mut self) { + fn clip_index (&self) -> usize { self.clip.load(Relaxed) } + fn set_clip_index (&self, value: usize) { self.clip.store(value, Relaxed); } + fn mode (&self) -> &Option { &self.mode } + fn mode_mut (&mut self) -> &mut Option { &mut self.mode } + fn begin_clip_length (&mut self) { let length = self.clips()[self.clip_index()].read().unwrap().length; *self.mode_mut() = Some(PoolMode::Length( self.clip_index(), @@ -90,21 +74,21 @@ impl MidiPool { ClipLengthFocus::Bar )); } - pub fn begin_clip_rename (&mut self) { + fn begin_clip_rename (&mut self) { let name = self.clips()[self.clip_index()].read().unwrap().name.clone(); *self.mode_mut() = Some(PoolMode::Rename( self.clip_index(), name )); } - pub fn begin_import (&mut self) -> Usually<()> { + fn begin_import (&mut self) -> Usually<()> { *self.mode_mut() = Some(PoolMode::Import( self.clip_index(), FileBrowser::new(None)? )); Ok(()) } - pub fn begin_export (&mut self) -> Usually<()> { + fn begin_export (&mut self) -> Usually<()> { *self.mode_mut() = Some(PoolMode::Export( self.clip_index(), FileBrowser::new(None)? @@ -116,34 +100,34 @@ impl MidiPool { #[derive(Clone)] pub struct ClipLength { /// Pulses per beat (quaver) - pub ppq: usize, + ppq: usize, /// Beats per bar - pub bpb: usize, + bpb: usize, /// Length of clip in pulses - pub pulses: usize, + pulses: usize, /// Selected subdivision - pub focus: Option, + focus: Option, } impl ClipLength { - pub fn new (pulses: usize, focus: Option) -> Self { + fn new (pulses: usize, focus: Option) -> Self { Self { ppq: PPQ, bpb: 4, pulses, focus } } - pub fn bars (&self) -> usize { + fn bars (&self) -> usize { self.pulses / (self.bpb * self.ppq) } - pub fn beats (&self) -> usize { + fn beats (&self) -> usize { (self.pulses % (self.bpb * self.ppq)) / self.ppq } - pub fn ticks (&self) -> usize { + fn ticks (&self) -> usize { self.pulses % self.ppq } - pub fn bars_string (&self) -> Arc { + fn bars_string (&self) -> Arc { format!("{}", self.bars()).into() } - pub fn beats_string (&self) -> Arc { + fn beats_string (&self) -> Arc { format!("{}", self.beats()).into() } - pub fn ticks_string (&self) -> Arc { + fn ticks_string (&self) -> Arc { format!("{:>02}", self.ticks()).into() } } @@ -158,17 +142,17 @@ pub enum ClipLengthFocus { Tick, } impl ClipLengthFocus { - pub fn next (&mut self) { + fn next (&mut self) { *self = match self { Self::Bar => Self::Beat, Self::Beat => Self::Tick, Self::Tick => Self::Bar, } } - pub fn prev (&mut self) { + fn prev (&mut self) { *self = match self { Self::Bar => Self::Tick, Self::Beat => Self::Bar, Self::Tick => Self::Beat, } } } pub struct PoolView<'a>(pub bool, pub &'a MidiPool); content!(TuiOut: |self: PoolView<'a>| { let Self(compact, model) = self; - let MidiPool { clips, mode, .. } = self.1; + let MidiPool { clips, .. } = self.1; //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); @@ -237,16 +221,30 @@ handle!(TuiIn: |self: MidiPool, input|{ }) }); edn_provide!(bool: |self: MidiPool| {}); -edn_provide!(MidiClip: |self: MidiPool| { - ":new-clip" => MidiClip::new( - "Clip", true, 4 * PPQ, None, Some(ItemPalette::random()) - ), - ":cloned-clip" => { +impl MidiPool { + pub fn new_clip (&self) -> MidiClip { + MidiClip::new("Clip", true, 4 * PPQ, None, Some(ItemPalette::random())) + } + pub fn cloned_clip (&self) -> MidiClip { let index = self.clip_index(); let mut clip = self.clips()[index].read().unwrap().duplicate(); clip.color = ItemPalette::random_near(clip.color, 0.25); clip } + pub fn add_new_clip (&self) -> (usize, Arc>) { + let clip = Arc::new(RwLock::new(self.new_clip())); + let index = { + let mut clips = self.clips.write().unwrap(); + clips.push(clip.clone()); + clips.len().saturating_sub(1) + }; + self.clip.store(index, Relaxed); + (index, clip) + } +} +edn_provide!(MidiClip: |self: MidiPool| { + ":new-clip" => self.new_clip(), + ":cloned-clip" => self.cloned_clip(), }); edn_provide!(PathBuf: |self: MidiPool| {}); edn_provide!(Arc: |self: MidiPool| {}); diff --git a/tek/src/lib.rs b/tek/src/lib.rs index 6d81ac31..de595dd7 100644 --- a/tek/src/lib.rs +++ b/tek/src/lib.rs @@ -120,9 +120,13 @@ impl TekCli { } } #[derive(Default, Debug)] struct Tek { + /// Must not be dropped for the duration of the process pub jack: Arc>, + /// View definition pub edn: String, + /// Source of time pub clock: Clock, + /// Theme pub color: ItemPalette, pub pool: Option, pub editor: Option, @@ -675,16 +679,14 @@ command!(|self: TekCommand, app: Tek|match self { if let Some(ref mut editor) = app.editor { editor.set_clip(match app.selected { Selection::Clip(t, s) if let Some(Some(Some(clip))) = app - .scenes - .get(s) - .map(|s|s.clips.get(t)) => Some(clip), + .scenes.get(s).map(|s|s.clips.get(t)) => Some(clip), _ => None }); } None }, Self::Edit(value) => { - // TODO: autocreate: create new clip when entering empty cell + // autocreate: create new clip when entering empty cell if let Some(value) = value { if app.is_editing() != value { app.editing.store(value, Relaxed); @@ -692,6 +694,22 @@ command!(|self: TekCommand, app: Tek|match self { } else { app.editing.store(!app.is_editing(), Relaxed); }; + if app.is_editing() { + if let Selection::Clip(t, s) = app.selected { + if let Some(Some(c)) = app.scenes.get_mut(s).map(|s|s.clips.get_mut(t)) { + if c.is_none() { + if let Some(ref pool) = app.pool { + let (index, clip) = pool.add_new_clip(); + if let Some(ref mut editor) = app.editor { + editor.set_clip(Some(&clip)); + } + *c = Some(clip); + } + + } + } + } + } None }, Self::Clock(cmd) => cmd.delegate(app, Self::Clock)?,