mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-08 12:46:42 +01:00
779 lines
28 KiB
Rust
779 lines
28 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> {
|
|
Some((self.editor().map(|e|e.note_pos()).unwrap() as u8).into())
|
|
}
|
|
/// Width of display
|
|
pub(crate) fn w (&self) -> u16 {
|
|
self.size.w() as u16
|
|
}
|
|
/// Width allocated for sidebar.
|
|
pub(crate) fn w_sidebar (&self) -> u16 {
|
|
self.w() / if self.is_editing() { 16 } else { 8 } as u16
|
|
}
|
|
/// Width taken by all tracks.
|
|
pub(crate) fn w_tracks (&self) -> u16 {
|
|
self.tracks_with_sizes().last().map(|(_, _, _, x)|x as u16).unwrap_or(0)
|
|
}
|
|
/// Width available to display tracks.
|
|
pub(crate) fn w_tracks_area (&self) -> u16 {
|
|
self.w().saturating_sub(2 * self.w_sidebar())
|
|
}
|
|
/// Height of display
|
|
pub(crate) fn h (&self) -> u16 {
|
|
self.size.h() as u16
|
|
}
|
|
/// Height available to display track headers.
|
|
pub(crate) fn h_tracks_area (&self) -> u16 {
|
|
5 // FIXME
|
|
//self.h().saturating_sub(self.h_inputs() + self.h_outputs())
|
|
}
|
|
/// Height available to display tracks.
|
|
pub(crate) fn h_scenes_area (&self) -> u16 {
|
|
//15
|
|
self.h().saturating_sub(
|
|
self.h_inputs() +
|
|
self.h_outputs() +
|
|
self.h_devices() +
|
|
13 // FIXME
|
|
)
|
|
}
|
|
/// Height taken by all scenes.
|
|
pub(crate) fn h_scenes (&self) -> u16 {
|
|
self.scenes_with_sizes(self.is_editing(), Self::H_SCENE, Self::H_EDITOR).last()
|
|
.map(|(_, _, _, y)|y as u16).unwrap_or(0)
|
|
}
|
|
/// Height taken by all inputs.
|
|
pub(crate) fn h_inputs (&self) -> u16 {
|
|
self.inputs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
|
|
}
|
|
/// Height taken by all outputs.
|
|
pub(crate) fn h_outputs (&self) -> u16 {
|
|
self.outputs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
|
|
}
|
|
/// Height taken by visible device slots.
|
|
pub(crate) fn h_devices (&self) -> u16 {
|
|
2
|
|
//1 + self.devices_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
|
|
}
|
|
fn scene_count (&self) -> 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) -> 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.get_time_lock()
|
|
}
|
|
fn time_lock_toggled (&self) -> bool {
|
|
!self.get_time_lock()
|
|
}
|
|
|
|
fn note_length (&self) -> usize {
|
|
self.get_note_len()
|
|
}
|
|
|
|
fn note_pos (&self) -> usize {
|
|
self.get_note_pos()
|
|
}
|
|
fn note_pos_next (&self) -> usize {
|
|
self.get_note_pos() + 1
|
|
}
|
|
fn note_pos_next_octave (&self) -> usize {
|
|
self.get_note_pos() + 12
|
|
}
|
|
fn note_pos_prev (&self) -> usize {
|
|
self.get_note_pos().saturating_sub(1)
|
|
}
|
|
fn note_pos_prev_octave (&self) -> usize {
|
|
self.get_note_pos().saturating_sub(12)
|
|
}
|
|
|
|
fn note_len (&self) -> usize {
|
|
self.get_note_len()
|
|
}
|
|
fn note_len_next (&self) -> usize {
|
|
self.get_note_len() + 1
|
|
}
|
|
fn note_len_prev (&self) -> usize {
|
|
self.get_note_len().saturating_sub(1)
|
|
}
|
|
|
|
fn note_range (&self) -> usize {
|
|
self.get_note_axis()
|
|
}
|
|
fn note_range_next (&self) -> usize {
|
|
self.get_note_axis() + 1
|
|
}
|
|
fn note_range_prev (&self) -> usize {
|
|
self.get_note_axis().saturating_sub(1)
|
|
}
|
|
|
|
fn time_pos (&self) -> usize {
|
|
self.get_time_pos()
|
|
}
|
|
fn time_pos_next (&self) -> usize {
|
|
self.get_time_pos() + self.time_zoom()
|
|
}
|
|
fn time_pos_prev (&self) -> usize {
|
|
self.get_time_pos().saturating_sub(self.time_zoom())
|
|
}
|
|
|
|
fn time_zoom (&self) -> usize {
|
|
self.get_time_zoom()
|
|
}
|
|
fn time_zoom_next (&self) -> usize {
|
|
self.get_time_zoom() + 1
|
|
}
|
|
fn time_zoom_prev (&self) -> usize {
|
|
self.get_time_zoom().saturating_sub(1).max(1)
|
|
}
|
|
}
|
|
|
|
#[tengri_proc::command(Tek)]
|
|
impl TekCommand {
|
|
fn toggle_help (&self, tek: &mut Tek, value: bool) -> Perhaps<Self> {
|
|
tek.toggle_dialog(Some(Dialog::Help));
|
|
Ok(None)
|
|
}
|
|
fn toggle_menu (&self, tek: &mut Tek, value: bool) -> Perhaps<Self> {
|
|
tek.toggle_dialog(Some(Dialog::Menu));
|
|
Ok(None)
|
|
}
|
|
fn toggle_edit (&self, tek: &mut Tek, value: bool) -> Perhaps<Self> {
|
|
tek.toggle_editor(Some(value));
|
|
Ok(None)
|
|
}
|
|
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, tek: &mut Tek) -> Perhaps<Self> {
|
|
tek.midi_in_add()?;
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
#[tengri_proc::command(Tek)]
|
|
impl OutputCommand {
|
|
fn add (&self, tek: &mut Tek) -> Perhaps<Self> {
|
|
tek.midi_out_add()?;
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
#[tengri_proc::command(Tek)]
|
|
impl DeviceCommand {
|
|
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 {
|
|
fn dismiss (&self, tek: &mut Tek) -> Perhaps<Self> {
|
|
tek.message_dismiss();
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
#[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))))
|
|
}
|
|
|
|
#[tengri_proc::command(MidiPool)]
|
|
impl PoolCommand {
|
|
/// Toggle visibility of pool
|
|
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
|
|
fn select (&self, pool: &mut MidiPool, index: usize) -> Perhaps<Self> {
|
|
pool.set_clip_index(index);
|
|
Ok(None)
|
|
}
|
|
/// Rename a clip
|
|
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
|
|
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
|
|
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
|
|
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
|
|
fn clip (&self, pool: &mut MidiPool, command: PoolClipCommand) -> Perhaps<Self> {
|
|
command.execute(pool)?.map(|command|Self::Clip{command})
|
|
}
|
|
}
|
|
|
|
#[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);
|
|
}
|
|
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!();
|
|
}
|
|
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)
|
|
}
|
|
}
|