implement passable #[command]
Some checks are pending
/ build (push) Waiting to run

This commit is contained in:
i do not exist 2026-08-09 09:23:24 +03:00
parent 68236a7210
commit ae496987c8
7 changed files with 458 additions and 172 deletions

10
Cargo.lock generated
View file

@ -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"

View file

@ -14,6 +14,7 @@ path = "src/tek.rs"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
[dependencies]
tek_proc = { path = "./proc" }
tengri = { path = "./tengri", features = [ "term", "lang" ] }
ansi_term = { version = "0.12.1" }

11
proc/Cargo.toml Normal file
View file

@ -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"] }

189
proc/src/lib.rs Normal file
View file

@ -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<Ident, (Fields, Expr)>
);
impl Parse for Meta {
fn parse (input: ParseStream) -> Result<Self> {
Ok(Self(input.parse()?))
}
}
impl Parse for Item {
fn parse (input: ParseStream) -> Result<Self> {
let mut item: ItemEnum = input.parse()?;
let mut branches: HashMap<Ident, (Fields, Expr)> = 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::<Expr>(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<Self> {
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<Ident, Vec<(Literal, BTreeMap<Ident, Type>)>>
);
impl Parse for Meta {
fn parse (input: ParseStream) -> Result<Self> {
Ok(Self(input.parse()?))
}
}
impl Parse for Item {
fn parse (input: ParseStream) -> Result<Self> {
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: ToTokens> (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);
}
}

View file

@ -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<OsString>, Vec<OsString>)> {
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))
}

View file

@ -33,6 +33,7 @@ pub fn draw_dialog (
}
impl App {
pub fn get_dialog (&self, src: impl Language) -> Perhaps<Dialog> {
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<AppCommand> {
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<AppCommand> {
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<AppCommand> {
Ok(match &self.dialog {
Dialog::Menu(index, items) => {

View file

@ -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, &lt, &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<RwLock<Option<Arc<str>>>>
}
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<Self> {
match self {
$(Self::$Variant $({ $($arg),* })? => $body,)*
_ => unimplemented!("Act<{}>: {self:?}", stringify!($State)),
}
}
}
});
pub fn swap_value <T: Clone + PartialEq, U> (
target: &mut T, value: &T, returned: impl Fn(T)->U
@ -930,23 +921,6 @@ mod app {
}
}
pub fn scan (dir: &PathBuf) -> Usually<(Vec<OsString>, Vec<OsString>)> {
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<AppCommand> {
Ok(None)
}
fn cancel (&mut self) -> Perhaps<AppCommand> {
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<str> } => todo!(),
});
def_command!(AudioOutputCommand: |port: AudioOutput| {
Close => todo!(),
Connect { audio_in: Arc<str> } => todo!(),
});
def_command!(MidiInputCommand: |port: MidiInput| {
Close => todo!(),
Connect { midi_out: Arc<str> } => todo!(),
});
def_command!(MidiOutputCommand: |port: MidiOutput| {
Close => todo!(),
Connect { midi_in: Arc<str> } => todo!(),
});
pub struct Junction<T: JackPort>(T);
impl<T: JackPort> View<Tui> for Junction<T> {
@ -1301,36 +1330,38 @@ mod device {
T::KIND
}
}
#[tek_proc::command(AudioInput)]
#[tek_proc::keyword(AudioInput)]
#[derive(Debug)]
pub enum AudioInputCommand {
Close,
Connect(Arc<str>),
}
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<str>),
}
println!("Scenes:");
for (i, t) in project.scenes.iter().enumerate() {
println!(" Scene {i}: {} {:?}", &t.name, &t.clips);
#[tek_proc::command(MidiInput)]
#[tek_proc::keyword(MidiInput)]
#[derive(Debug)]
pub enum MidiInputCommand {
Close,
Connect(Arc<str>),
}
#[tek_proc::command(MidiOutput)]
#[tek_proc::keyword(MidiOutput)]
#[derive(Debug)]
pub enum MidiOutputCommand {
Close,
Connect(Arc<str>),
}
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<Self> {
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
}