diff --git a/Cargo.lock b/Cargo.lock index 68f62568..c592f3a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2301,6 +2301,7 @@ dependencies = [ "symphonia", "tek_engine", "tengri", + "tengri_proc", "uuid", "wavers", "winit", @@ -2367,6 +2368,7 @@ dependencies = [ name = "tengri_proc" version = "0.13.0" dependencies = [ + "heck", "proc-macro2", "quote", "syn", diff --git a/crates/app/src/api.rs b/crates/app/src/api.rs index 4f7504bf..0a15ee56 100644 --- a/crates/app/src/api.rs +++ b/crates/app/src/api.rs @@ -17,180 +17,188 @@ handle!(TuiIn: |self: Tek, input|Ok(if let Some(command) = self.config.keys.comm None })); -expose!([self: Tek] - ([bool] - (":mode-editor" self.is_editing()) - (":mode-message" matches!(self.dialog, Some(Dialog::Message(..)))) - (":mode-device-add" matches!(self.dialog, Some(Dialog::Device(..)))) - (":mode-clip" !self.is_editing() && self.selected.is_clip()) - (":mode-track" !self.is_editing() && self.selected.is_track()) - (":mode-scene" !self.is_editing() && self.selected.is_scene()) - (":mode-mix" !self.is_editing() && self.selected.is_mix()) - (":mode-pool-import" matches!( - self.pool.as_ref().map(|p|p.mode.as_ref()).flatten(), - Some(PoolMode::Import(..)))) - (":mode-pool-export" matches!( - self.pool.as_ref().map(|p|p.mode.as_ref()).flatten(), - Some(PoolMode::Export(..)))) - (":mode-pool-rename" matches!( - self.pool.as_ref().map(|p|p.mode.as_ref()).flatten(), - Some(PoolMode::Rename(..)))) - (":mode-pool-length" matches!( - self.pool.as_ref().map(|p|p.mode.as_ref()).flatten(), - Some(PoolMode::Length(..))))) - ([isize]) - ([Color]) - ([Arc>]) - ([u7] - (":pitch" (self.editor().map(|e|e.note_pos()).unwrap() as u8).into())) - ([u16] - (":w-sidebar" self.w_sidebar())) - ([usize] - (":scene-last" self.scenes.len()) - (":track-last" self.tracks.len()) - (":device-kind" if let Some(Dialog::Device(index)) = self.dialog { +#[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 { + (self.editor().map(|e|e.note_pos()).unwrap() as u8).into() + } + fn w_sidebar (&self) -> Option { + self.w_sidebar() + } + fn scene_count (&self) -> Option { + 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) -> Option { + 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 - }) - (":device-kind-prev" if let Some(Dialog::Device(index)) = self.dialog { + } + } + 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 - }) - (":device-kind-next" if let Some(Dialog::Device(index)) = self.dialog { + } + } + fn device_kind_next (&self) -> usize { + if let Some(Dialog::Device(index)) = self.dialog { (index + 1) % self.device_kinds().len() } else { 0 - })) - ([Option] - (":scene" self.selected.scene()) - (":track" self.selected.track())) - ([MaybeClip] - (":clip" match self.selected { - Selection::TrackClip { track, scene } => self.scenes[scene].clips[track].clone(), - _ => None - })) - ([Selection] - (":scene-next" self.selected.scene_next(self.scenes.len())) - (":scene-prev" self.selected.scene_prev()) - (":track-next" self.selected.track_next(self.tracks.len())) - (":track-prev" self.selected.track_prev()))); -expose!([self: MidiPool] - ([bool]) - ([PathBuf]) - ([Arc]) - ([MidiClip] - (":new-clip" self.new_clip()) - (":cloned-clip" self.cloned_clip())) - ([usize] - (":current" 0) - (":after" 0) - (":previous" 0) - (":next" 0)) - ([ItemColor] - (":random-color" ItemColor::random()))); -expose!([self: MidiEditor] - ([bool] - (":true" true) - (":false" false) - (":time-lock" self.time_lock().get()) - (":time-lock-toggle" !self.time_lock().get())) - ([usize] - (":note-length" self.note_len()) - (":note-pos" self.note_pos()) - (":note-pos-next" self.note_pos() + 1) - (":note-pos-prev" self.note_pos().saturating_sub(1)) - (":note-pos-next-octave" self.note_pos() + 12) - (":note-pos-prev-octave" self.note_pos().saturating_sub(12)) - (":note-len" self.note_len()) - (":note-len-next" self.note_len() + 1) - (":note-len-prev" self.note_len().saturating_sub(1)) - (":note-range" self.note_axis().get()) - (":note-range-prev" self.note_axis().get() + 1) - (":note-range-next" self.note_axis().get().saturating_sub(1)) - (":time-pos" self.time_pos()) - (":time-pos-next" self.time_pos() + self.time_zoom().get()) - (":time-pos-prev" self.time_pos().saturating_sub(self.time_zoom().get())) - (":time-zoom" self.time_zoom().get()) - (":time-zoom-next" self.time_zoom().get() + 1) - (":time-zoom-prev" self.time_zoom().get().saturating_sub(1).max(1)))); + } + } +} -impose!([app: Tek] - (TekCommand: - ("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>] Some(Self::Enqueue(c))) - ("launch" [] Some(Self::Launch)) - ("select" [t: Selection] Some(t.map(Self::Select).expect("no selection"))) - ("clock" [,..a] ns!(ClockCommand, app.clock(), a, Self::Clock)) - ("scene" [,..a] ns!(SceneCommand, app, a, Self::Scene)) - ("track" [,..a] ns!(TrackCommand, app, a, Self::Track)) - ("input" [,..a] ns!(InputCommand, app, a, Self::Input)) - ("output" [,..a] ns!(OutputCommand, app, a, Self::Output)) - ("clip" [,..a] ns!(ClipCommand, app, a, Self::Clip)) - ("device" [,..a] ns!(DeviceCommand, app, a, Self::Device)) - ("message" [,..a] ns!(MessageCommand, app, a, Self::Message)) - ("pool" [,..a] app.pool.as_ref().map(|p|ns!(PoolCommand, p, a, Self::Pool)).flatten()) - ("editor" [,..a] app.editor().map(|e|ns!(MidiEditCommand, e, a, Self::Editor)).flatten()) - ("sampler" [,..a] app.sampler().map(|s|ns!(SamplerCommand, s, a, Self::Sampler)).flatten()) - ("select" [t: usize, s: usize] Some(match (t.expect("no track"), s.expect("no scene")) { - (0, 0) => Self::Select(Selection::Mix), - (t, 0) => Self::Select(Selection::Track(t)), - (0, s) => Self::Select(Selection::Scene(s)), - (t, s) => Self::Select(Selection::TrackClip { track: t, scene: s }) }))) +#[tengri_proc::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() + } +} - (ClipCommand: - ("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)))) - - (InputCommand: - ("add" [] Some(Self::Add))) - - (OutputCommand: - ("add" [] Some(Self::Add))) - - (DeviceCommand: - ("picker" [] Some(Self::Picker)) - ("pick" [index: usize] Some(Self::Pick(index.unwrap()))) - ("add" [index: usize] Some(Self::Add(index.unwrap())))) - - (MessageCommand: - ("dismiss" [] Some(Self::Dismiss))) - - (SceneCommand: - ("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())))) - - (TrackCommand: - ("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::expose] +impl MidiEditor { + fn time_lock (&self) -> bool { + self.time_lock().get() + } + fn time_lock_toggle (&self) -> bool { + !self.time_lock().get() + } + fn note_length (&self) -> usize { + self.note_len() + } + fn note_pos (&self) -> usize { + self.note_pos() + } + fn note_pos_next (&self) -> usize { + self.note_pos() + 1 + } + fn note_pos_next_octave (&self) -> usize { + self.note_pos() + 12 + } + fn note_pos_prev (&self) -> usize { + self.note_pos().saturating_sub(1) + } + fn note_pos_prev_octave (&self) -> usize { + self.note_pos().saturating_sub(12) + } + fn note_len (&self) -> usize { + self.note_len() + } + fn note_len_next (&self) -> usize { + self.note_len() + 1 + } + fn note_len_prev (&self) -> usize { + self.note_len().saturating_sub(1) + } + fn note_range (&self) -> usize { + self.note_axis() + } + fn note_range_next (&self) -> usize { + self.note_axis() + 1 + } + fn note_range_prev (&self) -> usize { + self.note_axis().saturating_sub(1) + } + fn time_pos (&self) -> usize { + self.time_pos() + } + fn time_pos_next (&self) -> usize { + self.time_pos() + self.time_zoom().get() + } + fn time_pos_prev (&self) -> usize { + self.time_pos().saturating_sub(self.time_zoom().get()) + } + fn time_zoom (&self) -> usize { + self.time_zoom() + } + fn time_zoom_next (&self) -> usize { + self.time_zoom() + 1 + } + fn time_zoom_prev (&self) -> usize { + self.time_zoom().get().saturating_sub(1).max(1) + } +} //#[tengri_proc::input(TuiIn)] //impl Tek { @@ -204,77 +212,154 @@ impose!([app: Tek] //} //} -defcom!([self, app: Tek] +#[tengri_proc::command(Tek)] +impl TekCommand { + //(Sampler [cmd: SamplerCommand] app.sampler_mut().map(|s|cmd.delegate(s, Self::Sampler)).transpose()?.flatten()) + //(Scene [cmd: SceneCommand] cmd.delegate(app, Self::Scene)?) + //(Track [cmd: TrackCommand] cmd.delegate(app, Self::Track)?) + //(Output [cmd: OutputCommand] cmd.delegate(app, Self::Output)?) + //(Input [cmd: InputCommand] cmd.delegate(app, Self::Input)?) + //(Clip [cmd: ClipCommand] cmd.delegate(app, Self::Clip)?) + //(Clock [cmd: ClockCommand] cmd.delegate(app, Self::Clock)?) + //(Device [cmd: DeviceCommand] cmd.delegate(app, Self::Device)?) + //(Message [cmd: MessageCommand] cmd.delegate(app, Self::Message)?) + //(Editor [cmd: MidiEditCommand] delegate_to_editor(app, cmd)?) + //(Pool [cmd: PoolCommand] delegate_to_pool(app, cmd)?) + //(ToggleHelp [] cmd!(app.toggle_dialog(Some(Dialog::Help)))) + //(ToggleMenu [] cmd!(app.toggle_dialog(Some(Dialog::Menu)))) + //(Color [p: ItemTheme] app.set_color(Some(p)).map(Self::Color)) + //(Enqueue [c: MaybeClip] cmd_todo!("\n\rtodo: enqueue {c:?}")) + //(History [d: isize] cmd_todo!("\n\rtodo: history {d:?}")) + //(Zoom [z: Option] cmd_todo!("\n\rtodo: zoom {z:?}")) + //(Edit [value: Option] cmd!(app.toggle_editor(value))) + //(Launch [] cmd!(app.launch())) + //(Select [s: Selection] cmd!(app.select(s))) + //(StopAll [] cmd!(app.stop_all()))) + //("menu" [] Some(Self::ToggleMenu)) + //("help" [] Some(Self::ToggleHelp)) + //("stop" [] Some(Self::StopAll)) + //("undo" [d: usize] Some(Self::History(-(d.unwrap_or(0) as isize)))) + //("redo" [d: usize] Some(Self::History(d.unwrap_or(0) as isize))) + //("zoom" [z: usize] Some(Self::Zoom(z))) + //("edit" [] Some(Self::Edit(None))) + //("edit" [c: bool] Some(Self::Edit(c))) + //("color" [] Some(Self::Color(ItemTheme::random()))) + //("color" [c: Color] Some(Self::Color(c.map(ItemTheme::from).expect("no color")))) + //("enqueue" [c: Arc>] Some(Self::Enqueue(c))) + //("launch" [] Some(Self::Launch)) + //("select" [t: Selection] Some(t.map(Self::Select).expect("no selection"))) + //("clock" [,..a] ns!(ClockCommand, app.clock(), a, Self::Clock)) + //("scene" [,..a] ns!(SceneCommand, app, a, Self::Scene)) + //("track" [,..a] ns!(TrackCommand, app, a, Self::Track)) + //("input" [,..a] ns!(InputCommand, app, a, Self::Input)) + //("output" [,..a] ns!(OutputCommand, app, a, Self::Output)) + //("clip" [,..a] ns!(ClipCommand, app, a, Self::Clip)) + //("device" [,..a] ns!(DeviceCommand, app, a, Self::Device)) + //("message" [,..a] ns!(MessageCommand, app, a, Self::Message)) + //("pool" [,..a] app.pool.as_ref().map(|p|ns!(PoolCommand, p, a, Self::Pool)).flatten()) + //("editor" [,..a] app.editor().map(|e|ns!(MidiEditCommand, e, a, Self::Editor)).flatten()) + //("sampler" [,..a] app.sampler().map(|s|ns!(SamplerCommand, s, a, Self::Sampler)).flatten()) + //("select" [t: usize, s: usize] Some(match (t.expect("no track"), s.expect("no scene")) { + //(0, 0) => Self::Select(Selection::Mix), + //(t, 0) => Self::Select(Selection::Track(t)), + //(0, s) => Self::Select(Selection::Scene(s)), + //(t, s) => Self::Select(Selection::TrackClip { track: t, scene: s }) }))) +} - (TekCommand - (Sampler [cmd: SamplerCommand] app.sampler_mut().map(|s|cmd.delegate(s, Self::Sampler)).transpose()?.flatten()) - (Scene [cmd: SceneCommand] cmd.delegate(app, Self::Scene)?) - (Track [cmd: TrackCommand] cmd.delegate(app, Self::Track)?) - (Output [cmd: OutputCommand] cmd.delegate(app, Self::Output)?) - (Input [cmd: InputCommand] cmd.delegate(app, Self::Input)?) - (Clip [cmd: ClipCommand] cmd.delegate(app, Self::Clip)?) - (Clock [cmd: ClockCommand] cmd.delegate(app, Self::Clock)?) - (Device [cmd: DeviceCommand] cmd.delegate(app, Self::Device)?) - (Message [cmd: MessageCommand] cmd.delegate(app, Self::Message)?) - (Editor [cmd: MidiEditCommand] delegate_to_editor(app, cmd)?) - (Pool [cmd: PoolCommand] delegate_to_pool(app, cmd)?) - (ToggleHelp [] cmd!(app.toggle_dialog(Some(Dialog::Help)))) - (ToggleMenu [] cmd!(app.toggle_dialog(Some(Dialog::Menu)))) - (Color [p: ItemTheme] app.set_color(Some(p)).map(Self::Color)) - (Enqueue [c: MaybeClip] cmd_todo!("\n\rtodo: enqueue {c:?}")) - (History [d: isize] cmd_todo!("\n\rtodo: history {d:?}")) - (Zoom [z: Option] cmd_todo!("\n\rtodo: zoom {z:?}")) - (Edit [value: Option] cmd!(app.toggle_editor(value))) - (Launch [] cmd!(app.launch())) - (Select [s: Selection] cmd!(app.select(s))) - (StopAll [] cmd!(app.stop_all()))) +#[tengri_proc::command(Tek)] +impl InputCommand { + fn add (&self, state: &mut tek) -> option { + state.midi_in_add()?; + none + } +} - (InputCommand - (Add [] cmd!(app.midi_in_add()?))) +#[tengri_proc::command(Tek)] +impl OutputCommand { + fn add (&self, state: &mut tek) -> option { + state.midi_out_add()?; + none + } +} - (OutputCommand - (Add [] cmd!(app.midi_out_add()?))) +#[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())))) +} - (DeviceCommand - (Picker [] cmd!(app.device_picker_show())) - (Pick [i: usize] cmd!(app.device_pick(i))) - (Add [i: usize] cmd!(app.device_add(i)))) +#[tengri_proc::command(Tek)] +impl MessageCommand { + //(Dismiss [] cmd!(app.message_dismiss()))) + //("dismiss" [] Some(Self::Dismiss))) +} - (MessageCommand - (Dismiss [] cmd!(app.message_dismiss()))) +#[tengri_proc::command(Tek)] +impl TrackCommand { + //(TogglePlay [] Some(Self::TogglePlay)) + //(ToggleSolo [] Some(Self::ToggleSolo)) + //(SetSize [t: usize] cmd_todo!("\n\rtodo: {self:?}")) + //(SetZoom [z: usize] cmd_todo!("\n\rtodo: {self:?}")) + //(Swap [a: usize, b: usize] cmd_todo!("\n\rtodo: {self:?}")) + //(Del [index: usize] cmd!(app.track_del(index))) + //(Stop [index: usize] cmd!(app.tracks[index].player.enqueue_next(None))) + //(Add [] Some(Self::Del(app.track_add_focus()?))) + //(SetColor [i: usize, c: ItemTheme] Some(Self::SetColor(i, app.track_set_color(i, c)))) + //(ToggleRec [] { app.track_toggle_record(); Some(Self::ToggleRec) }) + //(ToggleMon [] { app.track_toggle_monitor(); Some(Self::ToggleMon) })) + //("add" [] Some(Self::Add)) + //("size" [a: usize] Some(Self::SetSize(a.unwrap()))) + //("zoom" [a: usize] Some(Self::SetZoom(a.unwrap()))) + //("color" [a: usize] Some(Self::SetColor(a.unwrap(), ItemTheme::random()))) + //("delete" [a: Option] 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)))); +} - (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) })) +#[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())))) +} - (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))))) - - (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))))); +#[tengri_proc::command(Tek)] +impl ClipCommand { + //(Get [a: usize, b: usize] cmd_todo!("\n\rtodo: clip: get: {a} {b}")) + //(Edit [clip: MaybeClip] cmd_todo!("\n\rtodo: clip: edit: {clip:?}")) + //(SetLoop [t: usize, s: usize, l: bool] cmd_todo!("\n\rtodo: {self:?}")) + //(Put [t: usize, s: usize, c: MaybeClip] + //Some(Self::Put(t, s, app.clip_put(t, s, c)))) + //(Enqueue [t: usize, s: usize] + //cmd!(app.tracks[t].player.enqueue_next(app.scenes[s].clips[t].as_ref()))) + //(SetColor [t: usize, s: usize, c: ItemTheme] + //app.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o))))); + //("edit" [a: MaybeClip] Some(Self::Edit(a.unwrap()))) + //("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random()))) + //("enqueue" [a: usize, b: usize] Some(Self::Enqueue(a.unwrap(), b.unwrap()))) + //("get" [a: usize, b: usize] Some(Self::Get(a.unwrap(), b.unwrap()))) + //("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap()))) + //("put" [a: usize, b: usize, c: MaybeClip] Some(Self::Put(a.unwrap(), b.unwrap(), c.unwrap()))) + //("delete" [a: usize, b: usize] Some(Self::Put(a.unwrap(), b.unwrap(), None)))) +} fn delegate_to_editor (app: &mut Tek, cmd: MidiEditCommand) -> Perhaps { Ok(app.editor.as_mut().map(|editor|cmd.delegate(editor, TekCommand::Editor)) diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index 5778861b..164065b3 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -76,13 +76,14 @@ impl Tek { ))) )) } +} +impl Tek { fn view_dialog_menu (&self) -> impl Content { let options = ||["Projects", "Settings", "Help", "Quit"].iter(); let option = |a,i|Tui::fg(Rgb(255,255,255), format!("{}", a)); Bsp::s(Tui::bold(true, "tek!"), Bsp::s("", Map::south(1, options, option))) } - fn view_dialog_help (&self) -> impl Content + use<'_> { let bindings = ||self.config.keys.layers.iter() .filter_map(|a|(a.0)(self).then_some(a.1)) diff --git a/crates/device/Cargo.toml b/crates/device/Cargo.toml index a84b08a9..f07255a4 100644 --- a/crates/device/Cargo.toml +++ b/crates/device/Cargo.toml @@ -4,13 +4,16 @@ edition = { workspace = true } version = { workspace = true } [dependencies] -tengri = { workspace = true } +tengri = { workspace = true } +tengri_proc = { workspace = true } + tek_engine = { workspace = true } -uuid = { workspace = true, optional = true } -livi = { workspace = true, optional = true } -symphonia = { workspace = true, optional = true } -wavers = { workspace = true, optional = true } -winit = { workspace = true, optional = true } + +uuid = { workspace = true, optional = true } +livi = { workspace = true, optional = true } +symphonia = { workspace = true, optional = true } +wavers = { workspace = true, optional = true } +winit = { workspace = true, optional = true } [features] default = [ "clock", "sequencer", "sampler", "lv2" ] diff --git a/crates/device/src/sampler/sampler_api.rs b/crates/device/src/sampler/sampler_api.rs index f80a7247..e0bc58f4 100644 --- a/crates/device/src/sampler/sampler_api.rs +++ b/crates/device/src/sampler/sampler_api.rs @@ -1,72 +1,165 @@ use crate::*; -expose!([self: Sampler] - ([Arc]) - ([MaybeSample]) - ([PathBuf]) - ([f32]) - ([u7] - (":pitch" (self.note_pos() as u8).into()) // TODO - (":sample" (self.note_pos() as u8).into())) - ([usize] - (":sample-up" self.note_pos().min(119) + 8) - (":sample-down" self.note_pos().max(8) - 8) - (":sample-left" self.note_pos().min(126) + 1) - (":sample-right" self.note_pos().max(1) - 1))); - -impose!([state: Sampler] - (FileBrowserCommand: - ("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] Some(Self::Filter(f.expect("no filter"))))) - (SamplerCommand: - ("import" [,..a] - FileBrowserCommand::try_from_expr(state, a).map(Self::Import)) - ("select" [i: usize] - Some(Self::Select(i.expect("no index")))) - ("record/begin" [i: u7] - Some(Self::RecordBegin(i.expect("no index")))) - ("record/cancel" [] - Some(Self::RecordCancel)) - ("record/finish" [] - Some(Self::RecordFinish)) - ("set/sample" [i: u7, s: MaybeSample] - Some(Self::SetSample(i.expect("no index"), s.expect("no sampler")))) - ("set/start" [i: u7, s: usize] - Some(Self::SetStart(i.expect("no index"), s.expect("no start")))) - ("set/gain" [i: u7, g: f32] - Some(Self::SetGain(i.expect("no index"), g.expect("no gain")))) - ("note/on" [p: u7, v: u7] - Some(Self::NoteOn(p.expect("no pitch"), v.expect("no velocity")))) - ("note/off" [p: u7] - Some(Self::NoteOff(p.expect("no pitch")))))); - macro_rules! cmd { ($cmd:expr) => {{ $cmd; None }}; } macro_rules! cmd_todo { ($msg:literal) => {{ println!($msg); None }}; } -defcom!([self, state: Sampler] - (SamplerCommand - (Select [i: usize] Some(Self::Select(state.set_note_pos(i)))) - (RecordBegin [p: u7] cmd!(state.begin_recording(p.as_int() as usize))) - (RecordCancel [] cmd!(state.cancel_recording())) - (RecordFinish [] cmd!(state.finish_recording())) - (SetStart [p: u7, frame: usize] cmd_todo!("\n\rtodo: {self:?}")) - (SetGain [p: u7, gain: f32] cmd_todo!("\n\rtodo: {self:?}")) - (NoteOn [p: u7, velocity: u7] cmd_todo!("\n\rtodo: {self:?}")) - (NoteOff [p: u7] cmd_todo!("\n\rtodo: {self:?}")) - (SetSample [p: u7, s: MaybeSample] Some(Self::SetSample(p, state.set_sample(p, s)))) - (Import [c: FileBrowserCommand] match c { - FileBrowserCommand::Begin => { - //let voices = &state.state.voices; - //let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![]))); - state.mode = Some(SamplerMode::Import(0, FileBrowser::new(None)?)); - None - }, - _ => { - println!("\n\rtodo: import: filebrowser: {c:?}"); - None - } - }))); +#[tengri_proc::expose] +impl Sampler { + //fn file_browser_filter (&self) -> Arc { + //todo!() + //} + //fn file_browser_path (&self) -> PathBuf { + //todo!(); + //} + ///// Immutable reference to sample at cursor. + //fn sample_selected (&self) -> MaybeSample { + //for (i, sample) in self.mapped.iter().enumerate() { + //if i == self.cursor().0 { + //return sample.as_ref() + //} + //} + //for (i, sample) in self.unmapped.iter().enumerate() { + //if i + self.mapped.len() == self.cursor().0 { + //return Some(sample) + //} + //} + //None + //} + //fn sample_gain (&self) -> f32 { + //todo!() + //} + //fn sample_above () -> usize { + //self.note_pos().min(119) + 8 + //} + //fn sample_below () -> usize { + //self.note_pos().max(8) - 8 + //} + //fn sample_to_left () -> usize { + //self.note_pos().min(126) + 1 + //} + //fn sample_to_right () -> usize { + //self.note_pos().max(1) - 1 + //} + //fn selected_pitch () -> u7 { + //(self.note_pos() as u8).into() // TODO + //} + //fn selected_sample () -> u7 { // TODO + //(self.note_pos() as u8).into() + //} +} + +#[tengri_proc::command(Sampler)] +impl SamplerCommand { + //fn select (&self, state: &mut Sampler, i: usize) -> Option { + //Self::Select(state.set_note_pos(i)) + //} + ///// Assign sample to pitch + //fn set (&self, pitch: u7, sample: MaybeSample) -> Option { + //let i = pitch.as_int() as usize; + //let old = self.mapped[i].clone(); + //self.mapped[i] = sample; + //Some(Self::Set(old)) + //} + //fn record_begin (&self, state: &mut Sampler, pitch: u7) -> Option { + //self.recording = Some(( + //pitch.as_int() as usize, + //Arc::new(RwLock::new(Sample::new("Sample", 0, 0, vec![vec![];self.audio_ins.len()]))) + //)); + //None + //} + //fn record_cancel (&self, state: &mut Sampler) -> Option { + //self.recording = None; + //None + //} + //fn record_finish (&self, state: &mut Sampler) -> Option { + //let recording = self.recording.take(); + //let _sample = if let Some((index, sample)) = recording { + //let old = self.mapped[index].clone(); + //self.mapped[index] = Some(sample); + //old + //} else { + //None + //}; + //None + //} + //fn set_start (&self, state: &mut Sampler, pitch: u7, frame: usize) -> Option { + //todo!() + //} + //fn set_gain (&self, state: &mut Sampler, pitch: u7, g: f32) -> Option { + //todo!() + //} + //fn note_on (&self, state: &mut Sampler, pitch: u7, v: u7) -> Option { + //todo!() + //} + //fn note_off (&self, state: &mut Sampler, pitch: u7) -> Option { + //todo!() + //} + //fn set_sample (&self, state: &mut Sampler, pitch: u7, s: MaybeSample) -> Option { + //Some(Self::SetSample(p, state.set_sample(p, s))) + //} + //fn import (&self, state: &mut Sampler, c: FileBrowserCommand) -> Option { + //match c { + //FileBrowserCommand::Begin => { + ////let voices = &state.state.voices; + ////let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![]))); + //state.mode = Some(SamplerMode::Import(0, FileBrowser::new(None)?)); + //None + //}, + //_ => { + //println!("\n\rtodo: import: filebrowser: {c:?}"); + //None + //} + //} + //} + ////(Select [i: usize] Some(Self::Select(state.set_note_pos(i)))) + ////(RecordBegin [p: u7] cmd!(state.begin_recording(p.as_int() as usize))) + ////(RecordCancel [] cmd!(state.cancel_recording())) + ////(RecordFinish [] cmd!(state.finish_recording())) + ////(SetStart [p: u7, frame: usize] cmd_todo!("\n\rtodo: {self:?}")) + ////(SetGain [p: u7, gain: f32] cmd_todo!("\n\rtodo: {self:?}")) + ////(NoteOn [p: u7, velocity: u7] cmd_todo!("\n\rtodo: {self:?}")) + ////(NoteOff [p: u7] cmd_todo!("\n\rtodo: {self:?}")) + ////(SetSample [p: u7, s: MaybeSample] Some(Self::SetSample(p, state.set_sample(p, s)))) + ////(Import [c: FileBrowserCommand] match c { + ////FileBrowserCommand::Begin => { + //////let voices = &state.state.voices; + //////let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![]))); + ////state.mode = Some(SamplerMode::Import(0, FileBrowser::new(None)?)); + ////None + ////}, + ////_ => { + ////println!("\n\rtodo: import: filebrowser: {c:?}"); + ////None + ////} + ////}))); + ////("import" [,..a] + ////FileBrowserCommand::try_from_expr(state, a).map(Self::Import)) + ////("select" [i: usize] + ////Some(Self::Select(i.expect("no index")))) + ////("record/begin" [i: u7] + ////Some(Self::RecordBegin(i.expect("no index")))) + ////("record/cancel" [] + ////Some(Self::RecordCancel)) + ////("record/finish" [] + ////Some(Self::RecordFinish)) + ////("set/sample" [i: u7, s: MaybeSample] + ////Some(Self::SetSample(i.expect("no index"), s.expect("no sampler")))) + ////("set/start" [i: u7, s: usize] + ////Some(Self::SetStart(i.expect("no index"), s.expect("no start")))) + ////("set/gain" [i: u7, g: f32] + ////Some(Self::SetGain(i.expect("no index"), g.expect("no gain")))) + ////("note/on" [p: u7, v: u7] + ////Some(Self::NoteOn(p.expect("no pitch"), v.expect("no velocity")))) + ////("note/off" [p: u7] + ////Some(Self::NoteOff(p.expect("no pitch")))))); +} + +#[tengri_proc::command(Sampler)] +impl FileBrowserCommand { + //("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] Some(Self::Filter(f.expect("no filter"))))) +} diff --git a/crates/device/src/sampler/sampler_model.rs b/crates/device/src/sampler/sampler_model.rs index 4d1f1b6b..3c0d1c4c 100644 --- a/crates/device/src/sampler/sampler_model.rs +++ b/crates/device/src/sampler/sampler_model.rs @@ -77,50 +77,10 @@ impl Sampler { ..Default::default() }) } - pub fn cancel_recording (&mut self) { - self.recording = None; - } - pub fn begin_recording (&mut self, index: usize) { - self.recording = Some(( - index, - Arc::new(RwLock::new(Sample::new("Sample", 0, 0, vec![vec![];self.audio_ins.len()]))) - )); - } - pub fn finish_recording (&mut self) -> MaybeSample { - let recording = self.recording.take(); - if let Some((index, sample)) = recording { - let old = self.mapped[index].clone(); - self.mapped[index] = Some(sample); - old - } else { - None - } - } - /// Immutable reference to sample at cursor. - pub fn sample (&self) -> Option<&Arc>> { - for (i, sample) in self.mapped.iter().enumerate() { - if i == self.cursor().0 { - return sample.as_ref() - } - } - for (i, sample) in self.unmapped.iter().enumerate() { - if i + self.mapped.len() == self.cursor().0 { - return Some(sample) - } - } - None - } /// Value of cursor pub fn cursor (&self) -> (usize, usize) { (self.cursor.0.load(Relaxed), self.cursor.1.load(Relaxed)) } - /// Assign sample to pitch - pub fn set_sample (&mut self, pitch: u7, sample: MaybeSample) -> MaybeSample { - let i = pitch.as_int() as usize; - let old = self.mapped[i].clone(); - self.mapped[i] = sample; - old - } } impl NoteRange for Sampler { @@ -189,4 +149,3 @@ pub enum SamplerMode { // Load sample from path Import(usize, FileBrowser), } - diff --git a/crates/device/src/sampler/sampler_view.rs b/crates/device/src/sampler/sampler_view.rs index 880d670d..15875aa1 100644 --- a/crates/device/src/sampler/sampler_view.rs +++ b/crates/device/src/sampler/sampler_view.rs @@ -80,6 +80,7 @@ impl Sampler { Tui::fg_bg(fg, bg, format!("{note:3} {}", self.view_list_item(note, compact))) })) } + pub fn view_list_item (&self, note: usize, compact: bool) -> String { if compact { String::default() @@ -87,6 +88,7 @@ impl Sampler { draw_list_item(&self.mapped[note]) } } + pub fn view_sample (&self, note_pt: usize) -> impl Content + use<'_> { Outer(true, Style::default().fg(Tui::g(96))).enclose(draw_viewer(if let Some((_, sample)) = &self.recording { Some(sample) @@ -96,6 +98,7 @@ impl Sampler { None })) } + pub fn status (&self, index: usize) -> impl Content { draw_status(self.mapped[index].as_ref()) } diff --git a/deps/tengri b/deps/tengri index b543c43e..7df7cb83 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit b543c43e68154f049019da648064f36af1537434 +Subproject commit 7df7cb839c14c0e010ce36519c75ffacc0e76c18 diff --git a/target/.gitkeep b/target/.gitkeep deleted file mode 100644 index e69de29b..00000000