autocreate on tab

This commit is contained in:
🪞👃🪞 2025-01-16 17:22:20 +01:00
parent c08d1bee5d
commit 2a5af2c753
2 changed files with 65 additions and 49 deletions

View file

@ -1,5 +1,4 @@
use crate::*; use crate::*;
use KeyCode::*;
pub type ClipPool = Vec<Arc<RwLock<MidiClip>>>; pub type ClipPool = Vec<Arc<RwLock<MidiClip>>>;
pub trait HasClips { pub trait HasClips {
fn clips <'a> (&'a self) -> std::sync::RwLockReadGuard<'a, ClipPool>; fn clips <'a> (&'a self) -> std::sync::RwLockReadGuard<'a, ClipPool>;
@ -63,26 +62,11 @@ from!(|clip:&Arc<RwLock<MidiClip>>|MidiPool = {
has_clips!(|self: MidiPool|self.clips); has_clips!(|self: MidiPool|self.clips);
has_clip!(|self: MidiPool|self.clips().get(self.clip_index()).map(|c|c.clone())); has_clip!(|self: MidiPool|self.clips().get(self.clip_index()).map(|c|c.clone()));
impl MidiPool { impl MidiPool {
pub(crate) fn clip_index (&self) -> usize { fn clip_index (&self) -> usize { self.clip.load(Relaxed) }
self.clip.load(Relaxed) fn set_clip_index (&self, value: usize) { self.clip.store(value, Relaxed); }
} fn mode (&self) -> &Option<PoolMode> { &self.mode }
pub(crate) fn set_clip_index (&self, value: usize) { fn mode_mut (&mut self) -> &mut Option<PoolMode> { &mut self.mode }
self.clip.store(value, Relaxed); fn begin_clip_length (&mut self) {
}
pub(crate) fn mode (&self) -> &Option<PoolMode> {
&self.mode
}
pub(crate) fn mode_mut (&mut self) -> &mut Option<PoolMode> {
&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) {
let length = self.clips()[self.clip_index()].read().unwrap().length; let length = self.clips()[self.clip_index()].read().unwrap().length;
*self.mode_mut() = Some(PoolMode::Length( *self.mode_mut() = Some(PoolMode::Length(
self.clip_index(), self.clip_index(),
@ -90,21 +74,21 @@ impl MidiPool {
ClipLengthFocus::Bar 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(); let name = self.clips()[self.clip_index()].read().unwrap().name.clone();
*self.mode_mut() = Some(PoolMode::Rename( *self.mode_mut() = Some(PoolMode::Rename(
self.clip_index(), self.clip_index(),
name name
)); ));
} }
pub fn begin_import (&mut self) -> Usually<()> { fn begin_import (&mut self) -> Usually<()> {
*self.mode_mut() = Some(PoolMode::Import( *self.mode_mut() = Some(PoolMode::Import(
self.clip_index(), self.clip_index(),
FileBrowser::new(None)? FileBrowser::new(None)?
)); ));
Ok(()) Ok(())
} }
pub fn begin_export (&mut self) -> Usually<()> { fn begin_export (&mut self) -> Usually<()> {
*self.mode_mut() = Some(PoolMode::Export( *self.mode_mut() = Some(PoolMode::Export(
self.clip_index(), self.clip_index(),
FileBrowser::new(None)? FileBrowser::new(None)?
@ -116,34 +100,34 @@ impl MidiPool {
#[derive(Clone)] #[derive(Clone)]
pub struct ClipLength { pub struct ClipLength {
/// Pulses per beat (quaver) /// Pulses per beat (quaver)
pub ppq: usize, ppq: usize,
/// Beats per bar /// Beats per bar
pub bpb: usize, bpb: usize,
/// Length of clip in pulses /// Length of clip in pulses
pub pulses: usize, pulses: usize,
/// Selected subdivision /// Selected subdivision
pub focus: Option<ClipLengthFocus>, focus: Option<ClipLengthFocus>,
} }
impl ClipLength { impl ClipLength {
pub fn new (pulses: usize, focus: Option<ClipLengthFocus>) -> Self { fn new (pulses: usize, focus: Option<ClipLengthFocus>) -> Self {
Self { ppq: PPQ, bpb: 4, pulses, focus } Self { ppq: PPQ, bpb: 4, pulses, focus }
} }
pub fn bars (&self) -> usize { fn bars (&self) -> usize {
self.pulses / (self.bpb * self.ppq) self.pulses / (self.bpb * self.ppq)
} }
pub fn beats (&self) -> usize { fn beats (&self) -> usize {
(self.pulses % (self.bpb * self.ppq)) / self.ppq (self.pulses % (self.bpb * self.ppq)) / self.ppq
} }
pub fn ticks (&self) -> usize { fn ticks (&self) -> usize {
self.pulses % self.ppq self.pulses % self.ppq
} }
pub fn bars_string (&self) -> Arc<str> { fn bars_string (&self) -> Arc<str> {
format!("{}", self.bars()).into() format!("{}", self.bars()).into()
} }
pub fn beats_string (&self) -> Arc<str> { fn beats_string (&self) -> Arc<str> {
format!("{}", self.beats()).into() format!("{}", self.beats()).into()
} }
pub fn ticks_string (&self) -> Arc<str> { fn ticks_string (&self) -> Arc<str> {
format!("{:>02}", self.ticks()).into() format!("{:>02}", self.ticks()).into()
} }
} }
@ -158,17 +142,17 @@ pub enum ClipLengthFocus {
Tick, Tick,
} }
impl ClipLengthFocus { 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, } *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, } *self = match self { Self::Bar => Self::Tick, Self::Beat => Self::Bar, Self::Tick => Self::Beat, }
} }
} }
pub struct PoolView<'a>(pub bool, pub &'a MidiPool); pub struct PoolView<'a>(pub bool, pub &'a MidiPool);
content!(TuiOut: |self: PoolView<'a>| { content!(TuiOut: |self: PoolView<'a>| {
let Self(compact, model) = self; 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 color = self.1.clip().map(|c|c.read().unwrap().color).unwrap_or_else(||TuiTheme::g(32).into());
let on_bg = |x|x;//Bsp::b(Repeat(" "), Tui::bg(color.darkest.rgb, x)); let on_bg = |x|x;//Bsp::b(Repeat(" "), Tui::bg(color.darkest.rgb, x));
let border = |x|x;//Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb)).enclose(x); let border = |x|x;//Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb)).enclose(x);
@ -237,16 +221,30 @@ handle!(TuiIn: |self: MidiPool, input|{
}) })
}); });
edn_provide!(bool: |self: MidiPool| {}); edn_provide!(bool: |self: MidiPool| {});
edn_provide!(MidiClip: |self: MidiPool| { impl MidiPool {
":new-clip" => MidiClip::new( pub fn new_clip (&self) -> MidiClip {
"Clip", true, 4 * PPQ, None, Some(ItemPalette::random()) MidiClip::new("Clip", true, 4 * PPQ, None, Some(ItemPalette::random()))
), }
":cloned-clip" => { pub fn cloned_clip (&self) -> MidiClip {
let index = self.clip_index(); let index = self.clip_index();
let mut clip = self.clips()[index].read().unwrap().duplicate(); let mut clip = self.clips()[index].read().unwrap().duplicate();
clip.color = ItemPalette::random_near(clip.color, 0.25); clip.color = ItemPalette::random_near(clip.color, 0.25);
clip clip
} }
pub fn add_new_clip (&self) -> (usize, Arc<RwLock<MidiClip>>) {
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!(PathBuf: |self: MidiPool| {});
edn_provide!(Arc<str>: |self: MidiPool| {}); edn_provide!(Arc<str>: |self: MidiPool| {});

View file

@ -120,9 +120,13 @@ impl TekCli {
} }
} }
#[derive(Default, Debug)] struct Tek { #[derive(Default, Debug)] struct Tek {
/// Must not be dropped for the duration of the process
pub jack: Arc<RwLock<JackConnection>>, pub jack: Arc<RwLock<JackConnection>>,
/// View definition
pub edn: String, pub edn: String,
/// Source of time
pub clock: Clock, pub clock: Clock,
/// Theme
pub color: ItemPalette, pub color: ItemPalette,
pub pool: Option<MidiPool>, pub pool: Option<MidiPool>,
pub editor: Option<MidiEditor>, pub editor: Option<MidiEditor>,
@ -675,16 +679,14 @@ command!(|self: TekCommand, app: Tek|match self {
if let Some(ref mut editor) = app.editor { if let Some(ref mut editor) = app.editor {
editor.set_clip(match app.selected { editor.set_clip(match app.selected {
Selection::Clip(t, s) if let Some(Some(Some(clip))) = app Selection::Clip(t, s) if let Some(Some(Some(clip))) = app
.scenes .scenes.get(s).map(|s|s.clips.get(t)) => Some(clip),
.get(s)
.map(|s|s.clips.get(t)) => Some(clip),
_ => None _ => None
}); });
} }
None None
}, },
Self::Edit(value) => { 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 let Some(value) = value {
if app.is_editing() != value { if app.is_editing() != value {
app.editing.store(value, Relaxed); app.editing.store(value, Relaxed);
@ -692,6 +694,22 @@ command!(|self: TekCommand, app: Tek|match self {
} else { } else {
app.editing.store(!app.is_editing(), Relaxed); 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 None
}, },
Self::Clock(cmd) => cmd.delegate(app, Self::Clock)?, Self::Clock(cmd) => cmd.delegate(app, Self::Clock)?,