From ae496987c8bdc85e3a8880be1ec0139b55a99c26 Mon Sep 17 00:00:00 2001 From: i do not exist Date: Sun, 9 Aug 2026 09:23:24 +0300 Subject: [PATCH] implement passable #[command] --- Cargo.lock | 10 ++ Cargo.toml | 3 +- proc/Cargo.toml | 11 ++ proc/src/lib.rs | 189 ++++++++++++++++++++++ src/device/browse.rs | 30 +++- src/device/dialog.rs | 16 +- src/tek.rs | 371 ++++++++++++++++++++++++------------------- 7 files changed, 458 insertions(+), 172 deletions(-) create mode 100644 proc/Cargo.toml create mode 100644 proc/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index f49c67e0..10dda47d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3145,6 +3145,7 @@ dependencies = [ "rand 0.8.7", "symphonia", "tek", + "tek_proc", "tengri", "toml 0.9.12+spec-1.1.0", "uuid", @@ -3153,6 +3154,15 @@ 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 b398b732..fcc3c5d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,8 @@ path = "src/tek.rs" rustflags = ["-C", "link-arg=-fuse-ld=mold"] [dependencies] -tengri = { path = "./tengri", features = [ "term", "lang" ] } +tek_proc = { path = "./proc" } +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 new file mode 100644 index 00000000..bea4fa22 --- /dev/null +++ b/proc/Cargo.toml @@ -0,0 +1,11 @@ +[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 new file mode 100644 index 00000000..9ef4169a --- /dev/null +++ b/proc/src/lib.rs @@ -0,0 +1,189 @@ +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/browse.rs b/src/device/browse.rs index a5f61c7a..985a98a7 100644 --- a/src/device/browse.rs +++ b/src/device/browse.rs @@ -1,6 +1,17 @@ use crate::*; -def_command!(FileBrowserCommand: |sampler: Sampler|{ +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|{ //("begin" [] Some(Self::Begin)) //("cancel" [] Some(Self::Cancel)) //("confirm" [] Some(Self::Confirm)) @@ -539,3 +550,20 @@ 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 7b28cf07..c54e5dea 100644 --- a/src/device/dialog.rs +++ b/src/device/dialog.rs @@ -33,6 +33,7 @@ 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, @@ -64,31 +65,36 @@ impl App { ) })).transpose() } - /// Set modal dialog. + + /// Set currently active 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, mut dialog: Dialog) -> Dialog { + pub fn set_dialog (&mut self, dialog: &Dialog) -> Dialog { + let mut dialog = dialog.clone(); 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 { dialog: self.dialog.menu_next() }.act(self)?, + AppCommand::SetDialog(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 { dialog: self.dialog.menu_prev() }.act(self)?, + AppCommand::SetDialog(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.rs b/src/tek.rs index 520197a9..8dcc90c8 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -218,14 +218,26 @@ 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))?; @@ -476,12 +488,13 @@ 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 { @@ -713,6 +726,7 @@ mod app { /// Error, if any pub error: Arc>>> } + impl App { /// Create a new application instance from a backend, project, config, and mode /// @@ -856,55 +870,32 @@ 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 @@ -930,23 +921,6 @@ mod app { } } - 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)?; @@ -1072,30 +1046,58 @@ mod app { impl_debug!(Condition |self, w| { write!(w, "*") }); - impl_default!(AppCommand: Self::Nop); + #[tek_proc::command(App)] + #[tek_proc::keyword(App)] + #[derive(Debug, Default)] + pub enum AppCommand { + #[default] + #[command(App::nop)] + #[keyword("nop")] + Nop, - 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::cancel)] + #[keyword("cancel")] + Cancel, - 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, - }); + #[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 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. /// /// ``` @@ -1107,6 +1109,52 @@ mod app { //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 } } @@ -1158,12 +1206,12 @@ mod app { _ => { panic!("{event:?}"); } } } -} -pub use self::device::*; -mod device { - use crate::*; - def_command!(DeviceCommand: |device: Device| {}); + #[tek_proc::command(Device)] + #[tek_proc::keyword(Device)] + #[derive(Debug)] + pub enum DeviceCommand {} + impl Device { pub fn name (&self) -> &str { match self { @@ -1196,6 +1244,7 @@ mod device { } } } + /// A device that can be plugged into the chain. /// /// ``` @@ -1274,26 +1323,6 @@ 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 { @@ -1301,36 +1330,38 @@ mod device { T::KIND } } -} -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(AudioInput)] + #[tek_proc::keyword(AudioInput)] + #[derive(Debug)] + pub enum AudioInputCommand { + Close, + Connect(Arc), } - println!("Scenes:"); - for (i, t) in project.scenes.iter().enumerate() { - println!(" Scene {i}: {} {:?}", &t.name, &t.clips); + + #[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!("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 ( @@ -1354,26 +1385,6 @@ pub fn print_status (project: &Arrangement) { //})?)? //} -/// 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::*; @@ -1958,3 +1969,33 @@ mod draw { } } + +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 +}