tek/crates/app/src/api.rs

692 lines
27 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 }}; }
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
}));
#[tengri_proc::expose]
impl Tek {
fn focus_editor (&self) -> bool {
self.is_editing()
}
fn focus_message (&self) -> bool {
matches!(self.dialog, Some(Dialog::Message(..)))
}
fn focus_device_add (&self) -> bool {
matches!(self.dialog, Some(Dialog::Device(..)))
}
fn focus_clip (&self) -> bool {
!self.is_editing() && self.selected.is_clip()
}
fn focus_track (&self) -> bool {
!self.is_editing() && self.selected.is_track()
}
fn focus_scene (&self) -> bool {
!self.is_editing() && self.selected.is_scene()
}
fn focus_mix (&self) -> bool {
!self.is_editing() && self.selected.is_mix()
}
fn focus_pool_import (&self) -> bool {
matches!(self.pool.as_ref().map(|p|p.mode.as_ref()).flatten(), Some(PoolMode::Import(..)))
}
fn focus_pool_export (&self) -> bool {
matches!(self.pool.as_ref().map(|p|p.mode.as_ref()).flatten(), Some(PoolMode::Export(..)))
}
fn focus_pool_rename (&self) -> bool {
matches!(self.pool.as_ref().map(|p|p.mode.as_ref()).flatten(), Some(PoolMode::Rename(..)))
}
fn focus_pool_length (&self) -> bool {
matches!(self.pool.as_ref().map(|p|p.mode.as_ref()).flatten(), Some(PoolMode::Length(..)))
}
fn editor_pitch (&self) -> Option<u7> {
(self.editor().map(|e|e.note_pos()).unwrap() as u8).into()
}
fn w_sidebar (&self) -> Option<u7> {
self.w_sidebar()
}
fn scene_count (&self) -> Option<usize> {
self.scenes.len()
}
fn scene_selected (&self) -> Option<usize> {
self.selected.scene()
}
fn scene_select_next (&self) -> Selection {
self.selected.scene_next(self.scenes.len())
}
fn scene_select_prev (&self) -> Selection {
self.selected.scene_prev()
}
fn track_count (&self) -> Option<usize> {
self.tracks.len()
}
fn track_selected (&self) -> Option<usize> {
self.selected.track()
}
fn track_select_next (&self) -> Selection {
self.selected.track_next(self.tracks.len())
}
fn track_select_prev (&self) -> Selection {
self.selected.track_prev()
}
fn clip_selected (&self) -> Option<Arc<RwLock<MidiClip>>> {
match self.selected {
Selection::TrackClip { track, scene } => self.scenes[scene].clips[track].clone(),
_ => None
}
}
fn device_kind (&self) -> usize {
if let Some(Dialog::Device(index)) = self.dialog {
index
} else {
0
}
}
fn device_kind_prev (&self) -> usize {
if let Some(Dialog::Device(index)) = self.dialog {
index.overflowing_sub(1).0.min(self.device_kinds().len().saturating_sub(1))
} else {
0
}
}
fn device_kind_next (&self) -> usize {
if let Some(Dialog::Device(index)) = self.dialog {
(index + 1) % self.device_kinds().len()
} else {
0
}
}
}
#[tengri_proc::expose]
impl MidiPool {
fn clip_new (&self) -> MidiClip {
self.new_clip()
}
fn clip_cloned (&self) -> MidiClip {
self.cloned_clip()
}
fn clip_index_current (&self) -> usize {
0
}
fn clip_index_after (&self) -> usize {
0
}
fn clip_index_previous (&self) -> usize {
0
}
fn clip_index_next (&self) -> usize {
0
}
fn color_random (&self) -> ItemColor {
ItemColor::random()
}
}
#[tengri_proc::expose]
impl MidiEditor {
fn time_lock (&self) -> bool {
self.time_lock().get()
}
fn time_lock_toggle (&self) -> bool {
!self.time_lock().get()
}
fn note_length (&self) -> usize {
self.note_len()
}
fn note_pos (&self) -> usize {
self.note_pos()
}
fn note_pos_next (&self) -> usize {
self.note_pos() + 1
}
fn note_pos_next_octave (&self) -> usize {
self.note_pos() + 12
}
fn note_pos_prev (&self) -> usize {
self.note_pos().saturating_sub(1)
}
fn note_pos_prev_octave (&self) -> usize {
self.note_pos().saturating_sub(12)
}
fn note_len (&self) -> usize {
self.note_len()
}
fn note_len_next (&self) -> usize {
self.note_len() + 1
}
fn note_len_prev (&self) -> usize {
self.note_len().saturating_sub(1)
}
fn note_range (&self) -> usize {
self.note_axis()
}
fn note_range_next (&self) -> usize {
self.note_axis() + 1
}
fn note_range_prev (&self) -> usize {
self.note_axis().saturating_sub(1)
}
fn time_pos (&self) -> usize {
self.time_pos()
}
fn time_pos_next (&self) -> usize {
self.time_pos() + self.time_zoom().get()
}
fn time_pos_prev (&self) -> usize {
self.time_pos().saturating_sub(self.time_zoom().get())
}
fn time_zoom (&self) -> usize {
self.time_zoom()
}
fn time_zoom_next (&self) -> usize {
self.time_zoom() + 1
}
fn time_zoom_prev (&self) -> usize {
self.time_zoom().get().saturating_sub(1).max(1)
}
}
//#[tengri_proc::input(TuiIn)]
//impl Tek {
//#[tengri::command("sampler", TekCommand::Sampler)]
//fn cmd_sampler (&mut self, cmd: SamplerCommand) -> Perhaps<TekCommand> {
//self.sampler_mut().map(|s|cmd.delegate(s, Self::Sampler)).transpose()?.flatten())
//}
//#[tengri::command("scene", TekCommand::Scene)]
//fn cmd_scene (&mut self, cmd: SceneCommand) -> Perhaps<TekCommand> {
//cmd.delegate(self, scene)
//}
//}
#[tengri_proc::command(Tek)]
impl 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)?)
//(Device [cmd: DeviceCommand] cmd.delegate(app, Self::Device)?)
//(Message [cmd: MessageCommand] cmd.delegate(app, Self::Message)?)
//(Editor [cmd: MidiEditCommand] delegate_to_editor(app, cmd)?)
//(Pool [cmd: PoolCommand] delegate_to_pool(app, cmd)?)
//(ToggleHelp [] cmd!(app.toggle_dialog(Some(Dialog::Help))))
//(ToggleMenu [] cmd!(app.toggle_dialog(Some(Dialog::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())))
//("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))
//("device" [,..a] ns!(DeviceCommand, app, a, Self::Device))
//("message" [,..a] ns!(MessageCommand, app, a, Self::Message))
//("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 }) })))
}
#[tengri_proc::command(Tek)]
impl InputCommand {
fn add (&self, state: &mut tek) -> option<self> {
state.midi_in_add()?;
none
}
}
#[tengri_proc::command(Tek)]
impl OutputCommand {
fn add (&self, state: &mut tek) -> option<self> {
state.midi_out_add()?;
none
}
}
#[tengri_proc::command(Tek)]
impl DeviceCommand {
//(Picker [] cmd!(app.device_picker_show()))
//(Pick [i: usize] cmd!(app.device_pick(i)))
//(Add [i: usize] cmd!(app.device_add(i))))
//("picker" [] Some(Self::Picker))
//("pick" [index: usize] Some(Self::Pick(index.unwrap())))
//("add" [index: usize] Some(Self::Add(index.unwrap()))))
}
#[tengri_proc::command(Tek)]
impl MessageCommand {
//(Dismiss [] cmd!(app.message_dismiss())))
//("dismiss" [] Some(Self::Dismiss)))
}
#[tengri_proc::command(Tek)]
impl 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) }))
//("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))));
}
#[tengri_proc::command(Tek)]
impl 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)))))
//("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()))))
}
#[tengri_proc::command(Tek)]
impl 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)))));
//("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))))
}
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
});