mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
autocreate on tab
This commit is contained in:
parent
c08d1bee5d
commit
2a5af2c753
2 changed files with 65 additions and 49 deletions
|
|
@ -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| {});
|
||||||
|
|
|
||||||
|
|
@ -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)?,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue