diff --git a/crates/cli/edn/arranger_keys.edn b/crates/app/edn/arranger_keys.edn similarity index 100% rename from crates/cli/edn/arranger_keys.edn rename to crates/app/edn/arranger_keys.edn diff --git a/crates/cli/edn/arranger_keys_clip.edn b/crates/app/edn/arranger_keys_clip.edn similarity index 100% rename from crates/cli/edn/arranger_keys_clip.edn rename to crates/app/edn/arranger_keys_clip.edn diff --git a/crates/cli/edn/arranger_keys_mix.edn b/crates/app/edn/arranger_keys_mix.edn similarity index 100% rename from crates/cli/edn/arranger_keys_mix.edn rename to crates/app/edn/arranger_keys_mix.edn diff --git a/crates/cli/edn/arranger_keys_scene.edn b/crates/app/edn/arranger_keys_scene.edn similarity index 100% rename from crates/cli/edn/arranger_keys_scene.edn rename to crates/app/edn/arranger_keys_scene.edn diff --git a/crates/cli/edn/arranger_keys_track.edn b/crates/app/edn/arranger_keys_track.edn similarity index 100% rename from crates/cli/edn/arranger_keys_track.edn rename to crates/app/edn/arranger_keys_track.edn diff --git a/crates/app/edn/sampler_keys.edn b/crates/app/edn/sampler_keys.edn new file mode 100644 index 00000000..dc8b9554 --- /dev/null +++ b/crates/app/edn/sampler_keys.edn @@ -0,0 +1,11 @@ +(@up select :sample-up) +(@w select :sample-up) + +(@down select :sample-down) +(@s select :sample-down) + +(@left select :sample-left) +(@a select :sample-left) + +(@right select :sample-right) +(@d select :sample-right) diff --git a/crates/app/src/api.rs b/crates/app/src/api.rs index b217633a..f87b70f5 100644 --- a/crates/app/src/api.rs +++ b/crates/app/src/api.rs @@ -543,27 +543,55 @@ impose!([app: Tek] { }); -handle!(TuiIn: |self: Tek, input|Ok({ +handle!(TuiIn: |self: Tek, input|if let Some(handler) = self.handler { + handler(self, input) +} else { + Ok(None) +}); + +pub fn handle_arranger (app: &mut Tek, input: &TuiIn) -> Perhaps { + // If editing, editor keys take priority - if self.is_editing() { - if self.editor.handle(input)? == Some(true) { + if app.is_editing() { + if app.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); } + if let Some(command) = SourceIter(include_str!("../edn/arranger_keys.edn")) + .command::<_, TekCommand, _>(app, input) + { + if let Some(undo) = command.execute(app)? { app.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); } + if let Some(command) = match app.selected() { + Selection::Clip(_, _) => SourceIter(include_str!("../edn/arranger_keys_clip.edn")), + Selection::Track(_) => SourceIter(include_str!("../edn/arranger_keys_track.edn")), + Selection::Scene(_) => SourceIter(include_str!("../edn/arranger_keys_scene.edn")), + Selection::Mix => SourceIter(include_str!("../edn/arranger_keys_mix.edn")), + }.command::<_, TekCommand, _>(app, input) { + if let Some(undo) = command.execute(app)? { + app.history.push(undo); + } return Ok(Some(true)) } - None -})); + + Ok(None) + +} + +pub fn handle_sampler (app: &mut Tek, input: &TuiIn) -> Perhaps { + let sampler = app.tracks[0].sampler_mut(0).expect("no sampler"); + if let Some(command) = SourceIter(include_str!("../edn/sampler_keys.edn")) + .command::<_, SamplerCommand, _>(sampler, input) + { + if let Some(undo) = command.execute(sampler)? { + //app.history.push(undo); // TODO UNDO + } + return Ok(Some(true)) + } + Ok(None) +} diff --git a/crates/app/src/model.rs b/crates/app/src/model.rs index fa0030b0..f93a157c 100644 --- a/crates/app/src/model.rs +++ b/crates/app/src/model.rs @@ -59,17 +59,22 @@ use crate::*; pub keys_mix: SourceIter<'static>, // Cache of formatted strings pub view_cache: Arc>, + // Input handler function + pub handler: OptionResult, Box<(dyn std::error::Error + 'static)>>> } impl Tek { + pub(crate) fn clip (&self) -> Option>> { self.scene()?.clips.get(self.selected().track()?)?.clone() } + pub(crate) fn toggle_loop (&mut self) { if let Some(clip) = self.clip() { clip.write().unwrap().toggle_loop() } } + pub(crate) fn activate (&mut self) -> Usually<()> { let selected = self.selected().clone(); match selected { @@ -95,6 +100,7 @@ impl Tek { } Ok(()) } + pub fn tracks_add ( &mut self, count: usize, width: Option, midi_from: &[PortConnect], midi_to: &[PortConnect], @@ -111,6 +117,7 @@ impl Tek { } Ok(()) } + pub fn track_add ( &mut self, name: Option<&str>, color: Option, midi_froms: &[PortConnect], @@ -141,12 +148,14 @@ impl Tek { } Ok((index, &mut self.tracks_mut()[index])) } + pub fn track_del (&mut self, index: usize) { self.tracks_mut().remove(index); for scene in self.scenes_mut().iter_mut() { scene.clips.remove(index); } } + pub fn scenes_add (&mut self, n: usize) -> Usually<()> { let scene_color_1 = ItemColor::random(); let scene_color_2 = ItemColor::random(); @@ -157,6 +166,7 @@ impl Tek { } Ok(()) } + pub fn scene_add (&mut self, name: Option<&str>, color: Option) -> Usually<(usize, &mut Scene)> { @@ -169,9 +179,11 @@ impl Tek { let index = self.scenes().len() - 1; Ok((index, &mut self.scenes_mut()[index])) } + pub fn scene_default_name (&self) -> Arc { format!("Sc{:3>}", self.scenes().len() + 1).into() } + } has_size!(|self: Tek|&self.size); @@ -385,6 +397,19 @@ impl Track { } None } + pub fn sampler_mut (&mut self, mut nth: usize) -> Option<&mut Sampler> { + for device in self.devices.iter_mut() { + match device { + Device::Sampler(s) => if nth == 0 { + return Some(s); + } else { + nth -= 1; + }, + _ => {} + } + } + None + } } impl HasTracks for Tek { diff --git a/crates/cli/tek.rs b/crates/cli/tek.rs index 465959f2..ac00d7b5 100644 --- a/crates/cli/tek.rs +++ b/crates/cli/tek.rs @@ -1,10 +1,12 @@ pub(crate) use tek::*; pub(crate) use std::sync::{Arc, RwLock}; pub(crate) use clap::{self, Parser, Subcommand}; + /// Application entrypoint. pub fn main () -> Usually<()> { Cli::parse().run() } + #[derive(Debug, Parser)] #[command(version, about = Some(HEADER), long_about = Some(HEADER))] pub struct Cli { @@ -37,7 +39,10 @@ pub struct Cli { /// Audio ins to connect from right output #[arg(short='R', long)] right_to: Vec, } -#[derive(Debug, Clone, Subcommand)] pub enum Mode { + +/// Application modes +#[derive(Debug, Clone, Subcommand)] +pub enum Mode { /// ⏯️ A standalone transport clock. Clock, /// 🎼 A MIDI sequencer. @@ -62,6 +67,7 @@ pub struct Cli { /// TODO: An audio plugin host Plugin, } + impl Cli { pub fn run (&self) -> Usually<()> { let name = self.name.as_ref().map_or("tek", |x|x.as_str()); @@ -122,11 +128,12 @@ impl Cli { }, color: ItemPalette::random(), clock: Clock::new(jack, self.bpm)?, - keys: SourceIter(include_str!("./edn/arranger_keys.edn")), - keys_clip: SourceIter(include_str!("./edn/arranger_keys_clip.edn")), - keys_track: SourceIter(include_str!("./edn/arranger_keys_track.edn")), - keys_scene: SourceIter(include_str!("./edn/arranger_keys_scene.edn")), - keys_mix: SourceIter(include_str!("./edn/arranger_keys_mix.edn")), + + handler: Some(match mode { + Mode::Sampler => handle_sampler, + _ => handle_arranger, + }), + tracks: match mode { Mode::Sequencer => vec![ @@ -196,6 +203,7 @@ impl Cli { } } +/// CLI header const HEADER: &'static str = r#" ░▒▓████████▓▒░▒▓███████▓▒░▒▓█▓▒░░▒▓█▓▒░░ diff --git a/crates/sampler/edn/sampler_keys.edn b/crates/sampler/edn/sampler_keys.edn deleted file mode 100644 index e69de29b..00000000 diff --git a/crates/sampler/src/sampler_api.rs b/crates/sampler/src/sampler_api.rs index e60b262e..b07c887d 100644 --- a/crates/sampler/src/sampler_api.rs +++ b/crates/sampler/src/sampler_api.rs @@ -1,10 +1,20 @@ use crate::*; -provide!(Arc: |self: Sampler| {}); -provide!(Option>>: |self: Sampler| {}); -provide!(PathBuf: |self: Sampler| {}); -provide!(f32: |self: Sampler| {}); -provide!(u7: |self: Sampler| {}); -provide!(usize: |self: Sampler| {}); + +expose! { + [self: Sampler] { + [Arc] => {} + [Option>>] => {} + [PathBuf] => {} + [f32] => {} + [u7] => {} + [usize] => { + ":sample-up" => 0, + ":sample-down" => 0, + ":sample-left" => 0, + ":sample-right" => 0, + } + } +} defcom! { |self, state: Sampler| @@ -82,17 +92,13 @@ atom_command!(FileBrowserCommand: |state: Sampler| { ("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")))) }); + atom_command!(SamplerCommand: |state: Sampler| { - ("select" [i: usize] - Some(Self::Select(i.expect("no index")))) - ("import" [,..a] - FileBrowserCommand::try_from_expr(state, a).map(Self::Import)) - ("record/begin" [i: u7] - Some(Self::RecordBegin(i.expect("no index")))) - ("record/cancel" [] - Some(Self::RecordCancel)) - ("record/finish" [] - Some(Self::RecordFinish)) + ("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: Option>>] Some(Self::SetSample(i.expect("no index"), s.expect("no sampler")))) ("set/start" [i: u7, s: usize]