mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
561 lines
24 KiB
Rust
561 lines
24 KiB
Rust
use crate::*;
|
|
use std::path::PathBuf;
|
|
|
|
type MaybeClip = Option<Arc<RwLock<MidiClip>>>;
|
|
|
|
macro_rules! ns { ($C:ty, $s:expr, $a:expr, $W:expr) => { <$C>::try_from_expr($s, $a).map($W) } }
|
|
macro_rules! cmd { ($cmd:expr) => {{ $cmd; None }}; }
|
|
macro_rules! cmd_todo { ($msg:literal) => {{ println!($msg); None }}; }
|
|
|
|
expose!([self: Tek]
|
|
([bool]
|
|
(":mode-editor" self.is_editing())
|
|
(":mode-clip" !self.is_editing() && self.selected.is_clip())
|
|
(":mode-track" !self.is_editing() && self.selected.is_track())
|
|
(":mode-scene" !self.is_editing() && self.selected.is_scene())
|
|
(":mode-mix" !self.is_editing() && self.selected.is_mix())
|
|
(":mode-pool-import" matches!(
|
|
self.pool.as_ref().map(|p|p.mode.as_ref()).flatten(),
|
|
Some(PoolMode::Import(..))))
|
|
(":mode-pool-export" matches!(
|
|
self.pool.as_ref().map(|p|p.mode.as_ref()).flatten(),
|
|
Some(PoolMode::Export(..))))
|
|
(":mode-pool-rename" matches!(
|
|
self.pool.as_ref().map(|p|p.mode.as_ref()).flatten(),
|
|
Some(PoolMode::Rename(..))))
|
|
(":mode-pool-length" matches!(
|
|
self.pool.as_ref().map(|p|p.mode.as_ref()).flatten(),
|
|
Some(PoolMode::Length(..)))))
|
|
([isize])
|
|
([Color])
|
|
([Arc<RwLock<MidiClip>>])
|
|
([u7]
|
|
(":pitch" (self.editor().map(|e|e.note_pos()).unwrap() as u8).into()))
|
|
([u16]
|
|
(":w-sidebar" self.w_sidebar()))
|
|
([usize]
|
|
(":scene-last" self.scenes.len())
|
|
(":track-last" self.tracks.len()))
|
|
([Option<usize>]
|
|
(":scene" self.selected.scene())
|
|
(":track" self.selected.track()))
|
|
([MaybeClip]
|
|
(":clip" match self.selected {
|
|
Selection::TrackClip { track, scene } => self.scenes[scene].clips[track].clone(),
|
|
_ => None
|
|
}))
|
|
([Selection]
|
|
(":scene-next" self.selected.scene_next(self.scenes.len()))
|
|
(":scene-prev" self.selected.scene_prev())
|
|
(":track-next" self.selected.track_next(self.tracks.len()))
|
|
(":track-prev" self.selected.track_prev())));
|
|
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()
|
|
});
|
|
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),
|
|
});
|
|
|
|
handle!(TuiIn: |self: Tek, input|Ok(if let Some(command) = self.config.keys.command(self, input) {
|
|
let undo = command.execute(self)?;
|
|
if let Some(undo) = undo {
|
|
self.history.push(undo);
|
|
}
|
|
Some(true)
|
|
} else {
|
|
None
|
|
}));
|
|
|
|
impose!([app: Tek]
|
|
(TekCommand:
|
|
("menu" [] Some(Self::ToggleMenu))
|
|
("help" [] Some(Self::ToggleHelp))
|
|
("stop" [] Some(Self::StopAll))
|
|
("undo" [d: usize] Some(Self::History(-(d.unwrap_or(0) as isize))))
|
|
("redo" [d: usize] Some(Self::History(d.unwrap_or(0) as isize)))
|
|
("zoom" [z: usize] Some(Self::Zoom(z)))
|
|
("edit" [] Some(Self::Edit(None)))
|
|
("edit" [c: bool] Some(Self::Edit(c)))
|
|
("color" [] Some(Self::Color(ItemTheme::random())))
|
|
("color" [c: Color] Some(Self::Color(c.map(ItemTheme::from).expect("no color"))))
|
|
("enqueue" [c: Arc<RwLock<MidiClip>>] Some(Self::Enqueue(c)))
|
|
("launch" [] Some(Self::Launch))
|
|
("select" [t: Selection] Some(t.map(Self::Select).expect("no selection")))
|
|
("clock" [,..a] ns!(ClockCommand, app.clock(), a, Self::Clock))
|
|
("scene" [,..a] ns!(SceneCommand, app, a, Self::Scene))
|
|
("track" [,..a] ns!(TrackCommand, app, a, Self::Track))
|
|
("input" [,..a] ns!(InputCommand, app, a, Self::Input))
|
|
("output" [,..a] ns!(OutputCommand, app, a, Self::Output))
|
|
("clip" [,..a] ns!(ClipCommand, app, a, Self::Clip))
|
|
("pool" [,..a] app.pool.as_ref().map(|p|ns!(PoolCommand, p, a, Self::Pool)).flatten())
|
|
("editor" [,..a] app.editor().map(|e|ns!(MidiEditCommand, e, a, Self::Editor)).flatten())
|
|
("sampler" [,..a] app.sampler().map(|s|ns!(SamplerCommand, s, a, Self::Sampler)).flatten())
|
|
("select" [t: usize, s: usize] Some(match (t.expect("no track"), s.expect("no scene")) {
|
|
(0, 0) => Self::Select(Selection::Mix),
|
|
(t, 0) => Self::Select(Selection::Track(t)),
|
|
(0, s) => Self::Select(Selection::Scene(s)),
|
|
(t, s) => Self::Select(Selection::TrackClip { track: t, scene: s }) })))
|
|
|
|
(ClipCommand:
|
|
("edit" [a: MaybeClip] Some(Self::Edit(a.unwrap())))
|
|
("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random())))
|
|
("enqueue" [a: usize, b: usize] Some(Self::Enqueue(a.unwrap(), b.unwrap())))
|
|
("get" [a: usize, b: usize] Some(Self::Get(a.unwrap(), b.unwrap())))
|
|
("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap())))
|
|
("put" [a: usize, b: usize, c: MaybeClip] Some(Self::Put(a.unwrap(), b.unwrap(), c.unwrap())))
|
|
("delete" [a: usize, b: usize] Some(Self::Put(a.unwrap(), b.unwrap(), None))))
|
|
|
|
(InputCommand:
|
|
("add" [] Some(Self::Add)))
|
|
|
|
(OutputCommand:
|
|
("add" [] Some(Self::Add)))
|
|
|
|
(SceneCommand:
|
|
("add" [] Some(Self::Add))
|
|
("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())))
|
|
("swap" [a: usize, b: usize] Some(Self::Swap(a.unwrap(), b.unwrap()))))
|
|
|
|
(TrackCommand:
|
|
("add" [] Some(Self::Add))
|
|
("size" [a: usize] Some(Self::SetSize(a.unwrap())))
|
|
("zoom" [a: usize] Some(Self::SetZoom(a.unwrap())))
|
|
("color" [a: usize] Some(Self::SetColor(a.unwrap(), ItemTheme::random())))
|
|
("delete" [a: Option<usize>] Some(Self::Del(a.flatten().unwrap())))
|
|
("stop" [a: usize] Some(Self::Stop(a.unwrap())))
|
|
("swap" [a: usize, b: usize] Some(Self::Swap(a.unwrap(), b.unwrap())))
|
|
("play" [] Some(Self::TogglePlay))
|
|
("solo" [] Some(Self::ToggleSolo))
|
|
("rec" [] Some(Self::ToggleRec))
|
|
("mon" [] Some(Self::ToggleMon))));
|
|
|
|
defcom!([self, app: Tek]
|
|
|
|
(TekCommand
|
|
(Sampler [cmd: SamplerCommand] app.sampler_mut().map(|s|cmd.delegate(s, Self::Sampler)).transpose()?.flatten())
|
|
(Scene [cmd: SceneCommand] cmd.delegate(app, Self::Scene)?)
|
|
(Track [cmd: TrackCommand] cmd.delegate(app, Self::Track)?)
|
|
(Output [cmd: OutputCommand] cmd.delegate(app, Self::Output)?)
|
|
(Input [cmd: InputCommand] cmd.delegate(app, Self::Input)?)
|
|
(Clip [cmd: ClipCommand] cmd.delegate(app, Self::Clip)?)
|
|
(Clock [cmd: ClockCommand] cmd.delegate(app, Self::Clock)?)
|
|
(Editor [cmd: MidiEditCommand] delegate_to_editor(app, cmd)?)
|
|
(Pool [cmd: PoolCommand] delegate_to_pool(app, cmd)?)
|
|
(ToggleHelp [] cmd!(app.toggle_modal(Some(Modal::Help))))
|
|
(ToggleMenu [] cmd!(app.toggle_modal(Some(Modal::Menu))))
|
|
(Color [p: ItemTheme] app.set_color(Some(p)).map(Self::Color))
|
|
(Enqueue [c: MaybeClip] cmd_todo!("\n\rtodo: enqueue {c:?}"))
|
|
(History [d: isize] cmd_todo!("\n\rtodo: history {d:?}"))
|
|
(Zoom [z: Option<usize>] cmd_todo!("\n\rtodo: zoom {z:?}"))
|
|
(Edit [value: Option<bool>] cmd!(app.toggle_editor(value)))
|
|
(Launch [] cmd!(app.launch()))
|
|
(Select [s: Selection] cmd!(app.select(s)))
|
|
(StopAll [] cmd!(app.stop_all())))
|
|
|
|
(InputCommand
|
|
(Add [] cmd!(app.add_midi_in()?)))
|
|
|
|
(OutputCommand
|
|
(Add [] cmd!(app.add_midi_out()?)))
|
|
|
|
(TrackCommand
|
|
(TogglePlay [] Some(Self::TogglePlay))
|
|
(ToggleSolo [] Some(Self::ToggleSolo))
|
|
(SetSize [t: usize] cmd_todo!("\n\rtodo: {self:?}"))
|
|
(SetZoom [z: usize] cmd_todo!("\n\rtodo: {self:?}"))
|
|
(Swap [a: usize, b: usize] cmd_todo!("\n\rtodo: {self:?}"))
|
|
(Del [index: usize] cmd!(app.track_del(index)))
|
|
(Stop [index: usize] cmd!(app.tracks[index].player.enqueue_next(None)))
|
|
(Add [] Some(Self::Del(app.track_add_focus()?)))
|
|
(SetColor [i: usize, c: ItemTheme] Some(Self::SetColor(i, app.track_set_color(i, c))))
|
|
(ToggleRec [] { app.track_toggle_record(); Some(Self::ToggleRec) })
|
|
(ToggleMon [] { app.track_toggle_monitor(); Some(Self::ToggleMon) }))
|
|
|
|
(SceneCommand
|
|
(Swap [a: usize, b: usize] cmd_todo!("\n\rtodo: {self:?}"))
|
|
(SetSize [index: usize] cmd_todo!("\n\rtodo: {self:?}"))
|
|
(SetZoom [zoom: usize] cmd_todo!("\n\rtodo: {self:?}"))
|
|
(Enqueue [scene: usize] cmd!(app.scene_enqueue(scene)))
|
|
(Del [index: usize] cmd!(app.scene_del(index)))
|
|
(Add [] Some(Self::Del(app.scene_add_focus()?)))
|
|
(SetColor [i: usize, c: ItemTheme] Some(Self::SetColor(i, app.scene_set_color(i, c)))))
|
|
|
|
(ClipCommand
|
|
(Get [a: usize, b: usize] cmd_todo!("\n\rtodo: clip: get: {a} {b}"))
|
|
(Edit [clip: MaybeClip] cmd_todo!("\n\rtodo: clip: edit: {clip:?}"))
|
|
(SetLoop [t: usize, s: usize, l: bool] cmd_todo!("\n\rtodo: {self:?}"))
|
|
(Put [t: usize, s: usize, c: MaybeClip]
|
|
Some(Self::Put(t, s, app.clip_put(t, s, c))))
|
|
(Enqueue [t: usize, s: usize]
|
|
cmd!(app.tracks[t].player.enqueue_next(app.scenes[s].clips[t].as_ref())))
|
|
(SetColor [t: usize, s: usize, c: ItemTheme]
|
|
app.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o)))));
|
|
|
|
fn delegate_to_editor (app: &mut Tek, cmd: MidiEditCommand) -> Perhaps<TekCommand> {
|
|
Ok(app.editor.as_mut().map(|editor|cmd.delegate(editor, TekCommand::Editor))
|
|
.transpose()?
|
|
.flatten())
|
|
}
|
|
|
|
fn delegate_to_pool (app: &mut Tek, cmd: PoolCommand) -> Perhaps<TekCommand> {
|
|
Ok(if let Some(pool) = app.pool.as_mut() {
|
|
let undo = cmd.clone().delegate(pool, TekCommand::Pool)?;
|
|
if let Some(editor) = app.editor.as_mut() {
|
|
match cmd {
|
|
// autoselect: automatically load selected clip in editor
|
|
// autocolor: update color in all places simultaneously
|
|
PoolCommand::Select(_) | PoolCommand::Clip(PoolClipCommand::SetColor(_, _)) =>
|
|
editor.set_clip(pool.clip().as_ref()),
|
|
_ => {}
|
|
}
|
|
};
|
|
undo
|
|
} else {
|
|
None
|
|
})
|
|
}
|
|
|
|
#[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),
|
|
}
|
|
|
|
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))
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
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"))))
|
|
});
|
|
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"))))
|
|
});
|
|
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"))))
|
|
});
|
|
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())))
|
|
});
|
|
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"))))
|
|
});
|
|
// TODO: 1-9 seek markers that by default start every 8th of the clip
|
|
defcom!([self, state: MidiEditor]
|
|
(MidiEditCommand
|
|
(AppendNote [] { state.put_note(true); None })
|
|
(PutNote [] { state.put_note(false); None })
|
|
(DelNote [] { None })
|
|
(SetNoteCursor [x: usize] { state.set_note_pos(x.min(127)); None })
|
|
(SetNoteLength [x: usize] {
|
|
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();
|
|
//}
|
|
None
|
|
})
|
|
(SetNoteScroll [x: usize] { state.note_lo().set(x.min(127)); None })
|
|
(SetTimeCursor [x: usize] { state.set_time_pos(x); None })
|
|
(SetTimeScroll [x: usize] { state.time_start().set(x); None })
|
|
(SetTimeZoom [x: usize] { state.time_zoom().set(x); state.redraw(); None })
|
|
(SetTimeLock [x: bool] { state.time_lock().set(x); None })
|
|
(Show [x: MaybeClip] { state.set_clip(x.as_ref()); None })));
|
|
|
|
#[derive(Clone, Debug, PartialEq)] pub enum ClipRenameCommand {
|
|
Begin,
|
|
Cancel,
|
|
Confirm,
|
|
Set(Arc<str>),
|
|
}
|
|
|
|
|
|
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!()
|
|
});
|
|
|
|
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,
|
|
}
|
|
|
|
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
|
|
});
|