From a355e482c77692c3c3f8d2b38a87808946d7467d Mon Sep 17 00:00:00 2001 From: i do not exist Date: Sun, 4 Oct 2026 23:10:23 +0300 Subject: [PATCH] axis config: bring failure under test --- src/.scratch.rs | 393 ++++++++++++++++++++ src/cli.rs | 285 --------------- src/conf.rs | 295 +++++++++------ src/{ => conf}/bind.rs | 265 ++++++++------ src/{ => conf}/mode.rs | 82 ++--- src/{ => conf}/view.rs | 158 +++----- src/deps.rs | 1 + src/{ => device}/clap.rs | 0 src/{ => device}/lv2.rs | 0 src/{ => device}/lv2_gui.rs | 0 src/{ => device}/mixer.rs | 98 +++-- src/{ => device}/sample.rs | 0 src/{ => device}/vst2.rs | 0 src/{ => device}/vst3.rs | 0 src/project.rs | 193 +++++----- src/tek.rs | 707 +++++++++++++++++++----------------- 16 files changed, 1328 insertions(+), 1149 deletions(-) delete mode 100644 src/cli.rs rename src/{ => conf}/bind.rs (55%) rename src/{ => conf}/mode.rs (75%) rename src/{ => conf}/view.rs (94%) rename src/{ => device}/clap.rs (100%) rename src/{ => device}/lv2.rs (100%) rename src/{ => device}/lv2_gui.rs (100%) rename src/{ => device}/mixer.rs (96%) rename src/{ => device}/sample.rs (100%) rename src/{ => device}/vst2.rs (100%) rename src/{ => device}/vst3.rs (100%) diff --git a/src/.scratch.rs b/src/.scratch.rs index 39e3a044..5a61ab40 100644 --- a/src/.scratch.rs +++ b/src/.scratch.rs @@ -1207,3 +1207,396 @@ //Ok(app) //})?)? //} + +//impl<'a> Iterator for EntriesIterator<'a, Tui> { + //type Item = impl Draw; + //fn next (&mut self) -> Option { + //let dirs = self.browser.dirs.len(); + //let files = self.browser.files.len(); + //let index = self.index; + //if self.index < dirs { + //self.index += 1; + //Some(bold(true, self.browser.dirs[index].1.as_str())) + //} else if self.index < dirs + files { + //self.index += 1; + //Some(bold(false, self.browser.files[index - dirs].1.as_str())) + //} else { + //None + //} + //} +//} + +//impl<'a> PoolView<'a> { + //fn tui (&self) -> impl Draw<'_, Tui> { + //let Self(pool) = self; + ////let color = self.1.clip().map(|c|c.try_read().unwrap().color).unwrap_or_else(||g(32).into()); + ////let on_bg = |x|x;//below(Repeat(" "), bg(color.darkest.term, x)); + ////let border = |x|x;//Outer(Style::default().fg(color.dark.term).bg(color.darkest.term)).enclose(x); + ////let height = pool.clips.try_read().unwrap().len() as u16; + //iter( + //||pool.clips.clone().into_iter(), + //move|clip: Arc>, i: usize|{ + //let MidiClip { ref name, color, length, .. } = *clip.try_read().unwrap(); + //let item_height = 1; + //let _item_offset = i as u16 * item_height; + //let selected = i == pool.clip_index(); + //let b = if selected { color.light.term } else { color.base.term }; + //let f = color.lightest.term; + //let name = if false { format!(" {i:>3}") } else { format!(" {i:>3} {name}") }; + //let length = if false { String::default() } else { format!("{length} ") }; + //bg(b, below!( + //fg(f, bold(selected, name)).origin_w().full_w(), + //fg(f, bold(selected, length)).origin_e().full_w(), + //when(selected, bold(true, fg(g(255), "▶"))).origin_w().full_w(), + //when(selected, bold(true, fg(g(255), "◀"))).origin_e().full_w(), + //)).exact_h(1) + //}).origin_n().full_h().exact_w(20) + //} +//} + +//impl ClipLength { + //fn tui (&self) -> impl Draw<'_, Tui> { + //use ClipLengthFocus::*; + //let bars = format!("{}", self.bars()); + //let beats = format!("{}", self.beats()); + //let ticks = format!("{:>02}", self.ticks()); + //match self.focus { + //None => east!(" ", bars, ".", beats, ".", ticks), + //Some(Bar) => east!("[", bars, "]", beats, ".", ticks), + //Some(Beat) => east!(" ", bars, "[", beats, "]", ticks), + //Some(Tick) => east!(" ", bars, ".", beats, "[", ticks), + //} + //} +//} + + //pub fn tui ( + //app: Arc>, + //jack: Jack, + //sync_lead: &bool, + //sync_follow: &bool, + //) -> Usually<()> { + //// Run the [Tui] and [Jack] threads with the [App] state. + //Tui::run_main(&jack.run(move|jack|{ + //// Between jack init and app's first cycle: + ////jack.sync_lead(*sync_lead, |mut state|{ + ////let clock = app.try_write().unwrap().clock(); + ////clock.playhead.update_from_sample(state.position.frame() as f64); + ////state.position.bbt = Some(clock.bbt()); + ////state.position + ////})?; + ////jack.sync_follow(*sync_follow)?; + //// FIXME: They don't work properly. + //Ok(app) + //})?)? + //} + +//impl + //+ Namespace + //+ Namespace> + //+ Namespace + //+ Namespace + //+ Namespace + //+ Namespace +//> PoolControl for T {} + +//#[tek_proc::commands(PolCmd = "pool")] pub trait PoolControl: HasPool + //+ Namespace + //+ Namespace + //+ Namespace> + //+ Namespace + //+ Namespace + //+ Namespace + //+ Namespace +//{ + ///// Toggle visibility of pool + //#[command(Show = "show")] fn show (&mut self, visible: bool) -> Perhaps { + //self.pool_mut().visible = visible; + //Ok(Some(PoolCmd::Show { visible: !visible })) + //} + ///// Set selected clip + //#[command(Select = "select")] fn select (&mut self, index: usize) -> Perhaps { + //self.pool_mut().set_clip_index(index); + //Ok(None) + //} + ///// Delete a clip from the pool + //#[command(Delete = "delete")] fn delete (&mut self, index: usize) -> Perhaps { + //let clip = self.pool_mut().remove(index).read().clone(); + //Ok(Some(PoolCmd::Add { index, clip })) + //} + ///// Switch places of two clips in pool + //#[command(Swap = "swap")] fn swap (&mut self, index: usize, other: usize) -> Perhaps { + //self.pool_mut().swap(index, other); + //Ok(Some(PoolCmd::Swap { index, other })) + //} + ///// Add clip to pool + //#[command(Add = "add")] fn clip_add (&mut self, index: usize, clip: MidiClip) -> Perhaps { + //let mut index = index; + //let clip = Arc::new(RwLock::new(clip.clone())); + //let mut clips = self.pool_mut().clips_mut(); + //if index >= clips.len() { + //index = clips.len(); + //clips.push(clip) + //} else { + //clips.insert(index, clip); + //} + //Ok(Some(PoolCmd::Delete { index })) + //} + ///// Set name of clip + //#[command(SetName = "set-name")] fn clip_set_name (&mut self, index: usize, name: Arc) -> Perhaps { + //Ok(self.pool_mut().clips.read().get(index).map(|clip|{ + //let old_name = clip.read().name.clone(); + //clip.write().name = name.clone(); + //PoolCmd::SetName { index, name: old_name } + //})) + //} + ///// Set length of clip + //#[command(SetLength = "set-length")] fn clip_set_length (&mut self, index: usize, length: usize) -> Perhaps { + //Ok(self.pool_mut().clips.read().get(index).map(|clip|{ + //let old_len = clip.read().length; + //clip.write().length = length; + //PoolCmd::SetLength { index, length: old_len } + //})) + //} + ///// Set color of clip + //#[command(SetColor = "set-color")] fn clip_set_color (&mut self, index: usize, color: ItemColor) -> Perhaps { + //let mut color = ItemTheme::from(color); + //std::mem::swap(&mut color, &mut self.pool().clips.read()[index].write().color); + //Ok(Some(PoolCmd::SetColor { index, color: color.base })) + //} + ///// Enter crop mode + //#[command(CropBegin = "crop/begin")] fn crop_begin (&mut self) -> Perhaps { + //let index = self.pool().clip_index(); + //let length = self.pool().clips.read()[index].read().length; + //*self.pool_mut().mode_mut() = Some(PoolMode::Length(index, length, ClipLengthFocus::Bar)); + //Ok(None) + //} + ///// Exit crop mode, discard + //#[command(CropCancel = "crop/cancel")] fn crop_cancel (&mut self) -> Perhaps { + //if let Some(PoolMode::Length(..)) = self.pool_mut().mode_mut().clone() { + //*self.pool_mut().mode_mut() = None; + //} + //Ok(None) + //} + //#[command(CropSet = "crop/set")] fn crop_set (&mut self, _length: usize) -> Perhaps { + //if let Some(PoolMode::Length(clip, ref mut length, ref mut _focus)) + //= self.pool_mut().mode_mut().clone() + //{ + //let old_length; + //{ + //let clip = self.pool().clips.read()[clip].clone();//.write(); + //old_length = Some(clip.read().length); + //clip.write().length = *length; + //} + //*self.pool_mut().mode_mut() = None; + //return Ok(old_length.map(|l|PoolCmd::CropSet { _length: l })) + //} + //Ok(None) + //} + //#[command(CropNext = "crop/next")] fn crop_next (&mut self) -> Perhaps { + //if let Some(PoolMode::Length( + //_clip, ref mut _length, ref mut focus + //)) = self.pool_mut().mode_mut().clone() { + //focus.next() + //}; + //Ok(None) + //} + //#[command(CropPrev = "crop/prev")] fn crop_prev (&mut self) -> Perhaps { + //if let Some(PoolMode::Length( + //_clip, ref mut _length, ref mut focus + //)) = self.pool_mut().mode_mut().clone() { + //focus.prev() + //}; + //Ok(None) + //} + //#[command(CropInc = "crop/inc")] fn crop_inc (&mut self) -> Perhaps { + //if let Some(PoolMode::Length( + //_clip, ref mut length, ref mut focus + //)) = self.pool_mut().mode_mut().clone() { + //match focus { + //ClipLengthFocus::Bar => { *length += 4 * PPQ }, + //ClipLengthFocus::Beat => { *length += PPQ }, + //ClipLengthFocus::Tick => { *length += 1 }, + //} + //} + //Ok(None) + //} + //#[command(CropDec = "crop/dec")] fn crop_dec (&mut self) -> Perhaps { + //if let Some(PoolMode::Length( + //_clip, ref mut length, ref mut focus + //)) = self.pool_mut().mode_mut().clone() { + //match focus { + //ClipLengthFocus::Bar => { *length = length.saturating_sub(4 * PPQ) }, + //ClipLengthFocus::Beat => { *length = length.saturating_sub(PPQ) }, + //ClipLengthFocus::Tick => { *length = length.saturating_sub(1) }, + //} + //} + //Ok(None) + //} + ///// Enter rename mode + //#[command(RenameBegin = "rename/begin")] fn rename_begin (&mut self) -> Perhaps { + //let index = self.pool().clip_index(); + //let name = self.pool().clips.read()[index].read().name.clone(); + //*self.pool_mut().mode_mut() = Some(PoolMode::Rename(index, name)); + //Ok(None) + //} + ///// Exit rename mode, discard + //#[command(RenameCancel = "rename/cancel")] fn rename_cancel (&mut self) -> Perhaps { + //if let Some(PoolMode::Rename(clip, ref mut old_name)) = self.pool_mut().mode_mut().clone() { + //self.pool().clips.read()[clip].write().name = old_name.clone().into(); + //} + //Ok(None) + //} + ///// Exit rename mode, update name + //#[command(RenameConfirm = "rename/confirm")] fn rename_confirm (&mut self) -> Perhaps { + //Ok(if let Some(PoolMode::Rename(_clip, ref mut old_name)) = self.pool_mut().mode_mut().clone() { + //let old_name = old_name.clone(); + //*self.pool_mut().mode_mut() = None; + //Some(PoolCmd::RenameSet { value: old_name }) + //} else { + //None + //}) + //} + //#[command(RenameSet = "rename/set")] fn rename_set (&mut self, value: Arc) -> Perhaps { + //if let Some(PoolMode::Rename(clip, ref mut _old_name)) = self.pool_mut().mode_mut().clone() { + //self.pool().clips.read()[clip].write().name = value.clone(); + //} + //Ok(None) + //} + /////// Import from file + ////#[command(Browse = "browse")] + ////fn browse (&mut self, command: BrowseCmd) -> Perhaps { + ////Ok(if let Some(browse) = self.pool_mut().browse.as_mut() { + ////command.act(browse)?.map(|command|PoolCmd::Browse{command}) + ////} else { + ////None + ////}) + ////} + ///// Import clip to pool from file + //#[command(Import = "import")] + //fn clip_import (&mut self, index: usize, path: PathBuf) -> Perhaps + //where Self: Sized + //{ + //let bytes = std::fs::read(&path)?; + //let smf = Smf::parse(bytes.as_slice())?; + //let mut t = 0u32; + //let mut events = vec![]; + //for track in smf.tracks.iter() { + //for event in track.iter() { + //t += event.delta.as_int(); + //if let TrackEventKind::Midi { channel, message } = event.kind { + //events.push((t, channel.as_int(), message)); + //} + //} + //} + //let mut clip = MidiClip::new("imported", true, t as usize + 1, None, None); + //for event in events.iter() { + //clip.notes[event.0 as usize].push(event.2); + //} + //Ok(PoolCmd::Add { index, clip }.dispatch(self)?) + //} + /////// Export to file + ////#[command(Export = "export")] + ////fn export (&mut self, command: BrowseCmd) -> Perhaps { + ////Ok(if let Some(browse) = self.pool_mut().browse.as_mut() { + ////command.act(browse)?.map(|command|PoolCmd::Export{command}) + ////} else { + ////None + ////}) + ////} +//} + +//pub(crate) struct EntriesIterator<'a, S: Screen> { + //pub browser: &'a Browse, + //pub offset: usize, + //pub length: usize, + //pub index: usize, + //_screen: std::marker::PhantomData +//} + +// Commands supported by [Browse] +//#[derive(Debug, Clone, PartialEq)] +//pub enum BrowseCommand { + //Begin, + //Cancel, + //Confirm, + //Select(usize), + //Chdir(PathBuf), + //Filter(Arc), +//} + +//impl AsRef> for MenuItems { + //fn as_ref (&self) -> &Arc<[MenuItem]> { + //&self.0 + //} +//} + + +//impl_from!(Pool: |clip:&Arc>|{ + //let model = Self::default(); + //model.clips.try_write().unwrap().push(clip.clone()); + //model.clip.store(1, Relaxed); + //model +//}); +//impl Pool { + //pub fn clip_index (&self) -> usize { + //self.clip.load(Relaxed) + //} + //pub fn set_clip_index (&self, value: usize) { + //self.clip.store(value, Relaxed); + //} + //pub fn mode (&self) -> &Option { + //&self.mode + //} + //pub fn mode_mut (&mut self) -> &mut Option { + //&mut self.mode + //} + //pub fn begin_import (&mut self) -> Usually<()> { + //*self.mode_mut() = Some(PoolMode::Import( + //self.clip_index(), + //Browse::new(None)? + //)); + //Ok(()) + //} + //pub fn begin_export (&mut self) -> Usually<()> { + //*self.mode_mut() = Some(PoolMode::Export( + //self.clip_index(), + //Browse::new(None)? + //)); + //Ok(()) + //} + //pub fn new_clip (&self) -> MidiClip { + //MidiClip::new("Clip", true, 4 * PPQ, None, Some(ItemTheme::random())) + //} + //pub fn cloned_clip (&self) -> MidiClip { + //let index = self.clip_index(); + //let mut clip = self.clips.read()[index].read().duplicate(); + //clip.color = ItemTheme::random_near(clip.color, 0.25); + //clip + //} + //pub fn add_new_clip (&self) -> (usize, Arc>) { + //let clip = Arc::new(RwLock::new(self.new_clip())); + //let index = { + //let mut clips = self.clips.try_write().unwrap(); + //clips.push(clip.clone()); + //clips.len().saturating_sub(1) + //}; + //self.clip.store(index, Relaxed); + //(index, clip) + //} + //pub fn delete_clip (&mut self, clip: &MidiClip) -> bool { + //let index = self.clips.try_read().unwrap().iter().position(|x|*x.try_read().unwrap()==*clip); + //if let Some(index) = index { + //self.clips.try_write().unwrap().remove(index); + //return true + //} + //false + //} + //fn _clip_new (&self) -> MidiClip { self.new_clip() } + //fn _clip_cloned (&self) -> MidiClip { self.cloned_clip() } + //fn _clip_index_current (&self) -> usize { 0 } + //fn _clip_index_after (&self) -> usize { 0 } + //fn _clip_index_previous (&self) -> usize { 0 } + //fn _clip_index_next (&self) -> usize { 0 } + //fn _color_random (&self) -> ItemColor { ItemColor::random() } +//} diff --git a/src/cli.rs b/src/cli.rs deleted file mode 100644 index 1110543c..00000000 --- a/src/cli.rs +++ /dev/null @@ -1,285 +0,0 @@ -use crate::*; - -/// Banner. -pub(crate) const HEADER: &'static str = r#" -~ █▀█▀█ █▀▀█ █ █ ~~~ ~ ~ ~~ ~ ~ ~ ~~ ~ ~ ~ ~ - █ █▀ █▀▀▄ ~ heatwave is the new darkwave ~ - ~ ▀ █▀▀█ ▀ ▀ ~ ~~~ ~ ~ ~ ~ ~~~ ~~~ ~ ~~ "#; - -pub fn show_version () { - println!("versions aint real man"); -} - -pub fn run_with_config (config: Arc) -> Usually<()> { - Cli::parse().run(Some(config)) -} - -/// 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<()> { - if config.is_none() { - config = Some(Config::init_new(None)?); - } - self.action.run(config.unwrap(), 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.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(()) - } -} - -#[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 Self { - name, bpm, tracks, scenes, - sync_lead: _, sync_follow: _, - left_from, right_from, midi_from, midi_from_re, - left_to, right_to, midi_to, midi_to_re, - .. - } = self; - let name = name.as_ref().map_or("tek", |x|x.as_str()); - let jack = Jack::new(&name)?; - let mut proj = Project::new( - &jack, - name.into(), - Clock::new(&jack, *bpm)?, - [].into_iter(), - [].into_iter(), - connect_midi_ins( - &jack, &"M".to_string(), midi_from.as_ref(), Some(midi_from_re) - )?.into_iter(), - connect_midi_outs( - &jack, &"M".to_string(), midi_to.as_ref(), Some(midi_to_re) - )?.into_iter(), - [].into_iter() - .chain( - connect_audio_ins(&jack, &"L".to_string(), &left_from, None)?.into_iter() - ) - .chain( - connect_audio_ins(&jack, &"R".to_string(), &right_from, None)?.into_iter() - ), - [].into_iter() - .chain( - connect_audio_outs(&jack, &"L".to_string(), &left_to, None)?.into_iter() - ) - .chain( - connect_audio_outs(&jack, &"R".to_string(), &right_to, None)?.into_iter() - )); - proj.tracks_add_many(tracks.unwrap_or(0), None, [].into(), [].into())?; - proj.scenes_add_many(scenes.unwrap_or(0))?; - Ok(proj) - } -} - -pub fn print_status (project: &Project) { - println!("Name: {:?}", &project.name); - println!("JACK: {:?}", &project.jack); - println!("Buffer: {:?}", &project.clock.chunk); - println!("Sample rate: {:?}", &project.clock.timebase.sr); - println!("MIDI PPQ: {:?}", &project.clock.timebase.ppq); - println!("Tempo: {:?}", &project.clock.timebase.bpm); - println!("Quantize: {:?}", &project.clock.quant); - println!("Launch: {:?}", &project.clock.sync); - println!("Playhead: {:?}us", &project.clock.playhead.usec); - println!("Playhead: {:?}s", &project.clock.playhead.sample); - println!("Playhead: {:?}p", &project.clock.playhead.pulse); - println!("Started: {:?}", &project.clock.started); - println!("Tracks:"); - for (i, t) in project.tracks.iter().enumerate() { - println!(" Track {i}: {} {} {:?} {:?}", t.name, t.width, - &t.sequencer.play_clip, &t.sequencer.next_clip); - } - println!("Scenes:"); - for (i, t) in project.scenes.iter().enumerate() { - println!(" Scene {i}: {} {:?}", &t.name, &t.clips); - } - println!("MIDI Ins: {:?}", &project.midi_ins); - println!("MIDI Outs: {:?}", &project.midi_outs); - println!("Audio Ins: {:?}", &project.audio_ins); - println!("Audio Outs: {:?}", &project.audio_outs); - // TODO git integration - // TODO dawvert integration -} - -pub fn print_config (config: &Config) { - use ::ansi_term::Color::*; - println!("{:?}", config.dirs); - - config.views.for_each(|k, v|{ - println!("{} {} {}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}")), v.source); - }); - - 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); - } - } - }); - - 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!(); - }); -} - - //pub fn tui ( - //app: Arc>, - //jack: Jack, - //sync_lead: &bool, - //sync_follow: &bool, - //) -> Usually<()> { - //// Run the [Tui] and [Jack] threads with the [App] state. - //Tui::run_main(&jack.run(move|jack|{ - //// Between jack init and app's first cycle: - ////jack.sync_lead(*sync_lead, |mut state|{ - ////let clock = app.try_write().unwrap().clock(); - ////clock.playhead.update_from_sample(state.position.frame() as f64); - ////state.position.bbt = Some(clock.bbt()); - ////state.position - ////})?; - ////jack.sync_follow(*sync_follow)?; - //// FIXME: They don't work properly. - //Ok(app) - //})?)? - //} diff --git a/src/conf.rs b/src/conf.rs index 10285556..1ceddf9a 100644 --- a/src/conf.rs +++ b/src/conf.rs @@ -2,22 +2,67 @@ 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 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; +/// # use ::{std::sync::Arc, tek::*}; /// /// // Default empty config: -/// let config: tek::Config = Default::default(); +/// let config: Config = Default::default(); /// /// // Define a binding: -/// config.binds.add_keys(stringify!(Confirm (@enter confirm))).unwrap(); +/// config.binds_def_keys(stringify!(Confirm (@enter confirm))).unwrap(); /// /// // Define an axis: -/// config.binds.add_axis(stringify!(Y @up @down)).unwrap(); +/// config.binds_def_axis(stringify!(Y @up @down)).unwrap(); /// /// // Define a mode: -/// config.modes.add(stringify!(Menu +/// config.modes_add(stringify!(Menu /// (name Menu) /// (info Mode selector.) /// (axis Y Prev Next) @@ -26,75 +71,103 @@ use notify_debouncer_full::notify::{RecursiveMode, PollWatcher, Config as Notify /// )).unwrap(); /// /// // Find the mode: -/// let mode: Arc = config.get_mode("Menu").unwrap(); +/// let mode: Arc = config.modes_get("Menu").unwrap(); /// ``` #[derive(Default, Debug)] pub struct Config { - /// XDG base directories of running user. - pub dirs: BaseDirectories, + /// 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, - /// Error caught during (re)loading - pub error: RwLock>>, - /// Timestamp - pub stamp: AtomicU64, - /// Watcher - pub watch: RwLock> } -impl_as_ref!(Config: |self: Config| self); impl Config { - - /// Write initial contents of configuration. - pub fn init > (config: C) -> Usually { - if config.as_ref().find_file().is_none() { - std::fs::write(config.as_ref().place_file()?, Config::DEFAULTS)?; - } - Ok(if let Some(path) = config.as_ref().find_file() { - Config::load(config, std::fs::read_to_string(&path)?)? - } else { - return Err(format!("config_init: not found").into()) - }) + pub fn new () -> Self { + Self { stamp: quanta::Clock::new().raw().into(), ..Default::default() } } +} - pub fn load , L: Language> (config: C, src: L) -> Usually { - config.as_ref().clear(); - config.as_ref().stamp.store(quanta::Clock::new().raw(), Relaxed); - src.each(config, |c, s|Config::load_item(c, s.trim())) +impl> HasError> for T { + fn error (&self) -> &RwLock>> { + &self.as_ref().error } +} - pub fn load_item , L: Language> (config: C, src: L) -> Usually { - if let Some(expr) = src.expr()? { - match expr.head()? { - Some("mode") => config.as_ref().modes.add(expr.tail()), - Some("view") => config.as_ref().views.add(expr.tail()), - Some("bind") => config.as_ref().binds.add_keys(expr.tail()), - Some("axis") => config.as_ref().binds.add_axis(expr.tail()), - _ => return Err(format!("Config::load: expected view/keys/mode expr, got: {src:?}").into()) - }?; - Ok(config) - } else { - Err(format!("Config::add_one: tried to add empty item").into()) +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<()> { + 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.as_ref().error.try_write().unwrap() = Some(format!("{e:?}").into()); + Ok(_events) => if let Err(e) = config_init(config.as_ref()) { + config.error_set(Some(format!("{e:?}").into())); panic!("{e:?}"); } else { //println!("config updated"); @@ -105,7 +178,7 @@ impl Config { } } }, opts)?; - if let Some(path) = config.as_ref().get_file() { + if let Some(path) = config.get_file() { //println!("watching: {path:?}"); watcher.watch(&path, RecursiveMode::NonRecursive)?; *config.as_ref().watch.try_write().unwrap() = Some(watcher); @@ -114,79 +187,27 @@ impl Config { Err(format!("no config path").into()) } } - - /// Create a new 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::CONFIG_DIR, Self::CONFIG_SUB) - }), - stamp: quanta::Clock::new().raw().into(), - ..Default::default() - } - } - - /// Create, initialize, and watch a new configuration. - pub fn watched (callback: impl FnOnce(Arc)->T) -> Usually { - let config = Self::init_new(None)?; - Config::watch(config.clone(), None)?; - let result = callback(config); - Ok(result) - } - - /// Create and initialize a new configuration. - pub fn init_new (_dirs: Option) -> Usually> { - Config::init(Arc::new(Self::new(None))) - } - /// Find the config file. fn find_file (&self) -> Option { - self.dirs.find_config_file(Self::CONFIG) + self.dirs.find_config_file(Self::EDN) } - /// Place config file in default location. fn place_file (&self) -> Result { - self.dirs.place_config_file(Self::CONFIG) + self.dirs.place_config_file(Self::EDN) } - /// Get path to config file. fn get_file (&self) -> Option { - self.dirs.get_config_file(Self::CONFIG) + self.dirs.get_config_file(Self::EDN) } +} - /// Get a mode by name. - pub fn get_mode (&self, mode: impl AsRef) -> Option> { - self.modes.get(mode) +impl HasConfig for ConfigWatcher { + fn config (&self) -> &Config { + &self.config } - - /// Print the configuration. - pub fn print (&self) { - print_config(self) + fn config_mut (&mut self) -> &mut Config { + &mut self.config } - - /// Make this configuration empty. - pub fn clear (&self) { - *self.modes.0.try_write().unwrap() = Default::default(); - self.views.clear(); - self.binds.clear(); - } - - pub fn get_view (&self, name: impl AsRef) -> Option>> { - self.views.get(name) - } - - /// Default configuration directory. - const CONFIG_DIR: &'static str = "tek"; - - /// Default configuration subdirectory. - const CONFIG_SUB: &'static str = "v0"; - - /// Default configuration file name. - const CONFIG: &'static str = "tek.edn"; - - /// Default configuration contents. - const DEFAULTS: &'static str = include_str!("tek.edn"); - } pub fn eval_word (src: impl Language, eval: impl Fn(&str)->Usually) -> Perhaps { @@ -197,3 +218,57 @@ pub fn eval_word (src: impl Language, eval: impl Fn(&str)->Usually) -> Pe //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/bind.rs b/src/conf/bind.rs similarity index 55% rename from src/bind.rs rename to src/conf/bind.rs index ce54e960..dbdb68bb 100644 --- a/src/bind.rs +++ b/src/conf/bind.rs @@ -3,27 +3,55 @@ use crate::*; /// Collection of input bindings. /// /// ``` -/// let binds = tek::Binds::default(); -/// -/// /// Key binding maps keys to actions: -/// binds.add_keys(stringify!(Global (@f1 help) (@f2 rename))).unwrap(); -/// -/// /// Axis binding names a pair of keys to control a mode-dependent axis: -/// binds.add_axis(stringify!(X @left @right)).unwrap(); +/// # use tek::*; +/// let state = Binds::default(); +/// state.binds_def_keys(stringify!(Global (@f1 help) (@f2 rename))).unwrap(); +/// state.binds_def_axis(stringify!(X @left @right)).unwrap(); /// ``` #[derive(Default, Clone, Debug)] -pub struct Binds( - pub Arc, Arc>>>>>, - pub Arc, (Arc, Arc)>>>, -); +pub struct Binds { + pub keys: Arc, Arc>>>>>, + pub axis: Arc, Arc>>>>>, +} -/// An map of input events (e.g. [TuiEvent]) to [Binding]s. +impl Binds { + pub fn binds_clear (&self) { + *self.keys.write() = Default::default(); + *self.axis.write() = Default::default(); + } + pub fn binds_find (&self, id: impl AsRef) -> Option>>> { + self.keys.read().get(id.as_ref()).cloned() + } + pub fn binds_for_each ( + &self, mut cb: impl FnMut(&Arc, &Bind>)->T + ) { + for (k, v) in self.keys.read().iter() { + cb(k, v); + } + } + pub fn binds_def_keys (&self, expr: impl Language) -> Usually<()> { + self.keys.write().insert( + expr.head()?.ok_or("bind: missing name")?.into(), + Bind::new(expr.tail()?.unwrap_or(""))?.into() + ); + Ok(()) + } + pub fn binds_def_axis (&self, expr: impl Language) -> Usually<()> { + self.axis.write().insert( + expr.head()?.ok_or("axis: missing name")?.into(), + Axis::new(expr.tail()?.unwrap_or(""))?.into() + ); + Ok(()) + } +} + +/// A map of input events (e.g. [TuiEvent]) to [Binding]s. /// /// ``` -/// use ::{std::sync::Arc, tek::{Bind, tengri::TuiEvent}}; +/// use ::{std::sync::Arc, tek::{*, tengri::*}}; /// /// let src = stringify!((@x (nop)) (@y (nop) (nop))); -/// let keys = Bind::>::load_keys(&src).unwrap(); +/// let keys = Bind::>::new(&src).unwrap(); /// /// // Key 'w' is not mapped: /// assert_eq!(keys.query(&'w'.into()).map(|x|x.len()), None); @@ -35,12 +63,6 @@ pub struct Binds( /// // Key 'y' has one binding containing multiple commands: /// assert_eq!(keys.query(&'y'.into()).map(|x|x.len()), Some(1)); /// assert_eq!(keys.query(&'y'.into()).map(|x|x[0].commands.len()), Some(2)); -/// -/// let src = stringify!(@up @down); -/// let axis = Bind::>::load_axis("Y", &src).unwrap(); -/// assert_eq!(axis.query(&"left".into()).map(|x|x.len()), None); -/// assert_eq!(axis.query(&"up".into()).map(|x|x.len()), Some(1)); -/// assert_eq!(axis.query(&"down".into()).map(|x|x.len()), Some(1)); /// ``` #[derive(Debug)] pub struct Bind( @@ -50,105 +72,20 @@ pub struct Bind( pub BTreeMap>> ); -/// A sequence of zero or more commands (e.g. [AppCmd]), -/// optionally filtered by [Condition] to form layers. -/// -/// ``` -/// # use std::sync::Arc; -/// let binding: tek::Binding> = Default::default(); -/// ``` -#[derive(Debug, Clone)] pub struct Binding { - pub commands: Arc<[C]>, - pub condition: Option, - pub description: Option>, - pub source: Option>, -} - -/// Condition that must evaluate to true in order to enable an input binding. -/// -/// ``` -/// let condition = tek::Condition(std::sync::Arc::new(Box::new(||{true}))); -/// ``` -#[derive(Clone)] -pub struct Condition(pub Arcbool + Send + Sync>>); - -/// Named control axis. -pub struct ControlAxis(pub Arc); - -impl Binds { - pub fn add_keys (&self, expr: impl Language) -> Usually<()> { - self.0.write().insert( - expr.head()?.ok_or("bind: missing name")?.into(), - Bind::load_keys(expr.tail()?.unwrap_or(""))?.into() - ); - Ok(()) - } - - pub fn add_axis (&self, expr: impl Language) -> Usually<()> { - let name = expr.head()?.ok_or("axis: missing name")?; - self.0.write().insert( - name.into(), - Bind::load_axis(name, expr.tail()?.unwrap_or(""))?.into() - ); - Ok(()) - } - - pub fn clear (&self) { - *self.0.write() = Default::default(); - } - - pub fn find (&self, id: impl AsRef) -> Option>>> { - self.0.read().get(id.as_ref()).cloned() - } - - pub fn for_each ( - &self, mut cb: impl FnMut(&Arc, &Bind>)->T - ) { - for (k, v) in self.0.read().iter() { - cb(k, v); - } - } -} - /// Default is always empty map regardless if `E` and `C` implement [Default]. impl Default for Bind { fn default () -> Self { Self(Default::default()) } } -impl Default for Binding { - fn default () -> Self { - Self { - commands: Default::default(), - condition: Default::default(), - description: Default::default(), - source: Default::default(), - } - } -} - impl Bind> { - pub fn load_keys (body: impl Language) -> Usually { - let mut bind: Bind> = Bind::new(); + pub fn new (body: impl Language) -> Usually { + let mut bind: Bind> = Bind::default(); body.each((), |_, item: &str|if matches!(item.expr().head(), Ok(Some("see"))) { bind.add_ref(item) } else if let Ok(Some(_word)) = item.expr().head().word() { bind.add_def(item) } else { - return err!("Bind::load_keys: unexpected: {item:?}") - })?; - Ok(bind) - } - - pub fn load_axis (name: &str, body: impl Language) -> Usually { - let mut bind: Bind> = Bind::new(); - body.each(0, |index, key: &str|if index == 0 { - bind.add_def(&format!("({key} (axis (dec {name})))"))?; - Ok(1) - } else if index == 1 { - bind.add_def(&format!("({key} (axis (inc {name})))"))?; - Ok(2) - } else { - err!("top-level (axis) takes 2 keysyms: dec and inc") + return err!("Bind::new: unexpected: {item:?}") })?; Ok(bind) } @@ -175,11 +112,51 @@ impl Bind> { } } -impl Bind { - /// Create a new event map - pub fn new () -> Self { - Default::default() +/// ``` +/// use ::{std::sync::Arc, tek::*}; +/// let axis = Axis::>::new("@up @down").unwrap(); +/// ``` +pub struct Axis(T, T); + +impl_debug!( Axis: |self, w| { write!(w, "Axis({:?} {:?})", self.0, self.1) }); + +impl From<&'a str>, U: AsRef, V: AsRef> From<(U, V)> for Axis { + fn from ((decr, incr): (U, V)) -> Self { + Self(decr.as_ref().into(), incr.as_ref().into()) } +} + +impl From<&'a str>> Axis { + pub fn new (body: impl Language) -> Usually { + let mut axis: (T, T) = Default::default(); + body.each(0, |index, item: &str|if index == 0 { + axis.0 = item.into(); + Ok(1) + } else if index == 1 { + axis.0 = item.into(); + Ok(2) + } else { + err!("def (axis X dec inc)") + })?; + Ok(Self(axis.0, axis.1)) + } +} + + //pub fn load_axis_bind (name: &str, body: impl Language) -> Usually { + //let mut bind: Bind> = Bind::new(); + //body.each(0, |index, key: &str|if index == 0 { + //bind.add_def(&format!("({key} (axis (dec {name})))"))?; + //Ok(1) + //} else if index == 1 { + //bind.add_def(&format!("({key} (axis (inc {name})))"))?; + //Ok(2) + //} else { + //err!("top-level (axis) takes 2 keysyms: dec and inc") + //})?; + //Ok(bind) + //} + +impl Bind { /// Add a binding to an owned event map. pub fn def (mut self, event: E, binding: Binding) -> Self { self.add(event, binding); @@ -205,7 +182,67 @@ impl Bind { } } +/// A sequence of zero or more commands (e.g. [AppCmd]), +/// optionally filtered by [Condition] to form layers. +/// +/// ``` +/// # use std::sync::Arc; +/// let binding: tek::Binding> = Default::default(); +/// ``` +#[derive(Debug, Clone)] pub struct Binding { + pub commands: Arc<[C]>, + pub condition: Option, + pub description: Option>, + pub source: Option>, +} + +impl Default for Binding { + fn default () -> Self { + Self { + commands: Default::default(), + condition: Default::default(), + description: Default::default(), + source: Default::default(), + } + } +} + +/// Condition that must evaluate to true in order to enable an input binding. +/// +/// ``` +/// let condition = tek::Condition(std::sync::Arc::new(Box::new(||{true}))); +/// ``` +#[derive(Clone)] +pub struct Condition(pub Arcbool + Send + Sync>>); + +/// Named control axis. +pub struct ControlAxis(pub Arc); + impl_debug!(Condition: |self, w| { write!(w, "*") }); -//take!(DeviceCmd|state: Project, iter|state.selected_device().as_ref() - //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten())); +impl HasBinds for App { + fn binds_keys (&self) -> &RwLock, Arc>>>> { + &self.config.config.binds.keys + } + fn binds_axis (&self) -> &RwLock, Arc>>>> { + &self.config.config.binds.axis + } +} + +impl HasBinds for ConfigWatcher { + fn binds_keys (&self) -> &RwLock, Arc>>>> { + &self.config.binds.keys + } + fn binds_axis (&self) -> &RwLock, Arc>>>> { + &self.config.binds.axis + } +} + +impl HasBinds for Config { + fn binds_keys (&self) -> &RwLock, Arc>>>> { + &self.binds.keys + } + fn binds_axis (&self) -> &RwLock, Arc>>>> { + &self.binds.axis + } +} diff --git a/src/mode.rs b/src/conf/mode.rs similarity index 75% rename from src/mode.rs rename to src/conf/mode.rs index 4aa492b5..86f692f8 100644 --- a/src/mode.rs +++ b/src/conf/mode.rs @@ -1,17 +1,20 @@ use crate::*; +impl_as_mut!(Mode: |self: Mode| self); + /// Collection of UI modes. /// /// ``` +/// # use tek::*; /// // Empty mode registry: -/// let mut modes = tek::Modes::default(); -/// assert_eq!(modes.len(), 0); -/// assert!(modes.get("Foo").is_none()); +/// let mut state = Modes::default(); +/// assert_eq!(state.modes_count(), 0); +/// assert!(state.modes_get("Foo").is_none()); /// /// // Add a mode: -/// let _ = modes.add(&"Foo (name Foo) (info Bar)").unwrap(); -/// assert_eq!(modes.len(), 1); -/// assert!(modes.get("Foo").is_some()); +/// let _ = state.modes_add(&"Foo (name Foo) (info Bar)").unwrap(); +/// assert_eq!(state.modes_count(), 1); +/// assert!(state.modes_get("Foo").is_some()); /// ``` #[derive(Default, Debug, Clone)] pub struct Modes(pub Arc, Arc>>>); @@ -32,37 +35,6 @@ pub struct Mode { pub modes: Modes, } -impl Modes { - - /// Register a mode. - pub fn 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.0.write().insert(name.into(), Arc::new(mode)); - Ok(()) - } - - /// Get a mode by name. - pub fn get (&self, name: impl AsRef) -> Option> { - self.0.read().get(name.as_ref()).cloned() - } - - /// Run something for each mode. - pub fn for_each (&self, mut ator: impl FnMut(&str, &Mode)->T) { - for (k, v) in self.0.read().iter() { - let _ = ator(k.as_ref(), v.as_ref()); - } - } - - /// Count modes. - pub fn len (&self) -> usize { - self.0.read().len() - } - -} - impl Mode { /// Add a definition to the mode. @@ -142,14 +114,38 @@ impl Mode { } -impl AsMut for Mode { - fn as_mut (&mut self) -> &mut Self { - self - } -} - impl ControlAxis { pub fn get_axis <'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 + } +} diff --git a/src/view.rs b/src/conf/view.rs similarity index 94% rename from src/view.rs rename to src/conf/view.rs index c6b314eb..db8e03bd 100644 --- a/src/view.rs +++ b/src/conf/view.rs @@ -1,8 +1,29 @@ use crate::*; +impl_debug!( View: |self, w| { write!(w, "View({})", self.source) }); +impl_display!( View: |self, w| { write!(w, "View({})", self.source) }); +impl_default!(ClockView: { + let mut beat = String::with_capacity(16); + let _ = write!(beat, "{}", Self::BEAT_EMPTY); + let mut time = String::with_capacity(16); + let _ = write!(time, "{}", Self::TIME_EMPTY); + let mut bpm = String::with_capacity(16); + let _ = write!(bpm, "{}", Self::BPM_EMPTY); + Self { + beat: Memo::new(None, beat), + time: Memo::new(None, time), + bpm: Memo::new(None, bpm), + sr: Memo::new(None, String::with_capacity(16)), + buf: Memo::new(None, String::with_capacity(16)), + lat: Memo::new(None, String::with_capacity(16)), + } +}); + /// Collection of custom view definitions. #[derive(Default, Clone, Debug)] -pub struct Views(pub Arc, Arc>>>>); +pub struct Views( + pub Arc, Arc>>>> +); /// Custom view definition is a boxed closure emitting a [Draw]able from state `S`. pub struct View { @@ -57,7 +78,7 @@ pub fn view_debug (perf: &PerfModel, size: &Sizer) -> impl Draw { pub fn draw_mode_stack (to: &mut Tui, state: &App) -> Usually<()> { for name in state.modes.read().iter() { let mut error = false; - if let Some(mode) = state.config.get_mode(name).as_ref().map(Arc::clone) { + if let Some(mode) = state.modes_get(name).as_ref().map(Arc::clone) { for (index, view) in mode.view.iter().enumerate() { match (view.render)(state, to) { Ok(None) => {}, @@ -86,35 +107,6 @@ pub fn draw_mode_stack (to: &mut Tui, state: &App) -> Usually<()> { Ok(()) } -impl Views { - /// Load custom view definition. - pub fn add <'a> (&self, expr: impl Language) -> UsuallyRef<'a, ()> { - self.0.write().insert( - expr.head()?.ok_or("view: missing name")?.into(), - View::new(expr.tail()?.ok_or("view: missing body")?)?.into() - ); - Ok(()) - } - - pub fn clear (&self) { - *self.0.try_write().unwrap() = Default::default(); - } - - pub fn get (&self, name: impl AsRef) -> Option>> { - self.0.try_read().map(|views|views.get(name.as_ref()).cloned()).flatten() - } - - pub fn for_each (&self, cb: impl Fn(&Arc, &Arc>)->T) -> () { - for (k, v) in self.0.read().iter() { - cb(k, v); - } - } -} - -impl_debug!( View: |self, w| { write!(w, "View({})", self.source) }); - -impl_display!( View: |self, w| { write!(w, "View({})", self.source) }); - impl View { pub fn new (source: impl AsRef) -> Usually { @@ -402,7 +394,7 @@ impl View { Some("TracksOutputsAdd") => view!(|app|app.project.view_track_output_add()), Some("TracksOutputsCells") => view!(|app|app.project.view_track_output_cells()), Some("TracksOutputsCount") => view!(|app|app.project.view_track_output_count()), - Some(_) => Box::new(move|state, to|if let Some(view) = state.config.get_view(word.as_ref()) { + Some(_) => Box::new(move|state, to|if let Some(view) = state.views_get(word.as_ref()) { (view.render)(state, to) } else { fg(Color::Rgb(128, 32, 32), format!("undefined: {word:?}")).draw(to) @@ -451,7 +443,7 @@ pub fn view_config (app: &App) -> impl Draw { let XYWH(x0, y0, w, h) = area; let mut y = 0; for key in app.modes.read().iter().rev() { - if let Some(mode) = app.config.modes.get(key) { + if let Some(mode) = app.modes_get(key) { to.clip(XYWH(x0 + 1, y0 + y, w, 1), &|to|Split::East.bar(16, key, mode.info.get(0)).draw(to))?; @@ -465,14 +457,14 @@ pub fn view_config (app: &App) -> impl Draw { for bind in mode.keys.iter() { to.clip(XYWH(x0 + 3, y0 + y, w, 1), &|to|bind.draw(to))?; next_line!(h, y, 1); - if let Some(bind) = app.config.binds.find(bind) { + if let Some(bind) = app.binds_find(bind) { for (key, binding) in bind.0.iter() { to.clip(XYWH(x0 + 5, y0 + y, 40, 1), &|to|Split::East.bar(40, format!("{}", key), //binding.description.get(0) "").draw(to))?; next_line!(h, y, 1); } - } + } } next_line!(h, y, 1); } @@ -946,7 +938,7 @@ fn view_modes <'a, S: HasModeStack> (state: &'a S) -> impl Draw + 'a { let XYWH(x, y, w, h) = to.area(); let mut x_next = x; let mut w_used = 0u16; - for item in state.modes().iter() { + for item in state.mode_stack().read().iter() { let area = XYWH(x_next, y, w.minus(w_used), h); if let Some(used) = to.draw(area, item)? { let used_w = used.w() + 1; @@ -1289,10 +1281,10 @@ pub fn view_selection <'a> (state: &'a App) -> impl Draw + use<'a> { } pub fn view_templates <'a> (state: &'a App) -> impl Draw + use<'a> { - let height = (state.config.modes.len() * 2) as u16; + let height = (state.config.modes_count() * 2) as u16; draw(move |to: &mut Tui|{ let mut index = 0; - state.config.modes.for_each(&mut |id: &str, profile: &Mode| { + state.config.modes_for_each(&mut |id: &str, profile: &Mode| { let b = if index == 0 { Rgb(70,70,70) } else { Rgb(50,50,50) }; let name = profile.name.get(0).map(|x|x.as_ref()).unwrap_or(""); let info = profile.info.get(0).map(|x|x.as_ref()).unwrap_or(""); @@ -1651,23 +1643,6 @@ impl Memo { pub time: Memo, String>, } -impl_default!(ClockView: { - let mut beat = String::with_capacity(16); - let _ = write!(beat, "{}", Self::BEAT_EMPTY); - let mut time = String::with_capacity(16); - let _ = write!(time, "{}", Self::TIME_EMPTY); - let mut bpm = String::with_capacity(16); - let _ = write!(bpm, "{}", Self::BPM_EMPTY); - Self { - beat: Memo::new(None, beat), - time: Memo::new(None, time), - bpm: Memo::new(None, bpm), - sr: Memo::new(None, String::with_capacity(16)), - buf: Memo::new(None, String::with_capacity(16)), - lat: Memo::new(None, String::with_capacity(16)), - } -}); - impl ClockView { pub const BEAT_EMPTY: &'static str = "-.-.--"; pub const TIME_EMPTY: &'static str = "-.---s"; @@ -1716,6 +1691,18 @@ impl ClockView { } } +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 + } +} + #[cfg(test)] mod test_view { use super::*; use proptest::prelude::*; @@ -1735,64 +1722,3 @@ impl ClockView { } } } - -//impl<'a> Iterator for EntriesIterator<'a, Tui> { - //type Item = impl Draw; - //fn next (&mut self) -> Option { - //let dirs = self.browser.dirs.len(); - //let files = self.browser.files.len(); - //let index = self.index; - //if self.index < dirs { - //self.index += 1; - //Some(bold(true, self.browser.dirs[index].1.as_str())) - //} else if self.index < dirs + files { - //self.index += 1; - //Some(bold(false, self.browser.files[index - dirs].1.as_str())) - //} else { - //None - //} - //} -//} - -//impl<'a> PoolView<'a> { - //fn tui (&self) -> impl Draw<'_, Tui> { - //let Self(pool) = self; - ////let color = self.1.clip().map(|c|c.try_read().unwrap().color).unwrap_or_else(||g(32).into()); - ////let on_bg = |x|x;//below(Repeat(" "), bg(color.darkest.term, x)); - ////let border = |x|x;//Outer(Style::default().fg(color.dark.term).bg(color.darkest.term)).enclose(x); - ////let height = pool.clips.try_read().unwrap().len() as u16; - //iter( - //||pool.clips.clone().into_iter(), - //move|clip: Arc>, i: usize|{ - //let MidiClip { ref name, color, length, .. } = *clip.try_read().unwrap(); - //let item_height = 1; - //let _item_offset = i as u16 * item_height; - //let selected = i == pool.clip_index(); - //let b = if selected { color.light.term } else { color.base.term }; - //let f = color.lightest.term; - //let name = if false { format!(" {i:>3}") } else { format!(" {i:>3} {name}") }; - //let length = if false { String::default() } else { format!("{length} ") }; - //bg(b, below!( - //fg(f, bold(selected, name)).origin_w().full_w(), - //fg(f, bold(selected, length)).origin_e().full_w(), - //when(selected, bold(true, fg(g(255), "▶"))).origin_w().full_w(), - //when(selected, bold(true, fg(g(255), "◀"))).origin_e().full_w(), - //)).exact_h(1) - //}).origin_n().full_h().exact_w(20) - //} -//} - -//impl ClipLength { - //fn tui (&self) -> impl Draw<'_, Tui> { - //use ClipLengthFocus::*; - //let bars = format!("{}", self.bars()); - //let beats = format!("{}", self.beats()); - //let ticks = format!("{:>02}", self.ticks()); - //match self.focus { - //None => east!(" ", bars, ".", beats, ".", ticks), - //Some(Bar) => east!("[", bars, "]", beats, ".", ticks), - //Some(Beat) => east!(" ", bars, "[", beats, "]", ticks), - //Some(Tick) => east!(" ", bars, ".", beats, "[", ticks), - //} - //} -//} diff --git a/src/deps.rs b/src/deps.rs index 964a91e1..92bc394b 100644 --- a/src/deps.rs +++ b/src/deps.rs @@ -5,6 +5,7 @@ pub extern crate tengri; #[allow(unused)] pub(crate) use ::{ std::{ + borrow::Borrow, cmp::Ord, collections::{BTreeMap, HashMap}, error::Error, diff --git a/src/clap.rs b/src/device/clap.rs similarity index 100% rename from src/clap.rs rename to src/device/clap.rs diff --git a/src/lv2.rs b/src/device/lv2.rs similarity index 100% rename from src/lv2.rs rename to src/device/lv2.rs diff --git a/src/lv2_gui.rs b/src/device/lv2_gui.rs similarity index 100% rename from src/lv2_gui.rs rename to src/device/lv2_gui.rs diff --git a/src/mixer.rs b/src/device/mixer.rs similarity index 96% rename from src/mixer.rs rename to src/device/mixer.rs index 9233a192..7a0f2e51 100644 --- a/src/mixer.rs +++ b/src/device/mixer.rs @@ -1,5 +1,50 @@ use crate::*; +def_sizes_iter!(TracksSizes => Track); +impl_as_mut!(Vec: |self: App| self.project.as_mut()); +impl_as_ref!(Vec: |self: App| self.project.as_ref()); +impl_has!(Sequencer: |self: Track| self.sequencer); +impl_has!(Vec: |self: Project| self.tracks); +impl_audio!(|self: DeviceAudio<'a>, client, scope|{ + use Device::*; + match self.0 { + Mute => { Control::Continue }, + Bypass => { /*TODO*/ Control::Continue }, + #[cfg(feature = "sampler")] Sampler(sampler) => sampler.process(client, scope), + #[cfg(feature = "lv2")] Lv2(lv2) => lv2.process(client, scope), + #[cfg(feature = "vst2")] Vst2 => { todo!() }, // TODO + #[cfg(feature = "vst3")] Vst3 => { todo!() }, // TODO + #[cfg(feature = "clap")] Clap => { todo!() }, // TODO + #[cfg(feature = "sf2")] Sf2 => { todo!() }, // TODO + } +}); +impl_draw!(|self: RmsMeter, to: Tui|{ + let XYWH(x, y, w, h) = to.area().into(); + let signal = f32::max(0.0, f32::min(100.0, self.0.abs())); + let v = (signal * h as f32).ceil() as u16; + let y2 = y + h; + //to.blit(&format!("\r{v} {} {signal}", self.0), x * 30, y, Some(Style::default())); + for y in y..(y + v) { + for x in x..(x + w) { + to.blit(&"▌", x, y2.saturating_sub(y), Some(Style::default().green())); + } + } + Ok(Some(to.area().into())) +}); +impl_draw!(|self: Log10Meter, to: Tui| { + let XYWH(x, y, w, h) = to.area().into(); + let signal = 100.0 - f32::max(0.0, f32::min(100.0, self.0.abs())); + let v = (signal * h as f32 / 100.0).ceil() as u16; + let y2 = y + h; + //to.blit(&format!("\r{v} {} {signal}", self.0), x * 20, y, None); + for y in y..(y + v) { + for x in x..(x + w) { + to.blit(&"▌", x, y2 - y, Some(Style::default().green())); + } + } + Ok(Some(to.area().into())) +}); + #[derive(Debug, Default)] pub enum MixingMode { #[default] Summing, @@ -41,10 +86,8 @@ pub fn mix_average ( true } -#[derive(Debug, Default)] pub enum MeteringMode { - #[default] Rms, - Log10, -} +#[derive(Debug, Default)] +pub enum MeteringMode { #[default] Rms, Log10, } #[derive(Debug, Default, Clone)] pub struct Log10Meter(pub f32); @@ -52,34 +95,6 @@ pub struct Log10Meter(pub f32); #[derive(Debug, Default, Clone)] pub struct RmsMeter(pub f32); -impl_draw!(|self: RmsMeter, to: Tui|{ - let XYWH(x, y, w, h) = to.area().into(); - let signal = f32::max(0.0, f32::min(100.0, self.0.abs())); - let v = (signal * h as f32).ceil() as u16; - let y2 = y + h; - //to.blit(&format!("\r{v} {} {signal}", self.0), x * 30, y, Some(Style::default())); - for y in y..(y + v) { - for x in x..(x + w) { - to.blit(&"▌", x, y2.saturating_sub(y), Some(Style::default().green())); - } - } - Ok(Some(to.area().into())) -}); - -impl_draw!(|self: Log10Meter, to: Tui| { - let XYWH(x, y, w, h) = to.area().into(); - let signal = 100.0 - f32::max(0.0, f32::min(100.0, self.0.abs())); - let v = (signal * h as f32 / 100.0).ceil() as u16; - let y2 = y + h; - //to.blit(&format!("\r{v} {} {signal}", self.0), x * 20, y, None); - for y in y..(y + v) { - for x in x..(x + w) { - to.blit(&"▌", x, y2 - y, Some(Style::default().green())); - } - } - Ok(Some(to.area().into())) -}); - pub fn to_log10 (samples: &[f32]) -> f32 { let total: f32 = samples.iter().map(|x|x.abs()).sum(); let count = samples.len() as f32; @@ -174,12 +189,6 @@ impl HasWidth for Track { } } -def_sizes_iter!(TracksSizes => Track); -impl_has!(Vec: |self: Project| self.tracks); -impl_as_ref!(Vec: |self: App| self.project.as_ref()); -impl_as_mut!(Vec: |self: App| self.project.as_mut()); -impl_has!(Sequencer: |self: Track| self.sequencer); - impl App { /// FIXME: generalize. Set picked device in device pick dialog. /// @@ -285,19 +294,6 @@ impl Device { /// Some sort of wrapper? pub struct DeviceAudio<'a>(pub &'a mut Device); -impl_audio!(|self: DeviceAudio<'a>, client, scope|{ - use Device::*; - match self.0 { - Mute => { Control::Continue }, - Bypass => { /*TODO*/ Control::Continue }, - #[cfg(feature = "sampler")] Sampler(sampler) => sampler.process(client, scope), - #[cfg(feature = "lv2")] Lv2(lv2) => lv2.process(client, scope), - #[cfg(feature = "vst2")] Vst2 => { todo!() }, // TODO - #[cfg(feature = "vst3")] Vst3 => { todo!() }, // TODO - #[cfg(feature = "clap")] Clap => { todo!() }, // TODO - #[cfg(feature = "sf2")] Sf2 => { todo!() }, // TODO - } -}); pub fn device_kinds () -> &'static [&'static str] { &[ diff --git a/src/sample.rs b/src/device/sample.rs similarity index 100% rename from src/sample.rs rename to src/device/sample.rs diff --git a/src/vst2.rs b/src/device/vst2.rs similarity index 100% rename from src/vst2.rs rename to src/device/vst2.rs diff --git a/src/vst3.rs b/src/device/vst3.rs similarity index 100% rename from src/vst3.rs rename to src/device/vst3.rs diff --git a/src/project.rs b/src/project.rs index 9822a08f..097d13a3 100644 --- a/src/project.rs +++ b/src/project.rs @@ -1,11 +1,89 @@ use crate::*; + +def_sizes_iter!(PortsSizes => Arc, [Connect]); +def_sizes_iter!(ScenesSizes => Scene); +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()); +impl_as_ref!(Option: |self: Project| &self.editor); +impl_as_ref_opt!(Scene: |self: Project| self.selected_scene()); +impl_as_ref_opt!(Track: |self: Project| self.selected_track()); +impl_has!(Clock: |self: Project| self.clock); +impl_has!(Jack<'static>: |self: Project| self.jack); +impl_has!(Selection: |self: Project| self.selection); +impl_has!(Sizer: |self: Project| self.size); +impl_has!(Vec: |self: Project| self.midi_ins); +impl_has!(Vec: |self: Project| self.midi_outs); + +#[cfg(feature = "cli")] pub use self::cli::*; +#[cfg(feature = "cli")] mod cli { + use super::*; + + /// 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) + } + } +} + /// Arranger. /// /// ``` /// let arranger = tek::Project::default(); /// ``` #[namespace(ClipPool ClipPool::eval)] -#[derive(Default, Debug)] pub struct Project { +#[derive(Default, Debug)] +pub struct Project { /// JACK client handle. pub jack: Jack<'static>, /// Project name. @@ -50,8 +128,24 @@ use crate::*; } impl Project { + pub fn new (jack: &Jack<'static>) -> Self { + Self { + jack: jack.clone(), + ..Default::default() + } + } + + pub fn new_jack (name: impl AsRef) -> Usually { + let name = name.as_ref(); + Ok(Self { + name: name.into(), + jack: Jack::new(name)?, + ..Default::default() + }) + } + /// Create a new arrangement. - pub fn new ( + pub fn create ( jack: &Jack<'static>, name: Arc, clock: Clock, @@ -106,15 +200,6 @@ impl Project { } } -impl_has!(Jack<'static>: |self: Project| self.jack); -impl_has!(Sizer: |self: Project| self.size); -impl_has!(Clock: |self: Project| self.clock); -impl_has!(Selection: |self: Project| self.selection); -impl_as_ref_opt!(Scene: |self: Project| self.selected_scene()); -impl_as_mut_opt!(Scene: |self: Project| self.selected_scene_mut()); -impl_as_ref!(Option: |self: Project| &self.editor); -impl_as_mut!(Option: |self: Project| &mut self.editor); - impl HasJack<'static> for Project { fn jack (&self) -> &Jack<'static> { &self.jack @@ -272,13 +357,6 @@ impl HasScenes for Project { } } -def_sizes_iter!(PortsSizes => Arc, [Connect]); - -impl_has!(Vec: |self: Project| self.midi_ins); -impl_has!(Vec: |self: Project| self.midi_outs); -impl_has!(Vec: |self: App|self.project.midi_ins); -impl_has!(Vec: |self: App|self.project.midi_outs); - pub struct Junction(T); impl Draw for Junction { @@ -287,11 +365,6 @@ impl Draw for Junction { } } -impl_as_ref_opt!(Track: |self: Project| self.selected_track()); -impl_as_mut_opt!(Track: |self: Project| self.selected_track_mut()); -impl_as_ref_opt!(Track: |self: App| self.project.as_ref_opt()); -impl_as_mut_opt!(Track: |self: App| self.project.as_mut_opt()); - /// Represents the current user selection in the arranger #[derive(PartialEq, Clone, Copy, Debug, Default)] pub enum Selection { @@ -518,78 +591,4 @@ impl Scene { } } - pub type SceneWith<'a, T> = (usize, &'a Scene, usize, usize, T); - -def_sizes_iter!(ScenesSizes => Scene); -impl_as_ref_opt!(Scene: |self: App| self.project.as_ref_opt()); -impl_as_mut_opt!(Scene: |self: App| self.project.as_mut_opt()); - -//impl_from!(Pool: |clip:&Arc>|{ - //let model = Self::default(); - //model.clips.try_write().unwrap().push(clip.clone()); - //model.clip.store(1, Relaxed); - //model -//}); -//impl Pool { - //pub fn clip_index (&self) -> usize { - //self.clip.load(Relaxed) - //} - //pub fn set_clip_index (&self, value: usize) { - //self.clip.store(value, Relaxed); - //} - //pub fn mode (&self) -> &Option { - //&self.mode - //} - //pub fn mode_mut (&mut self) -> &mut Option { - //&mut self.mode - //} - //pub fn begin_import (&mut self) -> Usually<()> { - //*self.mode_mut() = Some(PoolMode::Import( - //self.clip_index(), - //Browse::new(None)? - //)); - //Ok(()) - //} - //pub fn begin_export (&mut self) -> Usually<()> { - //*self.mode_mut() = Some(PoolMode::Export( - //self.clip_index(), - //Browse::new(None)? - //)); - //Ok(()) - //} - //pub fn new_clip (&self) -> MidiClip { - //MidiClip::new("Clip", true, 4 * PPQ, None, Some(ItemTheme::random())) - //} - //pub fn cloned_clip (&self) -> MidiClip { - //let index = self.clip_index(); - //let mut clip = self.clips.read()[index].read().duplicate(); - //clip.color = ItemTheme::random_near(clip.color, 0.25); - //clip - //} - //pub fn add_new_clip (&self) -> (usize, Arc>) { - //let clip = Arc::new(RwLock::new(self.new_clip())); - //let index = { - //let mut clips = self.clips.try_write().unwrap(); - //clips.push(clip.clone()); - //clips.len().saturating_sub(1) - //}; - //self.clip.store(index, Relaxed); - //(index, clip) - //} - //pub fn delete_clip (&mut self, clip: &MidiClip) -> bool { - //let index = self.clips.try_read().unwrap().iter().position(|x|*x.try_read().unwrap()==*clip); - //if let Some(index) = index { - //self.clips.try_write().unwrap().remove(index); - //return true - //} - //false - //} - //fn _clip_new (&self) -> MidiClip { self.new_clip() } - //fn _clip_cloned (&self) -> MidiClip { self.cloned_clip() } - //fn _clip_index_current (&self) -> usize { 0 } - //fn _clip_index_after (&self) -> usize { 0 } - //fn _clip_index_previous (&self) -> usize { 0 } - //fn _clip_index_next (&self) -> usize { 0 } - //fn _color_random (&self) -> ItemColor { ItemColor::random() } -//} diff --git a/src/tek.rs b/src/tek.rs index 143dd9b6..90df98dc 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -1,5 +1,43 @@ #![allow(clippy::unit_arg)] +impl_as_ref!(Config: |self: App|self.config.config.as_ref()); +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_audio!(App: tek_jack_process, tek_jack_event); + +impl_has!(Clock: |self: App|self.project.clock); +impl_has!(Dialog: |self: App|self.dialog); +impl_has!(Jack<'static>: |self: App|self.jack); +impl_has!(Pool: |self: App|self.project.clips); +impl_has!(Pool: |self: App|self.project.samples); +impl_has!(Selection: |self: App|self.project.selection); +impl_has!(Sizer: |self: App|self.size); +impl_has!(Vec: |self: App|self.project.midi_ins); +impl_has!(Vec: |self: App|self.project.midi_outs); + +impl_from!(AppCmd: |x: BrowseCmd| AppCmd::Browse { command: x }); +impl_from!(AppCmd: |x: MidiClipCmd| AppCmd::MidiClip { command: x }); +impl_from!(AppCmd: |x: MidiEditCmd| AppCmd::MidiEdit { command: x }); +impl_from!(AppCmd: |x: ProjectCmd| AppCmd::Project { command: x }); +impl_from!(AppCmd: |x: SceneCmd| AppCmd::Scene { command: x }); +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); + +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),+) => { @@ -8,34 +46,36 @@ macro_rules! def_sizes_iter { } } -mod bind; pub use self::bind::*; -mod cli; pub use self::cli::*; mod conf; pub use self::conf::*; mod deps; pub use self::deps::*; -mod mixer; pub use self::mixer::*; -mod mode; pub use self::mode::*; mod pool; pub use self::pool::*; mod project; pub use self::project::*; -mod sample; pub use self::sample::*; mod sequence; pub use self::sequence::*; -mod view; pub use self::view::*; -#[cfg(feature = "lv2")] mod lv2; -#[cfg(feature = "lv2")] pub use self::lv2::*; +pub use self::device::*; +mod device { + mod mixer; pub use self::mixer::*; + mod sample; pub use self::sample::*; -#[cfg(feature = "lv2_gui")] mod lv2_gui; -#[cfg(feature = "lv2_gui")] pub use self::lv2_gui::*; + #[cfg(feature = "lv2")] mod lv2; + #[cfg(feature = "lv2")] pub use self::lv2::*; -#[cfg(feature = "vst2")] mod vst2; -#[cfg(feature = "vst2")] pub use self::vst2::*; + #[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)] #[hotpath::main] fn main () -> Usually<()> { tengri::Tui::setup_panic(); - #[cfg(feature = "cli")] let outcome = Config::watched(run_with_config); - #[cfg(not(feature = "cli"))] let outcome = Config::watched(run_new_plain); + #[cfg(not(feature = "cli"))] + let outcome = ConfigWatcher::run_watched(main_plain, None); + #[cfg(feature = "cli")] + let outcome = ConfigWatcher::run_watched(main_cli, None); if let Err(e) = outcome { println!("{e:#?}"); std::process::exit(1); @@ -44,7 +84,7 @@ fn main () -> Usually<()> { } #[cfg(not(feature = "cli"))] -fn run_new_plain (config: Config) -> Usually<()> { +fn main_plain (config: Arc) -> Usually<()> { let name = "tek"; tengri::Tui::run_main(Jack::new_run(name, move|jack|{ let mode = "Menu"; @@ -53,7 +93,7 @@ fn run_new_plain (config: Config) -> Usually<()> { let clock = Clock::new(&jack, Some(bpm))?; let tracks = []; let scenes = []; - let project = Project::new( + let project = Project::create( &jack, title.into(), clock, @@ -72,6 +112,119 @@ fn run_new_plain (config: Config) -> 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(()) + } + } +} + /// Total application state. /// /// ``` @@ -96,34 +249,34 @@ fn run_new_plain (config: Config) -> Usually<()> { /// let _ = app.project.h_scenes(); /// ``` #[derive(Default, Debug)] -#[namespace(Arc<[Connect]> get_arc_array_connect)] -#[namespace(Arc get_arc_str)] -#[namespace(Color get_color)] -#[namespace(ControlAxis ControlAxis::get_axis)] -#[namespace(Dialog Dialog::get_dialog)] +#[namespace(Arc<[Connect]> get_arc_array_connect)] +#[namespace(Arc get_arc_str)] +#[namespace(Color get_color)] +#[namespace(ControlAxis ControlAxis::get_axis)] +#[namespace(Dialog Dialog::get_dialog)] #[namespace(ItemColor)] #[namespace(ItemTheme)] #[namespace(MidiClip)] -#[namespace(ClipSlot ClipSlot::eval)] -#[namespace(ClipPool ClipPool::eval)] -#[namespace(SampleSlot SampleSlot::eval)] -#[namespace(SamplePool SamplePool::eval)] -#[namespace(Option> get_opt_arc_array_connect)] -#[namespace(Option> get_opt_arc_str)] -#[namespace(Option get_opt_itemtheme)] -#[namespace(Option get_opt_bool)] -#[namespace(Option get_opt_u16)] -#[namespace(Option get_opt_u32)] -#[namespace(Option get_opt_usize)] -#[namespace(PathBuf get_path_buf)] -#[namespace(Vec> get_vec_arc_str)] -#[namespace(bool get_bool)] -#[namespace(f64 get_f64)] +#[namespace(ClipSlot ClipSlot::eval)] +#[namespace(ClipPool ClipPool::eval)] +#[namespace(SampleSlot SampleSlot::eval)] +#[namespace(SamplePool SamplePool::eval)] +#[namespace(Option> get_opt_arc_array_connect)] +#[namespace(Option> get_opt_arc_str)] +#[namespace(Option get_opt_itemtheme)] +#[namespace(Option get_opt_bool)] +#[namespace(Option get_opt_u16)] +#[namespace(Option get_opt_u32)] +#[namespace(Option get_opt_usize)] +#[namespace(PathBuf get_path_buf)] +#[namespace(Vec> get_vec_arc_str)] +#[namespace(bool get_bool)] +#[namespace(f64 get_f64)] #[namespace(isize)] -#[namespace(u16 get_u16)] +#[namespace(u16 get_u16)] #[namespace(u32)] #[namespace(u8)] -#[namespace(usize get_usize)] +#[namespace(usize get_usize)] pub struct App { /// Exit flag pub exit: Exit, @@ -136,7 +289,7 @@ pub struct App { /// Performance counter pub perf: PerfModel, /// Available view modes and input bindings - pub config: Arc, + pub config: Arc, /// Currently selected mode pub modes: Arc>>>, /// Undo history @@ -155,16 +308,29 @@ impl App { /// Create a new application instance from a backend, project, config, and mode /// /// ``` - /// let mut proj = tek::Project::default(); - /// proj.jack = tek::tengri::Jack::new(&"test_tek").expect("failed to connect to jack"); - /// let config = "(mode hi (name Hello) (info World))"; - /// let mut conf = tek::Config::load(tek::Config::default(), config).unwrap(); - /// let tek = tek::App::new(None, proj, conf.into(), "hello"); + /// # 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!( + /// (mode Arranger (name Arranger) (info Launch grid.) + /// (axis X Track) + /// (axis Y Scene) + /// (bind Global Clock Tracks Scenes Clip Launch Color History) + /// (view (bar/n 2 StatusBar (bar/s 8 (bar/e 16 (align/nw Transport) + /// (bar/w 20 (bar/s 2 (bar/w 4 TracksOutputsAdd TracksOutputsCount) + /// (bar/s 2 (bar/w 4 TracksDevicesAdd TracksDevicesCount) + /// (bar/s 2 (bar/w 4 TracksNamesAdd TracksNamesCount) + /// (bar/w 4 TracksInputsAdd TracksInputsCount)))) + /// (align/x (bar/s 2 TracksOutputsCells + /// (bar/s 2 TracksDevicesCells + /// (bar/s 2 TracksNamesCells TracksInputsCells)))))) + /// (bar/w 20 (text) (bar/e 16 ScenesNames ScenesClips)))))) + /// )).unwrap(); /// ``` pub fn new ( exit: Option, project: Project, - config: Arc, + config: impl Into>>, mode: impl AsRef ) -> Self { App { @@ -173,7 +339,7 @@ impl App { color: ItemTheme::random(), dialog: Dialog::welcome(), modes: Arc::new(RwLock::new(vec![mode.as_ref().into()])), - config, + config: config.into().unwrap_or_default(), project, ..Default::default() } @@ -186,7 +352,6 @@ impl HasJack<'static> for App { } } -impl_audio!(App: tek_jack_process, tek_jack_event); fn tek_jack_process (state: &mut App, client: &Client, scope: &ProcessScope) -> Control { let t0 = state.perf.get_t0(); @@ -388,27 +553,9 @@ fn get_vec_arc_str <'a> (_: &App, _: impl Language) -> Perhaps>> { todo!() } -primitive!(u8: try_to_u8); -primitive!(u16: try_to_u16); -primitive!(usize: try_to_usize); -primitive!(isize: try_to_isize); -impl_has!(Clock: |self: App|self.project.clock); -impl_has!(Dialog: |self: App|self.dialog); -impl_has!(Jack<'static>: |self: App|self.jack); -impl_has!(Pool: |self: App|self.project.clips); -impl_has!(Pool: |self: App|self.project.samples); -impl_has!(Selection: |self: App|self.project.selection); -impl_has!(Sizer: |self: App|self.size); -impl_as_ref!(Option: |self: App|todo!()); -impl_as_mut!(Option: |self: App|todo!()); -impl_as_ref!(Option: |self: App|self.project.as_ref()); -impl_as_mut!(Option: |self: App|self.project.as_mut()); impl HasModeStack for App { - fn modes (&self) -> RwLockReadGuard<'_, Vec>> { - self.modes.read() - } - fn modes_mut (&mut self) -> RwLockWriteGuard<'_, Vec>> { - self.modes.write() + fn mode_stack (&self) -> &RwLock>> { + &self.modes } } impl HasClip for App { @@ -633,8 +780,6 @@ pub const fn wrap_dec (index: usize, count: usize) -> usize { /// ``` #[derive(Clone)] pub struct MenuItem(pub Arc, pub Arc<[AppCmd]>); -impl_debug!(MenuItem: |self, w| { write!(w, "{}", &self.0) }); -impl_default!(MenuItem: Self::new("", [])); impl PartialEq for MenuItem { fn eq (&self, other: &Self) -> bool { @@ -753,12 +898,126 @@ impl App { } } +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 { - /// Readable mode stack. - fn modes (&self) -> RwLockReadGuard<'_, Vec>>; - /// Writable mode stack. - fn modes_mut (&mut self) -> RwLockWriteGuard<'_, Vec>>; + /// Stack of active modes. + fn mode_stack (&self) -> &RwLock>>; } /// Has a MIDI editor. @@ -979,26 +1238,28 @@ impl App { Ok(()) } /// ``` - /// let app = tek::App::default(); - /// tek::Config::load(&app.config, stringify!( - /// (axis Y @down @up) - /// (mode Menu (axis Y prev next) (bind (@enter confirm))) + /// # 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(); - /// panic!("{app:#?}"); /// assert!(app.input_cmds_find(&"left".into()).is_none()); /// assert!(app.input_cmds_find(&"enter".into()).is_some()); - /// assert!(app.input_cmds_find(&"up".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.config.get_mode(name).as_ref().map(Arc::clone); + 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) + if let Some(event_map) = self.config.binds_find(id) && let Some(bindings) = event_map.query(input) { return Some(bindings.into()) } @@ -1033,15 +1294,6 @@ impl App { } } -impl_from!(AppCmd: |x: BrowseCmd| AppCmd::Browse { command: x }); -impl_from!(AppCmd: |x: MidiClipCmd| AppCmd::MidiClip { command: x }); -impl_from!(AppCmd: |x: MidiEditCmd| AppCmd::MidiEdit { command: x }); -impl_from!(AppCmd: |x: ProjectCmd| AppCmd::Project { command: x }); -impl_from!(AppCmd: |x: SceneCmd| AppCmd::Scene { command: x }); -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 }); - #[tek_proc::commands(AppCmd)] impl App { /// Exit the application. #[command(Exit = "exit")] pub fn cmd_exit (&mut self) -> Perhaps { @@ -1050,12 +1302,12 @@ impl_from!(AppCmd: |x: TracksCmd| AppCmd::Tracks { command: x }); } /// Switch to another set of contextual modes. #[command(Mode = "mode")] pub fn cmd_mode (&mut self, modes: Vec>) -> Perhaps { - *self.modes_mut() = modes.into(); + *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.modes_mut(); + let mut modes = self.mode_stack().write(); Ok(if modes.last() != Some(&mode) { modes.push(mode); Some(AppCmd::Leave) @@ -1065,7 +1317,7 @@ impl_from!(AppCmd: |x: TracksCmd| AppCmd::Tracks { command: x }); } /// Leave a contextual mode. #[command(Leave = "leave")] fn leave_mode (&mut self) -> Perhaps { - let mut modes = self.modes_mut(); + let mut modes = self.mode_stack().write(); if modes.len() > 1 { let mode = modes.pop(); Ok(mode.map(|mode|AppCmd::Enter { mode })) @@ -1771,243 +2023,32 @@ pub enum MidiOutputCmd { Connect(Arc), } -//impl - //+ Namespace - //+ Namespace> - //+ Namespace - //+ Namespace - //+ Namespace - //+ Namespace -//> PoolControl for T {} - -//#[tek_proc::commands(PolCmd = "pool")] pub trait PoolControl: HasPool - //+ Namespace - //+ Namespace - //+ Namespace> - //+ Namespace - //+ Namespace - //+ Namespace - //+ Namespace -//{ - ///// Toggle visibility of pool - //#[command(Show = "show")] fn show (&mut self, visible: bool) -> Perhaps { - //self.pool_mut().visible = visible; - //Ok(Some(PoolCmd::Show { visible: !visible })) - //} - ///// Set selected clip - //#[command(Select = "select")] fn select (&mut self, index: usize) -> Perhaps { - //self.pool_mut().set_clip_index(index); - //Ok(None) - //} - ///// Delete a clip from the pool - //#[command(Delete = "delete")] fn delete (&mut self, index: usize) -> Perhaps { - //let clip = self.pool_mut().remove(index).read().clone(); - //Ok(Some(PoolCmd::Add { index, clip })) - //} - ///// Switch places of two clips in pool - //#[command(Swap = "swap")] fn swap (&mut self, index: usize, other: usize) -> Perhaps { - //self.pool_mut().swap(index, other); - //Ok(Some(PoolCmd::Swap { index, other })) - //} - ///// Add clip to pool - //#[command(Add = "add")] fn clip_add (&mut self, index: usize, clip: MidiClip) -> Perhaps { - //let mut index = index; - //let clip = Arc::new(RwLock::new(clip.clone())); - //let mut clips = self.pool_mut().clips_mut(); - //if index >= clips.len() { - //index = clips.len(); - //clips.push(clip) - //} else { - //clips.insert(index, clip); - //} - //Ok(Some(PoolCmd::Delete { index })) - //} - ///// Set name of clip - //#[command(SetName = "set-name")] fn clip_set_name (&mut self, index: usize, name: Arc) -> Perhaps { - //Ok(self.pool_mut().clips.read().get(index).map(|clip|{ - //let old_name = clip.read().name.clone(); - //clip.write().name = name.clone(); - //PoolCmd::SetName { index, name: old_name } - //})) - //} - ///// Set length of clip - //#[command(SetLength = "set-length")] fn clip_set_length (&mut self, index: usize, length: usize) -> Perhaps { - //Ok(self.pool_mut().clips.read().get(index).map(|clip|{ - //let old_len = clip.read().length; - //clip.write().length = length; - //PoolCmd::SetLength { index, length: old_len } - //})) - //} - ///// Set color of clip - //#[command(SetColor = "set-color")] fn clip_set_color (&mut self, index: usize, color: ItemColor) -> Perhaps { - //let mut color = ItemTheme::from(color); - //std::mem::swap(&mut color, &mut self.pool().clips.read()[index].write().color); - //Ok(Some(PoolCmd::SetColor { index, color: color.base })) - //} - ///// Enter crop mode - //#[command(CropBegin = "crop/begin")] fn crop_begin (&mut self) -> Perhaps { - //let index = self.pool().clip_index(); - //let length = self.pool().clips.read()[index].read().length; - //*self.pool_mut().mode_mut() = Some(PoolMode::Length(index, length, ClipLengthFocus::Bar)); - //Ok(None) - //} - ///// Exit crop mode, discard - //#[command(CropCancel = "crop/cancel")] fn crop_cancel (&mut self) -> Perhaps { - //if let Some(PoolMode::Length(..)) = self.pool_mut().mode_mut().clone() { - //*self.pool_mut().mode_mut() = None; - //} - //Ok(None) - //} - //#[command(CropSet = "crop/set")] fn crop_set (&mut self, _length: usize) -> Perhaps { - //if let Some(PoolMode::Length(clip, ref mut length, ref mut _focus)) - //= self.pool_mut().mode_mut().clone() - //{ - //let old_length; - //{ - //let clip = self.pool().clips.read()[clip].clone();//.write(); - //old_length = Some(clip.read().length); - //clip.write().length = *length; - //} - //*self.pool_mut().mode_mut() = None; - //return Ok(old_length.map(|l|PoolCmd::CropSet { _length: l })) - //} - //Ok(None) - //} - //#[command(CropNext = "crop/next")] fn crop_next (&mut self) -> Perhaps { - //if let Some(PoolMode::Length( - //_clip, ref mut _length, ref mut focus - //)) = self.pool_mut().mode_mut().clone() { - //focus.next() - //}; - //Ok(None) - //} - //#[command(CropPrev = "crop/prev")] fn crop_prev (&mut self) -> Perhaps { - //if let Some(PoolMode::Length( - //_clip, ref mut _length, ref mut focus - //)) = self.pool_mut().mode_mut().clone() { - //focus.prev() - //}; - //Ok(None) - //} - //#[command(CropInc = "crop/inc")] fn crop_inc (&mut self) -> Perhaps { - //if let Some(PoolMode::Length( - //_clip, ref mut length, ref mut focus - //)) = self.pool_mut().mode_mut().clone() { - //match focus { - //ClipLengthFocus::Bar => { *length += 4 * PPQ }, - //ClipLengthFocus::Beat => { *length += PPQ }, - //ClipLengthFocus::Tick => { *length += 1 }, - //} - //} - //Ok(None) - //} - //#[command(CropDec = "crop/dec")] fn crop_dec (&mut self) -> Perhaps { - //if let Some(PoolMode::Length( - //_clip, ref mut length, ref mut focus - //)) = self.pool_mut().mode_mut().clone() { - //match focus { - //ClipLengthFocus::Bar => { *length = length.saturating_sub(4 * PPQ) }, - //ClipLengthFocus::Beat => { *length = length.saturating_sub(PPQ) }, - //ClipLengthFocus::Tick => { *length = length.saturating_sub(1) }, - //} - //} - //Ok(None) - //} - ///// Enter rename mode - //#[command(RenameBegin = "rename/begin")] fn rename_begin (&mut self) -> Perhaps { - //let index = self.pool().clip_index(); - //let name = self.pool().clips.read()[index].read().name.clone(); - //*self.pool_mut().mode_mut() = Some(PoolMode::Rename(index, name)); - //Ok(None) - //} - ///// Exit rename mode, discard - //#[command(RenameCancel = "rename/cancel")] fn rename_cancel (&mut self) -> Perhaps { - //if let Some(PoolMode::Rename(clip, ref mut old_name)) = self.pool_mut().mode_mut().clone() { - //self.pool().clips.read()[clip].write().name = old_name.clone().into(); - //} - //Ok(None) - //} - ///// Exit rename mode, update name - //#[command(RenameConfirm = "rename/confirm")] fn rename_confirm (&mut self) -> Perhaps { - //Ok(if let Some(PoolMode::Rename(_clip, ref mut old_name)) = self.pool_mut().mode_mut().clone() { - //let old_name = old_name.clone(); - //*self.pool_mut().mode_mut() = None; - //Some(PoolCmd::RenameSet { value: old_name }) - //} else { - //None - //}) - //} - //#[command(RenameSet = "rename/set")] fn rename_set (&mut self, value: Arc) -> Perhaps { - //if let Some(PoolMode::Rename(clip, ref mut _old_name)) = self.pool_mut().mode_mut().clone() { - //self.pool().clips.read()[clip].write().name = value.clone(); - //} - //Ok(None) - //} - /////// Import from file - ////#[command(Browse = "browse")] - ////fn browse (&mut self, command: BrowseCmd) -> Perhaps { - ////Ok(if let Some(browse) = self.pool_mut().browse.as_mut() { - ////command.act(browse)?.map(|command|PoolCmd::Browse{command}) - ////} else { - ////None - ////}) - ////} - ///// Import clip to pool from file - //#[command(Import = "import")] - //fn clip_import (&mut self, index: usize, path: PathBuf) -> Perhaps - //where Self: Sized - //{ - //let bytes = std::fs::read(&path)?; - //let smf = Smf::parse(bytes.as_slice())?; - //let mut t = 0u32; - //let mut events = vec![]; - //for track in smf.tracks.iter() { - //for event in track.iter() { - //t += event.delta.as_int(); - //if let TrackEventKind::Midi { channel, message } = event.kind { - //events.push((t, channel.as_int(), message)); - //} - //} - //} - //let mut clip = MidiClip::new("imported", true, t as usize + 1, None, None); - //for event in events.iter() { - //clip.notes[event.0 as usize].push(event.2); - //} - //Ok(PoolCmd::Add { index, clip }.dispatch(self)?) - //} - /////// Export to file - ////#[command(Export = "export")] - ////fn export (&mut self, command: BrowseCmd) -> Perhaps { - ////Ok(if let Some(browse) = self.pool_mut().browse.as_mut() { - ////command.act(browse)?.map(|command|PoolCmd::Export{command}) - ////} else { - ////None - ////}) - ////} -//} - -//pub(crate) struct EntriesIterator<'a, S: Screen> { - //pub browser: &'a Browse, - //pub offset: usize, - //pub length: usize, - //pub index: usize, - //_screen: std::marker::PhantomData -//} - -// Commands supported by [Browse] -//#[derive(Debug, Clone, PartialEq)] -//pub enum BrowseCommand { - //Begin, - //Cancel, - //Confirm, - //Select(usize), - //Chdir(PathBuf), - //Filter(Arc), -//} - -//impl AsRef> for MenuItems { - //fn as_ref (&self) -> &Arc<[MenuItem]> { - //&self.0 - //} -//} +pub fn print_status (project: &Project) { + println!("Name: {:?}", &project.name); + println!("JACK: {:?}", &project.jack); + println!("Buffer: {:?}", &project.clock.chunk); + println!("Sample rate: {:?}", &project.clock.timebase.sr); + println!("MIDI PPQ: {:?}", &project.clock.timebase.ppq); + println!("Tempo: {:?}", &project.clock.timebase.bpm); + println!("Quantize: {:?}", &project.clock.quant); + println!("Launch: {:?}", &project.clock.sync); + println!("Playhead: {:?}us", &project.clock.playhead.usec); + println!("Playhead: {:?}s", &project.clock.playhead.sample); + println!("Playhead: {:?}p", &project.clock.playhead.pulse); + println!("Started: {:?}", &project.clock.started); + println!("Tracks:"); + for (i, t) in project.tracks.iter().enumerate() { + println!(" Track {i}: {} {} {:?} {:?}", t.name, t.width, + &t.sequencer.play_clip, &t.sequencer.next_clip); + } + println!("Scenes:"); + for (i, t) in project.scenes.iter().enumerate() { + println!(" Scene {i}: {} {:?}", &t.name, &t.clips); + } + println!("MIDI Ins: {:?}", &project.midi_ins); + println!("MIDI Outs: {:?}", &project.midi_outs); + println!("Audio Ins: {:?}", &project.audio_ins); + println!("Audio Outs: {:?}", &project.audio_outs); + // TODO git integration + // TODO dawvert integration +}