diff --git a/Cargo.lock b/Cargo.lock index 10dda47d..f49c67e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3145,7 +3145,6 @@ dependencies = [ "rand 0.8.7", "symphonia", "tek", - "tek_proc", "tengri", "toml 0.9.12+spec-1.1.0", "uuid", @@ -3154,15 +3153,6 @@ dependencies = [ "xdg", ] -[[package]] -name = "tek_proc" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "tempfile" version = "3.27.0" diff --git a/Cargo.toml b/Cargo.toml index fcc3c5d6..b398b732 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,8 +14,7 @@ path = "src/tek.rs" rustflags = ["-C", "link-arg=-fuse-ld=mold"] [dependencies] -tek_proc = { path = "./proc" } -tengri = { path = "./tengri", features = [ "term", "lang" ] } +tengri = { path = "./tengri", features = [ "term", "lang" ] } ansi_term = { version = "0.12.1" } atomic_float = { version = "1.0.0" } diff --git a/proc/Cargo.toml b/proc/Cargo.toml deleted file mode 100644 index bea4fa22..00000000 --- a/proc/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "tek_proc" -description = "Shorthands for implementing Tek commands." -version = "0.1.0" -edition = "2024" -[lib] -proc-macro = true -[dependencies] -proc-macro2 = "1.0.106" -quote = "1.0.46" -syn = { version = "2.0.119", features = ["full", "extra-traits"] } diff --git a/proc/src/lib.rs b/proc/src/lib.rs deleted file mode 100644 index 9ef4169a..00000000 --- a/proc/src/lib.rs +++ /dev/null @@ -1,189 +0,0 @@ -use proc_macro::{TokenStream, Literal}; -use proc_macro2::{TokenStream as TokenStream2, Span}; -use quote::{quote, ToTokens, TokenStreamExt}; -use std::collections::{HashMap, BTreeMap}; -use syn::{ - Path, ItemEnum, Ident, Type, Variant, MetaList, Expr, ExprPath, ExprBinary, Fields, BinOp, - parse::{ParseStream, Parse, Result} -}; - -macro_rules! attribute { - ($name:ident { $($body:tt)* }) => { - #[proc_macro_attribute] pub fn $name (meta: TokenStream, item: TokenStream) -> TokenStream { - write(self::$name::Def( - syn::parse_macro_input!(meta as self::$name::Meta), - syn::parse_macro_input!(item as self::$name::Item), - )) - } - mod $name { - use crate::*; - $($body)* - } - } -} - -attribute!(command { - #[derive(Debug, Clone)] pub struct Def( - pub Meta, pub Item - ); - #[derive(Debug, Clone)] pub struct Meta( - pub Path - ); - #[derive(Debug, Clone)] pub struct Item( - pub ItemEnum, pub HashMap - ); - impl Parse for Meta { - fn parse (input: ParseStream) -> Result { - Ok(Self(input.parse()?)) - } - } - impl Parse for Item { - fn parse (input: ParseStream) -> Result { - let mut item: ItemEnum = input.parse()?; - let mut branches: HashMap = Default::default(); - let mut variants_filtered = item.variants.clone(); - variants_filtered.clear(); - for variant in item.variants.iter_mut() { - let Variant { attrs, ident, fields, discriminant } = variant; - let mut attrs_filtered = attrs.clone(); - attrs_filtered.clear(); - for attr in attrs.iter() { - if let syn::Meta::List(MetaList { ref path, ref tokens, .. }) = attr.meta - && path == &Path::from(Ident::new("command", Span::call_site())) - && let Ok(handler) = syn::parse2::(tokens.clone()) - { - branches.insert(ident.clone(), (fields.clone(), handler)); - } else { - attrs_filtered.push(attr.clone()); - } - } - *attrs = attrs_filtered; - variants_filtered.push(variant.clone()) - } - item.variants = variants_filtered; - Ok(Self(item, branches)) - } - } - impl ToTokens for Def { - fn to_tokens (&self, out: &mut TokenStream2) { - let Self(Meta(state), Item(item, branches)) = self; - let ident = &item.ident; - let mut body = quote! {}; - for (variant, (fields, handler)) in branches.iter() { - let (handler, wrapper) = if let Expr::Path(ExprPath { path, .. }) = handler { - (path, None) - } else if let Expr::Binary(ExprBinary { - op: BinOp::BitOr(_), left, right, .. - }) = handler && let Expr::Path(ExprPath { - path: handler, .. - }) = &**left && let Expr::Path(ExprPath { - path: wrapper, .. - }) = &**right { - (handler, Some(wrapper)) - } else { - panic!() - }; - match fields { - Fields::Named(fields) => todo!("named command fields"), - Fields::Unnamed(fields) => { - let mut params = quote! {}; - let mut values = quote! {}; - for (index, _field) in fields.unnamed.iter().enumerate() { - let name = Ident::new(&format!("arg{index}"), Span::call_site()); - append(&mut params, quote! { #name, }); - append(&mut values, quote! { #name, }); - } - let invocation = if let Some(wrapper) = wrapper { - quote! { Ok(Some(#wrapper(#handler(state, #values)))) } - } else { - quote! { #handler(state, #values) } - }; - append(&mut body, quote! { - #ident::#variant (#params) => #invocation, - }); - }, - _ => { - append(&mut body, quote! { - #ident::#variant => #handler(state), - }); - } - } - } - append(out, quote! { - #item - - impl dizzle::Act<#state> for #ident { - fn act (&self, state: &mut #state) -> Perhaps { - match self { - #body - _ => unreachable!() - } - } - } - }) - } - } -}); - -attribute!(keyword { - #[derive(Debug, Clone)] pub struct Def( - pub Meta, pub Item - ); - #[derive(Debug, Clone)] pub struct Meta( - pub Path - ); - #[derive(Debug, Clone)] pub struct Item( - pub ItemEnum, pub BTreeMap)>> - ); - impl Parse for Meta { - fn parse (input: ParseStream) -> Result { - Ok(Self(input.parse()?)) - } - } - impl Parse for Item { - fn parse (input: ParseStream) -> Result { - let mut item: ItemEnum = input.parse()?; - for Variant { attrs, ident, fields, discriminant } in item.variants.iter_mut() { - attrs.retain(|attr|if let syn::Meta::List(MetaList { - ref path, ref tokens, .. - }) = attr.meta && path == &Path::from(Ident::new( - "keyword", Span::call_site() - )) { - // TODO - false - } else { - true - }); - } - Ok(Self(item, Default::default())) - } - } - impl ToTokens for Def { - fn to_tokens (&self, out: &mut TokenStream2) { - let Self(Meta(state), Item(item, variants)) = self; - let ident = &item.ident; - let body = quote! {}; - append(out, quote! { - #item - - impl<'a> Namespace<'a, #ident> for #state { - symbols!('a |stte| -> #ident { - #body, - }); - } - }) - } - } -}); - -fn write (t: T) -> TokenStream { - let mut out = TokenStream2::new(); - t.to_tokens(&mut out); - out.into() -} - -fn append (out: &mut TokenStream2, quote: TokenStream2) { - for token in quote { - out.append(token); - } -} diff --git a/src/device/arrange.rs b/src/device/arrange.rs index e86227c6..5f871a38 100644 --- a/src/device/arrange.rs +++ b/src/device/arrange.rs @@ -609,15 +609,9 @@ impl Track { pub fn per <'a, T: Draw + 'a, U: TracksSizes<'a>> ( tracks: impl Fn() -> U + Send + Sync + 'a, - callback: &'a (impl Fn(usize, &'a Track)->T + Send + Sync + 'a) + callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a ) -> impl Draw + 'a { - iter_east(move||tracks().map(|(index, track, x1, x2): (usize, &Track, usize, usize)|{ - fg_bg( - track.color.lightest.term, - track.color.base.term, - callback(index, track) - ).exact_w((x2 - x1) as u16) - })) + view_track_per(tracks, callback) } } @@ -954,11 +948,10 @@ impl Arrangement { pub fn view_track_devices (&self, theme: ItemTheme) -> impl Draw { let height = self.devices_height(); - let btn1 = button_3("d", "evice", format!("{}", self.track().map(|t|t.devices.len()).unwrap_or(0)), false); - let btn2 = button_2("D", "+", false); - view_track_row_section(theme, btn1, btn2, iter_east(move||self.tracks_with_sizes() - .enumerate() - .map(move|(index, (_, track, _x1, _x2))|bg( + view_track_row_section(theme, + button_3("d", "evice", format!("{}", self.track().map(|t|t.devices.len()).unwrap_or(0)), false), + button_2("D", "+", false), + iter_once(self.tracks_with_sizes(), move|(_, track, _x1, _x2), index|bg( track.color.dark.term, iter_south(move||(0..height).map(|_|fg_bg( ItemTheme::G[32].lightest.term, @@ -970,7 +963,7 @@ impl Arrangement { .exact_wh( Some(track_width(index, track)), Some(height + 1), - )))) + ))) } fn devices_height (&self) -> u16 { diff --git a/src/device/browse.rs b/src/device/browse.rs index 985a98a7..a5f61c7a 100644 --- a/src/device/browse.rs +++ b/src/device/browse.rs @@ -1,17 +1,6 @@ use crate::*; -impl App { - /// Return reference to content browser if open. - /// - /// ``` - /// assert_eq!(tek::App::default().browser(), None); - /// ``` - pub fn browser (&self) -> Option<&Browse> { - if let Dialog::Browse(_, ref b) = self.dialog { Some(b) } else { None } - } -} - -def_command!(FileBrowserCommand: |_browse: Browse|{ +def_command!(FileBrowserCommand: |sampler: Sampler|{ //("begin" [] Some(Self::Begin)) //("cancel" [] Some(Self::Cancel)) //("confirm" [] Some(Self::Confirm)) @@ -550,20 +539,3 @@ def_command!(CropCommand: |pool: Pool| { Ok(None) } }); - -pub fn scan (dir: &PathBuf) -> Usually<(Vec, Vec)> { - let (mut subdirs, mut files) = std::fs::read_dir(dir)? - .fold((vec!["..".into()], vec![]), |(mut subdirs, mut files), entry|{ - let entry = entry.expect("failed to read drectory entry"); - let meta = entry.metadata().expect("failed to read entry metadata"); - if meta.is_file() { - files.push(entry.file_name()); - } else if meta.is_dir() { - subdirs.push(entry.file_name()); - } - (subdirs, files) - }); - subdirs.sort(); - files.sort(); - Ok((subdirs, files)) -} diff --git a/src/device/dialog.rs b/src/device/dialog.rs index c54e5dea..7b28cf07 100644 --- a/src/device/dialog.rs +++ b/src/device/dialog.rs @@ -33,7 +33,6 @@ pub fn draw_dialog ( } impl App { - pub fn get_dialog (&self, src: impl Language) -> Perhaps { src.word()?.map(|word|Ok(match word { ":dialog/none" => Dialog::None, @@ -65,36 +64,31 @@ impl App { ) })).transpose() } - - /// Set currently active modal dialog. + /// Set modal dialog. /// /// ``` - /// let previous: tek::Dialog = tek::App::default().set_dialog(&tek::Dialog::welcome()); + /// let previous: tek::Dialog = tek::App::default().set_dialog(tek::Dialog::welcome()); /// ``` - pub fn set_dialog (&mut self, dialog: &Dialog) -> Dialog { - let mut dialog = dialog.clone(); + pub fn set_dialog (&mut self, mut dialog: Dialog) -> Dialog { std::mem::swap(&mut self.dialog, &mut dialog); dialog } - pub fn inc (&mut self, axis: &ControlAxis) -> Perhaps { Ok(match (&self.dialog, axis) { (Dialog::None, _) => todo!(), (Dialog::Menu(_, _), ControlAxis::Y) => - AppCommand::SetDialog(self.dialog.menu_next()).act(self)?, + AppCommand::SetDialog { dialog: self.dialog.menu_next() }.act(self)?, _ => todo!() }) } - pub fn dec (&mut self, axis: &ControlAxis) -> Perhaps { Ok(match (&self.dialog, axis) { (Dialog::None, _) => None, (Dialog::Menu(_, _), ControlAxis::Y) => - AppCommand::SetDialog(self.dialog.menu_prev()).act(self)?, + AppCommand::SetDialog { dialog: self.dialog.menu_prev() }.act(self)?, _ => todo!() }) } - pub fn confirm (&mut self) -> Perhaps { Ok(match &self.dialog { Dialog::Menu(index, items) => { diff --git a/src/tek.edn b/src/tek.edn index ef4b6566..7dac49a6 100644 --- a/src/tek.edn +++ b/src/tek.edn @@ -1,7 +1,7 @@ -(view :logo (bsp/s (bg (rgb 100 70 40) (text ~~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ )) - (bsp/s (bg (rgb 90 70 50) (text ~~~~ ║ ~ ╟─╌ ~╟─< ~~ heatwave is the new darkwave ~~ )) - (bsp/s (bg (rgb 80 70 60) (text ~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ )) - (align/x (text .)))))) +(view :logo (bsp/s (text ~~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ) + (bsp/s (text ~~~~ ║ ~ ╟─╌ ~╟─< ~~ heatwave is the new darkwave ~~ ) + (bsp/s (text ~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ) + (text))))) (view :browse (bsp/s (padding 3 1 :browse-title) @@ -15,36 +15,11 @@ (mode :menu (name Menu) (info Mode selector.) (keys :axis/y :confirm) (view (bsp/s - (align/s (bsp/e :ports/out (bsp/e :transport :ports/in))) - (align/c (bsp/s (align/x (bg (g 36) :logo)) (bg (g 24) :dialog/menu)))))) - -(mode :arranger (name Arranger) (info Launch grid.) - (keys (see :clock :color :launch :scenes :tracks :global) - (@tab project/edit) (@shift/I project/input/add) (@shift/O project/output/add) - (@shift/D dialog/show :dialog/device)) - (mode :editor (keys :editor)) - (mode :dialog (keys :dialog)) - (mode :message (keys :message)) - (mode :add-device (keys :add-device)) - (mode :browse (keys :browse)) - (mode :rename (keys :input)) - (mode :length (keys :rename)) - (mode :clip (keys :clip)) - (mode :track (keys :track)) - (mode :scene (keys :scene)) - (mode :mix (keys :mix)) - (view - (bsp/n (bg (g 10) (bsp/e :transport :status)) - (bsp/w (bg (g 20) (exact/x 4 (align/ne :meters/output))) - (bsp/e (bg (g 30) (exact/x 4 (align/nw :meters/input))) - (full/xy (align/c (max/xy 80 80 - (bsp/s (bg (g 40) (exact/y 4 :tracks/outputs)) - (bsp/s (bg (g 50) (exact/y 2 :tracks/names)) - (bsp/s (bg (g 60) (exact/y 4 :tracks/devices)) - (bsp/s (bg (g 70) (exact/y 4 :tracks/inputs)) - (either :mode/editor - (bg (g 80) (bsp/s :scenes/names :editor)) - (bg (g 90) :scenes)))))))))))))) + (align/s (bsp/e :ports/out + (bsp/e :transport + :ports/in))) + (align/c (bsp/s (align/x (bg (g 36) :logo)) + (bg (g 24) :dialog/menu)))))) (view :ports/out (bsp/s (align/w (text L-AUDIO-OUT)) @@ -61,14 +36,14 @@ (mode browse (keys :browse)) (mode rename (keys :pool/rename)) (mode length (keys :pool/length)) - (bsp/s (exact/y 1 :transport) - (bsp/n (exact/y 1 :status) + (bsp/s (fixed/y 1 :transport) + (bsp/n (fixed/y 1 :status) (fill (bsp/a (fill/xy (align/e :pool)) :editor))))) (mode :sampler (name Sampler) (info Sample player.) (keys :sampler/directions :sampler/record :sampler/play) - (bsp/s (exact/y 1 :transport) - (bsp/n (exact/y 1 :status) + (bsp/s (fixed/y 1 :transport) + (bsp/n (fixed/y 1 :status) (fill :samples/grid)))) (mode :groovebox (name Groovebox) (info Sequencer with sampler.) @@ -82,10 +57,27 @@ (view :groove/editor (bsp/n :groove/sample :groove/sequence)) -(view :groove/sample (exact/y :h-sample-detail (bsp/e (fill/y (exact/x 20 (align/nw :sample-status))) :sample-viewer))) +(view :groove/sample (fixed/y :h-sample-detail (bsp/e (fill/y (fixed/x 20 (align/nw :sample-status))) :sample-viewer))) (view :groove/sequence (bsp/e (fill/y (align/n (bsp/s :status/v :editor-status))) (bsp/e :samples/keys :editor))) +(mode :arranger (name Arranger) (info Launch grid.) + (keys (see :clock :color :launch :scenes :tracks :global) + (@tab project/edit) (@shift/I project/input/add) (@shift/O project/output/add) + (@shift/D dialog/show :dialog/device)) + (mode :editor (keys :editor)) + (mode :dialog (keys :dialog)) + (mode :message (keys :message)) + (mode :add-device (keys :add-device)) + (mode :browse (keys :browse)) + (mode :rename (keys :input)) + (mode :length (keys :rename)) + (mode :clip (keys :clip)) + (mode :track (keys :track)) + (mode :scene (keys :scene)) + (mode :mix (keys :mix)) + (view (bg (g 64) (bsp/n :status (text test))))) + (keys :back (@escape back)) (keys :confirm (@enter confirm)) (keys :axis/x (@left x/dec) (@right x/inc)) diff --git a/src/tek.rs b/src/tek.rs index 8dcc90c8..85efad6a 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -218,26 +218,14 @@ fn run_new_plain (config: Config) -> Usually<()> { Clock::new(&jack, *bpm)?, [].into_iter(), [].into_iter(), - connect_midi_ins( - &jack, &"M".to_string(), midi_from.as_ref(), Some(midi_from_re) - )?.into_iter(), - connect_midi_outs( - &jack, &"M".to_string(), midi_to.as_ref(), Some(midi_to_re) - )?.into_iter(), + connect_midi_ins(&jack, &"M".to_string(), midi_from.as_ref(), Some(midi_from_re))?.into_iter(), + connect_midi_outs(&jack, &"M".to_string(), midi_to.as_ref(), Some(midi_to_re))?.into_iter(), [].into_iter() - .chain( - connect_audio_ins(&jack, &"L".to_string(), &left_from, None)?.into_iter() - ) - .chain( - connect_audio_ins(&jack, &"R".to_string(), &right_from, None)?.into_iter() - ), + .chain(connect_audio_ins(&jack, &"L".to_string(), &left_from, None)?.into_iter()) + .chain(connect_audio_ins(&jack, &"R".to_string(), &right_from, None)?.into_iter()), [].into_iter() - .chain( - connect_audio_outs(&jack, &"L".to_string(), &left_to, None)?.into_iter() - ) - .chain( - connect_audio_outs(&jack, &"R".to_string(), &right_to, None)?.into_iter() - )); + .chain(connect_audio_outs(&jack, &"L".to_string(), &left_to, None)?.into_iter()) + .chain(connect_audio_outs(&jack, &"R".to_string(), &right_to, None)?.into_iter())); //&jack, Clock::new(&jack, *bpm)?, &lf, <, &rf, &rt, &mf, &mt, &mfr, &mtr)?; proj.tracks_add(tracks.unwrap_or(0), None, &[], &[])?; proj.scenes_add(scenes.unwrap_or(0))?; @@ -488,13 +476,12 @@ mod config { let tail = expr.tail()?; let name = tail.head()?; let body = tail.tail()?; + //println!("Config::load: {} {} {}", head.unwrap_or_default(), name.unwrap_or_default(), body.unwrap_or_default()); match head { Some("mode") if let Some(name) = name => self.modes.add(&name, &body)?, Some("keys") if let Some(name) = name => load_bind(&self.binds, &name, &body)?, Some("view") if let Some(name) = name => load_view(&self.views, &name, &body)?, - _ => return Err( - format!("Config::load: expected view/keys/mode, got: {item:?}").into() - ) + _ => return Err(format!("Config::load: expected view/keys/mode, got: {item:?}").into()) } Ok(()) } else { @@ -726,7 +713,6 @@ mod app { /// Error, if any pub error: Arc>>> } - impl App { /// Create a new application instance from a backend, project, config, and mode /// @@ -870,32 +856,55 @@ mod app { })).transpose() } + /// FIXME: generalize. Set picked device in device pick dialog. + /// + /// ``` + /// tek::App::default().device_pick(0); + /// ``` + pub fn device_pick (&mut self, index: usize) { + self.dialog = Dialog::Device(index); + } + + /// FIXME: generalize. Add device to current track. + pub fn add_device (&mut self, index: usize) -> Usually<()> { + match index { + 0 => { + let name = self.jack.with_client(|c|c.name().to_string()); + let midi = self.project.track().expect("no active track").sequencer.midi_outs[0].port_name(); + let track = self.track().expect("no active track"); + let port = format!("{}/Sampler", &track.name); + let connect = Connect::exact(format!("{name}:{midi}")); + let sampler = if let Ok(sampler) = Sampler::new( + &self.jack, &port, &[connect], &[&[], &[]], &[&[], &[]] + ) { + self.dialog = Dialog::None; + Device::Sampler(sampler) + } else { + self.dialog = Dialog::Message("Failed to add device.".into()); + return Err("failed to add device".into()) + }; + let track = self.track_mut().expect("no active track"); + track.devices.push(sampler); + Ok(()) + }, + 1 => { + todo!(); + //Ok(()) + }, + _ => unreachable!(), + } + } + + /// Return reference to content browser if open. + /// + /// ``` + /// assert_eq!(tek::App::default().browser(), None); + /// ``` + pub fn browser (&self) -> Option<&Browse> { + if let Dialog::Browse(_, ref b) = self.dialog { Some(b) } else { None } + } + } -} - -pub use self::bind::*; -mod bind { - use crate::*; - - /// Define an enum containing commands, and implement [Command] trait for over given `State`. - #[macro_export] macro_rules! def_command ( - ($Command:ident: |$state:ident: $State:ty| { - // FIXME: support attrs (docstrings) - $($Variant:ident$({$($arg:ident:$Arg:ty),+ $(,)?})?=>$body:expr),* $(,)? - })=>{ - #[derive(Debug)] pub enum $Command { - // FIXME: support attrs (docstrings) - $($Variant $({ $($arg: $Arg),* })?),* - } - impl ::tengri::dizzle::Act<$State> for $Command { - fn act (&self, $state: &mut $State) -> Perhaps { - match self { - $(Self::$Variant $({ $($arg),* })? => $body,)* - _ => unimplemented!("Act<{}>: {self:?}", stringify!($State)), - } - } - } - }); pub fn swap_value ( target: &mut T, value: &T, returned: impl Fn(T)->U @@ -921,6 +930,23 @@ mod bind { } } + pub fn scan (dir: &PathBuf) -> Usually<(Vec, Vec)> { + let (mut subdirs, mut files) = std::fs::read_dir(dir)? + .fold((vec!["..".into()], vec![]), |(mut subdirs, mut files), entry|{ + let entry = entry.expect("failed to read drectory entry"); + let meta = entry.metadata().expect("failed to read entry metadata"); + if meta.is_file() { + files.push(entry.file_name()); + } else if meta.is_dir() { + subdirs.push(entry.file_name()); + } + (subdirs, files) + }); + subdirs.sort(); + files.sort(); + Ok((subdirs, files)) + } + tui_keys!(self: App, input { let commands = tek_commands_collect(self, input)?; let results = tek_commands_execute(self, commands)?; @@ -1046,58 +1072,30 @@ mod bind { impl_debug!(Condition |self, w| { write!(w, "*") }); - #[tek_proc::command(App)] - #[tek_proc::keyword(App)] - #[derive(Debug, Default)] - pub enum AppCommand { - #[default] - #[command(App::nop)] - #[keyword("nop")] - Nop, + impl_default!(AppCommand: Self::Nop); - #[command(App::cancel)] - #[keyword("cancel")] - Cancel, + def_command!(AppCommand: |app: App| { + Nop => Ok(None), + Cancel => todo!(), // TODO delegate: + Confirm => app.confirm(), + Inc { axis: ControlAxis } => app.inc(axis), + Dec { axis: ControlAxis } => app.dec(axis), + SetDialog { dialog: Dialog } => { + swap_value(&mut app.dialog, dialog, |dialog|Self::SetDialog { dialog }) + }, + }); - #[command(App::confirm)] - #[keyword("confirm")] - Confirm, - - #[command(App::inc)] - #[keyword("x/inc", ControlAxis::X)] - #[keyword("y/inc", ControlAxis::Y)] - Inc(ControlAxis), - - #[command(App::dec)] - #[keyword("x/dec", ControlAxis::X)] - #[keyword("y/dec", ControlAxis::Y)] - Dec(ControlAxis), - - #[command(App::set_dialog | Self::SetDialog)] - #[keyword("dialog")] - SetDialog(Dialog), + impl<'a> Namespace<'a, AppCommand> for App { + symbols!('a |app| -> AppCommand { + "x/inc" => AppCommand::Inc { axis: ControlAxis::X }, + "x/dec" => AppCommand::Dec { axis: ControlAxis::X }, + "y/inc" => AppCommand::Inc { axis: ControlAxis::Y }, + "y/dec" => AppCommand::Dec { axis: ControlAxis::Y }, + "confirm" => AppCommand::Confirm, + "cancel" => AppCommand::Cancel, + }); } - impl App { - fn nop (&mut self) -> Perhaps { - Ok(None) - } - fn cancel (&mut self) -> Perhaps { - todo!() - } - } - - //impl<'a> Namespace<'a, AppCommand> for App { - //symbols!('a |app| -> AppCommand { - //"x/inc" => AppCommand::Inc { axis: ControlAxis::X }, - //"x/dec" => AppCommand::Dec { axis: ControlAxis::X }, - //"y/inc" => AppCommand::Inc { axis: ControlAxis::Y }, - //"y/dec" => AppCommand::Dec { axis: ControlAxis::Y }, - //"confirm" => AppCommand::Confirm, - //"cancel" => AppCommand::Cancel, - //}); - //} - /// A control axis. /// /// ``` @@ -1109,52 +1107,6 @@ mod bind { //take!(DeviceCommand|state: Arrangement, iter|state.selected_device().as_ref() //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten())); -} - -pub use self::device::*; -mod device { - use crate::*; - - impl App { - /// FIXME: generalize. Set picked device in device pick dialog. - /// - /// ``` - /// tek::App::default().device_pick(0); - /// ``` - pub fn device_pick (&mut self, index: usize) { - self.dialog = Dialog::Device(index); - } - - /// FIXME: generalize. Add device to current track. - pub fn add_device (&mut self, index: usize) -> Usually<()> { - match index { - 0 => { - let name = self.jack.with_client(|c|c.name().to_string()); - let midi = self.project.track().expect("no active track").sequencer.midi_outs[0].port_name(); - let track = self.track().expect("no active track"); - let port = format!("{}/Sampler", &track.name); - let connect = Connect::exact(format!("{name}:{midi}")); - let sampler = if let Ok(sampler) = Sampler::new( - &self.jack, &port, &[connect], &[&[], &[]], &[&[], &[]] - ) { - self.dialog = Dialog::None; - Device::Sampler(sampler) - } else { - self.dialog = Dialog::Message("Failed to add device.".into()); - return Err("failed to add device".into()) - }; - let track = self.track_mut().expect("no active track"); - track.devices.push(sampler); - Ok(()) - }, - 1 => { - todo!(); - //Ok(()) - }, - _ => unreachable!(), - } - } - } impl HasJack<'static> for App { fn jack (&self) -> &Jack<'static> { &self.jack } } @@ -1206,12 +1158,12 @@ mod device { _ => { panic!("{event:?}"); } } } +} - #[tek_proc::command(Device)] - #[tek_proc::keyword(Device)] - #[derive(Debug)] - pub enum DeviceCommand {} - +pub use self::device::*; +mod device { + use crate::*; + def_command!(DeviceCommand: |device: Device| {}); impl Device { pub fn name (&self) -> &str { match self { @@ -1244,7 +1196,6 @@ mod device { } } } - /// A device that can be plugged into the chain. /// /// ``` @@ -1323,6 +1274,26 @@ mod device { #[cfg(feature = "plugin")] pub mod plugin; #[cfg(feature = "plugin")] pub use self::plugin::*; + def_command!(AudioInputCommand: |port: AudioInput| { + Close => todo!(), + Connect { audio_out: Arc } => todo!(), + }); + + def_command!(AudioOutputCommand: |port: AudioOutput| { + Close => todo!(), + Connect { audio_in: Arc } => todo!(), + }); + + def_command!(MidiInputCommand: |port: MidiInput| { + Close => todo!(), + Connect { midi_out: Arc } => todo!(), + }); + + def_command!(MidiOutputCommand: |port: MidiOutput| { + Close => todo!(), + Connect { midi_in: Arc } => todo!(), + }); + pub struct Junction(T); impl View for Junction { @@ -1330,38 +1301,36 @@ mod device { T::KIND } } +} - #[tek_proc::command(AudioInput)] - #[tek_proc::keyword(AudioInput)] - #[derive(Debug)] - pub enum AudioInputCommand { - Close, - Connect(Arc), +pub fn print_status (project: &Arrangement) { + println!("Name: {:?}", &project.name); + println!("JACK: {:?}", &project.jack); + println!("Buffer: {:?}", &project.clock.chunk); + println!("Sample rate: {:?}", &project.clock.timebase.sr); + println!("MIDI PPQ: {:?}", &project.clock.timebase.ppq); + println!("Tempo: {:?}", &project.clock.timebase.bpm); + println!("Quantize: {:?}", &project.clock.quant); + println!("Launch: {:?}", &project.clock.sync); + println!("Playhead: {:?}us", &project.clock.playhead.usec); + println!("Playhead: {:?}s", &project.clock.playhead.sample); + println!("Playhead: {:?}p", &project.clock.playhead.pulse); + println!("Started: {:?}", &project.clock.started); + println!("Tracks:"); + for (i, t) in project.tracks.iter().enumerate() { + println!(" Track {i}: {} {} {:?} {:?}", t.name, t.width, + &t.sequencer.play_clip, &t.sequencer.next_clip); } - - #[tek_proc::command(AudioOutput)] - #[tek_proc::keyword(AudioOutput)] - #[derive(Debug)] - pub enum AudioOutputCommand { - Close, - Connect(Arc), - } - - #[tek_proc::command(MidiInput)] - #[tek_proc::keyword(MidiInput)] - #[derive(Debug)] - pub enum MidiInputCommand { - Close, - Connect(Arc), - } - - #[tek_proc::command(MidiOutput)] - #[tek_proc::keyword(MidiOutput)] - #[derive(Debug)] - pub enum MidiOutputCommand { - Close, - Connect(Arc), + println!("Scenes:"); + for (i, t) in project.scenes.iter().enumerate() { + println!(" Scene {i}: {} {:?}", &t.name, &t.clips); } + println!("MIDI Ins: {:?}", &project.midi_ins); + println!("MIDI Outs: {:?}", &project.midi_outs); + println!("Audio Ins: {:?}", &project.audio_ins); + println!("Audio Outs: {:?}", &project.audio_outs); + // TODO git integration + // TODO dawvert integration } //pub fn tui ( @@ -1385,13 +1354,35 @@ mod device { //})?)? //} +/// Define an enum containing commands, and implement [Command] trait for over given `State`. +#[macro_export] macro_rules! def_command ( + ($Command:ident: |$state:ident: $State:ty| { + // FIXME: support attrs (docstrings) + $($Variant:ident$({$($arg:ident:$Arg:ty),+ $(,)?})?=>$body:expr),* $(,)? + })=>{ + #[derive(Debug)] pub enum $Command { + // FIXME: support attrs (docstrings) + $($Variant $({ $($arg: $Arg),* })?),* + } + impl ::tengri::dizzle::Act<$State> for $Command { + fn act (&self, $state: &mut $State) -> Perhaps { + match self { + $(Self::$Variant $({ $($arg),* })? => $body,)* + _ => unimplemented!("Act<{}>: {self:?}", stringify!($State)), + } + } + } + }); + pub use self::draw::*; mod draw { use crate::*; /// Load custom view definition. pub(crate) fn load_view ( - views: &Views, name: &impl AsRef, body: &impl Language, + views: &Views, + name: &impl AsRef, + body: &impl Language, ) -> Usually<()> { views.write().unwrap().insert( name.as_ref().into(), @@ -1402,7 +1393,7 @@ mod draw { impl Keywords> for App { fn keywords () -> impl Iterator Perhaps>> { - [kw_when, kw_either, kw_split, kw_align, kw_exact, kw_min, kw_max, kw_push, kw_full, + [kw_when, kw_either, kw_split, kw_align, kw_exact, kw_min, kw_max, kw_push, kw_tui_text, kw_tui_fg, kw_tui_bg] .into_iter() } @@ -1476,26 +1467,11 @@ mod draw { let mut frags = lang.src()?.unwrap().split("/"); match frags.next() { //Some(":logo") => view_logo().draw(to), - Some(":meters") => match frags.next() { - Some("input") => bg(Rgb(30, 30, 30), "Input Meters".align_s().full_h()).draw(to), - Some("output") => bg(Rgb(30, 30, 30), "Output Meters".align_s().full_h()).draw(to), - _ => panic!() - }, - Some(":tracks") => match frags.next() { - None => "TODO tracks".draw(to), - Some("names") => self.project.view_track_names(self.color.clone()).draw(to),//bg(Rgb(40, 40, 40), full_w(align_w("Track Names")))), - Some("inputs") => self.project.view_track_inputs(self.color.clone()).draw(to),//bg(Rgb(40, 40, 40), "Track Inputs".align_w().full_w()).draw(to), - Some("devices") => self.project.view_track_devices(self.color.clone()).draw(to),//bg(Rgb(40, 40, 40), "Track Devices".align_w().full_w()).draw(to), - Some("outputs") => self.project.view_track_outputs(self.color.clone(), 0).draw(to), - _ => panic!() - }, - Some(":scenes") => match frags.next() { - None => "TODO Scenes".draw(to), - Some(":scenes/names") => "TODO Scene Names".draw(to), - _ => panic!() - }, + Some(":meters") => draw_meter_section(to, frags), + Some(":tracks") => draw_tracks(to, frags, self), + Some(":scenes") => draw_scenes(to, frags), Some(":dialog") => draw_dialog(to, frags, self, lang), - Some(":templates") => view_templates(frags, self).draw(to), + Some(":templates") => draw_templates(to, frags, self), Some(":sessions") => view_sessions().draw(to), Some(":browse/title") => view_browse_title(self).draw(to), Some(":device") => view_device(self).draw(to), @@ -1510,7 +1486,7 @@ mod draw { std::mem::drop(views); self.interpret(to, &lang) } else { - fg(Color::Rgb(128, 32, 32), format!("undefined: {lang:?}")).draw(to) + unimplemented!("{lang:?}"); } }, _ => unreachable!() @@ -1528,7 +1504,34 @@ mod draw { fn width_dec (&mut self); } - pub fn view_templates (_frags: std::str::Split<&str>, state: &App) -> impl Draw { + pub fn draw_meter_section (to: &mut Tui, mut frags: std::str::Split<&str>) -> Drawn { + match frags.next() { + Some("input") => bg(Rgb(30, 30, 30), "Input Meters".align_s().full_h()).draw(to), + Some("output") => bg(Rgb(30, 30, 30), "Output Meters".align_s().full_h()).draw(to), + _ => panic!() + } + } + + pub fn draw_tracks (to: &mut Tui, mut frags: std::str::Split<&str>, state: &App) -> Drawn { + match frags.next() { + None => "TODO tracks".draw(to), + Some("names") => state.project.view_track_names(state.color.clone()).draw(to),//bg(Rgb(40, 40, 40), full_w(align_w("Track Names")))), + Some("inputs") => bg(Rgb(40, 40, 40), "Track Inputs".align_w().full_w()).draw(to), + Some("devices") => bg(Rgb(40, 40, 40), "Track Devices".align_w().full_w()).draw(to), + Some("outputs") => bg(Rgb(40, 40, 40), "Track Outputs".align_w().full_w()).draw(to), + _ => panic!() + } + } + + pub fn draw_scenes (to: &mut Tui, mut frags: std::str::Split<&str>) -> Drawn { + match frags.next() { + None => "TODO Scenes".draw(to), + Some(":scenes/names") => "TODO Scene Names".draw(to), + _ => panic!() + } + } + + pub fn draw_templates (to: &mut Tui, _frags: std::str::Split<&str>, state: &App) -> Drawn { let height = (state.config.modes.len() * 2) as u16; draw(move |to: &mut Tui|{ let mut index = 0; @@ -1546,29 +1549,29 @@ mod draw { index += 1; }); Ok(Some(to.area().into())) - }).min_w(30).exact_h(height) + }).min_w(30).exact_h(height).draw(to) } - //pub fn per_track <'a, T: Draw + 'a, U: TracksSizes<'a>> ( - //tracks: impl Fn() -> U + Send + Sync + 'a, - //callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a - //) -> impl Draw + 'a { - //per_track_top(tracks, move|index, track|callback(index, track).full_h().align_y()) - //} + pub fn per_track <'a, T: Draw + 'a, U: TracksSizes<'a>> ( + tracks: impl Fn() -> U + Send + Sync + 'a, + callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a + ) -> impl Draw + 'a { + per_track_top(tracks, move|index, track|callback(index, track).full_h().align_y()) + } - //pub fn per_track_top <'a, T: Draw + 'a, U: TracksSizes<'a>> ( - //tracks: impl Fn() -> U + Send + Sync + 'a, - //callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a - //) -> impl Draw + 'a { - //bg(Reset, iter_east(||tracks() - //.map(move|(index, track, x1, x2): (usize, &'a Track, usize, usize)|{ - //fg_bg( - //track.color.lightest.term, - //track.color.base.term, - //callback(index, track) - //).exact_w((x2 - x1) as u16) - //})).align_x()) - //} + pub fn per_track_top <'a, T: Draw + 'a, U: TracksSizes<'a>> ( + tracks: impl Fn() -> U + Send + Sync + 'a, + callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a + ) -> impl Draw + 'a { + bg(Reset, iter_east(tracks, + move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{ + fg_bg( + track.color.lightest.term, + track.color.base.term, + callback(index, track) + ).exact_w((x2 - x1) as u16) + }).align_x()) + } pub fn field_h ( _theme: ItemTheme, _head: impl Draw, _body: impl Draw @@ -1929,73 +1932,17 @@ mod draw { track.map(move|track|view_ports_status(theme, "Audio outs:", &track.audio_outs())) } - //pub fn view_track_per <'a, T: Draw + 'a, U: TracksSizes<'a>> ( - //tracks: impl Fn() -> U + Send + Sync + 'a, - //callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a - //) -> impl Draw { - //} - - /// ``` - /// let _ = tek::button_2("", "", true); - /// let _ = tek::button_2("", "", false); - /// ``` - pub fn button_2 <'a> (key: impl Draw, label: impl Draw, hide: bool) -> impl Draw { - let c1 = tui_orange(); - let c2 = tui_g(0); - let c3 = tui_g(96); - let c4 = tui_g(255); - bold(true, fg_bg(c1, c2, - east!(fg(c2, "▐"), key, fg(c3, "▐"), when(!hide, fg_bg(c4, c3, label))))) - } - - /// ``` - /// let _ = tek::button_3("", "", "", true); - /// let _ = tek::button_3("", "", "", false); - /// ``` - pub fn button_3 <'a> ( - key: impl Draw, label: impl Draw, value: impl Draw, editing: bool, + pub fn view_track_per <'a, T: Draw + 'a, U: TracksSizes<'a>> ( + tracks: impl Fn() -> U + Send + Sync + 'a, + callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a ) -> impl Draw { - let c1 = tui_orange(); - let c2 = tui_g(0); - let c3 = tui_g(96); - let c4 = tui_g(255); - let c5 = tui_g(128); - bold(true, east( - fg_bg(c1, c2, - east(fg(c2, "▐"), east(key, fg(if editing { c5 } else { c3 }, "▐")))), - east( - when(!editing, east(fg_bg(c4, c3, label), fg_bg(c5, c3, "▐"),)), - east(fg_bg(tui_g(224), c5, value), fg_bg(c5, Reset, "▌"), )))) + iter_east(tracks, move|(index, track, x1, x2): (usize, &Track, usize, usize), _|{ + fg_bg( + track.color.lightest.term, + track.color.base.term, + callback(index, track) + ).exact_w((x2 - x1) as u16) + }) } } - -pub fn print_status (project: &Arrangement) { - println!("Name: {:?}", &project.name); - println!("JACK: {:?}", &project.jack); - println!("Buffer: {:?}", &project.clock.chunk); - println!("Sample rate: {:?}", &project.clock.timebase.sr); - println!("MIDI PPQ: {:?}", &project.clock.timebase.ppq); - println!("Tempo: {:?}", &project.clock.timebase.bpm); - println!("Quantize: {:?}", &project.clock.quant); - println!("Launch: {:?}", &project.clock.sync); - println!("Playhead: {:?}us", &project.clock.playhead.usec); - println!("Playhead: {:?}s", &project.clock.playhead.sample); - println!("Playhead: {:?}p", &project.clock.playhead.pulse); - println!("Started: {:?}", &project.clock.started); - println!("Tracks:"); - for (i, t) in project.tracks.iter().enumerate() { - println!(" Track {i}: {} {} {:?} {:?}", t.name, t.width, - &t.sequencer.play_clip, &t.sequencer.next_clip); - } - println!("Scenes:"); - for (i, t) in project.scenes.iter().enumerate() { - println!(" Scene {i}: {} {:?}", &t.name, &t.clips); - } - println!("MIDI Ins: {:?}", &project.midi_ins); - println!("MIDI Outs: {:?}", &project.midi_outs); - println!("Audio Ins: {:?}", &project.audio_ins); - println!("Audio Outs: {:?}", &project.audio_outs); - // TODO git integration - // TODO dawvert integration -} diff --git a/tengri b/tengri index 8a6eb19e..799e4976 160000 --- a/tengri +++ b/tengri @@ -1 +1 @@ -Subproject commit 8a6eb19e279afefc126008c27b38cc66645e1c96 +Subproject commit 799e49762250f48778dadd92874a29495114a6af