use crate::*; handle!(TuiIn: |self: Tek, input|Ok({ // If editing, editor keys take priority if self.is_editing() { if self.editor.handle(input)? == Some(true) { return Ok(Some(true)) } } // Handle from root keymap if let Some(command) = self.keys.command::<_, TekCommand, _>(self, input) { if let Some(undo) = command.execute(self)? { self.history.push(undo); } return Ok(Some(true)) } // Handle from selection-dependent keymaps if let Some(command) = match self.selected() { Selection::Clip(_, _) => self.keys_clip, Selection::Track(_) => self.keys_track, Selection::Scene(_) => self.keys_scene, Selection::Mix => self.keys_mix, }.command::<_, TekCommand, _>(self, input) { if let Some(undo) = command.execute(self)? { self.history.push(undo); } return Ok(Some(true)) } None })); macro_rules! defcom { ($($Command:ident { $( $Variant:ident $(($($Param:ty),+))? )* $(,)? })*) => { $(#[derive(Clone, Debug)] pub enum $Command { $($Variant $(($($Param),+))?),* })* } } defcom! { TekCommand { Clip(ClipCommand) Clock(ClockCommand) Color(ItemPalette) Edit(Option) Editor(MidiEditCommand) Enqueue(Option>>) History(isize) Input(InputCommand) Launch Output(OutputCommand) Pool(PoolCommand) Sampler(SamplerCommand) Scene(SceneCommand) Select(Selection) StopAll Track(TrackCommand) Zoom(Option) } InputCommand { Add } OutputCommand { Add } TrackCommand { Add Del(usize) Stop(usize) Swap(usize, usize) SetSize(usize) SetZoom(usize) SetColor(usize, ItemPalette) TogglePlay ToggleSolo ToggleRecord ToggleMonitor } SceneCommand { Add Del(usize) Swap(usize, usize) SetSize(usize) SetZoom(usize) SetColor(usize, ItemPalette) Enqueue(usize) } ClipCommand { Get(usize, usize) Put(usize, usize, Option>>) Enqueue(usize, usize) Edit(Option>>) SetLoop(usize, usize, bool) SetColor(usize, usize, ItemPalette) } } command!(|self: TekCommand, app: Tek|match self { Self::Zoom(_) => { println!("\n\rtodo: global zoom"); None }, Self::History(delta) => { println!("\n\rtodo: undo/redo"); None }, Self::Select(s) => { app.selected = s; // autoedit: load focused clip in editor. if let Some(ref mut editor) = app.editor { editor.set_clip(match app.selected { Selection::Clip(t, s) if let Some(Some(Some(clip))) = app .scenes.get(s).map(|s|s.clips.get(t)) => Some(clip), _ => None }); } None }, Self::Edit(value) => { if let Some(value) = value { if app.is_editing() != value { app.editing.store(value, Relaxed); } } else { app.editing.store(!app.is_editing(), Relaxed); }; // autocreate: create new clip from pool when entering empty cell if let Some(ref pool) = app.pool { if app.is_editing() { if let Selection::Clip(t, s) = app.selected { if let Some(scene) = app.scenes.get_mut(s) { if let Some(slot) = scene.clips.get_mut(t) { if slot.is_none() { let (index, mut clip) = pool.add_new_clip(); // autocolor: new clip colors from scene and track color clip.write().unwrap().color = ItemColor::random_near( app.tracks[t].color.base.mix( scene.color.base, 0.5 ), 0.2 ).into(); if let Some(ref mut editor) = app.editor { editor.set_clip(Some(&clip)); } *slot = Some(clip); } } } } } } None }, Self::Clock(cmd) => cmd.delegate(app, Self::Clock)?, Self::Scene(cmd) => cmd.delegate(app, Self::Scene)?, Self::Track(cmd) => cmd.delegate(app, Self::Track)?, Self::Input(cmd) => cmd.delegate(app, Self::Input)?, Self::Output(cmd) => cmd.delegate(app, Self::Output)?, Self::Clip(cmd) => cmd.delegate(app, Self::Clip)?, Self::Editor(cmd) => app.editor.as_mut() .map(|editor|cmd.delegate(editor, Self::Editor)).transpose()?.flatten(), //Self::Sampler(cmd) => app.sampler.as_mut() //.map(|sampler|cmd.delegate(sampler, Self::Sampler)).transpose()?.flatten(), //Self::Enqueue(clip) => app.player.as_mut() //.map(|player|{player.enqueue_next(clip.as_ref());None}).flatten(), Self::Launch => { use Selection::*; match app.selected { Track(t) => app.tracks[t].player.enqueue_next(None), Clip(t, s) => app.tracks[t].player.enqueue_next(app.scenes[s].clips[t].as_ref()), Scene(s) => { for t in 0..app.tracks.len() { app.tracks[t].player.enqueue_next(app.scenes[s].clips[t].as_ref()) } }, _ => {} }; None }, Self::Color(palette) => { use Selection::*; Some(Self::Color(match app.selected { Mix => { let old = app.color; app.color = palette; old }, Track(t) => { let old = app.tracks[t].color; app.tracks[t].color = palette; old } Scene(s) => { let old = app.scenes[s].color; app.scenes[s].color = palette; old } Clip(t, s) => { if let Some(ref clip) = app.scenes[s].clips[t] { let mut clip = clip.write().unwrap(); let old = clip.color; clip.color = palette; old } else { return Ok(None) } } })) }, Self::StopAll => { for track in 0..app.tracks.len(){app.tracks[track].player.enqueue_next(None);} None }, Self::Pool(cmd) => if let Some(pool) = app.pool.as_mut() { let undo = cmd.clone().delegate(pool, Self::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 }, _ => todo!("{self:?}") }); command!(|self: InputCommand, app: Tek|match self { Self::Add => { app.midi_ins.push(JackMidiIn::new(&app.jack, &format!("M/{}", app.midi_ins.len()), &[])?); None }, }); command!(|self: OutputCommand, app: Tek|match self { Self::Add => { app.midi_outs.push(JackMidiOut::new(&app.jack, &format!("{}/M", app.midi_outs.len()), &[])?); None }, }); command!(|self: TrackCommand, app: Tek|match self { Self::Add => { use Selection::*; let index = app.track_add(None, None, &[], &[])?.0; app.selected = match app.selected { Track(t) => Track(index), Clip(t, s) => Clip(index, s), _ => app.selected }; Some(Self::Del(index)) }, Self::Del(index) => { app.track_del(index); None }, Self::Stop(track) => { app.tracks[track].player.enqueue_next(None); None }, Self::SetColor(index, color) => { let old = app.tracks[index].color; app.tracks[index].color = color; Some(Self::SetColor(index, old)) }, Self::TogglePlay => { Some(Self::TogglePlay) }, Self::ToggleSolo => { Some(Self::ToggleSolo) }, Self::ToggleRecord => { if let Some(t) = app.selected.track() { app.tracks[t-1].player.recording = !app.tracks[t-1].player.recording; } Some(Self::ToggleRecord) }, Self::ToggleMonitor => { if let Some(t) = app.selected.track() { app.tracks[t-1].player.monitoring = !app.tracks[t-1].player.monitoring; } Some(Self::ToggleMonitor) }, _ => None }); command!(|self: SceneCommand, app: Tek|match self { Self::Add => { use Selection::*; let index = app.scene_add(None, None)?.0; app.selected = match app.selected { Scene(s) => Scene(index), Clip(t, s) => Clip(t, index), _ => app.selected }; Some(Self::Del(index)) }, Self::Del(index) => { app.scene_del(index); None }, Self::SetColor(index, color) => { let old = app.scenes[index].color; app.scenes[index].color = color; Some(Self::SetColor(index, old)) }, Self::Enqueue(scene) => { for track in 0..app.tracks.len() { app.tracks[track].player.enqueue_next(app.scenes[scene].clips[track].as_ref()); } None }, _ => None }); command!(|self: ClipCommand, app: Tek|match self { Self::Get(track, scene) => { todo!() }, Self::Put(track, scene, clip) => { let old = app.scenes[scene].clips[track].clone(); app.scenes[scene].clips[track] = clip; Some(Self::Put(track, scene, old)) }, Self::Enqueue(track, scene) => { app.tracks[track].player.enqueue_next(app.scenes[scene].clips[track].as_ref()); None }, Self::SetColor(track, scene, color) => { app.scenes[scene].clips[track].as_ref().map(|clip|{ let mut clip = clip.write().unwrap(); let old = clip.color.clone(); clip.color = color.clone(); panic!("{color:?} {old:?}"); Self::SetColor(track, scene, old) }) }, _ => None });