diff --git a/src/conf.rs b/src/conf.rs deleted file mode 100644 index 86b0fa80..00000000 --- a/src/conf.rs +++ /dev/null @@ -1,275 +0,0 @@ -use crate::*; -use std::path::PathBuf; -use notify_debouncer_full::notify::{RecursiveMode, PollWatcher, Config as NotifyConfig, Watcher}; - -impl_as_ref!(Config: |self: Config| self); -impl_as_ref!(Config: |self: ConfigWatcher| &self.config); -impl_as_mut!(Config: |self: ConfigWatcher| &mut self.config); -impl_as_ref!(ConfigWatcher: |self: ConfigWatcher| &self); - -mod bind; pub use self::bind::*; -mod lang; pub use self::lang::*; -mod mode; pub use self::mode::*; -mod view; pub use self::view::*; - -pub fn config_init + AsRef> (config: C) -> Usually { - // Write initial contents of configuration: - let watcher: &ConfigWatcher = config.as_ref(); - if watcher.find_file().is_none() { - std::fs::write(watcher.place_file()?, CONFIG_DEFAULT)?; - } - // Load config from file: - Ok(if let Some(path) = watcher.find_file() { - config_reload(config, std::fs::read_to_string(&path)?)? - } else { - return Err(format!("config_init: not found").into()) - }) -} - -/// Reload configuration from source. -pub fn config_reload , L: Language> (state: C, src: L) -> Usually { - state.as_ref().config_clear(); - src.each(state, |c, s|config_add(c, s.trim())) -} - -/// Load configuration item. -fn config_add , L: Language> (state: C, src: L) -> Usually { - if let Some(expr) = src.expr()? { - match expr.head()? { - Some("mode") => state.as_ref().modes_add(expr.tail()), - Some("view") => state.as_ref().views_add(expr.tail()), - Some("bind") => state.as_ref().binds_def_keys(expr.tail()), - Some("axis") => state.as_ref().binds_def_axis(expr.tail()), - _ => return Err(format!("config_add: unexpected: {src:?}").into()) - }?; - Ok(state) - } else { - Err(format!("Config::add_one: tried to add empty item").into()) - } -} - -/// Configuration: mode, view, and bind definitions. -/// -/// ``` -/// # use ::{std::sync::Arc, tek::*}; -/// -/// // Default empty config: -/// let config: Config = Default::default(); -/// -/// // Define a binding: -/// config.binds_def_keys(stringify!(Confirm (@enter confirm))).unwrap(); -/// -/// // Define an axis: -/// config.binds_def_axis(stringify!(Y @up @down)).unwrap(); -/// -/// // Define a mode: -/// config.modes_add(stringify!(Menu -/// (name Menu) -/// (info Mode selector.) -/// (axis Y Prev Next) -/// (bind Confirm) -/// (view (bg (g 30) (bar/s 7 Logo (full/xy Dialog)))) -/// )).unwrap(); -/// -/// // Find the mode: -/// let mode: Arc = config.modes_get("Menu").unwrap(); -/// ``` -#[derive(Default, Debug)] -pub struct Config { - /// Error caught during (re)loading - pub error: RwLock>>, - /// Timestamp - pub stamp: AtomicU64, - /// Active collection of interaction modes. - pub modes: Modes, - /// Active collection of event bindings. - pub binds: Binds, - /// Active collection of view definitions. - pub views: Views, -} - - -impl Config { - pub fn new () -> Self { - Self { stamp: quanta::Clock::new().raw().into(), ..Default::default() } - } -} - -impl> HasError> for T { - fn error (&self) -> &RwLock>> { - &self.as_ref().error - } -} - -impl HasError> for Arc { - fn error (&self) -> &RwLock>> { - &self.as_ref().config.error - } -} - -impl HasConfig for Config { - fn config (&self) -> &Config { - self - } - fn config_mut (&mut self) -> &mut Config { - self - } -} - -/// Configuration watcher and manager. -/// -/// ``` -/// let config = tek::ConfigWatcher::new(None); -/// ``` -#[derive(Default, Debug)] -pub struct ConfigWatcher { - /// XDG base directories of running user. - pub dirs: BaseDirectories, - /// Watcher - pub watch: RwLock>, - /// Actual configuration - pub config: Config, -} - -/// Default configuration contents. -const CONFIG_DEFAULT: &'static str = include_str!("tek.edn"); - -impl ConfigWatcher { - /// Default configuration directory. - const DIR: &'static str = "tek"; - /// Default configuration subdirectory. - const SUB: &'static str = "v0"; - /// Default configuration file name. - const EDN: &'static str = "tek.edn"; - /// Create a new watched app configuration - /// from a set of XDG base directories, - pub fn new (dirs: Option) -> Self { - Self { - dirs: dirs.unwrap_or_else(||BaseDirectories::with_profile(Self::DIR, Self::SUB)), - ..Default::default() - } - } - /// Create, initialize, and watch a new configuration. - pub fn run_watched ( - callback: impl FnOnce(Arc)->T, - dirs: impl Into>, - ) -> Usually { - let watch = Arc::new(Self::new(dirs.into())); - Self::watch(watch.clone(), None)?; - let result = callback(watch); - Ok(result) - } - /// Watch a config's file for changes. - pub fn watch (config: Arc, poll: Option) -> Usually<()> { - let poll = poll.unwrap_or(Duration::from_millis(250)); - let opts = NotifyConfig::default().with_poll_interval(poll); - let mut watcher = ::notify_debouncer_full::notify::poll::PollWatcher::new({ - let config = config.clone(); - move|result|{ - match result { - Ok(_events) => if let Err(e) = config_init(config.as_ref()) { - config.error_set(Some(format!("{e:?}").into())); - panic!("{e:?}"); - } else { - //println!("config updated"); - }, - Err(errors) => { - panic!("{errors:?}"); - } - } - } - }, opts)?; - if let Some(path) = config.get_file() { - //println!("watching: {path:?}"); - watcher.watch(&path, RecursiveMode::NonRecursive)?; - *config.as_ref().watch.try_write().unwrap() = Some(watcher); - Ok(()) - } else { - Err(format!("no config path").into()) - } - } - /// Find the config file. - fn find_file (&self) -> Option { - self.dirs.find_config_file(Self::EDN) - } - /// Place config file in default location. - fn place_file (&self) -> Result { - self.dirs.place_config_file(Self::EDN) - } - /// Get path to config file. - fn get_file (&self) -> Option { - self.dirs.get_config_file(Self::EDN) - } -} - -impl HasConfig for ConfigWatcher { - fn config (&self) -> &Config { - &self.config - } - fn config_mut (&mut self) -> &mut Config { - &mut self.config - } -} - -pub fn eval_word (src: impl Language, eval: impl Fn(&str)->Usually) -> Perhaps { - src.word()?.map(eval).transpose() -} - -//pub fn eval_expr (src: impl Language, eval: impl Fn(&str)->Usually) -> Perhaps { - //Ok(match expr.head()? { - //}) -//} - -pub fn print_config_views (config: &Config) { - use ::ansi_term::Color::*; - config.views_for_each(|k, v|{ - println!("{} {} {}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}")), v.source); - }); -} - -pub fn print_config_binds (config: &Config) { - use ::ansi_term::Color::*; - config.binds_for_each(|k, v|{ - println!("{} {}", Green.paint("BIND"), Green.bold().paint(format!("{k:<16}"))); - for (k, v) in v.0.iter() { - print!("{} ", &Yellow.paint(match &k.0 { - Event::Key(KeyEvent { modifiers, .. }) => - format!("{:>16}", format!("{modifiers}")), - _ => unimplemented!() - })); - print!("{}", &Yellow.bold().paint(match &k.0 { - Event::Key(KeyEvent { code, .. }) => - format!("{:<10}", format!("{code}")), - _ => unimplemented!() - })); - for v in v.iter() { - print!(" => {:?}", v.commands); - print!(" {}", v.condition.as_ref().map(|x|format!("{x:?}")).unwrap_or_default()); - println!(" {}", v.description.as_ref().map(|x|x.as_ref()).unwrap_or_default()); - //println!(" {:?}", v.source); - } - } - }); -} - -pub fn print_config_modes (config: &Config) { - use ::ansi_term::Color::*; - config.modes_for_each(|k, v|{ - println!(); - for v in v.name.iter() { print!("{}", Green.bold().paint(format!("{v} "))); } - for v in v.info.iter() { print!("\n{}", Green.paint(format!("{v}"))); } - print!("\n{} {}", Blue.paint("TOOL"), Green.bold().paint(format!("{k:<16}"))); - print!("\n{}", Blue.paint("KEYS")); - for v in v.keys.iter() { print!("{}", Green.paint(format!(" {v}"))); } - println!(); - v.modes_for_each(|k, v|{ - print!("{} {} {:?}", Blue.paint("MODE"), Green.bold().paint(format!("{k:<16}")), v.name); - print!( " INFO={:?}", v.info); - print!( " VIEW={:?}", v.view); - println!(" KEYS={:?}", v.keys); - }); - print!("{}", Blue.paint("VIEW")); - for v in v.view.iter() { print!("{}", Green.paint(format!(" {v}"))); } - println!(); - }); -} diff --git a/src/conf/bind.rs b/src/conf/bind.rs index dbdb68bb..45c48557 100644 --- a/src/conf/bind.rs +++ b/src/conf/bind.rs @@ -220,6 +220,54 @@ pub struct ControlAxis(pub Arc); impl_debug!(Condition: |self, w| { write!(w, "*") }); +pub trait HasBinds { + fn binds_keys (&self) -> &RwLock, Arc>>>>; + fn binds_axis (&self) -> &RwLock, Arc>>>>; + + fn binds_clear (&self) { + *self.binds_keys().write() = Default::default(); + *self.binds_axis().write() = Default::default(); + } + fn binds_find (&self, id: impl AsRef) -> Option>>> { + self.binds_keys().read().get(id.as_ref()).cloned() + } + fn binds_for_each ( + &self, mut cb: impl FnMut(&Arc, &Bind>)->T + ) { + for (k, v) in self.binds_keys().read().iter() { + cb(k, v); + } + } + /// Define a top-level key binding. + /// + /// ``` + /// # use ::{std::sync::Arc, tek::*}; + /// let config: Config = Default::default(); + /// config.binds_def_keys(stringify!(Confirm (@enter confirm))).unwrap(); + /// ``` + fn binds_def_keys (&self, expr: impl Language) -> Usually<()> { + self.binds_keys().write().insert( + expr.head()?.ok_or("bind: missing name")?.into(), + Bind::new(expr.tail()?.unwrap_or(""))?.into() + ); + Ok(()) + } + /// Define an top-level axis binding (keypair). + /// + /// ``` + /// # use ::{std::sync::Arc, tek::*}; + /// let config: Config = Default::default(); + /// config.binds_def_axis(stringify!(Y @up @down)).unwrap(); + /// ``` + fn binds_def_axis (&self, expr: impl Language) -> Usually<()> { + self.binds_axis().write().insert( + expr.head()?.ok_or("axis: missing name")?.into(), + Axis::new(expr.tail()?.unwrap_or(""))?.into() + ); + Ok(()) + } +} + impl HasBinds for App { fn binds_keys (&self) -> &RwLock, Arc>>>> { &self.config.config.binds.keys @@ -246,3 +294,94 @@ impl HasBinds for Config { &self.binds.axis } } + +pub trait DispatchBind: HasBinds + HasModeStack + HasModes + Namespace { + fn input_cmds (&mut self, input: &TuiEvent) -> Usually<()> { + if let Some(bindings) = self.input_cmds_find(input) { + return self.input_cmds_exec(bindings) + } + Ok(()) + } + /// ``` + /// # use ::{std::sync::Arc, tek::*}; + /// let app = config_reload(App::default(), stringify!( + /// (axis Y @up @down) + /// (mode Menu + /// (axis Y prev next) + /// (bind (@enter confirm))) + /// )).unwrap(); + /// panic!("{:?}\n{:?}", app.config.config.binds, app.config.config.modes); + /// assert!(app.input_cmds_find(&"left".into()).is_none()); + /// assert!(app.input_cmds_find(&"enter".into()).is_some()); + /// assert!(app.input_cmds_find(&"down".into()).is_some()); + /// assert!(app.input_cmds_find(&"up".into()).is_some()); + /// ``` + fn input_cmds_find (&self, input: &TuiEvent) -> Option>]>> { + for name in self.mode_stack().read().iter().rev() { + let mode = self.modes_get(name).as_ref().map(Arc::clone); + if let Some(mode) = mode { + for (ctrl, (dec, inc)) in mode.axis.iter() { + panic!("{ctrl:?} {dec:?} {inc:?}"); + } + for id in mode.keys.iter() { + if let Some(event_map) = self.binds_find(id) + && let Some(bindings) = event_map.query(input) { + return Some(bindings.into()) + } + } + } + } + None + } + fn input_cmds_exec (&mut self, bindings: Arc<[Binding>]>) -> Usually<()> { + for binding in bindings.iter() { + for src in binding.commands.iter() { + if let Some(command) = Namespace::::namespace(self, &src)? { + self.cmd_dispatch(command)?; + } + } + } + Ok(()) + } + fn cmd_dispatch (&mut self, command: C) -> Usually<()>; +} + +impl DispatchBind for App { + fn cmd_dispatch (&mut self, command: AppCmd) -> Usually<()> { + match command.clone().dispatch(self) { + Ok(undo) => { + self.history.push((command, undo)); + Ok(()) + }, + Err(err) => { + self.history.push((command, None)); + Err(err) + } + } + } +} + +pub fn print_config_binds (config: &Config) { + use ::ansi_term::Color::*; + config.binds_for_each(|k, v|{ + println!("{} {}", Green.paint("BIND"), Green.bold().paint(format!("{k:<16}"))); + for (k, v) in v.0.iter() { + print!("{} ", &Yellow.paint(match &k.0 { + Event::Key(KeyEvent { modifiers, .. }) => + format!("{:>16}", format!("{modifiers}")), + _ => unimplemented!() + })); + print!("{}", &Yellow.bold().paint(match &k.0 { + Event::Key(KeyEvent { code, .. }) => + format!("{:<10}", format!("{code}")), + _ => unimplemented!() + })); + for v in v.iter() { + print!(" => {:?}", v.commands); + print!(" {}", v.condition.as_ref().map(|x|format!("{x:?}")).unwrap_or_default()); + println!(" {}", v.description.as_ref().map(|x|x.as_ref()).unwrap_or_default()); + //println!(" {:?}", v.source); + } + } + }); +} diff --git a/src/conf/lang.rs b/src/conf/lang.rs index 36e3aa31..4ac13194 100644 --- a/src/conf/lang.rs +++ b/src/conf/lang.rs @@ -1,5 +1,54 @@ use crate::*; +pub fn eval_word (src: impl Language, eval: impl Fn(&str)->Usually) -> Perhaps { + src.word()?.map(eval).transpose() +} + +pub fn swap_value ( + target: &mut T, value: &T, returned: impl Fn(T)->U +) -> Perhaps { + if *target == *value { + Ok(None) + } else { + let mut value = value.clone(); + std::mem::swap(target, &mut value); + Ok(Some(returned(value))) + } +} + +pub fn toggle_bool ( + target: &mut bool, value: &Option, returned: impl Fn(Option)->U +) -> Perhaps { + let mut value = value.unwrap_or(!*target); + if value == *target { + Ok(None) + } else { + std::mem::swap(target, &mut value); + Ok(Some(returned(Some(value)))) + } +} +/// Increment a wrapping counter. +pub const fn wrap_inc (index: usize, count: usize) -> usize { + if count > 0 { (index + 1) % count } else { 0 } +} + +/// Decrement a wrapping counter. +pub const fn wrap_dec (index: usize, count: usize) -> usize { + if count > 0 { + let a = index.overflowing_sub(1).0; + let b = count.saturating_sub(1); + if a < b { a } else { b } + } else { + 0 + } +} + + +//pub fn eval_expr (src: impl Language, eval: impl Fn(&str)->Usually) -> Perhaps { + //Ok(match expr.head()? { + //}) +//} + pub fn tek_arc_str <'a> (_: &App, src: impl Language) -> Perhaps> { Ok(src.src()?.map(|x|x.into())) } @@ -152,3 +201,738 @@ pub fn tek_path_buf <'a> (_: &App, _: impl Language) -> Perhaps { pub fn tek_vec_arc_str <'a> (_: &App, _: impl Language) -> Perhaps>> { todo!() } + +#[tek_proc::commands(AppCmd)] impl App { + /// Exit the application. + #[command(Exit = "exit")] pub fn cmd_exit (&mut self) -> Perhaps { + self.exit.exit(); + Ok(None) + } + /// Switch to another set of contextual modes. + #[command(Mode = "mode")] pub fn cmd_mode (&mut self, modes: Vec>) -> Perhaps { + *self.mode_stack().write() = modes.into(); + Ok(None) + } + /// Enter a contextual mode. + #[command(Enter = "enter")] pub fn enter_mode (&mut self, mode: Arc) -> Perhaps { + let mut modes = self.mode_stack().write(); + Ok(if modes.last() != Some(&mode) { + modes.push(mode); + Some(AppCmd::Leave) + } else { + None + }) + } + /// Leave a contextual mode. + #[command(Leave = "leave")] fn leave_mode (&mut self) -> Perhaps { + let mut modes = self.mode_stack().write(); + if modes.len() > 1 { + let mode = modes.pop(); + Ok(mode.map(|mode|AppCmd::Enter { mode })) + } else { + Ok(None) + } + } + /// Increment the given data axis. + #[command(Inc = "inc")] pub fn inc (&mut self, axis: Arc) -> Perhaps { + panic!("inc {axis:?}"); + //Ok(match (&self.dialog, axis) { + //(Dialog::None, _) => todo!(), + //(Dialog::Menu(_, _), ControlAxis::Y) => { + //AppCmd::Dialog { dialog: self.dialog.menu_next() }.dispatch(self)?; + //None + //}, + //_ => todo!() + //}) + } + /// Decrement the given data axis. + #[command(Dec = "dec")] pub fn dec (&mut self, axis: Arc) -> Perhaps { + panic!("dec {axis:?}"); + //Ok(match (&self.dialog, axis) { + //(Dialog::None, _) => None, + //(Dialog::Menu(_, _), ControlAxis::Y) => { + //AppCmd::Dialog { dialog: self.dialog.menu_prev() }.dispatch(self)?; + //None + //}, + //_ => todo!() + //}) + } + /// Cancel current selection. + #[command(Cancel = "cancel")] pub fn cmd_cancel (&mut self) -> Perhaps { + todo!("cancel") + } + /// Confirm current selection. + #[command(Confirm = "confirm")] pub fn cmd_confirm (&mut self) -> Perhaps { + Ok(match self.dialog.clone() { + Dialog::Menu(index, items) => { + for command in items[index].1.iter() { + App::cmd_dispatch(self, command.clone())?; + } + None + } + _ => todo!(), + }) + } + /// Swap currently active modal dialog. + /// + /// ``` + /// let _ = tek::App::default().cmd_dialog(tek::Dialog::welcome()); + /// ``` + #[command(Dialog = "dialog")] pub fn cmd_dialog (&mut self, dialog: Dialog) -> Perhaps { + let mut dialog = dialog.clone(); + std::mem::swap(&mut self.dialog, &mut dialog); + Ok(Some(AppCmd::Dialog { dialog })) + } + /// Command the file browser. + #[command(Browse = "browse")] pub fn cmd_browse (&mut self, command: BrowseCmd) -> Perhaps { + Ok(command.dispatch(self)?.map(Into::into)) + } + /// Commands that alter the current session. + #[command(Project = "session")] pub fn cmd_session (&mut self, command: ProjectCmd) -> Perhaps { + Ok(command.dispatch(self)?.map(Into::into)) + } + /// Commands pertaining to MIDI clips. + #[command(MidiClip = "clip")] pub fn cmd_midi_clip (&mut self, command: MidiClipCmd) -> Perhaps { + Ok(command.dispatch(self)?.map(Into::into)) + } + /// Commands pertaining to the MIDI clip editor. + #[command(MidiEdit = "edit")] pub fn cmd_midi_edit (&mut self, command: MidiEditCmd) -> Perhaps { + if matches!(command, MidiEditCmd::Toggle) { + if self.is_editing() { + self.leave_mode()?; + } else { + self.enter_mode("Edit".into())?; + } + } + Ok(command.dispatch(self)?.map(Into::into)) + } + #[command(Scene = "scene")] pub fn cmd_scene (&mut self, command: SceneCmd) -> Perhaps { + Ok(command.dispatch(self)?.map(Into::into)) + } + #[command(Scenes = "scenes")] pub fn cmd_scenes (&mut self, command: ScenesCmd) -> Perhaps { + Ok(command.dispatch(self)?.map(Into::into)) + } + #[command(Track = "track")] pub fn cmd_track (&mut self, command: TrackCmd) -> Perhaps { + Ok(command.dispatch(self)?.map(Into::into)) + } + #[command(Tracks = "tracks")] pub fn cmd_tracks (&mut self, command: TracksCmd) -> Perhaps { + Ok(command.dispatch(self)?.map(Into::into)) + } + //#[command(Pool = "pool")] pub fn cmd_pool (&mut self, command: PoolCmd) -> Perhaps { + //Ok(command.dispatch(self)?.map(Into::into)) + //} +} + +impl ProjectControl for T {} + +#[tek_proc::commands(ProjectCmd)] pub trait ProjectControl: HasProject { + #[command(LoadLast = "load-last")] fn load_last (&mut self) -> Perhaps { + todo!() + } + #[command(Load = "load")] fn load (&mut self) -> Perhaps { + todo!() + } +} + +impl ClockControl for T where T: HasClock + + Namespace + Namespace + Namespace> +{} +#[tek_proc::commands(ClockCmd = "clock")] pub trait ClockControl: HasClock + + Namespace + Namespace + Namespace> +{ + #[command(SeekUsec = "usec")] fn seek_usec (&mut self, usec: f64) -> Perhaps { + self.clock().playhead.update_from_usec(usec); + Ok(None) + } + #[command(SeekSample = "sample")] fn seek_sample (&mut self, sample: f64) -> Perhaps { + self.clock().playhead.update_from_sample(sample); + Ok(None) + } + #[command(SeekPulse = "pulse")] fn seek_pulse (&mut self, pulse: f64) -> Perhaps { + self.clock().playhead.update_from_pulse(pulse); + Ok(None) + } + #[command(SetBpm = "bpm")] fn set_bpm (&mut self, bpm: f64) -> Perhaps { + Ok(Some(ClockCmd::SetBpm { + bpm: self.clock().timebase().bpm.set(bpm) + })) + } + #[command(SetQuant = "quant")] fn set_quant (&mut self, quant: f64) -> Perhaps { + Ok(Some(ClockCmd::SetQuant { + quant: self.clock().quant.set(quant) + })) + } + #[command(SetSync = "sync")] fn set_sync (&mut self, sync: f64) -> Perhaps { + Ok(Some(ClockCmd::SetSync { + sync: self.clock().sync.set(sync) + })) + } + #[command(Play = "play")] fn play (&mut self, position: Option) -> Perhaps { + self.clock().play_from(position)?; + Ok(None) /* TODO Some(Pause(previousPosition)) */ + } + #[command(Pause = "pause")] fn pause (&mut self, position: Option) -> Perhaps { + self.clock().pause_at(position)?; + Ok(None) + } + #[command(TogglePlayback = "toggle")] fn toggle (&mut self, position: u32) -> Perhaps { + Ok(if self.clock().is_rolling() { + self.clock().pause_at(Some(position))?; + None + } else { + self.clock().play_from(Some(position))?; + None + }) + } +} + +impl TracksControl for T where T: HasTracks + + HasScenes + + HasSelection + + HasJack<'static> + + Namespace +{} + +#[tek_proc::commands(TracksCmd)] pub trait TracksControl: HasTracks + + HasScenes + + HasSelection + + HasJack<'static> + + Namespace +{ + /// Stop all playing clips + #[command(Stop = "stop")] fn tracks_stop_all (&mut self) -> Perhaps { + for track in self.tracks_mut().iter_mut() { + track.sequencer.enqueue_next(None); + } + Ok(None) + } + /// Launch multiple clips + #[command(Launch = "launch")] fn tracks_launch (&mut self, clips: ClipPool) + -> Perhaps + { + if clips.len() == 0 { + for track in self.tracks_mut().iter_mut() { + track.sequencer.enqueue_next(None); + } + return Ok(None) + } + for (clip, track) in clips.0.read().iter().zip(self.tracks_mut()) { + track.sequencer.enqueue_next(clip.read().as_ref()); + } + Ok(None) + } + #[command(Add = "add")] fn tracks_add (&mut self) -> Perhaps + where Self: HasScenes + HasJack<'static> + Sized + { + let (_index, _) = self.tracks_add_one(None, None, [].into(), [].into())?; + Ok(None) + } + #[command(Select = "select")] fn track_select (&mut self) -> Perhaps { + let tracks = self.tracks().len(); + let select = self.selection_mut().select_track(tracks); + *self.selection_mut() = select; + Ok(None) + } + #[command(Next = "next")] fn track_select_next (&mut self) -> Perhaps { + let tracks = self.tracks().len(); + let select = self.selection().select_track_next(tracks); + *self.selection_mut() = select; + Ok(None) + } + #[command(Prev = "prev")] fn track_select_prev (&mut self) -> Perhaps { + let select = self.selection_mut().select_track_prev(); + *self.selection_mut() = select; + Ok(None) + } + + /// Run audio callbacks for every track and every device + fn tracks_jack_process (&mut self, client: &Client, scope: &ProcessScope) -> Control + where Self: HasJack<'static> + { + for track in self.tracks_mut().iter_mut() { + if Control::Quit == Audio::process(&mut track.sequencer, client, scope) { + return Control::Quit + } + for device in track.devices.iter_mut() { + if Control::Quit == DeviceAudio(device).process(client, scope) { + return Control::Quit + } + } + } + Control::Continue + } + + /// Add multiple tracks + fn tracks_add_many ( + &mut self, + count: usize, + width: Option, + mins: Arc<[Connect]>, + mouts: Arc<[Connect]>, + ) -> Usually<()> + where Self: HasJack<'static> + HasScenes + { + let track_color_1 = ItemColor::random(); + let track_color_2 = ItemColor::random(); + for i in 0..count { + let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into(); + let track = self.tracks_add_one(None, Some(color), mins.clone(), mouts.clone())?.1; + if let Some(width) = width { + track.width = width; + } + } + Ok(()) + } + + /// Add a track + fn tracks_add_one ( + &mut self, + name: Option>, + color: Option, + mins: Arc<[Connect]>, + mouts: Arc<[Connect]>, + ) -> Usually<(usize, &mut Track)> + where Self: HasJack<'static> + HasScenes + { + let name: Arc = name.map_or_else( + ||format!("trk{}", self.tracks_last()).into(), + |x|x.to_string().into() + ); + *self.tracks_last_mut() += 1; + let track = Track::new_added(self.jack(), Some(self.clock()), &name, color, None, mins, mouts)?; + self.tracks_mut().push(track); + let len = self.tracks().len(); + let index = len - 1; + for scene in self.scenes_mut().iter_mut() { + while scene.clips.len() < len { + scene.clips.append(None); + } + } + Ok((index, &mut self.tracks_mut()[index])) + } + + fn track_longest_name (&self) -> usize { + self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max) + } +} + +impl + + Namespace> + + Namespace + + Namespace> +> TrackControl for T {} + +#[tek_proc::commands(TrackCmd)] pub trait TrackControl: HasTrack + + Namespace + + Namespace> + + Namespace + + Namespace> +{ + #[command(Stop = "track/stop")] + fn track_stop (&mut self) -> Perhaps { + self.track_mut().map(|track|track.sequencer.enqueue_next(None)); + Ok(None) + } + #[command(SetMute = "track/mute")] + fn track_set_mute (&mut self, _mute: Option) -> Perhaps { + todo!() + } + #[command(SetSolo = "track/solo")] + fn track_set_solo (&mut self, _solo: Option) -> Perhaps { + todo!() + } + #[command(SetSize = "track/size")] + fn track_set_size (&mut self, _size: usize) -> Perhaps { + todo!() + } + #[command(SetZoom = "track/zoom")] + fn track_set_zoom (&mut self, _zoom: usize) -> Perhaps { + todo!() + } + #[command(SetName = "track/name")] + fn track_set_name (&mut self, name: Arc) -> Perhaps { + self.track_mut() + .map(|track|swap_value(&mut track.name, &name, |name|TrackCmd::SetName { name })) + .transpose() + .map(Option::flatten) + } + #[command(SetColor = "track/color")] + fn track_set_color (&mut self, color: ItemTheme) -> Perhaps { + self.track_mut() + .map(|track|swap_value(&mut track.color, &color, |color|TrackCmd::SetColor { color })) + .transpose() + .map(Option::flatten) + } + #[command(SetRec = "track/rec")] + fn track_set_rec (&mut self, rec: Option) -> Perhaps { + self.track_mut() + .map(|track|toggle_bool(&mut track.sequencer.recording, &rec, |rec|TrackCmd::SetRec { rec })) + .transpose() + .map(Option::flatten) + } + #[command(SetMon = "track/mon")] + fn track_set_mon (&mut self, mon: Option) -> Perhaps { + self.track_mut() + .map(|track|toggle_bool(&mut track.sequencer.monitoring, &mon, |mon|TrackCmd::SetMon { mon })) + .transpose() + .map(Option::flatten) + } +} + +impl +> ScenesControl for T {} + +#[tek_proc::commands(ScenesCmd)] pub trait ScenesControl: HasScenes + + HasTracks + + HasTrackScroll + + HasSelection + + HasJack<'static> +{ + #[command(Add = "add")] fn scenes_add (&mut self) -> Perhaps where Self: Sized { + let (_index, _) = self.scenes_add_one(None, None)?; + Ok(None) + } + #[command(Select = "select")] fn scene_select (&mut self) -> Perhaps { + let scenes = self.scenes().len(); + let select = self.selection_mut().select_scene(scenes); + *self.selection_mut() = select; + Ok(None) + } + #[command(Next = "next")] fn scene_select_next (&mut self) -> Perhaps { + let scenes = self.scenes().len(); + let select = self.selection().select_scene_next(scenes); + *self.selection_mut() = select; + Ok(None) + } + #[command(Prev = "prev")] fn scene_select_prev (&mut self) -> Perhaps { + let select = self.selection_mut().select_scene_prev(); + *self.selection_mut() = select; + Ok(None) + } + /// Generate the default name for a new scene + fn scenes_default_name (&self) -> Arc { + format!("s{:3>}", self.scenes().len() + 1).into() + } + fn scenes_longest_name (&self) -> usize { + self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max) + } + /// Add multiple scenes + fn scenes_add_many (&mut self, n: usize) + -> Usually<()> where Self: HasTracks + { + let scene_color_1 = ItemColor::random(); + let scene_color_2 = ItemColor::random(); + for i in 0..n { + let _ = self.scenes_add_one(None, Some( + scene_color_1.mix(scene_color_2, i as f32 / n as f32).into() + ))?; + } + Ok(()) + } + /// Add a scene + fn scenes_add_one (&mut self, name: Option>, color: Option) + -> Usually<(usize, &mut Scene)> + where + Self: HasTracks + { + let scene = Scene { + name: name.map_or_else(||self.scenes_default_name(), |x|x.to_string().into()), + clips: Pool::from(vec![None;self.tracks().len()]), + color: color.unwrap_or_else(ItemTheme::random), + }; + self.scenes_mut().push(scene); + let index = self.scenes().len() - 1; + Ok((index, &mut self.scenes_mut()[index])) + } +} + +impl + Namespace> + Namespace +> SceneControl for T {} + +#[tek_proc::commands(SceneCmd)] pub trait SceneControl: HasScene + + Namespace + Namespace> + Namespace +{ + #[command(SetSize = "size")] fn scene_set_size (&mut self, _size: usize) -> Perhaps { + todo!() + } + #[command(SetZoom = "zoom")] fn scene_set_zoom (&mut self, _size: usize) -> Perhaps { + todo!() + } + #[command(SetName = "name")] fn scene_set_name (&mut self, name: Arc) -> Perhaps { + Ok(self.scene_mut().map(|scene|swap_value( + &mut scene.name, + &name, + |name|SceneCmd::SetName { name } + )).transpose()?.flatten()) + } + #[command(SetColor = "color")] fn scene_set_color (&mut self, color: ItemTheme) -> Perhaps { + Ok(self.scene_mut().map(|scene|swap_value( + &mut scene.color, + &color, + |color|SceneCmd::SetColor { color } + )).transpose()?.flatten()) + } +} + +#[tek_proc::commands(SamplerCmd = "sampler")] pub trait SamplerControl: HasSampler + + Namespace +{ + #[command(RecToggle = "rec-toggle")] fn record_toggle (&mut self, slot: usize) -> Perhaps + where Self: Sized + { + let sampler = self.sampler_mut(); + let recording = sampler.recording.as_ref().map(|x|x.0); + let _ = SamplerCmd::RecFinish.dispatch(self)?; + // autoslice: continue recording at next slot + if recording != Some(slot) { + SamplerCmd::RecBegin { slot }.dispatch(self) + } else { + Ok(None) + } + } + #[command(RecBegin = "rec-begin")] fn record_begin (&mut self, slot: usize) -> Perhaps { + let sampler = self.sampler_mut(); + sampler.recording = Some(( + slot, + Some(Arc::new(RwLock::new(Sample::new( + "Sample", 0, 0, vec![vec![]; sampler.audio_ins.len()] + )))) + )); + Ok(None) + } + #[command(RecFinish = "rec-finish")] fn record_finish (&mut self) -> Perhaps { + let sampler = self.sampler_mut(); + let _prev_sample = sampler.recording.as_mut().map(|(index, sample)|{ + std::mem::swap(sample, &mut sampler.samples.0[*index]); + sample + }); // TODO: undo + Ok(None) + } + #[command(RecCancel = "rec-cancel")] fn record_cancel (&mut self) -> Perhaps { + self.sampler_mut().recording = None; + Ok(None) + } + #[command(PlaySample = "sample-play")] fn sample_play (&mut self, slot: usize) -> Perhaps { + let sampler = self.sampler_mut(); + if let Some(ref sample) = sampler.samples.0[slot] { + sampler.voices.write().push(Sample::play(sample, 0, &u7::from(128))); + } + Ok(None) + } + #[command(StopSample = "sample-stop")] fn sample_stop (&mut self, slot: usize) -> Perhaps { + todo!(); + //Ok(None) + } +} + +impl + + Namespace + + Namespace + + Namespace + + Namespace> + + Namespace +> MidiEditControl for T {} + +#[tek_proc::commands(MidiEditCmd)] pub trait MidiEditControl: HasClip + + HasEditor + + Namespace + + Namespace + + Namespace + + Namespace + + Namespace> + + Namespace +{ + #[command(Toggle = "toggle")] fn edit_toggle (&mut self) -> Perhaps { + if self.is_editing() { + *self.editor_mut() = None; + } else if let Some(clip) = self.clip() { + let editor = MidiEditor::new(); + *editor.mode.clip.0.write() = clip.clone(); + editor.redraw(); + *self.editor_mut() = Some(editor); + } else { + return Ok(None) + } + Ok(Some(MidiEditCmd::Toggle)) + } + #[command(Show = "show")] fn edit_show (&mut self, clip: ClipSlot) -> Perhaps { + Ok(self.editor_mut().as_mut().map(|editor|{ + editor.mode.clip.0 = clip.0; + editor.redraw(); + None + }).flatten()) + } + #[command(DeleteNote = "delete")] fn edit_note_delete (&mut self) -> Perhaps { + Ok(self.editor_mut().as_mut().map(|editor|{ + editor.redraw(); + todo!() + }).flatten()) + } + #[command(AppendNote = "append")] fn edit_note_append (&mut self, advance: bool) -> Perhaps { + Ok(self.editor_mut().as_mut().map(|editor|{ + editor.put_note(advance); + editor.redraw(); + None + }).flatten()) + } + #[command(SetNotePos = "note-pos")] fn edit_note_set_pos (&mut self, pos: usize) -> Perhaps { + Ok(self.editor_mut().as_mut().map(|editor|{ + editor.set_note_pos((pos).min(127)); + editor.redraw(); + None + }).flatten()) + } + #[command(SetNoteLen = "note-len")] fn edit_note_set_len (&mut self, len: usize) -> Perhaps { + Ok(self.editor_mut().as_mut().map(|editor|{ + editor.set_note_len(len); + editor.redraw(); + None + }).flatten()) + } + #[command(SetNoteScroll = "note-scroll")] fn edit_note_set_scroll (&mut self, scroll: usize) -> Perhaps { + Ok(self.editor_mut().as_mut().map(|editor|{ + editor.set_note_lo((scroll).min(127)); + editor.redraw(); + None + }).flatten()) + } + #[command(SetTimePos = "time-pos")] fn edit_time_set_pos (&mut self, pos: usize) -> Perhaps { + Ok(self.editor_mut().as_mut().map(|editor|{ + editor.set_time_pos(pos); + editor.redraw(); + None + }).flatten()) + } + #[command(SetTimeScroll = "time-scroll")] fn edit_time_set_scroll (&mut self, scroll: usize) -> Perhaps { + Ok(self.editor_mut().as_mut().map(|editor|{ + editor.set_time_start(scroll); + editor.redraw(); + None + }).flatten()) + } + #[command(SetTimeZoom = "time-zoom")] fn edit_time_set_zoom (&mut self, zoom: usize) -> Perhaps { + Ok(self.editor_mut().as_mut().map(|editor|{ + editor.set_time_zoom(zoom); + editor.redraw(); + None + }).flatten()) + } + #[command(SetTimeLock = "time-lock")] fn edit_time_set_lock (&mut self, lock: bool) -> Perhaps { + Ok(self.editor_mut().as_mut().map(|editor|{ + editor.set_time_lock(lock); + editor.redraw(); + None + }).flatten()) + } + // TODO: 1-9 seek markers that by default start every 8th of the clip +} + +impl MidiClipControl for T where T: HasClip + + Namespace> + Namespace> {} +#[tek_proc::commands(MidiClipCmd)] pub trait MidiClipControl: HasClip + + Namespace> + Namespace> +{ + /// Create a clip at the current position if it does not already exist. + #[command(Upsert = "upsert")] fn clip_upsert ( + &mut self + ) -> Perhaps { + Ok(self.clip_mut().map(|mut clip|if clip.is_none() { + *clip = Some(Arc::new(RwLock::new(MidiClip { + ppq: 24, + length: 96, + color: ItemTheme::random(), + ..Default::default() + }))); + None // TODO undo + } else { + None + }).flatten()) + } + #[command(SetColor = "color")] fn clip_set_color ( + &mut self, color: Option + ) -> Perhaps { + Ok(self.clip_slot().map_inner_mut(|clip|MidiClipCmd::SetColor { + color: Some(clip.set_color(color)) + })) + } + /// Toggle looping for the active clip + #[command(SetLoop = "loop")] fn clip_toggle_loop ( + &mut self, looping: Option + ) -> Perhaps { + Ok(self.clip_slot().map_inner_mut(|clip|MidiClipCmd::SetLoop { + looping: Some(clip.toggle_loop(looping)) + })) + } +} + +impl BrowseControl for T where T: + Namespace + Namespace + Namespace> {} +#[tek_proc::commands(BrowseCmd = "browse")] pub trait BrowseControl: + Namespace + Namespace + Namespace> +{ + /// Toggle visibility of browser + #[command(Show = "show")] fn show (&mut self) -> Perhaps { + todo!() + } + /// Set current directory + #[command(Chdir = "chdir")] fn chdir (&mut self, _path: PathBuf) -> Perhaps { + todo!() + } + /// Set current filter + #[command(SetSearch = "set-search")] fn set_search (&mut self, _filter: Arc) -> Perhaps { + todo!() + } + /// Set selected item + #[command(SetCursor = "set-cursor")] fn set_cursor (&mut self, _index: usize) -> Perhaps { + todo!() + } +} + +#[tek_proc::commands(AxisCmd)] impl App { +} + +#[tek_proc::command(AudioInput)] +#[tek_proc::keyword(AudioInput)] +#[derive(Debug)] +pub enum AudioInputCmd { + Close, + Connect(Arc), +} + +#[tek_proc::command(AudioOutput)] +#[tek_proc::keyword(AudioOutput)] +#[derive(Debug)] +pub enum AudioOutputCmd { + Close, + Connect(Arc), +} + +#[tek_proc::command(MidiInput)] +#[tek_proc::keyword(MidiInput)] +#[derive(Debug)] +pub enum MidiInputCmd { + Close, + Connect(Arc), +} + +#[tek_proc::command(MidiOutput)] +#[tek_proc::keyword(MidiOutput)] +#[derive(Debug)] +pub enum MidiOutputCmd { + Close, + Connect(Arc), +} + +impl ControlAxis { + pub fn eval <'a> (_: &App, src: impl Language) -> Perhaps { + eval_word(src, |word|Ok(ControlAxis(word.into()))) + } +} diff --git a/src/conf/mode.rs b/src/conf/mode.rs index 5b1717ea..8e1737df 100644 --- a/src/conf/mode.rs +++ b/src/conf/mode.rs @@ -2,6 +2,95 @@ use crate::*; impl_as_mut!(Mode: |self: Mode| self); +/// Has a stack of currently active interaction modes. +pub trait HasModeStack { + /// Stack of active modes. + fn mode_stack (&self) -> &RwLock>>; +} + +impl HasModeStack for App { + fn mode_stack (&self) -> &RwLock>> { + &self.modes + } +} + +pub trait HasModes { + fn modes (&self) -> &RwLock, Arc>>; + + /// Register a mode. + /// + /// ``` + /// # use ::{std::sync::Arc, tek::*}; + /// let config: Config = Default::default(); + /// + /// config.modes_add(stringify!(Menu + /// (name Menu) + /// (info Mode selector.) + /// (axis Y Prev Next) + /// (bind Confirm) + /// (view (bg (g 30) (bar/s 7 Logo (full/xy Dialog)))) + /// )).unwrap(); + /// + /// let _: Arc = config.modes_get("Menu").unwrap(); + /// ``` + fn modes_add <'a> (&self, expr: impl Language) -> UsuallyRef<'a, ()> { + let name = expr.head()?.ok_or("mode: missing name")?; + let body = expr.tail()?.ok_or("mode: missing body")?; + let mut mode = Mode::default(); + body.each(&mut mode, |c, s|c.add(s))?; + self.modes().write().insert(name.into(), Arc::new(mode)); + Ok(()) + } + /// Get a mode by name. + fn modes_get (&self, name: impl AsRef) -> Option> { + self.modes().read().get(name.as_ref()).cloned() + } + /// Run something for each mode. + fn modes_for_each (&self, mut ator: impl FnMut(&str, &Mode)->T) { + for (k, v) in self.modes().read().iter() { + let _ = ator(k.as_ref(), v.as_ref()); + } + } + /// Count modes. + fn modes_count (&self) -> usize { + self.modes().read().len() + } + /// Clear modes. + fn modes_clear (&self) { + *self.modes().write() = Default::default(); + } +} + +impl HasModes for App { + fn modes (&self) -> &RwLock, Arc>> { + &self.config.config.modes.0 + } +} + +impl HasModes for ConfigWatcher { + fn modes (&self) -> &RwLock, Arc>> { + &self.config.modes.0 + } +} + +impl HasModes for Config { + fn modes (&self) -> &RwLock, Arc>> { + &self.modes.0 + } +} + +impl HasModes for Modes { + fn modes (&self) -> &RwLock, Arc>> { + &self.0 + } +} + +impl HasModes for Mode { + fn modes (&self) -> &RwLock, Arc>> { + &self.modes.0 + } +} + /// Collection of UI modes. /// /// ``` @@ -114,38 +203,24 @@ impl Mode { } -impl ControlAxis { - pub fn eval <'a> (_: &App, src: impl Language) -> Perhaps { - eval_word(src, |word|Ok(ControlAxis(word.into()))) - } -} - -impl HasModes for App { - fn modes (&self) -> &RwLock, Arc>> { - &self.config.config.modes.0 - } -} - -impl HasModes for ConfigWatcher { - fn modes (&self) -> &RwLock, Arc>> { - &self.config.modes.0 - } -} - -impl HasModes for Config { - fn modes (&self) -> &RwLock, Arc>> { - &self.modes.0 - } -} - -impl HasModes for Modes { - fn modes (&self) -> &RwLock, Arc>> { - &self.0 - } -} - -impl HasModes for Mode { - fn modes (&self) -> &RwLock, Arc>> { - &self.modes.0 - } +pub fn print_config_modes (config: &Config) { + use ::ansi_term::Color::*; + config.modes_for_each(|k, v|{ + println!(); + for v in v.name.iter() { print!("{}", Green.bold().paint(format!("{v} "))); } + for v in v.info.iter() { print!("\n{}", Green.paint(format!("{v}"))); } + print!("\n{} {}", Blue.paint("TOOL"), Green.bold().paint(format!("{k:<16}"))); + print!("\n{}", Blue.paint("KEYS")); + for v in v.keys.iter() { print!("{}", Green.paint(format!(" {v}"))); } + println!(); + v.modes_for_each(|k, v|{ + print!("{} {} {:?}", Blue.paint("MODE"), Green.bold().paint(format!("{k:<16}")), v.name); + print!( " INFO={:?}", v.info); + print!( " VIEW={:?}", v.view); + println!(" KEYS={:?}", v.keys); + }); + print!("{}", Blue.paint("VIEW")); + for v in v.view.iter() { print!("{}", Green.paint(format!(" {v}"))); } + println!(); + }); } diff --git a/src/conf/view.rs b/src/conf/view.rs index db8e03bd..2c41d08f 100644 --- a/src/conf/view.rs +++ b/src/conf/view.rs @@ -1691,12 +1691,33 @@ impl ClockView { } } +pub trait HasViews { + fn views (&self) -> &RwLock, Arc>>>; + /// Load custom view definition. + fn views_add <'a> (&self, expr: impl Language) -> UsuallyRef<'a, ()> { + self.views().write().insert( + expr.head()?.ok_or("view: missing name")?.into(), + View::new(expr.tail()?.ok_or("view: missing body")?)?.into() + ); + Ok(()) + } + fn views_clear (&self) { + *self.views().write() = Default::default(); + } + fn views_get (&self, name: impl AsRef) -> Option>> { + self.views().read().get(name.as_ref()).cloned() + } + fn views_for_each (&self, cb: impl Fn(&Arc, &Arc>)->T) -> () { + for (k, v) in self.views().read().iter() { + cb(k, v); + } + } +} impl HasViews for App { fn views (&self) -> &RwLock, Arc>>> { &self.config.config.views.0 } } - impl HasViews for Config { fn views (&self) -> &RwLock, Arc>>> { &self.views.0 @@ -1722,3 +1743,10 @@ impl HasViews for Config { } } } + +pub fn print_config_views (config: &Config) { + use ::ansi_term::Color::*; + config.views_for_each(|k, v|{ + println!("{} {} {}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}")), v.source); + }); +} diff --git a/src/deps.rs b/src/deps.rs index 92bc394b..1ee62df9 100644 --- a/src/deps.rs +++ b/src/deps.rs @@ -47,6 +47,9 @@ pub(crate) use ::{ RawRwLock, } }, + notify_debouncer_full::notify::{ + RecursiveMode, PollWatcher, Config as NotifyConfig, Watcher + } }; #[cfg(feature = "prof2")] diff --git a/src/gear/midi.rs b/src/gear/midi.rs index 935eeed9..9111b8a7 100644 --- a/src/gear/midi.rs +++ b/src/gear/midi.rs @@ -74,7 +74,7 @@ impl App { /// every integer between 1 and 2^53 can be represented exactly. /// This should mean that, even at 192kHz sampling rate, over 1 year of audio /// can be clocked in microseconds with f64 without losing precision. -pub trait TimeUnit: InteriorMutable {} +pub trait TimeUnit {} pub const DEFAULT_PPQ: f64 = 96.0; @@ -324,62 +324,6 @@ impl Clock { } } -impl_has!(Clock: |self: Track|self.sequencer.clock); -impl_default!(Timebase: Self::new(48000f64, 150f64, DEFAULT_PPQ)); - -/// Define and implement a unit of time -#[macro_export] macro_rules! impl_time_unit { - ($T:ident) => { - impl Gettable for $T { - fn get (&self) -> f64 { self.0.load(Relaxed) } - } - impl InteriorMutable for $T { - fn set (&self, value: f64) -> f64 { - let old = self.get(); - self.0.store(value, Relaxed); - old - } - } - impl TimeUnit for $T {} - impl_op!($T, Add, add, |a, b|{a + b}); - impl_op!($T, Sub, sub, |a, b|{a - b}); - impl_op!($T, Mul, mul, |a, b|{a * b}); - impl_op!($T, Div, div, |a, b|{a / b}); - impl_op!($T, Rem, rem, |a, b|{a % b}); - impl From for $T { fn from (value: f64) -> Self { Self(value.into()) } } - impl From for $T { fn from (value: usize) -> Self { Self((value as f64).into()) } } - impl From<$T> for f64 { fn from (value: $T) -> Self { value.get() } } - impl From<$T> for usize { fn from (value: $T) -> Self { value.get() as usize } } - impl From<&$T> for f64 { fn from (value: &$T) -> Self { value.get() } } - impl From<&$T> for usize { fn from (value: &$T) -> Self { value.get() as usize } } - impl Clone for $T { fn clone (&self) -> Self { Self(self.get().into()) } } - } -} - -impl_time_unit!(SampleCount); -impl_time_unit!(SampleRate); -impl_time_unit!(Microsecond); -impl_time_unit!(Quantize); -impl_time_unit!(Ppq); -impl_time_unit!(Pulse); -impl_time_unit!(Bpm); -impl_time_unit!(LaunchSync); - -pub trait Gettable { - /// Returns current value - fn get (&self) -> T; -} - -pub trait Mutable: Gettable { - /// Sets new value, returns old - fn set (&mut self, value: T) -> T; -} - -pub trait InteriorMutable: Gettable { - /// Sets new value, returns old - fn set (&self, value: T) -> T; -} - /// Temporal resolutions: sample rate, tempo, MIDI pulses per quaver (beat) /// /// ``` diff --git a/src/gear/pool.rs b/src/gear/pool.rs index 593524c4..1fd3a453 100644 --- a/src/gear/pool.rs +++ b/src/gear/pool.rs @@ -156,3 +156,63 @@ impl Slot { todo!() } } + +/// Modes for clip pool +#[derive(Debug, Clone)] pub enum PoolMode { + /// Renaming a pattern + Rename(usize, Arc), + /// Editing the length of a pattern + Length(usize, usize, ClipLengthFocus), + /// Load clip from disk + Import(usize, Browse), + /// Save clip to disk + Export(usize, Browse), +} + +/// Focused field of `ClipLength` +#[derive(Copy, Clone, Debug)] pub enum ClipLengthFocus { + /// Editing the number of bars + Bar, + /// Editing the number of beats + Beat, + /// Editing the number of ticks + Tick, +} + +impl ClipLengthFocus { + pub fn next (&mut self) { + use ClipLengthFocus::*; + *self = match self { Bar => Beat, Beat => Tick, Tick => Bar, } + } + pub fn prev (&mut self) { + use ClipLengthFocus::*; + *self = match self { Bar => Tick, Beat => Bar, Tick => Beat, } + } +} + +/// Displays and edits clip length. +#[derive(Clone, Debug, Default)] pub struct ClipLength { + /// Pulses per beat (quaver) + pub ppq: usize, + /// Beats per bar + pub bpb: usize, + /// Length of clip in pulses + pub pulses: usize, + /// Selected subdivision + pub focus: Option, +} + +impl ClipLength { + pub fn _new (pulses: usize, focus: Option) -> Self { + Self { ppq: PPQ, bpb: 4, pulses, focus } + } + pub fn bars (&self) -> usize { + self.pulses / (self.bpb * self.ppq) + } + pub fn beats (&self) -> usize { + (self.pulses % (self.bpb * self.ppq)) / self.ppq + } + pub fn ticks (&self) -> usize { + self.pulses % self.ppq + } +} diff --git a/src/tek.rs b/src/tek.rs index f334ce37..46b23e25 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -1,5 +1,71 @@ #![allow(clippy::unit_arg)] +/// Define and implement a unit of time +macro_rules! impl_time_unit { + ($T:ident) => { + impl TimeUnit for $T {} + impl_op!($T, Add, add, |a, b|{a + b}); + impl_op!($T, Sub, sub, |a, b|{a - b}); + impl_op!($T, Mul, mul, |a, b|{a * b}); + impl_op!($T, Div, div, |a, b|{a / b}); + impl_op!($T, Rem, rem, |a, b|{a % b}); + impl_from!($T: |value: f64| { Self(value.into()) }); + impl_from!($T: |value: usize| { Self((value as f64).into()) }); + impl From<$T> for f64 { fn from (value: $T) -> Self { value.get() } } + impl From<$T> for usize { fn from (value: $T) -> Self { value.get() as usize } } + impl From<&$T> for f64 { fn from (value: &$T) -> Self { value.get() } } + impl From<&$T> for usize { fn from (value: &$T) -> Self { value.get() as usize } } + impl Clone for $T { fn clone (&self) -> Self { Self(self.get().into()) } } + impl $T { + pub fn get (&self) -> f64 { + self.0.load(Relaxed) + } + pub fn set (&self, value: f64) -> f64 { + let old = self.get(); + self.0.store(value, Relaxed); + old + } + } + } +} + +impl_time_unit!(SampleCount); +impl_time_unit!(SampleRate); +impl_time_unit!(Microsecond); +impl_time_unit!(Quantize); +impl_time_unit!(Ppq); +impl_time_unit!(Pulse); +impl_time_unit!(Bpm); +impl_time_unit!(LaunchSync); + +/// Define a type alias for iterators of sized items (columns). +macro_rules! def_sizes_iter { + ($Type:ident => $($Item:ty),+) => { + pub trait $Type<'a>: Iterator + Send + Sync + 'a {} + impl<'a, T: Iterator + Send + Sync + 'a> $Type<'a> for T {} + } +} + +def_sizes_iter!(ScenesSizes => Scene); +def_sizes_iter!(TracksSizes => Track); +def_sizes_iter!(PortsSizes => Arc, [Connect]); + +impl_has!(Clock: |self: Track|self.sequencer.clock); +impl_default!(Timebase: Self::new(48000f64, 150f64, DEFAULT_PPQ)); + +primitive!(isize: try_to_isize); +primitive!(u16: try_to_u16); +primitive!(u8: try_to_u8); +primitive!(usize: try_to_usize); + +impl_as_ref!(Config: |self: Config| self); +impl_as_ref!(Config: |self: ConfigWatcher| &self.config); +impl_as_mut!(Config: |self: ConfigWatcher| &mut self.config); +impl_as_ref!(ConfigWatcher: |self: ConfigWatcher| &self); + +impl_debug!(MenuItem: |self, w| { write!(w, "{}", &self.0) }); +impl_default!(MenuItem: Self::new("", [])); + impl_as_mut!(Option: |self: Project| &mut self.editor); impl_as_mut_opt!(Scene: |self: Project| self.selected_scene_mut()); impl_as_mut_opt!(Track: |self: Project| self.selected_track_mut()); @@ -18,10 +84,10 @@ impl_as_mut!(Option: |self: App|self.project.as_mut()); impl_as_mut!(Option: |self: App|todo!()); impl_as_ref!(Option: |self: App|self.project.as_ref()); impl_as_ref!(Option: |self: App|todo!()); -impl_as_mut_opt!(Scene: |self: App| self.project.as_mut_opt()); -impl_as_mut_opt!(Track: |self: App| self.project.as_mut_opt()); -impl_as_ref_opt!(Scene: |self: App| self.project.as_ref_opt()); -impl_as_ref_opt!(Track: |self: App| self.project.as_ref_opt()); +impl_as_mut_opt!(Scene: |self: App|self.project.as_mut_opt()); +impl_as_mut_opt!(Track: |self: App|self.project.as_mut_opt()); +impl_as_ref_opt!(Scene: |self: App|self.project.as_ref_opt()); +impl_as_ref_opt!(Track: |self: App|self.project.as_ref_opt()); impl_audio!(App: tek_jack_process, tek_jack_event); impl_has!(Clock: |self: App|self.project.clock); @@ -43,43 +109,12 @@ impl_from!(AppCmd: |x: ScenesCmd| AppCmd::Scenes { command: x }); impl_from!(AppCmd: |x: TrackCmd| AppCmd::Track { command: x }); impl_from!(AppCmd: |x: TracksCmd| AppCmd::Tracks { command: x }); -primitive!(isize: try_to_isize); -primitive!(u16: try_to_u16); -primitive!(u8: try_to_u8); -primitive!(usize: try_to_usize); +tui_keys!(self: App, input { + #[cfg(feature = "prof2")] profiling::scope!("App::tui_keys!"); + self.input_cmds(input) +}); -impl_debug!(MenuItem: |self, w| { write!(w, "{}", &self.0) }); -impl_default!(MenuItem: Self::new("", [])); - -/// Define a type alias for iterators of sized items (columns). -macro_rules! def_sizes_iter { - ($Type:ident => $($Item:ty),+) => { - pub trait $Type<'a>: Iterator + Send + Sync + 'a {} - impl<'a, T: Iterator + Send + Sync + 'a> $Type<'a> for T {} - } -} - -def_sizes_iter!(ScenesSizes => Scene); -def_sizes_iter!(TracksSizes => Track); -def_sizes_iter!(PortsSizes => Arc, [Connect]); - -pub use self::conf::*; mod conf; pub use self::deps::*; mod deps; -pub use self::gear::*; mod gear { - mod pool; pub use self::pool::*; - mod midi; pub use self::midi::*; - mod mixer; pub use self::mixer::*; - mod sample; pub use self::sample::*; - - #[cfg(feature = "lv2")] mod lv2; - #[cfg(feature = "lv2")] pub use self::lv2::*; - - #[cfg(feature = "lv2_gui")] mod lv2_gui; - #[cfg(feature = "lv2_gui")] pub use self::lv2_gui::*; - - #[cfg(feature = "vst2")] mod vst2; - #[cfg(feature = "vst2")] pub use self::vst2::*; -} /// Command-line entrypoint. #[allow(unused)] @@ -126,175 +161,6 @@ fn main_plain (config: Arc) -> Usually<()> { })?) } -#[cfg(feature = "cli")] pub use self::cli::*; -#[cfg(feature = "cli")] mod cli { - use super::*; - - /// Banner. - pub(crate) const HEADER: &'static str = r#" - ~ █▀█▀█ █▀▀█ █ █ ~~~ ~ ~ ~~ ~ ~ ~ ~~ ~ ~ ~ ~ - █ █▀ █▀▀▄ ~ heatwave is the new darkwave ~ - ~ ▀ █▀▀█ ▀ ▀ ~ ~~~ ~ ~ ~ ~ ~~~ ~~~ ~ ~~ "#; - - pub fn show_version () { - println!("versions aint real man"); - } - - pub fn main_cli (config: Arc) -> Usually<()> { - Cli::parse().run(Some(config.into())) - } - - /// The command-line interface descriptor. - /// - /// ``` - /// let cli: tek::Cli = Default::default(); - /// - /// use clap::CommandFactory; - /// tek::Cli::command().debug_assert(); - /// ``` - #[derive(Parser, Debug, Default)] - #[command(name = "tek", version, about = Some(HEADER), long_about = Some(HEADER))] - pub struct Cli { - /// Pre-defined configuration modes. - /// - /// TODO: Replace these with scripted configurations. - #[command(subcommand)] - pub action: Action, - - /// Record data for performance flamegraph. - #[arg(long)] - trace: bool, - } - - /// Command-line configuration. - impl Cli { - pub fn run (&self, mut config: Option>) -> Usually<()> { - self.action.run( - config.unwrap_or_else(||Arc::new(ConfigWatcher::new(None))), - self.trace - ) - } - } - - /// Application modes that can be passed to the mommand line interface. - /// - /// ``` - /// let action: tek::Action = Default::default(); - /// ``` - #[derive(Debug, Clone, Subcommand, Default)] - pub enum Action { - /// Continue where you left off - #[default] Resume, - /// Run headlessly in current session. - Headless, - /// Show status of current session. - Status, - /// List known sessions. - List, - /// Continue work in a copy of the current session. - Fork, - /// Create a new empty session. - New(ProjectInit), - /// Import media as new session. - Import, - /// Show configuration. - Config, - /// Show version. - Version, - } - - impl Action { - fn run (&self, config: Arc, _trace: bool) -> Usually<()> { - use Action::*; - match self { - Version => show_version(), - Config => config.config_print(), - Resume => todo!("resume session"), - List => todo!("list sessions"), - New(sesh) => Exit::run(|exit|Tui::run_main( - exit.clone(), - { - #[allow(unused_mut)] - let mut app = App::new(Some(exit), sesh.init()?, config, "Menu"); - app.dialog = Dialog::welcome(); - #[cfg(feature = "prof2")] { - if trace { - app.guard = Some({ - use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; - //let tracy = tracing_tracy::TracyLayer::default(); - let (flame, _guard) = tracing_flame::FlameLayer::with_file("./tracing.folded").unwrap(); - //let registry = tracing_subscriber::registry().with(flame).init(); - //tracing::subscriber::set_global_default(registry)?; - tracing::subscriber::set_global_default(tracing_subscriber::registry().with(flame))?; - _guard - }) - } - } - Arc::new(RwLock::new(app)) - })).map(|_|())?, - _ => todo!() - } - Ok(()) - } - } - - /// Command to launch into new project from CLI. - #[derive(Debug, Clone, Parser, Default)] - pub struct ProjectInit { - /// Name of JACK client - #[arg(short='n', long)] name: Option, - /// Whether to attempt to become transport master - #[arg(short='Y', long, default_value_t = false)] sync_lead: bool, - /// Whether to sync to external transport master - #[arg(short='y', long, default_value_t = true)] sync_follow: bool, - /// Initial tempo in beats per minute - #[arg(short='b', long, default_value = None)] bpm: Option, - /// Whether to include a transport toolbar (default: true) - #[arg(short='c', long, default_value_t = true)] show_clock: bool, - /// MIDI outs to connect to (multiple instances accepted) - #[arg(short='I', long)] midi_from: Vec, - /// MIDI outs to connect to (multiple instances accepted) - #[arg(short='i', long)] midi_from_re: Vec, - /// MIDI ins to connect to (multiple instances accepted) - #[arg(short='O', long)] midi_to: Vec, - /// MIDI ins to connect to (multiple instances accepted) - #[arg(short='o', long)] midi_to_re: Vec, - /// Audio outs to connect to left input - #[arg(short='l', long)] left_from: Vec, - /// Audio outs to connect to right input - #[arg(short='r', long)] right_from: Vec, - /// Audio ins to connect from left output - #[arg(short='L', long)] left_to: Vec, - /// Audio ins to connect from right output - #[arg(short='R', long)] right_to: Vec, - /// Tracks to creat - #[arg(short='t', long)] tracks: Option, - /// Scenes to create - #[arg(short='s', long)] scenes: Option, - } - - impl ProjectInit { - pub fn init (&self) -> Usually { - let name = self.name.as_ref().map_or("tek", |x|x.as_str()); - let jack = Jack::new(&name)?; - let midi_ins = connect_midi_ins(&jack, &"M".to_string(), &self.midi_from, Some(&self.midi_from_re))?; - let midi_out = connect_midi_outs(&jack, &"M".to_string(), &self.midi_to, Some(&self.midi_to_re))?; - let inputs = [].into_iter() - .chain(connect_audio_ins(&jack, &"L".to_string(), &self.left_from, None)?.into_iter()) - .chain(connect_audio_ins(&jack, &"R".to_string(), &self.right_from, None)?.into_iter()); - let outputs = [].into_iter() - .chain(connect_audio_outs(&jack, &"L".to_string(), &self.left_to, None)?.into_iter()) - .chain(connect_audio_outs(&jack, &"R".to_string(), &self.right_to, None)?.into_iter()); - let mut proj = Project::create(&jack, name.into(), Clock::new(&jack, self.bpm)?, - [].into_iter(), [].into_iter(), midi_ins.into_iter(), midi_out.into_iter(), - inputs, outputs); - proj.tracks_add_many(self.tracks.unwrap_or(0), None, [].into(), [].into())?; - proj.scenes_add_many(self.scenes.unwrap_or(0))?; - Ok(proj) - } - } -} - /// Total application state. /// /// ``` @@ -380,8 +246,7 @@ impl App { /// ``` /// # use ::tek::{*, tengri::Jack}; /// let proj = Project::new_jack(&"test_tek").expect("failed to connect to jack"); - /// let tek = App::new(None, proj, None, "hello"); - /// config_reload(tek, stringify!( + /// let tek = config_reload(App::new(None, proj, None, "hello"), stringify!( /// (mode Arranger (name Arranger) (info Launch grid.) /// (axis X Track) /// (axis Y Scene) @@ -598,7 +463,6 @@ impl HasJack<'static> for Project { } } - impl ScenesView for Project { fn h_scenes (&self) -> u16 { (self.size.h() as u16).saturating_sub(20) @@ -611,90 +475,6 @@ impl ScenesView for Project { } } -pub fn swap_value ( - target: &mut T, value: &T, returned: impl Fn(T)->U -) -> Perhaps { - if *target == *value { - Ok(None) - } else { - let mut value = value.clone(); - std::mem::swap(target, &mut value); - Ok(Some(returned(value))) - } -} - -pub fn toggle_bool ( - target: &mut bool, value: &Option, returned: impl Fn(Option)->U -) -> Perhaps { - let mut value = value.unwrap_or(!*target); - if value == *target { - Ok(None) - } else { - std::mem::swap(target, &mut value); - Ok(Some(returned(Some(value)))) - } -} - -/// Modes for clip pool -#[derive(Debug, Clone)] pub enum PoolMode { - /// Renaming a pattern - Rename(usize, Arc), - /// Editing the length of a pattern - Length(usize, usize, ClipLengthFocus), - /// Load clip from disk - Import(usize, Browse), - /// Save clip to disk - Export(usize, Browse), -} - -/// Focused field of `ClipLength` -#[derive(Copy, Clone, Debug)] pub enum ClipLengthFocus { - /// Editing the number of bars - Bar, - /// Editing the number of beats - Beat, - /// Editing the number of ticks - Tick, -} - -impl ClipLengthFocus { - pub fn next (&mut self) { - use ClipLengthFocus::*; - *self = match self { Bar => Beat, Beat => Tick, Tick => Bar, } - } - pub fn prev (&mut self) { - use ClipLengthFocus::*; - *self = match self { Bar => Tick, Beat => Bar, Tick => Beat, } - } -} - -/// Displays and edits clip length. -#[derive(Clone, Debug, Default)] pub struct ClipLength { - /// Pulses per beat (quaver) - pub ppq: usize, - /// Beats per bar - pub bpb: usize, - /// Length of clip in pulses - pub pulses: usize, - /// Selected subdivision - pub focus: Option, -} - -impl ClipLength { - pub fn _new (pulses: usize, focus: Option) -> Self { - Self { ppq: PPQ, bpb: 4, pulses, focus } - } - pub fn bars (&self) -> usize { - self.pulses / (self.bpb * self.ppq) - } - pub fn beats (&self) -> usize { - (self.pulses % (self.bpb * self.ppq)) / self.ppq - } - pub fn ticks (&self) -> usize { - self.pulses % self.ppq - } -} - impl HasScenes for Project { fn scenes_scroll (&self) -> usize { self.scenes_scroll @@ -707,14 +487,6 @@ impl HasScenes for Project { } } -pub struct Junction(T); - -impl Draw for Junction { - fn draw (&self, to: &mut Tui) -> Drawn { - T::KIND.draw(to) - } -} - /// Represents the current user selection in the arranger #[derive(PartialEq, Clone, Copy, Debug, Default)] pub enum Selection { @@ -942,12 +714,6 @@ impl Scene { } pub type SceneWith<'a, T> = (usize, &'a Scene, usize, usize, T); - -impl HasModeStack for App { - fn mode_stack (&self) -> &RwLock>> { - &self.modes - } -} impl ScenesView for App { fn w_mid (&self) -> u16 { (self.size.w() as u16).saturating_sub(self.w_side()) @@ -971,6 +737,289 @@ impl HasScenes for App { } } +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 } + } +} + +pub trait HasError { + fn error (&self) -> &RwLock>; + fn error_set (&self, e: Option) { + *self.error().write() = e; + } +} + +/// Has a MIDI editor. +/// +/// ``` +/// use tek::{*, tengri::*}; +/// +/// struct Test(Option); +/// impl_as_ref!(Option: |self: Test|&self.0); +/// impl_as_mut!(Option: |self: Test|&mut self.0); +/// +/// let mut host = Test(Some(MidiEditor::default())); +/// let _ = host.editor(); +/// let _ = host.editor_mut(); +/// let _ = host.is_editing(); +/// let _ = host.editor_w(); +/// let _ = host.editor_h(); +/// ``` +impl HasEditor for T where T: + AsRef> + AsMut> {} +pub trait HasEditor: + AsRef> + AsMut> +{ + fn editor (&self) -> &Option { + self.as_ref() + } + fn editor_mut (&mut self) -> &mut Option { + self.as_mut() + } +} + +/// Has a pair of buffers for MIDI IO. +pub trait HasMidiBuffers { + fn note_buf_mut (&mut self) -> &mut Vec; + fn midi_buf_mut (&mut self) -> &mut Vec>>; +} + +/// Can have a currently playing clip. +pub trait HasPlayClip: HasClock { + fn reset (&self) -> bool; + fn reset_mut (&mut self) -> &mut bool; + fn play_clip (&self) -> &Option<(Moment, Option>>)>; + fn play_clip_mut (&mut self) -> &mut Option<(Moment, Option>>)>; + fn next_clip (&self) -> &Option<(Moment, Option>>)>; + fn next_clip_mut (&mut self) -> &mut Option<(Moment, Option>>)>; +} + +/// Has a MIDI sequencer. +impl HasSequencer for T where T: AsRef + AsMut {} +pub trait HasSequencer: AsRef + AsMut { + fn sequencer (&self) -> &Sequencer { + self.as_ref() + } + fn sequencer_mut (&mut self) -> &mut Sequencer { + self.as_mut() + } +} + +/// Has a clock. +pub trait HasClock: AsRef + AsMut { + fn clock (&self) -> &Clock { + self.as_ref() + } + fn clock_mut (&mut self) -> &mut Clock { + self.as_mut() + } +} +impl HasClock for T where T: AsRef + AsMut {} + +/// Has a chain of devices. +pub trait HasDevices: AsRef> + AsMut> { + fn devices (&self) -> &Vec { + self.as_ref() + } + fn devices_mut (&mut self) -> &mut Vec { + self.as_mut() + } +} + +pub trait HasProject: AsRef> + AsMut> {} + +/// Has a loaded project. +impl HasProject for T where T: AsRef> + AsMut> {} + +pub trait HasScene: AsRefOpt + AsMutOpt { + fn scene (&self) -> Option<&Scene> { + self.as_ref_opt() + } + fn scene_mut (&mut self) -> Option<&mut Scene> { + self.as_mut_opt() + } +} +impl HasScene for T where T: AsRefOpt + AsMutOpt + Send + Sync {} + +pub trait HasScenes { + fn scenes (&self) -> &Vec; + fn scenes_mut (&mut self) -> &mut Vec; + fn scenes_scroll (&self) -> usize { + 0 + } +} + +impl HasSelection for T where T: AsRef + AsMut {} +pub trait HasSelection: AsRef + AsMut { + fn selection (&self) -> &Selection { + self.as_ref() + } + fn selection_mut (&mut self) -> &mut Selection { + self.as_mut() + } +} + +impl HasTrack for T where T: AsRefOpt + AsMutOpt {} +pub trait HasTrack: AsRefOpt + AsMutOpt { + fn track (&self) -> Option<&Track> { + self.as_ref_opt() + } + fn track_mut (&mut self) -> Option<&mut Track> { + self.as_mut_opt() + } +} + +impl HasTracks for T where T: AsRef> + AsMut> + HasClock + HasTrackScroll {} +pub trait HasTracks: AsRef> + AsMut> + HasClock + HasTrackScroll { + /// Read-only reference to collection of [Track]s. + fn tracks (&self) -> &Vec { + self.as_ref() + } + /// Mutable reference to collection of [Track]s. + fn tracks_mut (&mut self) -> &mut Vec { + self.as_mut() + } +} + +pub trait HasSampler: AsRef + AsMut { + fn sampler (&self) -> &Sampler { + self.as_ref() + } + fn sampler_mut (&mut self) -> &mut Sampler { + self.as_mut() + } +} + +pub trait HasTrackScroll { + fn track_scroll (&self) -> usize; + fn tracks_last (&self) -> usize; + fn tracks_last_mut (&mut self) -> &mut usize; +} +impl HasTrackScroll for &mut Project { + fn track_scroll (&self) -> usize { + self.track_scroll + } + fn tracks_last (&self) -> usize { + self.track_last + } + fn tracks_last_mut (&mut self) -> &mut usize { + &mut self.track_last + } +} +impl HasTrackScroll for Project { + fn track_scroll (&self) -> usize { + self.track_scroll + } + fn tracks_last (&self) -> usize { + self.track_last + } + fn tracks_last_mut (&mut self) -> &mut usize { + &mut self.track_last + } +} +impl HasTrackScroll for App { + fn track_scroll (&self) -> usize { + self.project.track_scroll() + } + fn tracks_last (&self) -> usize { + self.project.track_last + } + fn tracks_last_mut (&mut self) -> &mut usize { + &mut self.project.track_last + } +} + +pub trait HasClip { + fn clip_slot (&self) -> Option>; + fn clip (&self) -> Option>>>> { + self.clip_slot().map(|slot|slot.read()) + } + fn clip_mut (&self) -> Option>>>> { + self.clip_slot().map(|slot|slot.write()) + } +} +impl HasClip for App { + fn clip_slot (&self) -> Option> { + self.project.clip_slot() + } +} +impl HasClip for Project { + fn clip_slot (&self) -> Option> { + match self.selection { + Selection::Clip(index) => + Some(self.clips.slot(index)), + Selection::TrackClip { track, scene } if let Some(scene) = self.scenes.get(scene) => + Some(scene.clips.slot(track)), + _ => + None + } + } +} + +pub trait HasWidth { + const MIN_WIDTH: usize; + /// Increment track width. + fn width_inc (&mut self); + /// Decrement track width, down to a hardcoded minimum of [Self::MIN_WIDTH]. + fn width_dec (&mut self); +} + +pub trait HasClipsSize { + /// Size of clips area. + fn clips_size (&self) -> &Sizer; + /// Draw content annotated with value of [Self::clips_size]. + fn with_clips_size (&self, show: bool, content: impl Draw) -> impl Draw { + let size = self.clips_size(); + above( + when(show, fg(Green, east!(size.w() as usize, "x", size.h() as usize)).align_se()), + size.of(content.align_c().full_wh()) + ) + } +} +impl HasClipsSize for App { + fn clips_size (&self) -> &Sizer { + &self.project.size_inner + } +} +impl HasClipsSize for Project { + fn clips_size (&self) -> &Sizer { + &self.size_inner + } +} + +pub struct Junction(T); + +impl Draw for Junction { + fn draw (&self, to: &mut Tui) -> Drawn { + T::KIND.draw(to) + } +} + +/// An item of a menu. +/// +/// ``` +/// let item: tek::MenuItem = Default::default(); +/// ``` +#[derive(Clone)] pub struct MenuItem(pub Arc, pub Arc<[AppCmd]>); + +impl PartialEq for MenuItem { + fn eq (&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl MenuItem { + pub fn new (name: impl AsRef, actions: [AppCmd; N]) -> Self { + Self(name.as_ref().into(), Arc::new(actions)) + } +} + /// Various possible dialog modes. /// /// ``` @@ -1126,42 +1175,6 @@ impl Dialog { } -/// Increment a wrapping counter. -pub const fn wrap_inc (index: usize, count: usize) -> usize { - if count > 0 { (index + 1) % count } else { 0 } -} - -/// Decrement a wrapping counter. -pub const fn wrap_dec (index: usize, count: usize) -> usize { - if count > 0 { - let a = index.overflowing_sub(1).0; - let b = count.saturating_sub(1); - if a < b { a } else { b } - } else { - 0 - } -} - -/// An item of a menu. -/// -/// ``` -/// let item: tek::MenuItem = Default::default(); -/// ``` -#[derive(Clone)] pub struct MenuItem(pub Arc, pub Arc<[AppCmd]>); - - -impl PartialEq for MenuItem { - fn eq (&self, other: &Self) -> bool { - self.0 == other.0 - } -} - -impl MenuItem { - pub fn new (name: impl AsRef, actions: [AppCmd; N]) -> Self { - Self(name.as_ref().into(), Arc::new(actions)) - } -} - /// Browses for files to load/save. /// /// ``` @@ -1256,405 +1269,6 @@ pub fn scan (dir: &PathBuf) -> Usually<(Vec, Vec)> { Ok((subdirs, files)) } -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 } - } -} - -pub trait HasError { - fn error (&self) -> &RwLock>; - fn error_set (&self, e: Option) { - *self.error().write() = e; - } -} - -pub trait HasConfig { - fn config (&self) -> &Config; - fn config_mut (&mut self) -> &mut Config; - /// Print the configuration. - fn config_print (&self) { - let config = self.config(); - print_config_views(config); - print_config_binds(config); - print_config_modes(config); - } - /// Make this configuration empty. - fn config_clear (&self) { - let config = self.config(); - config.modes_clear(); - config.views_clear(); - config.binds_clear(); - config.stamp.store(quanta::Clock::new().raw(), Relaxed); - } -} - -pub trait HasModes { - fn modes (&self) -> &RwLock, Arc>>; - - /// Register a mode. - fn modes_add <'a> (&self, expr: impl Language) -> UsuallyRef<'a, ()> { - let name = expr.head()?.ok_or("mode: missing name")?; - let body = expr.tail()?.ok_or("mode: missing body")?; - let mut mode = Mode::default(); - body.each(&mut mode, |c, s|c.add(s))?; - self.modes().write().insert(name.into(), Arc::new(mode)); - Ok(()) - } - /// Get a mode by name. - fn modes_get (&self, name: impl AsRef) -> Option> { - self.modes().read().get(name.as_ref()).cloned() - } - /// Run something for each mode. - fn modes_for_each (&self, mut ator: impl FnMut(&str, &Mode)->T) { - for (k, v) in self.modes().read().iter() { - let _ = ator(k.as_ref(), v.as_ref()); - } - } - /// Count modes. - fn modes_count (&self) -> usize { - self.modes().read().len() - } - /// Clear modes. - fn modes_clear (&self) { - *self.modes().write() = Default::default(); - } -} - -pub trait HasBinds { - fn binds_keys (&self) -> &RwLock, Arc>>>>; - fn binds_axis (&self) -> &RwLock, Arc>>>>; - - fn binds_clear (&self) { - *self.binds_keys().write() = Default::default(); - *self.binds_axis().write() = Default::default(); - } - fn binds_find (&self, id: impl AsRef) -> Option>>> { - self.binds_keys().read().get(id.as_ref()).cloned() - } - fn binds_for_each ( - &self, mut cb: impl FnMut(&Arc, &Bind>)->T - ) { - for (k, v) in self.binds_keys().read().iter() { - cb(k, v); - } - } - fn binds_def_keys (&self, expr: impl Language) -> Usually<()> { - self.binds_keys().write().insert( - expr.head()?.ok_or("bind: missing name")?.into(), - Bind::new(expr.tail()?.unwrap_or(""))?.into() - ); - Ok(()) - } - fn binds_def_axis (&self, expr: impl Language) -> Usually<()> { - self.binds_axis().write().insert( - expr.head()?.ok_or("axis: missing name")?.into(), - Axis::new(expr.tail()?.unwrap_or(""))?.into() - ); - Ok(()) - } -} - -pub trait HasViews { - fn views (&self) -> &RwLock, Arc>>>; - /// Load custom view definition. - fn views_add <'a> (&self, expr: impl Language) -> UsuallyRef<'a, ()> { - self.views().write().insert( - expr.head()?.ok_or("view: missing name")?.into(), - View::new(expr.tail()?.ok_or("view: missing body")?)?.into() - ); - Ok(()) - } - fn views_clear (&self) { - *self.views().write() = Default::default(); - } - fn views_get (&self, name: impl AsRef) -> Option>> { - self.views().read().get(name.as_ref()).cloned() - } - fn views_for_each (&self, cb: impl Fn(&Arc, &Arc>)->T) -> () { - for (k, v) in self.views().read().iter() { - cb(k, v); - } - } -} - -/// Has a stack of currently active interaction modes. -pub trait HasModeStack { - /// Stack of active modes. - fn mode_stack (&self) -> &RwLock>>; -} - -/// Has a MIDI editor. -/// -/// ``` -/// use tek::{*, tengri::*}; -/// -/// struct Test(Option); -/// impl_as_ref!(Option: |self: Test|&self.0); -/// impl_as_mut!(Option: |self: Test|&mut self.0); -/// -/// let mut host = Test(Some(MidiEditor::default())); -/// let _ = host.editor(); -/// let _ = host.editor_mut(); -/// let _ = host.is_editing(); -/// let _ = host.editor_w(); -/// let _ = host.editor_h(); -/// ``` -impl HasEditor for T where T: - AsRef> + AsMut> {} -pub trait HasEditor: - AsRef> + AsMut> -{ - fn editor (&self) -> &Option { - self.as_ref() - } - fn editor_mut (&mut self) -> &mut Option { - self.as_mut() - } -} - -/// Has a pair of buffers for MIDI IO. -pub trait HasMidiBuffers { - fn note_buf_mut (&mut self) -> &mut Vec; - fn midi_buf_mut (&mut self) -> &mut Vec>>; -} - -/// Can have a currently playing clip. -pub trait HasPlayClip: HasClock { - fn reset (&self) -> bool; - fn reset_mut (&mut self) -> &mut bool; - fn play_clip (&self) -> &Option<(Moment, Option>>)>; - fn play_clip_mut (&mut self) -> &mut Option<(Moment, Option>>)>; - fn next_clip (&self) -> &Option<(Moment, Option>>)>; - fn next_clip_mut (&mut self) -> &mut Option<(Moment, Option>>)>; -} - -/// Has a MIDI sequencer. -impl HasSequencer for T where T: - AsRef + AsMut {} -pub trait HasSequencer: - AsRef + AsMut -{ - fn sequencer (&self) -> &Sequencer { - self.as_ref() - } - fn sequencer_mut (&mut self) -> &mut Sequencer { - self.as_mut() - } -} - -impl HasClock for T where T: - AsRef + AsMut {} - -/// Has a clock. -pub trait HasClock: - AsRef + AsMut -{ - fn clock (&self) -> &Clock { - self.as_ref() - } - fn clock_mut (&mut self) -> &mut Clock { - self.as_mut() - } -} - -/// Has a chain of devices. -pub trait HasDevices: - AsRef> + AsMut> -{ - fn devices (&self) -> &Vec { - self.as_ref() - } - fn devices_mut (&mut self) -> &mut Vec { - self.as_mut() - } -} - -pub trait HasProject: - AsRef> + AsMut> -{} - -/// Has a loaded project. -impl HasProject for T where T: - AsRef> + AsMut> -{} - -impl HasScene for T where T: - AsRefOpt + AsMutOpt + Send + Sync -{} - -pub trait HasScene: - AsRefOpt + AsMutOpt -{ - fn scene (&self) -> Option<&Scene> { - self.as_ref_opt() - } - fn scene_mut (&mut self) -> Option<&mut Scene> { - self.as_mut_opt() - } -} - -pub trait HasScenes { - fn scenes (&self) -> &Vec; - fn scenes_mut (&mut self) -> &mut Vec; - fn scenes_scroll (&self) -> usize { - 0 - } -} - -impl HasSelection for T where T: - AsRef + AsMut -{} - -pub trait HasSelection: AsRef + AsMut { - fn selection (&self) -> &Selection { - self.as_ref() - } - fn selection_mut (&mut self) -> &mut Selection { - self.as_mut() - } -} - -impl HasTrack for T where T: - AsRefOpt + AsMutOpt -{} - -pub trait HasTrack: AsRefOpt + AsMutOpt { - fn track (&self) -> Option<&Track> { - self.as_ref_opt() - } - fn track_mut (&mut self) -> Option<&mut Track> { - self.as_mut_opt() - } -} - -impl HasTracks for T where T: - AsRef> + AsMut> + HasClock + HasTrackScroll -{} - -pub trait HasTracks: AsRef> + AsMut> + HasClock + HasTrackScroll { - /// Read-only reference to collection of [Track]s. - fn tracks (&self) -> &Vec { - self.as_ref() - } - /// Mutable reference to collection of [Track]s. - fn tracks_mut (&mut self) -> &mut Vec { - self.as_mut() - } -} - -pub trait HasTrackScroll { - fn track_scroll (&self) -> usize; - fn tracks_last (&self) -> usize; - fn tracks_last_mut (&mut self) -> &mut usize; -} -impl HasTrackScroll for &mut Project { - fn track_scroll (&self) -> usize { - self.track_scroll - } - fn tracks_last (&self) -> usize { - self.track_last - } - fn tracks_last_mut (&mut self) -> &mut usize { - &mut self.track_last - } -} -impl HasTrackScroll for Project { - fn track_scroll (&self) -> usize { - self.track_scroll - } - fn tracks_last (&self) -> usize { - self.track_last - } - fn tracks_last_mut (&mut self) -> &mut usize { - &mut self.track_last - } -} -impl HasTrackScroll for App { - fn track_scroll (&self) -> usize { - self.project.track_scroll() - } - fn tracks_last (&self) -> usize { - self.project.track_last - } - fn tracks_last_mut (&mut self) -> &mut usize { - &mut self.project.track_last - } -} - -pub trait HasSampler: AsRef + AsMut { - fn sampler (&self) -> &Sampler { - self.as_ref() - } - fn sampler_mut (&mut self) -> &mut Sampler { - self.as_mut() - } -} - -pub trait HasClip { - fn clip_slot (&self) -> Option>; - fn clip (&self) -> Option>>>> { - self.clip_slot().map(|slot|slot.read()) - } - fn clip_mut (&self) -> Option>>>> { - self.clip_slot().map(|slot|slot.write()) - } -} -impl HasClip for App { - fn clip_slot (&self) -> Option> { - self.project.clip_slot() - } -} -impl HasClip for Project { - fn clip_slot (&self) -> Option> { - match self.selection { - Selection::Clip(index) => - Some(self.clips.slot(index)), - Selection::TrackClip { track, scene } if let Some(scene) = self.scenes.get(scene) => - Some(scene.clips.slot(track)), - _ => - None - } - } -} - -pub trait HasWidth { - const MIN_WIDTH: usize; - /// Increment track width. - fn width_inc (&mut self); - /// Decrement track width, down to a hardcoded minimum of [Self::MIN_WIDTH]. - fn width_dec (&mut self); -} - -pub trait HasClipsSize { - /// Size of clips area. - fn clips_size (&self) -> &Sizer; - /// Draw content annotated with value of [Self::clips_size]. - fn with_clips_size (&self, show: bool, content: impl Draw) -> impl Draw { - let size = self.clips_size(); - above( - when(show, fg(Green, east!(size.w() as usize, "x", size.h() as usize)).align_se()), - size.of(content.align_c().full_wh()) - ) - } -} -impl HasClipsSize for App { - fn clips_size (&self) -> &Sizer { - &self.project.size_inner - } -} -impl HasClipsSize for Project { - fn clips_size (&self) -> &Sizer { - &self.size_inner - } -} - - pub fn print_status (project: &Project) { println!("Name: {:?}", &project.name); println!("JACK: {:?}", &project.jack); @@ -1685,800 +1299,400 @@ pub fn print_status (project: &Project) { // TODO dawvert integration } -tui_keys!(self: App, input { - #[cfg(feature = "prof2")] profiling::scope!("App::tui_keys!"); - self.input_cmds(input) -}); +#[cfg(feature = "cli")] pub use self::cli::*; +#[cfg(feature = "cli")] mod cli { + use super::*; -impl App { - pub fn input_cmds (&mut self, input: &TuiEvent) -> Usually<()> { - if let Some(bindings) = self.input_cmds_find(input) { - return self.input_cmds_exec(bindings) - } - Ok(()) - } - /// ``` - /// # use ::{std::sync::Arc, tek::*}; - /// let app = App::default(); - /// config_reload(&app, stringify!( - /// (axis Y @up @down) - /// (mode Menu - /// (axis Y prev next) - /// (bind (@enter confirm))) - /// )).unwrap(); - /// assert!(app.input_cmds_find(&"left".into()).is_none()); - /// assert!(app.input_cmds_find(&"enter".into()).is_some()); - /// assert!(app.input_cmds_find(&"down".into()).is_some()); - /// assert!(app.input_cmds_find(&"up".into()).is_some()); - /// ``` - pub fn input_cmds_find (&self, input: &TuiEvent) -> Option>]>> { - for name in self.modes.read().iter().rev() { - let mode = self.modes_get(name).as_ref().map(Arc::clone); - if let Some(mode) = mode { - for (ctrl, (dec, inc)) in mode.axis.iter() { - panic!("{ctrl:?} {dec:?} {inc:?}"); - } - for id in mode.keys.iter() { - if let Some(event_map) = self.config.binds_find(id) - && let Some(bindings) = event_map.query(input) { - return Some(bindings.into()) - } - } - } - } - None + /// Banner. + pub(crate) const HEADER: &'static str = r#" + ~ █▀█▀█ █▀▀█ █ █ ~~~ ~ ~ ~~ ~ ~ ~ ~~ ~ ~ ~ ~ + █ █▀ █▀▀▄ ~ heatwave is the new darkwave ~ + ~ ▀ █▀▀█ ▀ ▀ ~ ~~~ ~ ~ ~ ~ ~~~ ~~~ ~ ~~ "#; + + pub fn show_version () { + println!("versions aint real man"); } - pub fn input_cmds_exec (&mut self, bindings: Arc<[Binding>]>) -> Usually<()> { - for binding in bindings.iter() { - for src in binding.commands.iter() { - if let Some(command) = Namespace::::namespace(self, &src)? { - self.cmd_dispatch(command)?; - } - } - } - Ok(()) + pub fn main_cli (config: Arc) -> Usually<()> { + Cli::parse().run(Some(config.into())) } - pub fn cmd_dispatch (&mut self, command: AppCmd) -> Usually<()> { - match command.clone().dispatch(self) { - Ok(undo) => { - self.history.push((command, undo)); - Ok(()) - }, - Err(err) => { - self.history.push((command, None)); - Err(err) - } - } - } -} - -#[tek_proc::commands(AppCmd)] impl App { - /// Exit the application. - #[command(Exit = "exit")] pub fn cmd_exit (&mut self) -> Perhaps { - self.exit.exit(); - Ok(None) - } - /// Switch to another set of contextual modes. - #[command(Mode = "mode")] pub fn cmd_mode (&mut self, modes: Vec>) -> Perhaps { - *self.mode_stack().write() = modes.into(); - Ok(None) - } - /// Enter a contextual mode. - #[command(Enter = "enter")] pub fn enter_mode (&mut self, mode: Arc) -> Perhaps { - let mut modes = self.mode_stack().write(); - Ok(if modes.last() != Some(&mode) { - modes.push(mode); - Some(AppCmd::Leave) - } else { - None - }) - } - /// Leave a contextual mode. - #[command(Leave = "leave")] fn leave_mode (&mut self) -> Perhaps { - let mut modes = self.mode_stack().write(); - if modes.len() > 1 { - let mode = modes.pop(); - Ok(mode.map(|mode|AppCmd::Enter { mode })) - } else { - Ok(None) - } - } - /// Increment the given data axis. - #[command(Inc = "inc")] pub fn inc (&mut self, axis: Arc) -> Perhaps { - panic!("inc {axis:?}"); - //Ok(match (&self.dialog, axis) { - //(Dialog::None, _) => todo!(), - //(Dialog::Menu(_, _), ControlAxis::Y) => { - //AppCmd::Dialog { dialog: self.dialog.menu_next() }.dispatch(self)?; - //None - //}, - //_ => todo!() - //}) - } - /// Decrement the given data axis. - #[command(Dec = "dec")] pub fn dec (&mut self, axis: Arc) -> Perhaps { - panic!("dec {axis:?}"); - //Ok(match (&self.dialog, axis) { - //(Dialog::None, _) => None, - //(Dialog::Menu(_, _), ControlAxis::Y) => { - //AppCmd::Dialog { dialog: self.dialog.menu_prev() }.dispatch(self)?; - //None - //}, - //_ => todo!() - //}) - } - /// Cancel current selection. - #[command(Cancel = "cancel")] pub fn cmd_cancel (&mut self) -> Perhaps { - todo!("cancel") - } - /// Confirm current selection. - #[command(Confirm = "confirm")] pub fn cmd_confirm (&mut self) -> Perhaps { - Ok(match self.dialog.clone() { - Dialog::Menu(index, items) => { - for command in items[index].1.iter() { - App::cmd_dispatch(self, command.clone())?; - } - None - } - _ => todo!(), - }) - } - /// Swap currently active modal dialog. + /// The command-line interface descriptor. /// /// ``` - /// let _ = tek::App::default().cmd_dialog(tek::Dialog::welcome()); + /// let cli: tek::Cli = Default::default(); + /// + /// use clap::CommandFactory; + /// tek::Cli::command().debug_assert(); /// ``` - #[command(Dialog = "dialog")] pub fn cmd_dialog (&mut self, dialog: Dialog) -> Perhaps { - let mut dialog = dialog.clone(); - std::mem::swap(&mut self.dialog, &mut dialog); - Ok(Some(AppCmd::Dialog { dialog })) + #[derive(Parser, Debug, Default)] + #[command(name = "tek", version, about = Some(HEADER), long_about = Some(HEADER))] + pub struct Cli { + /// Pre-defined configuration modes. + /// + /// TODO: Replace these with scripted configurations. + #[command(subcommand)] + pub action: Action, + + /// Record data for performance flamegraph. + #[arg(long)] + trace: bool, } - /// Command the file browser. - #[command(Browse = "browse")] pub fn cmd_browse (&mut self, command: BrowseCmd) -> Perhaps { - Ok(command.dispatch(self)?.map(Into::into)) - } - /// Commands that alter the current session. - #[command(Project = "session")] pub fn cmd_session (&mut self, command: ProjectCmd) -> Perhaps { - Ok(command.dispatch(self)?.map(Into::into)) - } - /// Commands pertaining to MIDI clips. - #[command(MidiClip = "clip")] pub fn cmd_midi_clip (&mut self, command: MidiClipCmd) -> Perhaps { - Ok(command.dispatch(self)?.map(Into::into)) - } - /// Commands pertaining to the MIDI clip editor. - #[command(MidiEdit = "edit")] pub fn cmd_midi_edit (&mut self, command: MidiEditCmd) -> Perhaps { - if matches!(command, MidiEditCmd::Toggle) { - if self.is_editing() { - self.leave_mode()?; - } else { - self.enter_mode("Edit".into())?; - } + + /// Command-line configuration. + impl Cli { + pub fn run (&self, mut config: Option>) -> Usually<()> { + self.action.run( + config.unwrap_or_else(||Arc::new(ConfigWatcher::new(None))), + self.trace + ) } - Ok(command.dispatch(self)?.map(Into::into)) } - #[command(Scene = "scene")] pub fn cmd_scene (&mut self, command: SceneCmd) -> Perhaps { - Ok(command.dispatch(self)?.map(Into::into)) - } - #[command(Scenes = "scenes")] pub fn cmd_scenes (&mut self, command: ScenesCmd) -> Perhaps { - Ok(command.dispatch(self)?.map(Into::into)) - } - #[command(Track = "track")] pub fn cmd_track (&mut self, command: TrackCmd) -> Perhaps { - Ok(command.dispatch(self)?.map(Into::into)) - } - #[command(Tracks = "tracks")] pub fn cmd_tracks (&mut self, command: TracksCmd) -> Perhaps { - Ok(command.dispatch(self)?.map(Into::into)) - } - //#[command(Pool = "pool")] pub fn cmd_pool (&mut self, command: PoolCmd) -> Perhaps { - //Ok(command.dispatch(self)?.map(Into::into)) - //} -} -impl ProjectControl for T {} - -#[tek_proc::commands(ProjectCmd)] pub trait ProjectControl: HasProject { - #[command(LoadLast = "load-last")] fn load_last (&mut self) -> Perhaps { - todo!() + /// Application modes that can be passed to the mommand line interface. + /// + /// ``` + /// let action: tek::Action = Default::default(); + /// ``` + #[derive(Debug, Clone, Subcommand, Default)] + pub enum Action { + /// Continue where you left off + #[default] Resume, + /// Run headlessly in current session. + Headless, + /// Show status of current session. + Status, + /// List known sessions. + List, + /// Continue work in a copy of the current session. + Fork, + /// Create a new empty session. + New(ProjectInit), + /// Import media as new session. + Import, + /// Show configuration. + Config, + /// Show version. + Version, } - #[command(Load = "load")] fn load (&mut self) -> Perhaps { - todo!() + + impl Action { + fn run (&self, config: Arc, _trace: bool) -> Usually<()> { + use Action::*; + match self { + Version => show_version(), + Config => config.config_print(), + Resume => todo!("resume session"), + List => todo!("list sessions"), + New(sesh) => Exit::run(|exit|Tui::run_main( + exit.clone(), + { + #[allow(unused_mut)] + let mut app = App::new(Some(exit), sesh.init()?, config, "Menu"); + app.dialog = Dialog::welcome(); + #[cfg(feature = "prof2")] { + if trace { + app.guard = Some({ + use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; + //let tracy = tracing_tracy::TracyLayer::default(); + let (flame, _guard) = tracing_flame::FlameLayer::with_file("./tracing.folded").unwrap(); + //let registry = tracing_subscriber::registry().with(flame).init(); + //tracing::subscriber::set_global_default(registry)?; + tracing::subscriber::set_global_default(tracing_subscriber::registry().with(flame))?; + _guard + }) + } + } + Arc::new(RwLock::new(app)) + })).map(|_|())?, + _ => todo!() + } + Ok(()) + } + } + + /// Command to launch into new project from CLI. + #[derive(Debug, Clone, Parser, Default)] + pub struct ProjectInit { + /// Name of JACK client + #[arg(short='n', long)] name: Option, + /// Whether to attempt to become transport master + #[arg(short='Y', long, default_value_t = false)] sync_lead: bool, + /// Whether to sync to external transport master + #[arg(short='y', long, default_value_t = true)] sync_follow: bool, + /// Initial tempo in beats per minute + #[arg(short='b', long, default_value = None)] bpm: Option, + /// Whether to include a transport toolbar (default: true) + #[arg(short='c', long, default_value_t = true)] show_clock: bool, + /// MIDI outs to connect to (multiple instances accepted) + #[arg(short='I', long)] midi_from: Vec, + /// MIDI outs to connect to (multiple instances accepted) + #[arg(short='i', long)] midi_from_re: Vec, + /// MIDI ins to connect to (multiple instances accepted) + #[arg(short='O', long)] midi_to: Vec, + /// MIDI ins to connect to (multiple instances accepted) + #[arg(short='o', long)] midi_to_re: Vec, + /// Audio outs to connect to left input + #[arg(short='l', long)] left_from: Vec, + /// Audio outs to connect to right input + #[arg(short='r', long)] right_from: Vec, + /// Audio ins to connect from left output + #[arg(short='L', long)] left_to: Vec, + /// Audio ins to connect from right output + #[arg(short='R', long)] right_to: Vec, + /// Tracks to creat + #[arg(short='t', long)] tracks: Option, + /// Scenes to create + #[arg(short='s', long)] scenes: Option, + } + + impl ProjectInit { + pub fn init (&self) -> Usually { + let name = self.name.as_ref().map_or("tek", |x|x.as_str()); + let jack = Jack::new(&name)?; + let midi_ins = connect_midi_ins(&jack, &"M".to_string(), &self.midi_from, Some(&self.midi_from_re))?; + let midi_out = connect_midi_outs(&jack, &"M".to_string(), &self.midi_to, Some(&self.midi_to_re))?; + let inputs = [].into_iter() + .chain(connect_audio_ins(&jack, &"L".to_string(), &self.left_from, None)?.into_iter()) + .chain(connect_audio_ins(&jack, &"R".to_string(), &self.right_from, None)?.into_iter()); + let outputs = [].into_iter() + .chain(connect_audio_outs(&jack, &"L".to_string(), &self.left_to, None)?.into_iter()) + .chain(connect_audio_outs(&jack, &"R".to_string(), &self.right_to, None)?.into_iter()); + let mut proj = Project::create(&jack, name.into(), Clock::new(&jack, self.bpm)?, + [].into_iter(), [].into_iter(), midi_ins.into_iter(), midi_out.into_iter(), + inputs, outputs); + proj.tracks_add_many(self.tracks.unwrap_or(0), None, [].into(), [].into())?; + proj.scenes_add_many(self.scenes.unwrap_or(0))?; + Ok(proj) + } } } -impl ClockControl for T where T: HasClock - + Namespace + Namespace + Namespace> -{} -#[tek_proc::commands(ClockCmd = "clock")] pub trait ClockControl: HasClock - + Namespace + Namespace + Namespace> -{ - #[command(SeekUsec = "usec")] fn seek_usec (&mut self, usec: f64) -> Perhaps { - self.clock().playhead.update_from_usec(usec); - Ok(None) - } - #[command(SeekSample = "sample")] fn seek_sample (&mut self, sample: f64) -> Perhaps { - self.clock().playhead.update_from_sample(sample); - Ok(None) - } - #[command(SeekPulse = "pulse")] fn seek_pulse (&mut self, pulse: f64) -> Perhaps { - self.clock().playhead.update_from_pulse(pulse); - Ok(None) - } - #[command(SetBpm = "bpm")] fn set_bpm (&mut self, bpm: f64) -> Perhaps { - Ok(Some(ClockCmd::SetBpm { - bpm: self.clock().timebase().bpm.set(bpm) - })) - } - #[command(SetQuant = "quant")] fn set_quant (&mut self, quant: f64) -> Perhaps { - Ok(Some(ClockCmd::SetQuant { - quant: self.clock().quant.set(quant) - })) - } - #[command(SetSync = "sync")] fn set_sync (&mut self, sync: f64) -> Perhaps { - Ok(Some(ClockCmd::SetSync { - sync: self.clock().sync.set(sync) - })) - } - #[command(Play = "play")] fn play (&mut self, position: Option) -> Perhaps { - self.clock().play_from(position)?; - Ok(None) /* TODO Some(Pause(previousPosition)) */ - } - #[command(Pause = "pause")] fn pause (&mut self, position: Option) -> Perhaps { - self.clock().pause_at(position)?; - Ok(None) - } - #[command(TogglePlayback = "toggle")] fn toggle (&mut self, position: u32) -> Perhaps { - Ok(if self.clock().is_rolling() { - self.clock().pause_at(Some(position))?; - None +pub use self::conf::*; mod conf { + use crate::*; + + mod bind; pub use self::bind::*; + mod lang; pub use self::lang::*; + mod mode; pub use self::mode::*; + mod view; pub use self::view::*; + + /// Default configuration contents. + const CONFIG_DEFAULT: &'static str = include_str!("tek.edn"); + + pub fn config_init + AsRef> (config: C) -> Usually { + // Write initial contents of configuration: + let watcher: &ConfigWatcher = config.as_ref(); + if watcher.find_file().is_none() { + std::fs::write(watcher.place_file()?, CONFIG_DEFAULT)?; + } + // Load config from file: + Ok(if let Some(path) = watcher.find_file() { + config_reload(config, std::fs::read_to_string(&path)?)? } else { - self.clock().play_from(Some(position))?; - None + return Err(format!("config_init: not found").into()) }) } -} -impl TracksControl for T where T: HasTracks - + HasScenes - + HasSelection - + HasJack<'static> - + Namespace -{} - -#[tek_proc::commands(TracksCmd)] pub trait TracksControl: HasTracks - + HasScenes - + HasSelection - + HasJack<'static> - + Namespace -{ - /// Stop all playing clips - #[command(Stop = "stop")] fn tracks_stop_all (&mut self) -> Perhaps { - for track in self.tracks_mut().iter_mut() { - track.sequencer.enqueue_next(None); - } - Ok(None) - } - /// Launch multiple clips - #[command(Launch = "launch")] fn tracks_launch (&mut self, clips: ClipPool) - -> Perhaps - { - if clips.len() == 0 { - for track in self.tracks_mut().iter_mut() { - track.sequencer.enqueue_next(None); - } - return Ok(None) - } - for (clip, track) in clips.0.read().iter().zip(self.tracks_mut()) { - track.sequencer.enqueue_next(clip.read().as_ref()); - } - Ok(None) - } - #[command(Add = "add")] fn tracks_add (&mut self) -> Perhaps - where Self: HasScenes + HasJack<'static> + Sized - { - let (_index, _) = self.tracks_add_one(None, None, [].into(), [].into())?; - Ok(None) - } - #[command(Select = "select")] fn track_select (&mut self) -> Perhaps { - let tracks = self.tracks().len(); - let select = self.selection_mut().select_track(tracks); - *self.selection_mut() = select; - Ok(None) - } - #[command(Next = "next")] fn track_select_next (&mut self) -> Perhaps { - let tracks = self.tracks().len(); - let select = self.selection().select_track_next(tracks); - *self.selection_mut() = select; - Ok(None) - } - #[command(Prev = "prev")] fn track_select_prev (&mut self) -> Perhaps { - let select = self.selection_mut().select_track_prev(); - *self.selection_mut() = select; - Ok(None) + /// Reload configuration from source. + pub fn config_reload , L: Language> (state: C, src: L) -> Usually { + state.as_ref().config_clear(); + src.each(state, |c, s|config_add(c, s.trim())) } - /// Run audio callbacks for every track and every device - fn tracks_jack_process (&mut self, client: &Client, scope: &ProcessScope) -> Control - where Self: HasJack<'static> - { - for track in self.tracks_mut().iter_mut() { - if Control::Quit == Audio::process(&mut track.sequencer, client, scope) { - return Control::Quit - } - for device in track.devices.iter_mut() { - if Control::Quit == DeviceAudio(device).process(client, scope) { - return Control::Quit - } - } - } - Control::Continue - } - - /// Add multiple tracks - fn tracks_add_many ( - &mut self, - count: usize, - width: Option, - mins: Arc<[Connect]>, - mouts: Arc<[Connect]>, - ) -> Usually<()> - where Self: HasJack<'static> + HasScenes - { - let track_color_1 = ItemColor::random(); - let track_color_2 = ItemColor::random(); - for i in 0..count { - let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into(); - let track = self.tracks_add_one(None, Some(color), mins.clone(), mouts.clone())?.1; - if let Some(width) = width { - track.width = width; - } - } - Ok(()) - } - - /// Add a track - fn tracks_add_one ( - &mut self, - name: Option>, - color: Option, - mins: Arc<[Connect]>, - mouts: Arc<[Connect]>, - ) -> Usually<(usize, &mut Track)> - where Self: HasJack<'static> + HasScenes - { - let name: Arc = name.map_or_else( - ||format!("trk{}", self.tracks_last()).into(), - |x|x.to_string().into() - ); - *self.tracks_last_mut() += 1; - let track = Track::new_added(self.jack(), Some(self.clock()), &name, color, None, mins, mouts)?; - self.tracks_mut().push(track); - let len = self.tracks().len(); - let index = len - 1; - for scene in self.scenes_mut().iter_mut() { - while scene.clips.len() < len { - scene.clips.append(None); - } - } - Ok((index, &mut self.tracks_mut()[index])) - } - - fn track_longest_name (&self) -> usize { - self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max) - } -} - -impl - + Namespace> - + Namespace - + Namespace> -> TrackControl for T {} - -#[tek_proc::commands(TrackCmd)] pub trait TrackControl: HasTrack - + Namespace - + Namespace> - + Namespace - + Namespace> -{ - #[command(Stop = "track/stop")] - fn track_stop (&mut self) -> Perhaps { - self.track_mut().map(|track|track.sequencer.enqueue_next(None)); - Ok(None) - } - #[command(SetMute = "track/mute")] - fn track_set_mute (&mut self, _mute: Option) -> Perhaps { - todo!() - } - #[command(SetSolo = "track/solo")] - fn track_set_solo (&mut self, _solo: Option) -> Perhaps { - todo!() - } - #[command(SetSize = "track/size")] - fn track_set_size (&mut self, _size: usize) -> Perhaps { - todo!() - } - #[command(SetZoom = "track/zoom")] - fn track_set_zoom (&mut self, _zoom: usize) -> Perhaps { - todo!() - } - #[command(SetName = "track/name")] - fn track_set_name (&mut self, name: Arc) -> Perhaps { - self.track_mut() - .map(|track|swap_value(&mut track.name, &name, |name|TrackCmd::SetName { name })) - .transpose() - .map(Option::flatten) - } - #[command(SetColor = "track/color")] - fn track_set_color (&mut self, color: ItemTheme) -> Perhaps { - self.track_mut() - .map(|track|swap_value(&mut track.color, &color, |color|TrackCmd::SetColor { color })) - .transpose() - .map(Option::flatten) - } - #[command(SetRec = "track/rec")] - fn track_set_rec (&mut self, rec: Option) -> Perhaps { - self.track_mut() - .map(|track|toggle_bool(&mut track.sequencer.recording, &rec, |rec|TrackCmd::SetRec { rec })) - .transpose() - .map(Option::flatten) - } - #[command(SetMon = "track/mon")] - fn track_set_mon (&mut self, mon: Option) -> Perhaps { - self.track_mut() - .map(|track|toggle_bool(&mut track.sequencer.monitoring, &mon, |mon|TrackCmd::SetMon { mon })) - .transpose() - .map(Option::flatten) - } -} - -impl -> ScenesControl for T {} - -#[tek_proc::commands(ScenesCmd)] pub trait ScenesControl: HasScenes - + HasTracks - + HasTrackScroll - + HasSelection - + HasJack<'static> -{ - #[command(Add = "add")] fn scenes_add (&mut self) -> Perhaps where Self: Sized { - let (_index, _) = self.scenes_add_one(None, None)?; - Ok(None) - } - #[command(Select = "select")] fn scene_select (&mut self) -> Perhaps { - let scenes = self.scenes().len(); - let select = self.selection_mut().select_scene(scenes); - *self.selection_mut() = select; - Ok(None) - } - #[command(Next = "next")] fn scene_select_next (&mut self) -> Perhaps { - let scenes = self.scenes().len(); - let select = self.selection().select_scene_next(scenes); - *self.selection_mut() = select; - Ok(None) - } - #[command(Prev = "prev")] fn scene_select_prev (&mut self) -> Perhaps { - let select = self.selection_mut().select_scene_prev(); - *self.selection_mut() = select; - Ok(None) - } - /// Generate the default name for a new scene - fn scenes_default_name (&self) -> Arc { - format!("s{:3>}", self.scenes().len() + 1).into() - } - fn scenes_longest_name (&self) -> usize { - self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max) - } - /// Add multiple scenes - fn scenes_add_many (&mut self, n: usize) - -> Usually<()> where Self: HasTracks - { - let scene_color_1 = ItemColor::random(); - let scene_color_2 = ItemColor::random(); - for i in 0..n { - let _ = self.scenes_add_one(None, Some( - scene_color_1.mix(scene_color_2, i as f32 / n as f32).into() - ))?; - } - Ok(()) - } - /// Add a scene - fn scenes_add_one (&mut self, name: Option>, color: Option) - -> Usually<(usize, &mut Scene)> - where - Self: HasTracks - { - let scene = Scene { - name: name.map_or_else(||self.scenes_default_name(), |x|x.to_string().into()), - clips: Pool::from(vec![None;self.tracks().len()]), - color: color.unwrap_or_else(ItemTheme::random), - }; - self.scenes_mut().push(scene); - let index = self.scenes().len() - 1; - Ok((index, &mut self.scenes_mut()[index])) - } -} - -impl + Namespace> + Namespace -> SceneControl for T {} - -#[tek_proc::commands(SceneCmd)] pub trait SceneControl: HasScene - + Namespace + Namespace> + Namespace -{ - #[command(SetSize = "size")] fn scene_set_size (&mut self, _size: usize) -> Perhaps { - todo!() - } - #[command(SetZoom = "zoom")] fn scene_set_zoom (&mut self, _size: usize) -> Perhaps { - todo!() - } - #[command(SetName = "name")] fn scene_set_name (&mut self, name: Arc) -> Perhaps { - Ok(self.scene_mut().map(|scene|swap_value( - &mut scene.name, - &name, - |name|SceneCmd::SetName { name } - )).transpose()?.flatten()) - } - #[command(SetColor = "color")] fn scene_set_color (&mut self, color: ItemTheme) -> Perhaps { - Ok(self.scene_mut().map(|scene|swap_value( - &mut scene.color, - &color, - |color|SceneCmd::SetColor { color } - )).transpose()?.flatten()) - } -} - -#[tek_proc::commands(SamplerCmd = "sampler")] pub trait SamplerControl: HasSampler - + Namespace -{ - #[command(RecToggle = "rec-toggle")] fn record_toggle (&mut self, slot: usize) -> Perhaps - where Self: Sized - { - let sampler = self.sampler_mut(); - let recording = sampler.recording.as_ref().map(|x|x.0); - let _ = SamplerCmd::RecFinish.dispatch(self)?; - // autoslice: continue recording at next slot - if recording != Some(slot) { - SamplerCmd::RecBegin { slot }.dispatch(self) + /// Load configuration item. + fn config_add , L: Language> (state: C, src: L) -> Usually { + if let Some(expr) = src.expr()? { + match expr.head()? { + Some("mode") => state.as_ref().modes_add(expr.tail()), + Some("view") => state.as_ref().views_add(expr.tail()), + Some("bind") => state.as_ref().binds_def_keys(expr.tail()), + Some("axis") => state.as_ref().binds_def_axis(expr.tail()), + _ => return Err(format!("config_add: unexpected: {src:?}").into()) + }?; + Ok(state) } else { - Ok(None) + Err(format!("Config::add_one: tried to add empty item").into()) } } - #[command(RecBegin = "rec-begin")] fn record_begin (&mut self, slot: usize) -> Perhaps { - let sampler = self.sampler_mut(); - sampler.recording = Some(( - slot, - Some(Arc::new(RwLock::new(Sample::new( - "Sample", 0, 0, vec![vec![]; sampler.audio_ins.len()] - )))) - )); - Ok(None) + + /// Contains timestamped mode, view, and bind definitions. + /// + /// ``` + /// # use ::{std::sync::Arc, tek::*}; + /// + /// // Default empty config: + /// let config: Config = Default::default(); + /// + /// // Define a binding: + /// config.binds_def_keys(stringify!(Confirm (@enter confirm))).unwrap(); + /// + /// // Define an axis: + /// config.binds_def_axis(stringify!(Y @up @down)).unwrap(); + /// ``` + #[derive(Default, Debug)] + pub struct Config { + /// Error caught during (re)loading + pub error: RwLock>>, + /// Timestamp + pub stamp: AtomicU64, + /// Active collection of interaction modes. + pub modes: Modes, + /// Active collection of event bindings. + pub binds: Binds, + /// Active collection of view definitions. + pub views: Views, } - #[command(RecFinish = "rec-finish")] fn record_finish (&mut self) -> Perhaps { - let sampler = self.sampler_mut(); - let _prev_sample = sampler.recording.as_mut().map(|(index, sample)|{ - std::mem::swap(sample, &mut sampler.samples.0[*index]); - sample - }); // TODO: undo - Ok(None) - } - #[command(RecCancel = "rec-cancel")] fn record_cancel (&mut self) -> Perhaps { - self.sampler_mut().recording = None; - Ok(None) - } - #[command(PlaySample = "sample-play")] fn sample_play (&mut self, slot: usize) -> Perhaps { - let sampler = self.sampler_mut(); - if let Some(ref sample) = sampler.samples.0[slot] { - sampler.voices.write().push(Sample::play(sample, 0, &u7::from(128))); + + impl Config { + /// Create a new timestamped config state. + pub fn new () -> Self { + Self { stamp: quanta::Clock::new().raw().into(), ..Default::default() } } - Ok(None) } - #[command(StopSample = "sample-stop")] fn sample_stop (&mut self, slot: usize) -> Perhaps { - todo!(); - //Ok(None) - } -} -impl - + Namespace - + Namespace - + Namespace - + Namespace> - + Namespace -> MidiEditControl for T {} + /// Configuration watcher and manager. + /// + /// ``` + /// let config = tek::ConfigWatcher::new(None); + /// ``` + #[derive(Default, Debug)] + pub struct ConfigWatcher { + /// XDG base directories of running user. + pub dirs: BaseDirectories, + /// Watcher + pub watch: RwLock>, + /// Actual configuration + pub config: Config, + } -#[tek_proc::commands(MidiEditCmd)] pub trait MidiEditControl: HasClip - + HasEditor - + Namespace - + Namespace - + Namespace - + Namespace - + Namespace> - + Namespace -{ - #[command(Toggle = "toggle")] fn edit_toggle (&mut self) -> Perhaps { - if self.is_editing() { - *self.editor_mut() = None; - } else if let Some(clip) = self.clip() { - let editor = MidiEditor::new(); - *editor.mode.clip.0.write() = clip.clone(); - editor.redraw(); - *self.editor_mut() = Some(editor); - } else { - return Ok(None) - } - Ok(Some(MidiEditCmd::Toggle)) - } - #[command(Show = "show")] fn edit_show (&mut self, clip: ClipSlot) -> Perhaps { - Ok(self.editor_mut().as_mut().map(|editor|{ - editor.mode.clip.0 = clip.0; - editor.redraw(); - None - }).flatten()) - } - #[command(DeleteNote = "delete")] fn edit_note_delete (&mut self) -> Perhaps { - Ok(self.editor_mut().as_mut().map(|editor|{ - editor.redraw(); - todo!() - }).flatten()) - } - #[command(AppendNote = "append")] fn edit_note_append (&mut self, advance: bool) -> Perhaps { - Ok(self.editor_mut().as_mut().map(|editor|{ - editor.put_note(advance); - editor.redraw(); - None - }).flatten()) - } - #[command(SetNotePos = "note-pos")] fn edit_note_set_pos (&mut self, pos: usize) -> Perhaps { - Ok(self.editor_mut().as_mut().map(|editor|{ - editor.set_note_pos((pos).min(127)); - editor.redraw(); - None - }).flatten()) - } - #[command(SetNoteLen = "note-len")] fn edit_note_set_len (&mut self, len: usize) -> Perhaps { - Ok(self.editor_mut().as_mut().map(|editor|{ - editor.set_note_len(len); - editor.redraw(); - None - }).flatten()) - } - #[command(SetNoteScroll = "note-scroll")] fn edit_note_set_scroll (&mut self, scroll: usize) -> Perhaps { - Ok(self.editor_mut().as_mut().map(|editor|{ - editor.set_note_lo((scroll).min(127)); - editor.redraw(); - None - }).flatten()) - } - #[command(SetTimePos = "time-pos")] fn edit_time_set_pos (&mut self, pos: usize) -> Perhaps { - Ok(self.editor_mut().as_mut().map(|editor|{ - editor.set_time_pos(pos); - editor.redraw(); - None - }).flatten()) - } - #[command(SetTimeScroll = "time-scroll")] fn edit_time_set_scroll (&mut self, scroll: usize) -> Perhaps { - Ok(self.editor_mut().as_mut().map(|editor|{ - editor.set_time_start(scroll); - editor.redraw(); - None - }).flatten()) - } - #[command(SetTimeZoom = "time-zoom")] fn edit_time_set_zoom (&mut self, zoom: usize) -> Perhaps { - Ok(self.editor_mut().as_mut().map(|editor|{ - editor.set_time_zoom(zoom); - editor.redraw(); - None - }).flatten()) - } - #[command(SetTimeLock = "time-lock")] fn edit_time_set_lock (&mut self, lock: bool) -> Perhaps { - Ok(self.editor_mut().as_mut().map(|editor|{ - editor.set_time_lock(lock); - editor.redraw(); - None - }).flatten()) - } - // TODO: 1-9 seek markers that by default start every 8th of the clip -} - -impl MidiClipControl for T where T: HasClip - + Namespace> + Namespace> {} -#[tek_proc::commands(MidiClipCmd)] pub trait MidiClipControl: HasClip - + Namespace> + Namespace> -{ - /// Create a clip at the current position if it does not already exist. - #[command(Upsert = "upsert")] fn clip_upsert ( - &mut self - ) -> Perhaps { - Ok(self.clip_mut().map(|mut clip|if clip.is_none() { - *clip = Some(Arc::new(RwLock::new(MidiClip { - ppq: 24, - length: 96, - color: ItemTheme::random(), + impl ConfigWatcher { + /// Default configuration directory. + const DIR: &'static str = "tek"; + /// Default configuration subdirectory. + const SUB: &'static str = "v0"; + /// Default configuration file name. + const EDN: &'static str = "tek.edn"; + /// Create a new watched app configuration + /// from a set of XDG base directories, + pub fn new (dirs: Option) -> Self { + Self { + dirs: dirs.unwrap_or_else(||BaseDirectories::with_profile(Self::DIR, Self::SUB)), ..Default::default() - }))); - None // TODO undo - } else { - None - }).flatten()) + } + } + /// Create, initialize, and watch a new configuration. + pub fn run_watched ( + callback: impl FnOnce(Arc)->T, + dirs: impl Into>, + ) -> Usually { + let watch = Arc::new(Self::new(dirs.into())); + Self::watch(watch.clone(), None)?; + let result = callback(watch); + Ok(result) + } + /// Watch a config's file for changes. + pub fn watch (config: Arc, poll: Option) -> Usually<()> { + let poll = poll.unwrap_or(Duration::from_millis(250)); + let opts = NotifyConfig::default().with_poll_interval(poll); + let mut watcher = ::notify_debouncer_full::notify::poll::PollWatcher::new({ + let config = config.clone(); + move|result|{ + match result { + Ok(_events) => if let Err(e) = config_init(config.as_ref()) { + config.error_set(Some(format!("{e:?}").into())); + panic!("{e:?}"); + } else { + //println!("config updated"); + }, + Err(errors) => { + panic!("{errors:?}"); + } + } + } + }, opts)?; + if let Some(path) = config.get_file() { + //println!("watching: {path:?}"); + watcher.watch(&path, RecursiveMode::NonRecursive)?; + *config.as_ref().watch.try_write().unwrap() = Some(watcher); + Ok(()) + } else { + Err(format!("no config path").into()) + } + } + /// Find the config file. + fn find_file (&self) -> Option { + self.dirs.find_config_file(Self::EDN) + } + /// Place config file in default location. + fn place_file (&self) -> Result { + self.dirs.place_config_file(Self::EDN) + } + /// Get path to config file. + fn get_file (&self) -> Option { + self.dirs.get_config_file(Self::EDN) + } } - #[command(SetColor = "color")] fn clip_set_color ( - &mut self, color: Option - ) -> Perhaps { - Ok(self.clip_slot().map_inner_mut(|clip|MidiClipCmd::SetColor { - color: Some(clip.set_color(color)) - })) + + impl> HasError> for T { + fn error (&self) -> &RwLock>> { + &self.as_ref().error + } } - /// Toggle looping for the active clip - #[command(SetLoop = "loop")] fn clip_toggle_loop ( - &mut self, looping: Option - ) -> Perhaps { - Ok(self.clip_slot().map_inner_mut(|clip|MidiClipCmd::SetLoop { - looping: Some(clip.toggle_loop(looping)) - })) + impl HasError> for Arc { + fn error (&self) -> &RwLock>> { + &self.as_ref().config.error + } } + pub trait HasConfig { + fn config (&self) -> &Config; + fn config_mut (&mut self) -> &mut Config; + /// Print the configuration. + fn config_print (&self) { + let config = self.config(); + print_config_views(config); + print_config_binds(config); + print_config_modes(config); + } + /// Make this configuration empty. + fn config_clear (&self) { + let config = self.config(); + config.modes_clear(); + config.views_clear(); + config.binds_clear(); + config.stamp.store(quanta::Clock::new().raw(), Relaxed); + } + } + impl HasConfig for ConfigWatcher { + fn config (&self) -> &Config { + &self.config + } + fn config_mut (&mut self) -> &mut Config { + &mut self.config + } + } + impl HasConfig for Config { + fn config (&self) -> &Config { + self + } + fn config_mut (&mut self) -> &mut Config { + self + } + } + } -impl BrowseControl for T where T: - Namespace + Namespace + Namespace> {} -#[tek_proc::commands(BrowseCmd = "browse")] pub trait BrowseControl: - Namespace + Namespace + Namespace> -{ - /// Toggle visibility of browser - #[command(Show = "show")] fn show (&mut self) -> Perhaps { - todo!() - } - /// Set current directory - #[command(Chdir = "chdir")] fn chdir (&mut self, _path: PathBuf) -> Perhaps { - todo!() - } - /// Set current filter - #[command(SetSearch = "set-search")] fn set_search (&mut self, _filter: Arc) -> Perhaps { - todo!() - } - /// Set selected item - #[command(SetCursor = "set-cursor")] fn set_cursor (&mut self, _index: usize) -> Perhaps { - todo!() - } -} +pub use self::gear::*; mod gear { + mod pool; pub use self::pool::*; + mod midi; pub use self::midi::*; + mod mixer; pub use self::mixer::*; + mod sample; pub use self::sample::*; -#[tek_proc::commands(AxisCmd)] impl App { -} + #[cfg(feature = "lv2")] mod lv2; + #[cfg(feature = "lv2")] pub use self::lv2::*; -#[tek_proc::command(AudioInput)] -#[tek_proc::keyword(AudioInput)] -#[derive(Debug)] -pub enum AudioInputCmd { - Close, - Connect(Arc), -} + #[cfg(feature = "lv2_gui")] mod lv2_gui; + #[cfg(feature = "lv2_gui")] pub use self::lv2_gui::*; -#[tek_proc::command(AudioOutput)] -#[tek_proc::keyword(AudioOutput)] -#[derive(Debug)] -pub enum AudioOutputCmd { - Close, - Connect(Arc), -} - -#[tek_proc::command(MidiInput)] -#[tek_proc::keyword(MidiInput)] -#[derive(Debug)] -pub enum MidiInputCmd { - Close, - Connect(Arc), -} - -#[tek_proc::command(MidiOutput)] -#[tek_proc::keyword(MidiOutput)] -#[derive(Debug)] -pub enum MidiOutputCmd { - Close, - Connect(Arc), + #[cfg(feature = "vst2")] mod vst2; + #[cfg(feature = "vst2")] pub use self::vst2::*; }