refactor into fewer crates, pt.2

This commit is contained in:
🪞👃🪞 2025-05-01 18:03:27 +03:00
parent 77703d83a5
commit a22a793c31
48 changed files with 2155 additions and 2157 deletions

View file

@ -1,4 +1,5 @@
use crate::*;
use std::path::PathBuf;
type MaybeClip = Option<Arc<RwLock<MidiClip>>>;
@ -89,7 +90,7 @@ impose!([app: Tek]
(SceneCommand:
("add" [] Some(Self::Add))
("delete" [a: Option<usize>] Some(Self::Del(a.flatten().unwrap())))
("delete" [a: Option<usize>] Some(Self::Del(a.flatten().unwrap())))
("zoom" [a: usize] Some(Self::SetZoom(a.unwrap())))
("color" [a: usize] Some(Self::SetColor(a.unwrap(), ItemTheme::G[128])))
("enqueue" [a: usize] Some(Self::Enqueue(a.unwrap())))
@ -193,3 +194,411 @@ fn delegate_to_pool (app: &mut Tek, cmd: PoolCommand) -> Perhaps<TekCommand> {
None
})
}
handle!(TuiIn: |self: MidiPool, input|{
//Ok(if let Some(command) = match self.mode() {
//Some(PoolMode::Rename(..)) => self.keys_rename,
//Some(PoolMode::Length(..)) => self.keys_length,
//Some(PoolMode::Import(..)) | Some(PoolMode::Export(..)) => self.keys_file,
//_ => self.keys
//}.command::<Self, PoolCommand, TuiIn>(self, input) {
Ok(if let Some(command) = self.keys.command(self, input) {
let _undo = command.execute(self)?;
Some(true)
} else {
None
})
});
provide!(bool: |self: MidiPool| {});
provide!(MidiClip: |self: MidiPool| {
":new-clip" => self.new_clip(),
":cloned-clip" => self.cloned_clip(),
});
provide!(PathBuf: |self: MidiPool| {});
provide!(Arc<str>: |self: MidiPool| {});
provide!(usize: |self: MidiPool| {
":current" => 0,
":after" => 0,
":previous" => 0,
":next" => 0
});
provide!(ItemColor: |self: MidiPool| {
":random-color" => ItemColor::random()
});
#[derive(Clone, PartialEq, Debug)] pub enum PoolCommand {
/// Toggle visibility of pool
Show(bool),
/// Select a clip from the clip pool
Select(usize),
/// Rename a clip
Rename(ClipRenameCommand),
/// Change the length of a clip
Length(ClipLengthCommand),
/// Import from file
Import(FileBrowserCommand),
/// Export to file
Export(FileBrowserCommand),
/// Update the contents of the clip pool
Clip(PoolClipCommand),
}
atom_command!(PoolCommand: |state: MidiPool| {
("show" [a: bool] Some(Self::Show(a.expect("no flag"))))
("select" [i: usize] Some(Self::Select(i.expect("no index"))))
("rename" [,..a] ClipRenameCommand::try_from_expr(state, a).map(Self::Rename))
("length" [,..a] ClipLengthCommand::try_from_expr(state, a).map(Self::Length))
("import" [,..a] FileBrowserCommand::try_from_expr(state, a).map(Self::Import))
("export" [,..a] FileBrowserCommand::try_from_expr(state, a).map(Self::Export))
("clip" [,..a] PoolClipCommand::try_from_expr(state, a).map(Self::Clip))
});
command!(|self: PoolCommand, state: MidiPool|{
use PoolCommand::*;
match self {
Rename(ClipRenameCommand::Begin) => { state.begin_clip_rename(); None }
Rename(command) => command.delegate(state, Rename)?,
Length(ClipLengthCommand::Begin) => { state.begin_clip_length(); None },
Length(command) => command.delegate(state, Length)?,
Import(FileBrowserCommand::Begin) => { state.begin_import()?; None },
Import(command) => command.delegate(state, Import)?,
Export(FileBrowserCommand::Begin) => { state.begin_export()?; None },
Export(command) => command.delegate(state, Export)?,
Clip(command) => command.execute(state)?.map(Clip),
Show(visible) => { state.visible = visible; Some(Self::Show(!visible)) },
Select(clip) => { state.set_clip_index(clip); None },
}
});
#[derive(Clone, Debug, PartialEq)] pub enum PoolClipCommand {
Add(usize, MidiClip),
Delete(usize),
Swap(usize, usize),
Import(usize, PathBuf),
Export(usize, PathBuf),
SetName(usize, Arc<str>),
SetLength(usize, usize),
SetColor(usize, ItemColor),
}
atom_command!(PoolClipCommand: |state: MidiPool| {
("add" [i: usize, c: MidiClip]
Some(Self::Add(i.expect("no index"), c.expect("no clip"))))
("delete" [i: usize]
Some(Self::Delete(i.expect("no index"))))
("swap" [a: usize, b: usize]
Some(Self::Swap(a.expect("no index"), b.expect("no index"))))
("import" [i: usize, p: PathBuf]
Some(Self::Import(i.expect("no index"), p.expect("no path"))))
("export" [i: usize, p: PathBuf]
Some(Self::Export(i.expect("no index"), p.expect("no path"))))
("set-name" [i: usize, n: Arc<str>]
Some(Self::SetName(i.expect("no index"), n.expect("no name"))))
("set-length" [i: usize, l: usize]
Some(Self::SetLength(i.expect("no index"), l.expect("no length"))))
("set-color" [i: usize, c: ItemColor]
Some(Self::SetColor(i.expect("no index"), c.expect("no color"))))
});
impl<T: HasClips> Command<T> for PoolClipCommand {
fn execute (self, model: &mut T) -> Perhaps<Self> {
use PoolClipCommand::*;
Ok(match self {
Add(mut index, clip) => {
let clip = Arc::new(RwLock::new(clip));
let mut clips = model.clips_mut();
if index >= clips.len() {
index = clips.len();
clips.push(clip)
} else {
clips.insert(index, clip);
}
Some(Self::Delete(index))
},
Delete(index) => {
let clip = model.clips_mut().remove(index).read().unwrap().clone();
Some(Self::Add(index, clip))
},
Swap(index, other) => {
model.clips_mut().swap(index, other);
Some(Self::Swap(index, other))
},
Import(index, path) => {
let bytes = std::fs::read(&path)?;
let smf = Smf::parse(bytes.as_slice())?;
let mut t = 0u32;
let mut events = vec![];
for track in smf.tracks.iter() {
for event in track.iter() {
t += event.delta.as_int();
if let TrackEventKind::Midi { channel, message } = event.kind {
events.push((t, channel.as_int(), message));
}
}
}
let mut clip = MidiClip::new("imported", true, t as usize + 1, None, None);
for event in events.iter() {
clip.notes[event.0 as usize].push(event.2);
}
Self::Add(index, clip).execute(model)?
},
Export(_index, _path) => {
todo!("export clip to midi file");
},
SetName(index, 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 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) => {
let mut color = ItemTheme::from(color);
std::mem::swap(&mut color, &mut model.clips()[index].write().unwrap().color);
Some(Self::SetColor(index, color.base))
},
})
}
}
#[derive(Clone, Debug, PartialEq)] pub enum ClipRenameCommand {
Begin,
Cancel,
Confirm,
Set(Arc<str>),
}
atom_command!(ClipRenameCommand: |state: MidiPool| {
("begin" [] Some(Self::Begin))
("cancel" [] Some(Self::Cancel))
("confirm" [] Some(Self::Confirm))
("set" [n: Arc<str>] Some(Self::Set(n.expect("no name"))))
});
command!(|self: ClipRenameCommand, state: MidiPool|if let Some(
PoolMode::Rename(clip, ref mut old_name)
) = state.mode_mut().clone() {
match self {
Self::Set(s) => {
state.clips()[clip].write().unwrap().name = s;
return Ok(Some(Self::Set(old_name.clone().into())))
},
Self::Confirm => {
let old_name = old_name.clone();
*state.mode_mut() = None;
return Ok(Some(Self::Set(old_name)))
},
Self::Cancel => {
state.clips()[clip].write().unwrap().name = old_name.clone().into();
return Ok(None)
},
_ => unreachable!()
}
} else {
unreachable!()
});
atom_command!(FileBrowserCommand: |state: MidiPool| {
("begin" [] Some(Self::Begin))
("cancel" [] Some(Self::Cancel))
("confirm" [] Some(Self::Confirm))
("select" [i: usize] Some(Self::Select(i.expect("no index"))))
("chdir" [p: PathBuf] Some(Self::Chdir(p.expect("no path"))))
("filter" [f: Arc<str>] Some(Self::Filter(f.expect("no filter"))))
});
command!(|self: FileBrowserCommand, state: MidiPool|{
use PoolMode::*;
use FileBrowserCommand::*;
let mode = &mut state.mode;
match mode {
Some(Import(index, ref mut browser)) => match self {
Cancel => { *mode = None; },
Chdir(cwd) => { *mode = Some(Import(*index, FileBrowser::new(Some(cwd))?)); },
Select(index) => { browser.index = index; },
Confirm => if browser.is_file() {
let index = *index;
let path = browser.path();
*mode = None;
PoolClipCommand::Import(index, path).execute(state)?;
} else if browser.is_dir() {
*mode = Some(Import(*index, browser.chdir()?));
},
_ => todo!(),
},
Some(Export(index, ref mut browser)) => match self {
Cancel => { *mode = None; },
Chdir(cwd) => { *mode = Some(Export(*index, FileBrowser::new(Some(cwd))?)); },
Select(index) => { browser.index = index; },
_ => unreachable!()
},
_ => unreachable!(),
};
None
});
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ClipLengthCommand {
Begin,
Cancel,
Set(usize),
Next,
Prev,
Inc,
Dec,
}
atom_command!(ClipLengthCommand: |state: MidiPool| {
("begin" [] Some(Self::Begin))
("cancel" [] Some(Self::Cancel))
("next" [] Some(Self::Next))
("prev" [] Some(Self::Prev))
("inc" [] Some(Self::Inc))
("dec" [] Some(Self::Dec))
("set" [l: usize] Some(Self::Set(l.expect("no length"))))
});
command!(|self: ClipLengthCommand, state: MidiPool|{
use ClipLengthCommand::*;
use ClipLengthFocus::*;
if let Some(
PoolMode::Length(clip, ref mut length, ref mut focus)
) = state.mode_mut().clone() {
match self {
Cancel => { *state.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 old_length;
{
let clip = state.clips()[clip].clone();//.write().unwrap();
old_length = Some(clip.read().unwrap().length);
clip.write().unwrap().length = length;
}
*state.mode_mut() = None;
return Ok(old_length.map(Self::Set))
},
_ => unreachable!()
}
} else {
unreachable!();
}
None
});
provide!(bool: |self: MidiEditor| {
":true" => true,
":false" => false,
":time-lock" => self.time_lock().get(),
":time-lock-toggle" => !self.time_lock().get(),
});
provide!(usize: |self: MidiEditor| {
":note-length" => self.note_len(),
":note-pos" => self.note_pos(),
":note-pos-next" => self.note_pos() + 1,
":note-pos-prev" => self.note_pos().saturating_sub(1),
":note-pos-next-octave" => self.note_pos() + 12,
":note-pos-prev-octave" => self.note_pos().saturating_sub(12),
":note-len" => self.note_len(),
":note-len-next" => self.note_len() + 1,
":note-len-prev" => self.note_len().saturating_sub(1),
":note-range" => self.note_axis().get(),
":note-range-prev" => self.note_axis().get() + 1,
":note-range-next" => self.note_axis().get().saturating_sub(1),
":time-pos" => self.time_pos(),
":time-pos-next" => self.time_pos() + self.time_zoom().get(),
":time-pos-prev" => self.time_pos().saturating_sub(self.time_zoom().get()),
":time-zoom" => self.time_zoom().get(),
":time-zoom-next" => self.time_zoom().get() + 1,
":time-zoom-prev" => self.time_zoom().get().saturating_sub(1).max(1),
});
atom_command!(MidiEditCommand: |state: MidiEditor| {
("note/append" [] Some(Self::AppendNote))
("note/put" [] Some(Self::PutNote))
("note/del" [] Some(Self::DelNote))
("note/pos" [a: usize] Some(Self::SetNoteCursor(a.expect("no note cursor"))))
("note/len" [a: usize] Some(Self::SetNoteLength(a.expect("no note length"))))
("time/pos" [a: usize] Some(Self::SetTimeCursor(a.expect("no time cursor"))))
("time/zoom" [a: usize] Some(Self::SetTimeZoom(a.expect("no time zoom"))))
("time/lock" [a: bool] Some(Self::SetTimeLock(a.expect("no time lock"))))
("time/lock" [] Some(Self::SetTimeLock(!state.time_lock().get())))
});
#[derive(Clone, Debug)] pub enum MidiEditCommand {
// TODO: 1-9 seek markers that by default start every 8th of the clip
AppendNote,
PutNote,
DelNote,
SetNoteCursor(usize),
SetNoteLength(usize),
SetNoteScroll(usize),
SetTimeCursor(usize),
SetTimeScroll(usize),
SetTimeZoom(usize),
SetTimeLock(bool),
Show(Option<Arc<RwLock<MidiClip>>>),
}
handle!(TuiIn: |self: MidiEditor, input|Ok(if let Some(command) = self.keys.command(self, input) {
command.execute(self)?;
Some(true)
} else {
None
}));
impl Command<MidiEditor> for MidiEditCommand {
fn execute (self, state: &mut MidiEditor) -> Perhaps<Self> {
use MidiEditCommand::*;
match self {
Show(clip) => { state.set_clip(clip.as_ref()); },
DelNote => {},
PutNote => { state.put_note(false); },
AppendNote => { state.put_note(true); },
SetTimeZoom(x) => { state.time_zoom().set(x); state.redraw(); },
SetTimeLock(x) => { state.time_lock().set(x); },
SetTimeScroll(x) => { state.time_start().set(x); },
SetNoteScroll(x) => { state.note_lo().set(x.min(127)); },
SetNoteLength(x) => {
let note_len = state.note_len();
let time_zoom = state.time_zoom().get();
state.set_note_len(x);
//if note_len / time_zoom != x / time_zoom {
state.redraw();
//}
},
SetTimeCursor(x) => { state.set_time_pos(x); },
SetNoteCursor(note) => { state.set_note_pos(note.min(127)); },
//_ => todo!("{:?}", self)
}
Ok(None)
}
}