mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
wip: update all command definitions to use proc macro
This commit is contained in:
parent
a8be2e9dad
commit
ee7f9dcf12
3 changed files with 455 additions and 436 deletions
|
|
@ -256,111 +256,142 @@ impl MidiEditor {
|
|||
}
|
||||
}
|
||||
|
||||
//#[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 {
|
||||
fn toggle_help (&self, state: &mut Tek, value: Option<bool>) -> Option<Self> {
|
||||
//(ToggleHelp [] cmd!(app.toggle_dialog(Some(Dialog::Help))))
|
||||
None
|
||||
fn toggle_help (&self, tek: &mut Tek, value: bool) -> Perhaps<Self> {
|
||||
tek.toggle_dialog(Some(Dialog::Help));
|
||||
Ok(None)
|
||||
}
|
||||
fn toggle_menu (&self, state: &mut Tek, value: Option<bool>) -> Option<Self> {
|
||||
//(ToggleMenu [] cmd!(app.toggle_dialog(Some(Dialog::Menu))))
|
||||
None
|
||||
fn toggle_menu (&self, tek: &mut Tek, value: bool) -> Perhaps<Self> {
|
||||
tek.toggle_dialog(Some(Dialog::Menu));
|
||||
Ok(None)
|
||||
}
|
||||
fn toggle_edit (&self, state: &mut Tek, value: Option<bool>) -> Option<Self> {
|
||||
//(Edit [value: Option<bool>] cmd!(app.toggle_editor(value)))
|
||||
None
|
||||
fn toggle_edit (&self, tek: &mut Tek, value: bool) -> Perhaps<Self> {
|
||||
tek.toggle_editor(Some(value));
|
||||
Ok(None)
|
||||
}
|
||||
//(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)?)
|
||||
//(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:?}"))
|
||||
//(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())
|
||||
fn editor (&self, tek: &mut Tek, command: MidiEditCommand) -> Perhaps<Self> {
|
||||
Ok(tek.editor.as_mut().map(|editor|command.execute(editor))
|
||||
.transpose()?
|
||||
.flatten()
|
||||
.map(|undo|Self::Editor { command: undo }))
|
||||
}
|
||||
fn pool (&self, tek: &mut Tek, command: PoolCommand) -> Perhaps<Self> {
|
||||
Ok(if let Some(pool) = tek.pool.as_mut() {
|
||||
let undo = command.clone().delegate(pool, |command|TekCommand::Pool{command})?;
|
||||
// update linked editor after pool action
|
||||
tek.editor.as_mut().map(|editor|match command {
|
||||
// autoselect: automatically load selected clip in editor
|
||||
PoolCommand::Select { .. } |
|
||||
// autocolor: update color in all places simultaneously
|
||||
PoolCommand::Clip { command: PoolClipCommand::SetColor { .. } } =>
|
||||
editor.set_clip(pool.clip().as_ref()),
|
||||
_ => {}
|
||||
});
|
||||
undo
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
fn sampler (&self, tek: &mut Tek, ref command: SamplerCommand) -> Perhaps<Self> {
|
||||
Ok(tek.sampler_mut()
|
||||
.map(|s|command.delegate(s, |command|Self::Sampler{command}))
|
||||
.transpose()?
|
||||
.flatten())
|
||||
}
|
||||
fn scene (&self, tek: &mut Tek, command: SceneCommand) -> Perhaps<Self> {
|
||||
Ok(command.delegate(tek, |command|Self::Scene{command})?)
|
||||
}
|
||||
fn track (&self, tek: &mut Tek, command: TrackCommand) -> Perhaps<Self> {
|
||||
Ok(command.delegate(tek, |command|Self::Track{command})?)
|
||||
}
|
||||
fn input (&self, tek: &mut Tek, command: InputCommand) -> Perhaps<Self> {
|
||||
Ok(command.delegate(tek, |command|Self::Input{command})?)
|
||||
}
|
||||
fn output (&self, tek: &mut Tek, command: OutputCommand) -> Perhaps<Self> {
|
||||
Ok(command.delegate(tek, |command|Self::Output{command})?)
|
||||
}
|
||||
fn clip (&self, tek: &mut Tek, command: ClipCommand) -> Perhaps<Self> {
|
||||
Ok(command.delegate(tek, |command|Self::Clip{command})?)
|
||||
}
|
||||
fn clock (&self, tek: &mut Tek, command: ClockCommand) -> Perhaps<Self> {
|
||||
Ok(command.delegate(tek, |command|Self::Clock{command})?)
|
||||
}
|
||||
fn device (&self, tek: &mut Tek, command: DeviceCommand) -> Perhaps<Self> {
|
||||
Ok(command.delegate(tek, |command|Self::Device{command})?)
|
||||
}
|
||||
fn message (&self, tek: &mut Tek, command: MessageCommand) -> Perhaps<Self> {
|
||||
Ok(command.delegate(tek, |command|Self::Message{command})?)
|
||||
}
|
||||
fn color (&self, tek: &mut Tek, theme: ItemTheme) -> Perhaps<Self> {
|
||||
Ok(tek.set_color(Some(theme)).map(|theme|Self::Color{theme}))
|
||||
}
|
||||
fn enqueue (&self, tek: &mut Tek, clip: Option<Arc<RwLock<MidiClip>>>) -> Perhaps<Self> {
|
||||
todo!()
|
||||
}
|
||||
fn history (&self, tek: &mut Tek, delta: isize) -> Perhaps<Self> {
|
||||
todo!()
|
||||
}
|
||||
fn zoom (&self, tek: &mut Tek, zoom: usize) -> Perhaps<Self> {
|
||||
todo!()
|
||||
}
|
||||
fn launch (&self, tek: &mut Tek) -> Perhaps<Self> {
|
||||
tek.launch();
|
||||
Ok(None)
|
||||
}
|
||||
fn select (&self, tek: &mut Tek, selection: Selection) -> Perhaps<Self> {
|
||||
tek.select(selection);
|
||||
Ok(None)
|
||||
//("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 }) })))
|
||||
}
|
||||
fn stop_all (&self, tek: &mut Tek) -> Perhaps<Self> {
|
||||
tek.stop_all();
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[tengri_proc::command(Tek)]
|
||||
impl InputCommand {
|
||||
fn add (&self, state: &mut Tek) -> Option<Self> {
|
||||
state.midi_in_add()?;
|
||||
None
|
||||
fn add (&self, tek: &mut Tek) -> Perhaps<Self> {
|
||||
tek.midi_in_add()?;
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[tengri_proc::command(Tek)]
|
||||
impl OutputCommand {
|
||||
fn add (&self, state: &mut Tek) -> Option<Self> {
|
||||
state.midi_out_add()?;
|
||||
None
|
||||
fn add (&self, tek: &mut Tek) -> Perhaps<Self> {
|
||||
tek.midi_out_add()?;
|
||||
Ok(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()))))
|
||||
fn picker (&self, tek: &mut Tek) -> Perhaps<Self> {
|
||||
tek.device_picker_show();
|
||||
Ok(None)
|
||||
}
|
||||
fn pick (&self, tek: &mut Tek, i: usize) -> Perhaps<Self> {
|
||||
tek.device_pick(i);
|
||||
Ok(None)
|
||||
}
|
||||
fn add (&self, tek: &mut Tek, i: usize) -> Perhaps<Self> {
|
||||
tek.device_add(i);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[tengri_proc::command(Tek)]
|
||||
impl MessageCommand {
|
||||
//(Dismiss [] cmd!(app.message_dismiss())))
|
||||
//("dismiss" [] Some(Self::Dismiss)))
|
||||
fn dismiss (&self, tek: &mut Tek) -> Perhaps<Self> {
|
||||
tek.message_dismiss();
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[tengri_proc::command(Tek)]
|
||||
|
|
@ -426,332 +457,323 @@ impl ClipCommand {
|
|||
//("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 {
|
||||
#[tengri_proc::command(MidiPool)]
|
||||
impl PoolCommand {
|
||||
/// Toggle visibility of pool
|
||||
Show(bool),
|
||||
fn show (&self, pool: &mut MidiPool, visible: bool) -> Perhaps<Self> {
|
||||
pool.visible = visible;
|
||||
Ok(Some(Self::Show(!visible)))
|
||||
}
|
||||
/// Select a clip from the clip pool
|
||||
Select(usize),
|
||||
fn select (&self, pool: &mut MidiPool, index: usize) -> Perhaps<Self> {
|
||||
pool.set_clip_index(index);
|
||||
Ok(None)
|
||||
}
|
||||
/// Rename a clip
|
||||
Rename(ClipRenameCommand),
|
||||
fn rename (&self, pool: &mut MidiPool, command: ClipRenameCommand) -> Perhaps<Self> {
|
||||
Ok(match command {
|
||||
ClipRenameCommand::Begin => {
|
||||
pool.begin_clip_rename();
|
||||
None
|
||||
},
|
||||
_ => command.delegate(pool, |command|Self::Rename{command})?
|
||||
})
|
||||
}
|
||||
/// Change the length of a clip
|
||||
Length(ClipLengthCommand),
|
||||
fn length (&self, pool: &mut MidiPool, command: ClipLengthCommand) -> Perhaps<Self> {
|
||||
Ok(match command {
|
||||
ClipLengthCommand::Begin => {
|
||||
pool.begin_clip_length();
|
||||
None
|
||||
},
|
||||
_ => command.delegate(pool, |command|Self::Length{command})?
|
||||
})
|
||||
}
|
||||
/// Import from file
|
||||
Import(FileBrowserCommand),
|
||||
fn import (&self, pool: &mut MidiPool, command: FileBrowserCommand) -> Perhaps<Self> {
|
||||
Ok(match command {
|
||||
ClipImportCommand::Begin => {
|
||||
pool.begin_import();
|
||||
None
|
||||
},
|
||||
_ => command.delegate(pool, |command|Self::Import{command})?
|
||||
})
|
||||
}
|
||||
/// Export to file
|
||||
Export(FileBrowserCommand),
|
||||
fn export (&self, pool: &mut MidiPool, command: FileBrowserCommand) -> Perhaps<Self> {
|
||||
Ok(match command {
|
||||
ClipExportCommand::Begin => {
|
||||
pool.begin_export();
|
||||
None
|
||||
},
|
||||
_ => command.delegate(pool, |command|Self::Export{command})?
|
||||
})
|
||||
}
|
||||
/// 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))
|
||||
},
|
||||
})
|
||||
fn clip (&self, pool: &mut MidiPool, command: PoolClipCommand) -> Perhaps<Self> {
|
||||
command.execute(pool)?.map(|command|Self::Clip{command})
|
||||
}
|
||||
}
|
||||
|
||||
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!()
|
||||
#[tengri_proc::command(MidiPool)]
|
||||
impl PoolClipCommand {
|
||||
fn add (&self, pool: &mut MidiPool, index: usize, clip: MidiClip) -> Perhaps<Self> {
|
||||
let mut index = index;
|
||||
let clip = Arc::new(RwLock::new(clip));
|
||||
let mut clips = pool.clips_mut();
|
||||
if index >= clips.len() {
|
||||
index = clips.len();
|
||||
clips.push(clip)
|
||||
} else {
|
||||
clips.insert(index, clip);
|
||||
}
|
||||
} else {
|
||||
Ok(Some(Self::Delete(index)))
|
||||
}
|
||||
fn delete (&self, pool: &mut MidiPool, index: usize) -> Perhaps<Self> {
|
||||
let clip = pool.clips_mut().remove(index).read().unwrap().clone();
|
||||
Ok(Some(Self::Add(index, clip)))
|
||||
}
|
||||
fn swap (&self, pool: &mut MidiPool, index: usize, other: usize) -> Perhaps<Self> {
|
||||
pool.clips_mut().swap(index, other);
|
||||
Ok(Some(Self::Swap(index, other)))
|
||||
}
|
||||
fn import (&self, pool: &mut MidiPool, index: usize, path: PathBuf) -> Perhaps<Self> {
|
||||
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(pool)?
|
||||
}
|
||||
fn export (&self, pool: &mut MidiPool, index: usize, path: PathBuf) -> Perhaps<Self> {
|
||||
todo!("export clip to midi file");
|
||||
}
|
||||
fn set_name (&self, pool: &mut MidiPool, index: usize, name: Arc<str>) -> Perhaps<Self> {
|
||||
let clip = &mut pool.clips_mut()[index];
|
||||
let old_name = clip.read().unwrap().name.clone();
|
||||
clip.write().unwrap().name = name;
|
||||
Ok(Some(Self::SetName(index, old_name)))
|
||||
}
|
||||
fn set_length (&self, pool: &mut MidiPool, index: usize, length: usize) -> Perhaps<Self> {
|
||||
let clip = &mut pool.clips_mut()[index];
|
||||
let old_len = clip.read().unwrap().length;
|
||||
clip.write().unwrap().length = length;
|
||||
Ok(Some(Self::SetLength(index, old_len)))
|
||||
}
|
||||
fn set_color (&self, pool: &mut MidiPool, index: usize, color: ItemColor) -> Perhaps<Self> {
|
||||
let mut color = ItemTheme::from(color);
|
||||
std::mem::swap(&mut color, &mut pool.clips()[index].write().unwrap().color);
|
||||
Ok(Some(Self::SetColor(index, color.base)))
|
||||
}
|
||||
}
|
||||
|
||||
#[tengri_proc::command(MidiPool)]
|
||||
impl ClipRenameCommand {
|
||||
fn begin (&self, pool: &mut MidiPool) -> Perhaps<Self> {
|
||||
unreachable!();
|
||||
}
|
||||
None
|
||||
});
|
||||
fn cancel (&self, pool: &mut MidiPool) -> Perhaps<Self> {
|
||||
if let Some(PoolMode::Rename(clip, ref mut old_name)) = pool.mode_mut().clone() {
|
||||
pool.clips()[clip].write().unwrap().name = old_name.clone().into();
|
||||
}
|
||||
return Ok(None)
|
||||
}
|
||||
fn confirm (&self, pool: &mut MidiPool) -> Perhaps<Self> {
|
||||
if let Some(PoolMode::Rename(clip, ref mut old_name)) = pool.mode_mut().clone() {
|
||||
let old_name = old_name.clone();
|
||||
pool.mode_mut() = None;
|
||||
return Ok(Some(Self::Set(old_name)))
|
||||
}
|
||||
return Ok(None)
|
||||
}
|
||||
fn set (&self, pool: &mut MidiPool, value: Arc<str>) -> Perhaps<Self> {
|
||||
if let Some(PoolMode::Rename(clip, ref mut old_name)) = pool.mode_mut().clone() {
|
||||
pool.clips()[clip].write().unwrap().name = value;
|
||||
}
|
||||
return Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[tengri_proc::command(MidiPool)]
|
||||
impl ClipLengthCommand {
|
||||
fn begin (&self, pool: &mut MidiPool) -> Perhaps<Self> {
|
||||
unreachable!()
|
||||
}
|
||||
fn cancel (&self, pool: &mut MidiPool) -> Perhaps<Self> {
|
||||
if let Some(PoolMode::Length(..)) = pool.mode_mut().clone() {
|
||||
*pool.mode_mut() = None;
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
fn set (&self, pool: &mut MidiPool, length: usize) -> Perhaps<Self> {
|
||||
if let Some(PoolMode::Length(clip, ref mut length, ref mut focus))
|
||||
= pool.mode_mut().clone()
|
||||
{
|
||||
let old_length;
|
||||
{
|
||||
let clip = pool.clips()[clip].clone();//.write().unwrap();
|
||||
old_length = Some(clip.read().unwrap().length);
|
||||
clip.write().unwrap().length = length;
|
||||
}
|
||||
pool.mode_mut() = None;
|
||||
return Ok(old_length.map(|length|Self::Set { length }))
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
fn next (&self, pool: &mut MidiPool) -> Perhaps<Self> {
|
||||
if let Some(PoolMode::Length(clip, ref mut length, ref mut focus))
|
||||
= pool.mode_mut().clone()
|
||||
{
|
||||
focus.next()
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
fn prev (&self, pool: &mut MidiPool) -> Perhaps<Self> {
|
||||
if let Some(PoolMode::Length(clip, ref mut length, ref mut focus))
|
||||
= pool.mode_mut().clone()
|
||||
{
|
||||
focus.prev()
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
fn inc (&self, pool: &mut MidiPool) -> Perhaps<Self> {
|
||||
if let Some(PoolMode::Length(clip, ref mut length, ref mut focus))
|
||||
= pool.mode_mut().clone()
|
||||
{
|
||||
match focus {
|
||||
Bar => { *length += 4 * PPQ },
|
||||
Beat => { *length += PPQ },
|
||||
Tick => { *length += 1 },
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
fn dec (&self, pool: &mut MidiPool) -> Perhaps<Self> {
|
||||
if let Some(
|
||||
PoolMode::Length(clip, ref mut length, ref mut focus)
|
||||
) = pool.mode_mut().clone() {
|
||||
match focus {
|
||||
Bar => { *length = length.saturating_sub(4 * PPQ) },
|
||||
Beat => { *length = length.saturating_sub(PPQ) },
|
||||
Tick => { *length = length.saturating_sub(1) },
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[tengri_proc::command(MidiPool)]
|
||||
impl FileBrowserCommand {
|
||||
fn begin (&self, pool: &mut MidiPool) -> Perhaps<Self> {
|
||||
unreachable!();
|
||||
}
|
||||
fn cancel (&self, pool: &mut MidiPool) -> Perhaps<Self> {
|
||||
pool.mode = None;
|
||||
Ok(None)
|
||||
}
|
||||
fn confirm (&self, pool: &mut MidiPool) -> Perhaps<Self> {
|
||||
Ok(match pool.mode {
|
||||
Some(PoolMode::Import(index, ref mut browser)) => {
|
||||
if browser.is_file() {
|
||||
let index = *index;
|
||||
let path = browser.path();
|
||||
pool.mode = None;
|
||||
PoolClipCommand::Import(index, path).execute(pool)?;
|
||||
} else if browser.is_dir() {
|
||||
pool.mode = Some(PoolMode::Import(*index, browser.chdir()?));
|
||||
}
|
||||
},
|
||||
Some(PoolMode::Export(index, ref mut browser)) => match self {
|
||||
Cancel => { pool.mode = None; },
|
||||
_ => unreachable!()
|
||||
},
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
fn select (&self, pool: &mut MidiPool, index: usize) -> Perhaps<Self> {
|
||||
Ok(match pool.mode {
|
||||
Some(PoolMode::Import(index, ref mut browser)) => { browser.index = index; },
|
||||
Some(PoolMode::Export(index, ref mut browser)) => { browser.index = index; },
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
fn chdir (&self, pool: &mut MidiPool, dir: PathBuf) -> Perhaps<Self> {
|
||||
Ok(match pool.mode {
|
||||
Some(PoolMode::Import(index, ref mut browser)) => {
|
||||
pool.mode = Some(PoolMode::Import(*index, FileBrowser::new(Some(dir))?));
|
||||
},
|
||||
Some(PoolMode::Export(index, ref mut browser)) => {
|
||||
pool.mode = Some(PoolMode::Export(*index, FileBrowser::new(Some(dir))?));
|
||||
},
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
fn filter (&self, pool: &mut MidiPool, filter: Arc<str>) -> Perhaps<Self> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[tengri_proc::command(MidiEditor)]
|
||||
impl MidiEditCommand {
|
||||
// TODO: 1-9 seek markers that by default start every 8th of the clip
|
||||
fn note_append (&self, editor: &mut MidiEditor) -> Perhaps<Self> {
|
||||
editor.put_note(true);
|
||||
Ok(None)
|
||||
}
|
||||
fn note_put (&self, editor: &mut MidiEditor) -> Perhaps<Self> {
|
||||
editor.put_note(false);
|
||||
Ok(None)
|
||||
}
|
||||
fn note_del (&self, editor: &mut MidiEditor) -> Perhaps<Self> {
|
||||
todo!()
|
||||
}
|
||||
fn note_pos (&self, editor: &mut MidiEditor, pos: usize) -> Perhaps<Self> {
|
||||
editor.set_note_pos(pos.min(127));
|
||||
Ok(None)
|
||||
}
|
||||
fn note_len (&self, editor: &mut MidiEditor, value: usize) -> Perhaps<Self> {
|
||||
//let note_len = editor.get_note_len();
|
||||
//let time_zoom = editor.get_time_zoom();
|
||||
editor.set_note_len(value);
|
||||
//if note_len / time_zoom != x / time_zoom {
|
||||
editor.redraw();
|
||||
//}
|
||||
Ok(None)
|
||||
}
|
||||
fn note_scroll (&self, editor: &mut MidiEditor, value: usize) {
|
||||
editor.set_note_lo(value.min(127));
|
||||
Ok(None)
|
||||
}
|
||||
fn time_pos (&self, editor: &mut MidiEditor, value: usize) -> Perhaps<Self> {
|
||||
editor.set_time_pos(value);
|
||||
Ok(None)
|
||||
}
|
||||
fn time_scroll (&self, editor: &mut MidiEditor, value: usize) -> Perhaps<Self> {
|
||||
editor.set_time_start(value);
|
||||
Ok(None)
|
||||
}
|
||||
fn time_zoom (&self, editor: &mut MidiEditor, value: usize) -> Perhaps<Self> {
|
||||
editor.time_zoom().set(value);
|
||||
editor.redraw();
|
||||
Ok(None)
|
||||
}
|
||||
fn time_lock (&self, editor: &mut MidiEditor, value: bool) -> Perhaps<Self> {
|
||||
editor.set_time_lock(value);
|
||||
Ok(None)
|
||||
}
|
||||
fn show (&self, editor: &mut MidiEditor, clip: Option<Arc<RwLock<MidiClip>>>) -> Perhaps<Self> {
|
||||
editor.set_clip(clip.as_ref());
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,55 +1,52 @@
|
|||
use crate::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ClockCommand {
|
||||
Play(Option<u32>),
|
||||
Pause(Option<u32>),
|
||||
SeekUsec(f64),
|
||||
SeekSample(f64),
|
||||
SeekPulse(f64),
|
||||
SetBpm(f64),
|
||||
SetQuant(f64),
|
||||
SetSync(f64),
|
||||
}
|
||||
|
||||
provide_num!(u32: |self: Clock| {});
|
||||
|
||||
provide!(Option<u32>: |self: Clock| {});
|
||||
provide!(f64: |self: Clock| {});
|
||||
|
||||
atom_command!(ClockCommand: |state: Clock| {
|
||||
("play" [] Some(Self::Play(None)))
|
||||
("play" [t: u32] Some(Self::Play(t)))
|
||||
("pause" [] Some(Self::Pause(None)))
|
||||
("pause" [t: u32] Some(Self::Pause(t)))
|
||||
("toggle" [] Some(if state.is_rolling() { Self::Pause(None) } else { Self::Play(None) }))
|
||||
("toggle" [t: u32] Some(if state.is_rolling() { Self::Pause(t) } else { Self::Play(t) }))
|
||||
("seek/usec" [t: f64] Some(Self::SeekUsec(t.expect("no usec"))))
|
||||
("seek/pulse" [t: f64] Some(Self::SeekPulse(t.expect("no pulse"))))
|
||||
("seek/sample" [t: f64] Some(Self::SeekSample(t.expect("no sample"))))
|
||||
("set/bpm" [t: f64] Some(Self::SetBpm(t.expect("no bpm"))))
|
||||
("set/sync" [t: f64] Some(Self::SetSync(t.expect("no sync"))))
|
||||
("set/quant" [t: f64] Some(Self::SetQuant(t.expect("no quant"))))
|
||||
});
|
||||
|
||||
impl<T: HasClock> Command<T> for ClockCommand {
|
||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||
self.execute(state.clock_mut())
|
||||
self.execute(state.clock_mut()) // awesome
|
||||
}
|
||||
}
|
||||
|
||||
impl Command<Clock> for ClockCommand {
|
||||
fn execute (self, state: &mut Clock) -> Perhaps<Self> {
|
||||
use ClockCommand::*;
|
||||
match self {
|
||||
Play(start) => state.play_from(start)?,
|
||||
Pause(pause) => state.pause_at(pause)?,
|
||||
SeekUsec(usec) => state.playhead.update_from_usec(usec),
|
||||
SeekSample(sample) => state.playhead.update_from_sample(sample),
|
||||
SeekPulse(pulse) => state.playhead.update_from_pulse(pulse),
|
||||
SetBpm(bpm) => return Ok(Some(SetBpm(state.timebase().bpm.set(bpm)))),
|
||||
SetQuant(quant) => return Ok(Some(SetQuant(state.quant.set(quant)))),
|
||||
SetSync(sync) => return Ok(Some(SetSync(state.sync.set(sync)))),
|
||||
};
|
||||
#[tengri_proc::command(Clock)]
|
||||
impl ClockCommand {
|
||||
fn play (self, state: &mut Clock, position: Option<u32>) -> Perhaps<Self> {
|
||||
state.play_from(position)?;
|
||||
Ok(None) // TODO Some(Pause(previousPosition))
|
||||
}
|
||||
fn pause (self, state: &mut Clock, position: Option<u32>) -> Perhaps<Self> {
|
||||
state.pause_at(position)?;
|
||||
Ok(None)
|
||||
}
|
||||
fn toggle_playback (self, state: &mut Clock, position: Option<u32>) -> Perhaps<Self> {
|
||||
if state.is_rolling() {
|
||||
state.pause_at(position)?;
|
||||
} else {
|
||||
state.play_from(position)?;
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
fn seek_usec (self, state: &mut Clock, usec: f64) -> Perhaps<Self> {
|
||||
state.playhead.update_from_usec(usec);
|
||||
Ok(None)
|
||||
}
|
||||
fn seek_sample (self, state: &mut Clock, sample: f64) -> Perhaps<Self> {
|
||||
state.playhead.update_from_sample(sample);
|
||||
Ok(None)
|
||||
}
|
||||
fn seek_pulse (self, state: &mut Clock, pulse: f64) -> Perhaps<Self> {
|
||||
state.playhead.update_from_pulse(pulse);
|
||||
Ok(None)
|
||||
}
|
||||
fn set_bpm (self, state: &mut Clock, bpm: f64) -> Perhaps<Self> {
|
||||
Ok(Some(Self::SetBpm { bpm: state.timebase().bpm.set(bpm) }))
|
||||
}
|
||||
fn set_quant (self, state: &mut Clock, quant: f64) -> Perhaps<Self> {
|
||||
Ok(Some(Self::SetQuant { quant: state.quant.set(quant) }))
|
||||
}
|
||||
fn set_sync (self, state: &mut Clock, sync: f64) -> Perhaps<Self> {
|
||||
Ok(Some(Self::SetSync { sync: state.sync.set(sync) }))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue