use crate::*; use std::path::PathBuf; type MaybeClip = Option>>; 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 { 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 { 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 { 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>> { 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 { tek.toggle_dialog(Some(Dialog::Help)); Ok(None) } fn toggle_menu (&self, tek: &mut Tek, value: bool) -> Perhaps { tek.toggle_dialog(Some(Dialog::Menu)); Ok(None) } fn toggle_edit (&self, tek: &mut Tek, value: bool) -> Perhaps { tek.toggle_editor(Some(value)); Ok(None) } fn editor (&self, tek: &mut Tek, command: MidiEditCommand) -> Perhaps { 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 { 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 { Ok(tek.sampler_mut() .map(|s|command.delegate(s, |command|Self::Sampler{command})) .transpose()? .flatten()) } fn scene (&self, tek: &mut Tek, command: SceneCommand) -> Perhaps { Ok(command.delegate(tek, |command|Self::Scene{command})?) } fn track (&self, tek: &mut Tek, command: TrackCommand) -> Perhaps { Ok(command.delegate(tek, |command|Self::Track{command})?) } fn input (&self, tek: &mut Tek, command: InputCommand) -> Perhaps { Ok(command.delegate(tek, |command|Self::Input{command})?) } fn output (&self, tek: &mut Tek, command: OutputCommand) -> Perhaps { Ok(command.delegate(tek, |command|Self::Output{command})?) } fn clip (&self, tek: &mut Tek, command: ClipCommand) -> Perhaps { Ok(command.delegate(tek, |command|Self::Clip{command})?) } fn clock (&self, tek: &mut Tek, command: ClockCommand) -> Perhaps { Ok(command.delegate(tek, |command|Self::Clock{command})?) } fn device (&self, tek: &mut Tek, command: DeviceCommand) -> Perhaps { Ok(command.delegate(tek, |command|Self::Device{command})?) } fn message (&self, tek: &mut Tek, command: MessageCommand) -> Perhaps { Ok(command.delegate(tek, |command|Self::Message{command})?) } fn color (&self, tek: &mut Tek, theme: ItemTheme) -> Perhaps { Ok(tek.set_color(Some(theme)).map(|theme|Self::Color{theme})) } fn enqueue (&self, tek: &mut Tek, clip: Option>>) -> Perhaps { todo!() } fn history (&self, tek: &mut Tek, delta: isize) -> Perhaps { todo!() } fn zoom (&self, tek: &mut Tek, zoom: usize) -> Perhaps { todo!() } fn launch (&self, tek: &mut Tek) -> Perhaps { tek.launch(); Ok(None) } fn select (&self, tek: &mut Tek, selection: Selection) -> Perhaps { 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 { tek.stop_all(); Ok(None) } } #[tengri_proc::command(Tek)] impl InputCommand { fn add (&self, tek: &mut Tek) -> Perhaps { tek.midi_in_add()?; Ok(None) } } #[tengri_proc::command(Tek)] impl OutputCommand { fn add (&self, tek: &mut Tek) -> Perhaps { tek.midi_out_add()?; Ok(None) } } #[tengri_proc::command(Tek)] impl DeviceCommand { fn picker (&self, tek: &mut Tek) -> Perhaps { tek.device_picker_show(); Ok(None) } fn pick (&self, tek: &mut Tek, i: usize) -> Perhaps { tek.device_pick(i); Ok(None) } fn add (&self, tek: &mut Tek, i: usize) -> Perhaps { tek.device_add(i); Ok(None) } } #[tengri_proc::command(Tek)] impl MessageCommand { fn dismiss (&self, tek: &mut Tek) -> Perhaps { 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] 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] 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 { pool.visible = visible; Ok(Some(Self::Show(!visible))) } /// Select a clip from the clip pool fn select (&self, pool: &mut MidiPool, index: usize) -> Perhaps { pool.set_clip_index(index); Ok(None) } /// Rename a clip fn rename (&self, pool: &mut MidiPool, command: ClipRenameCommand) -> Perhaps { 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 { 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 { 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 { 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 { 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 { 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 { 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 { pool.clips_mut().swap(index, other); Ok(Some(Self::Swap(index, other))) } fn import (&self, pool: &mut MidiPool, index: usize, path: PathBuf) -> Perhaps { 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 { todo!("export clip to midi file"); } fn set_name (&self, pool: &mut MidiPool, index: usize, name: Arc) -> Perhaps { 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 { 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 { 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 { unreachable!(); } fn cancel (&self, pool: &mut MidiPool) -> Perhaps { 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 { 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) -> Perhaps { 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 { unreachable!() } fn cancel (&self, pool: &mut MidiPool) -> Perhaps { if let Some(PoolMode::Length(..)) = pool.mode_mut().clone() { *pool.mode_mut() = None; } Ok(None) } fn set (&self, pool: &mut MidiPool, length: usize) -> Perhaps { 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 { 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 { 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 { 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 { 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 { unreachable!(); } fn cancel (&self, pool: &mut MidiPool) -> Perhaps { pool.mode = None; Ok(None) } fn confirm (&self, pool: &mut MidiPool) -> Perhaps { 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 { 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 { 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) -> Perhaps { 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 { editor.put_note(true); Ok(None) } fn note_put (&self, editor: &mut MidiEditor) -> Perhaps { editor.put_note(false); Ok(None) } fn note_del (&self, editor: &mut MidiEditor) -> Perhaps { todo!() } fn note_pos (&self, editor: &mut MidiEditor, pos: usize) -> Perhaps { editor.set_note_pos(pos.min(127)); Ok(None) } fn note_len (&self, editor: &mut MidiEditor, value: usize) -> Perhaps { //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 { editor.set_time_pos(value); Ok(None) } fn time_scroll (&self, editor: &mut MidiEditor, value: usize) -> Perhaps { editor.set_time_start(value); Ok(None) } fn time_zoom (&self, editor: &mut MidiEditor, value: usize) -> Perhaps { editor.time_zoom().set(value); editor.redraw(); Ok(None) } fn time_lock (&self, editor: &mut MidiEditor, value: bool) -> Perhaps { editor.set_time_lock(value); Ok(None) } fn show (&self, editor: &mut MidiEditor, clip: Option>>) -> Perhaps { editor.set_clip(clip.as_ref()); Ok(None) } }