mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56: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,8 +1,7 @@
|
|||
use crate::*;
|
||||
use KeyCode::*;
|
||||
pub type ClipPool = Vec<Arc<RwLock<MidiClip>>>;
|
||||
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<RwLock<MidiClip>>) {
|
||||
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<RwLock<MidiClip>>|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<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) {
|
||||
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<PoolMode> { &self.mode }
|
||||
fn mode_mut (&mut self) -> &mut Option<PoolMode> { &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<ClipLengthFocus>,
|
||||
focus: Option<ClipLengthFocus>,
|
||||
}
|
||||
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 }
|
||||
}
|
||||
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<str> {
|
||||
fn bars_string (&self) -> Arc<str> {
|
||||
format!("{}", self.bars()).into()
|
||||
}
|
||||
pub fn beats_string (&self) -> Arc<str> {
|
||||
fn beats_string (&self) -> Arc<str> {
|
||||
format!("{}", self.beats()).into()
|
||||
}
|
||||
pub fn ticks_string (&self) -> Arc<str> {
|
||||
fn ticks_string (&self) -> Arc<str> {
|
||||
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<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!(Arc<str>: |self: MidiPool| {});
|
||||
|
|
|
|||
|
|
@ -120,9 +120,13 @@ impl TekCli {
|
|||
}
|
||||
}
|
||||
#[derive(Default, Debug)] struct Tek {
|
||||
/// Must not be dropped for the duration of the process
|
||||
pub jack: Arc<RwLock<JackConnection>>,
|
||||
/// View definition
|
||||
pub edn: String,
|
||||
/// Source of time
|
||||
pub clock: Clock,
|
||||
/// Theme
|
||||
pub color: ItemPalette,
|
||||
pub pool: Option<MidiPool>,
|
||||
pub editor: Option<MidiEditor>,
|
||||
|
|
@ -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)?,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue