axis config: bring failure under test

This commit is contained in:
i do not exist 2026-10-04 23:10:23 +03:00
parent 5aaa0f3e4e
commit a355e482c7
16 changed files with 1328 additions and 1149 deletions

View file

@ -1207,3 +1207,396 @@
//Ok(app)
//})?)?
//}
//impl<'a> Iterator for EntriesIterator<'a, Tui> {
//type Item = impl Draw<Tui>;
//fn next (&mut self) -> Option<Self::Item> {
//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<RwLock<MidiClip>>, 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<RwLock<App>>,
//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<T: HasPool
//+ Namespace<bool>
//+ Namespace<usize>
//+ Namespace<Arc<str>>
//+ Namespace<PathBuf>
//+ Namespace<MidiClip>
//+ Namespace<ItemColor>
//+ Namespace<BrowseCmd>
//> PoolControl for T {}
//#[tek_proc::commands(PolCmd = "pool")] pub trait PoolControl: HasPool
//+ Namespace<bool>
//+ Namespace<usize>
//+ Namespace<Arc<str>>
//+ Namespace<PathBuf>
//+ Namespace<MidiClip>
//+ Namespace<ItemColor>
//+ Namespace<BrowseCmd>
//{
///// Toggle visibility of pool
//#[command(Show = "show")] fn show (&mut self, visible: bool) -> Perhaps<PoolCmd> {
//self.pool_mut().visible = visible;
//Ok(Some(PoolCmd::Show { visible: !visible }))
//}
///// Set selected clip
//#[command(Select = "select")] fn select (&mut self, index: usize) -> Perhaps<PoolCmd> {
//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<PoolCmd> {
//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<PoolCmd> {
//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<PoolCmd> {
//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<str>) -> Perhaps<PoolCmd> {
//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<PoolCmd> {
//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<PoolCmd> {
//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<PoolCmd> {
//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<PoolCmd> {
//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<PoolCmd> {
//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<PoolCmd> {
//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<PoolCmd> {
//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<PoolCmd> {
//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<PoolCmd> {
//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<PoolCmd> {
//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<PoolCmd> {
//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<PoolCmd> {
//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<str>) -> Perhaps<PoolCmd> {
//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<PoolCmd> {
////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<PoolCmd>
//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<PoolCmd> {
////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<S>
//}
// Commands supported by [Browse]
//#[derive(Debug, Clone, PartialEq)]
//pub enum BrowseCommand {
//Begin,
//Cancel,
//Confirm,
//Select(usize),
//Chdir(PathBuf),
//Filter(Arc<str>),
//}
//impl AsRef<Arc<[MenuItem]>> for MenuItems {
//fn as_ref (&self) -> &Arc<[MenuItem]> {
//&self.0
//}
//}
//impl_from!(Pool<MidiClip>: |clip:&Arc<RwLock<MidiClip>>|{
//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<PoolMode> {
//&self.mode
//}
//pub fn mode_mut (&mut self) -> &mut Option<PoolMode> {
//&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<RwLock<MidiClip>>) {
//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() }
//}

View file

@ -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<Config>) -> 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<Arc<Config>>) -> 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<Config>, _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<String>,
/// 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<f64>,
/// 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<String>,
/// MIDI outs to connect to (multiple instances accepted)
#[arg(short='i', long)] midi_from_re: Vec<String>,
/// MIDI ins to connect to (multiple instances accepted)
#[arg(short='O', long)] midi_to: Vec<String>,
/// MIDI ins to connect to (multiple instances accepted)
#[arg(short='o', long)] midi_to_re: Vec<String>,
/// Audio outs to connect to left input
#[arg(short='l', long)] left_from: Vec<String>,
/// Audio outs to connect to right input
#[arg(short='r', long)] right_from: Vec<String>,
/// Audio ins to connect from left output
#[arg(short='L', long)] left_to: Vec<String>,
/// Audio ins to connect from right output
#[arg(short='R', long)] right_to: Vec<String>,
/// Tracks to creat
#[arg(short='t', long)] tracks: Option<usize>,
/// Scenes to create
#[arg(short='s', long)] scenes: Option<usize>,
}
impl ProjectInit {
pub fn init (&self) -> Usually<Project> {
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<RwLock<App>>,
//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)
//})?)?
//}

View file

@ -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 <C: AsRef<ConfigWatcher> + AsRef<Config>> (config: C) -> Usually<C> {
// 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 <C: AsRef<Config>, L: Language> (state: C, src: L) -> Usually<C> {
state.as_ref().config_clear();
src.each(state, |c, s|config_add(c, s.trim()))
}
/// Load configuration item.
fn config_add <C: AsRef<Config>, L: Language> (state: C, src: L) -> Usually<C> {
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<tek::Mode> = config.get_mode("Menu").unwrap();
/// let mode: Arc<Mode> = 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<Option<Arc<str>>>,
/// 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<Option<Arc<str>>>,
/// Timestamp
pub stamp: AtomicU64,
/// Watcher
pub watch: RwLock<Option<PollWatcher>>
}
impl_as_ref!(Config: |self: Config| self);
impl Config {
/// Write initial contents of configuration.
pub fn init <C: AsRef<Config>> (config: C) -> Usually<C> {
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 <C: AsRef<Config>, L: Language> (config: C, src: L) -> Usually<C> {
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<T: AsRef<Config>> HasError<Arc<str>> for T {
fn error (&self) -> &RwLock<Option<Arc<str>>> {
&self.as_ref().error
}
}
pub fn load_item <C: AsRef<Config>, L: Language> (config: C, src: L) -> Usually<C> {
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<Arc<str>> for Arc<ConfigWatcher> {
fn error (&self) -> &RwLock<Option<Arc<str>>> {
&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<Option<PollWatcher>>,
/// 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<BaseDirectories>) -> 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 <T: 'static> (
callback: impl FnOnce(Arc<Self>)->T,
dirs: impl Into<Option<BaseDirectories>>,
) -> Usually<T> {
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<Config>, poll: Option<Duration>
) -> Usually<()> {
pub fn watch (config: Arc<Self>, poll: Option<Duration>) -> 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<BaseDirectories>) -> 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 <T: 'static> (callback: impl FnOnce(Arc<Config>)->T) -> Usually<T> {
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<BaseDirectories>) -> Usually<Arc<Self>> {
Config::init(Arc::new(Self::new(None)))
}
/// Find the config file.
fn find_file (&self) -> Option<PathBuf> {
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<PathBuf, std::io::Error> {
self.dirs.place_config_file(Self::CONFIG)
self.dirs.place_config_file(Self::EDN)
}
/// Get path to config file.
fn get_file (&self) -> Option<PathBuf> {
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<str>) -> Option<Arc<Mode>> {
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<str>) -> Option<Arc<View<Tui, App>>> {
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 <T> (src: impl Language, eval: impl Fn(&str)->Usually<T>) -> Perhaps<T> {
@ -197,3 +218,57 @@ pub fn eval_word <T> (src: impl Language, eval: impl Fn(&str)->Usually<T>) -> 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!();
});
}

View file

@ -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<RwLock<BTreeMap<Arc<str>, Arc<Bind<TuiEvent, Arc<str>>>>>>,
pub Arc<RwLock<BTreeMap<Arc<str>, (Arc<str>, Arc<str>)>>>,
);
pub struct Binds {
pub keys: Arc<RwLock<BTreeMap<Arc<str>, Arc<Bind<TuiEvent, Arc<str>>>>>>,
pub axis: Arc<RwLock<BTreeMap<Arc<str>, Arc<Axis<Arc<str>>>>>>,
}
/// 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<str>) -> Option<Arc<Bind<TuiEvent, Arc<str>>>> {
self.keys.read().get(id.as_ref()).cloned()
}
pub fn binds_for_each <T> (
&self, mut cb: impl FnMut(&Arc<str>, &Bind<TuiEvent, Arc<str>>)->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::<TuiEvent, Arc<str>>::load_keys(&src).unwrap();
/// let keys = Bind::<TuiEvent, Arc<str>>::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::<TuiEvent, Arc<str>>::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<E, C>(
@ -50,105 +72,20 @@ pub struct Bind<E, C>(
pub BTreeMap<E, Vec<Binding<C>>>
);
/// 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<Arc<str>> = Default::default();
/// ```
#[derive(Debug, Clone)] pub struct Binding<C> {
pub commands: Arc<[C]>,
pub condition: Option<Condition>,
pub description: Option<Arc<str>>,
pub source: Option<Arc<PathBuf>>,
}
/// 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 Arc<Box<dyn Fn()->bool + Send + Sync>>);
/// Named control axis.
pub struct ControlAxis(pub Arc<str>);
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<str>) -> Option<Arc<Bind<TuiEvent, Arc<str>>>> {
self.0.read().get(id.as_ref()).cloned()
}
pub fn for_each <T> (
&self, mut cb: impl FnMut(&Arc<str>, &Bind<TuiEvent, Arc<str>>)->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<E, C> Default for Bind<E, C> {
fn default () -> Self { Self(Default::default()) }
}
impl<C: Default> Default for Binding<C> {
fn default () -> Self {
Self {
commands: Default::default(),
condition: Default::default(),
description: Default::default(),
source: Default::default(),
}
}
}
impl Bind<TuiEvent, Arc<str>> {
pub fn load_keys (body: impl Language) -> Usually<Self> {
let mut bind: Bind<TuiEvent, Arc<str>> = Bind::new();
pub fn new (body: impl Language) -> Usually<Self> {
let mut bind: Bind<TuiEvent, Arc<str>> = 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<Self> {
let mut bind: Bind<TuiEvent, Arc<str>> = 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<TuiEvent, Arc<str>> {
}
}
impl<E: Clone + Ord, C> Bind<E, C> {
/// Create a new event map
pub fn new () -> Self {
Default::default()
/// ```
/// use ::{std::sync::Arc, tek::*};
/// let axis = Axis::<Arc<str>>::new("@up @down").unwrap();
/// ```
pub struct Axis<T>(T, T);
impl_debug!(<T: Debug> Axis<T>: |self, w| { write!(w, "Axis({:?} {:?})", self.0, self.1) });
impl<T: for<'a> From<&'a str>, U: AsRef<str>, V: AsRef<str>> From<(U, V)> for Axis<T> {
fn from ((decr, incr): (U, V)) -> Self {
Self(decr.as_ref().into(), incr.as_ref().into())
}
}
impl<T: Default + for<'a> From<&'a str>> Axis<T> {
pub fn new (body: impl Language) -> Usually<Self> {
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<Self> {
//let mut bind: Bind<TuiEvent, Arc<str>> = 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<E: Clone + Ord, C> Bind<E, C> {
/// Add a binding to an owned event map.
pub fn def (mut self, event: E, binding: Binding<C>) -> Self {
self.add(event, binding);
@ -205,7 +182,67 @@ impl<E: Clone + Ord, C> Bind<E, C> {
}
}
/// 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<Arc<str>> = Default::default();
/// ```
#[derive(Debug, Clone)] pub struct Binding<C> {
pub commands: Arc<[C]>,
pub condition: Option<Condition>,
pub description: Option<Arc<str>>,
pub source: Option<Arc<PathBuf>>,
}
impl<C: Default> Default for Binding<C> {
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 Arc<Box<dyn Fn()->bool + Send + Sync>>);
/// Named control axis.
pub struct ControlAxis(pub Arc<str>);
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<BTreeMap<Arc<str>, Arc<Bind<TuiEvent, Arc<str>>>>> {
&self.config.config.binds.keys
}
fn binds_axis (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Axis<Arc<str>>>>> {
&self.config.config.binds.axis
}
}
impl HasBinds for ConfigWatcher {
fn binds_keys (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Bind<TuiEvent, Arc<str>>>>> {
&self.config.binds.keys
}
fn binds_axis (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Axis<Arc<str>>>>> {
&self.config.binds.axis
}
}
impl HasBinds for Config {
fn binds_keys (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Bind<TuiEvent, Arc<str>>>>> {
&self.binds.keys
}
fn binds_axis (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Axis<Arc<str>>>>> {
&self.binds.axis
}
}

View file

@ -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<RwLock<BTreeMap<Arc<str>, Arc<Mode>>>>);
@ -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<str>) -> Option<Arc<Mode>> {
self.0.read().get(name.as_ref()).cloned()
}
/// Run something for each mode.
pub fn for_each <T> (&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<Mode> for Mode {
fn as_mut (&mut self) -> &mut Self {
self
}
}
impl ControlAxis {
pub fn get_axis <'a> (_: &App, src: impl Language) -> Perhaps<ControlAxis> {
eval_word(src, |word|Ok(ControlAxis(word.into())))
}
}
impl HasModes for App {
fn modes (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Mode>>> {
&self.config.config.modes.0
}
}
impl HasModes for ConfigWatcher {
fn modes (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Mode>>> {
&self.config.modes.0
}
}
impl HasModes for Config {
fn modes (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Mode>>> {
&self.modes.0
}
}
impl HasModes for Modes {
fn modes (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Mode>>> {
&self.0
}
}
impl HasModes for Mode {
fn modes (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Mode>>> {
&self.modes.0
}
}

View file

@ -1,8 +1,29 @@
use crate::*;
impl_debug!(<S: Screen, T> View<S, T>: |self, w| { write!(w, "View({})", self.source) });
impl_display!(<S: Screen, T> View<S, T>: |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<RwLock<BTreeMap<Arc<str>, Arc<View<Tui, App>>>>>);
pub struct Views(
pub Arc<RwLock<BTreeMap<Arc<str>, Arc<View<Tui, App>>>>>
);
/// Custom view definition is a boxed closure emitting a [Draw]able from state `S`.
pub struct View<S: Screen, T> {
@ -57,7 +78,7 @@ pub fn view_debug (perf: &PerfModel, size: &Sizer) -> impl Draw<Tui> {
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<str>) -> Option<Arc<View<Tui, App>>> {
self.0.try_read().map(|views|views.get(name.as_ref()).cloned()).flatten()
}
pub fn for_each <T> (&self, cb: impl Fn(&Arc<str>, &Arc<View<Tui, App>>)->T) -> () {
for (k, v) in self.0.read().iter() {
cb(k, v);
}
}
}
impl_debug!(<S: Screen, T> View<S, T>: |self, w| { write!(w, "View({})", self.source) });
impl_display!(<S: Screen, T> View<S, T>: |self, w| { write!(w, "View({})", self.source) });
impl View<Tui, App> {
pub fn new (source: impl AsRef<str>) -> Usually<Self> {
@ -402,7 +394,7 @@ impl View<Tui, App> {
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<Tui> {
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<Tui> {
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<Tui> + '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<Tui> + use<'a> {
}
pub fn view_templates <'a> (state: &'a App) -> impl Draw<Tui> + 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("<no name>");
let info = profile.info.get(0).map(|x|x.as_ref()).unwrap_or("<no info>");
@ -1651,23 +1643,6 @@ impl<T: PartialEq, U> Memo<T, U> {
pub time: Memo<Option<f64>, 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<BTreeMap<Arc<str>, Arc<View<Tui, App>>>> {
&self.config.config.views.0
}
}
impl HasViews for Config {
fn views (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<View<Tui, App>>>> {
&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<Tui>;
//fn next (&mut self) -> Option<Self::Item> {
//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<RwLock<MidiClip>>, 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),
//}
//}
//}

View file

@ -5,6 +5,7 @@ pub extern crate tengri;
#[allow(unused)]
pub(crate) use ::{
std::{
borrow::Borrow,
cmp::Ord,
collections::{BTreeMap, HashMap},
error::Error,

View file

@ -1,5 +1,50 @@
use crate::*;
def_sizes_iter!(TracksSizes => Track);
impl_as_mut!(Vec<Track>: |self: App| self.project.as_mut());
impl_as_ref!(Vec<Track>: |self: App| self.project.as_ref());
impl_has!(Sequencer: |self: Track| self.sequencer);
impl_has!(Vec<Track>: |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 <const N: usize> (
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<Track>: |self: Project| self.tracks);
impl_as_ref!(Vec<Track>: |self: App| self.project.as_ref());
impl_as_mut!(Vec<Track>: |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] {
&[

View file

@ -1,11 +1,89 @@
use crate::*;
def_sizes_iter!(PortsSizes => Arc<str>, [Connect]);
def_sizes_iter!(ScenesSizes => Scene);
impl_as_mut!(Option<MidiEditor>: |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<MidiEditor>: |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<MidiInput>: |self: Project| self.midi_ins);
impl_has!(Vec<MidiOutput>: |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<String>,
/// 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<f64>,
/// 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<String>,
/// MIDI outs to connect to (multiple instances accepted)
#[arg(short='i', long)] midi_from_re: Vec<String>,
/// MIDI ins to connect to (multiple instances accepted)
#[arg(short='O', long)] midi_to: Vec<String>,
/// MIDI ins to connect to (multiple instances accepted)
#[arg(short='o', long)] midi_to_re: Vec<String>,
/// Audio outs to connect to left input
#[arg(short='l', long)] left_from: Vec<String>,
/// Audio outs to connect to right input
#[arg(short='r', long)] right_from: Vec<String>,
/// Audio ins to connect from left output
#[arg(short='L', long)] left_to: Vec<String>,
/// Audio ins to connect from right output
#[arg(short='R', long)] right_to: Vec<String>,
/// Tracks to creat
#[arg(short='t', long)] tracks: Option<usize>,
/// Scenes to create
#[arg(short='s', long)] scenes: Option<usize>,
}
impl ProjectInit {
pub fn init (&self) -> Usually<Project> {
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<str>) -> Usually<Self> {
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<str>,
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<MidiEditor>: |self: Project| &self.editor);
impl_as_mut!(Option<MidiEditor>: |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<str>, [Connect]);
impl_has!(Vec<MidiInput>: |self: Project| self.midi_ins);
impl_has!(Vec<MidiOutput>: |self: Project| self.midi_outs);
impl_has!(Vec<MidiInput>: |self: App|self.project.midi_ins);
impl_has!(Vec<MidiOutput>: |self: App|self.project.midi_outs);
pub struct Junction<T: JackPort>(T);
impl<T: JackPort> Draw<Tui> for Junction<T> {
@ -287,11 +365,6 @@ impl<T: JackPort> Draw<Tui> for Junction<T> {
}
}
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<MidiClip>: |clip:&Arc<RwLock<MidiClip>>|{
//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<PoolMode> {
//&self.mode
//}
//pub fn mode_mut (&mut self) -> &mut Option<PoolMode> {
//&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<RwLock<MidiClip>>) {
//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() }
//}

View file

@ -1,5 +1,43 @@
#![allow(clippy::unit_arg)]
impl_as_ref!(Config: |self: App|self.config.config.as_ref());
impl_as_mut!(Option<MidiEditor>: |self: App|self.project.as_mut());
impl_as_mut!(Option<Project>: |self: App|todo!());
impl_as_ref!(Option<MidiEditor>: |self: App|self.project.as_ref());
impl_as_ref!(Option<Project>: |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<MidiClip>: |self: App|self.project.clips);
impl_has!(Pool<Sample>: |self: App|self.project.samples);
impl_has!(Selection: |self: App|self.project.selection);
impl_has!(Sizer: |self: App|self.size);
impl_has!(Vec<MidiInput>: |self: App|self.project.midi_ins);
impl_has!(Vec<MidiOutput>: |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<ConfigWatcher>) -> 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<ConfigWatcher>) -> 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<Arc<ConfigWatcher>>) -> 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<ConfigWatcher>, _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<str> 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<str> 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<Arc<[Connect]>> get_opt_arc_array_connect)]
#[namespace(Option<Arc<str>> get_opt_arc_str)]
#[namespace(Option<ItemTheme> get_opt_itemtheme)]
#[namespace(Option<bool> get_opt_bool)]
#[namespace(Option<u16> get_opt_u16)]
#[namespace(Option<u32> get_opt_u32)]
#[namespace(Option<usize> get_opt_usize)]
#[namespace(PathBuf get_path_buf)]
#[namespace(Vec<Arc<str>> 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<Arc<[Connect]>> get_opt_arc_array_connect)]
#[namespace(Option<Arc<str>> get_opt_arc_str)]
#[namespace(Option<ItemTheme> get_opt_itemtheme)]
#[namespace(Option<bool> get_opt_bool)]
#[namespace(Option<u16> get_opt_u16)]
#[namespace(Option<u32> get_opt_u32)]
#[namespace(Option<usize> get_opt_usize)]
#[namespace(PathBuf get_path_buf)]
#[namespace(Vec<Arc<str>> 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<Config>,
pub config: Arc<ConfigWatcher>,
/// Currently selected mode
pub modes: Arc<RwLock<Vec<Arc<str>>>>,
/// 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<Exit>,
project: Project,
config: Arc<Config>,
config: impl Into<Option<Arc<ConfigWatcher>>>,
mode: impl AsRef<str>
) -> 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<Vec<Arc<str>>> {
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<MidiClip>: |self: App|self.project.clips);
impl_has!(Pool<Sample>: |self: App|self.project.samples);
impl_has!(Selection: |self: App|self.project.selection);
impl_has!(Sizer: |self: App|self.size);
impl_as_ref!(Option<Project>: |self: App|todo!());
impl_as_mut!(Option<Project>: |self: App|todo!());
impl_as_ref!(Option<MidiEditor>: |self: App|self.project.as_ref());
impl_as_mut!(Option<MidiEditor>: |self: App|self.project.as_mut());
impl HasModeStack for App {
fn modes (&self) -> RwLockReadGuard<'_, Vec<Arc<str>>> {
self.modes.read()
}
fn modes_mut (&mut self) -> RwLockWriteGuard<'_, Vec<Arc<str>>> {
self.modes.write()
fn mode_stack (&self) -> &RwLock<Vec<Arc<str>>> {
&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<str>, 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<T> {
fn error (&self) -> &RwLock<Option<T>>;
fn error_set (&self, e: Option<T>) {
*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<BTreeMap<Arc<str>, Arc<Mode>>>;
/// 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<str>) -> Option<Arc<Mode>> {
self.modes().read().get(name.as_ref()).cloned()
}
/// Run something for each mode.
fn modes_for_each <T> (&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<BTreeMap<Arc<str>, Arc<Bind<TuiEvent, Arc<str>>>>>;
fn binds_axis (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Axis<Arc<str>>>>>;
fn binds_clear (&self) {
*self.binds_keys().write() = Default::default();
*self.binds_axis().write() = Default::default();
}
fn binds_find (&self, id: impl AsRef<str>) -> Option<Arc<Bind<TuiEvent, Arc<str>>>> {
self.binds_keys().read().get(id.as_ref()).cloned()
}
fn binds_for_each <T> (
&self, mut cb: impl FnMut(&Arc<str>, &Bind<TuiEvent, Arc<str>>)->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<BTreeMap<Arc<str>, Arc<View<Tui, App>>>>;
/// 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<str>) -> Option<Arc<View<Tui, App>>> {
self.views().read().get(name.as_ref()).cloned()
}
fn views_for_each <T> (&self, cb: impl Fn(&Arc<str>, &Arc<View<Tui, App>>)->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<Arc<str>>>;
/// Writable mode stack.
fn modes_mut (&mut self) -> RwLockWriteGuard<'_, Vec<Arc<str>>>;
/// Stack of active modes.
fn mode_stack (&self) -> &RwLock<Vec<Arc<str>>>;
}
/// 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<Arc<[Binding<Arc<str>>]>> {
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<AppCmd> {
@ -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<Arc<str>>) -> Perhaps<AppCmd> {
*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<str>) -> Perhaps<AppCmd> {
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<AppCmd> {
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<str>),
}
//impl<T: HasPool
//+ Namespace<bool>
//+ Namespace<usize>
//+ Namespace<Arc<str>>
//+ Namespace<PathBuf>
//+ Namespace<MidiClip>
//+ Namespace<ItemColor>
//+ Namespace<BrowseCmd>
//> PoolControl for T {}
//#[tek_proc::commands(PolCmd = "pool")] pub trait PoolControl: HasPool
//+ Namespace<bool>
//+ Namespace<usize>
//+ Namespace<Arc<str>>
//+ Namespace<PathBuf>
//+ Namespace<MidiClip>
//+ Namespace<ItemColor>
//+ Namespace<BrowseCmd>
//{
///// Toggle visibility of pool
//#[command(Show = "show")] fn show (&mut self, visible: bool) -> Perhaps<PoolCmd> {
//self.pool_mut().visible = visible;
//Ok(Some(PoolCmd::Show { visible: !visible }))
//}
///// Set selected clip
//#[command(Select = "select")] fn select (&mut self, index: usize) -> Perhaps<PoolCmd> {
//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<PoolCmd> {
//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<PoolCmd> {
//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<PoolCmd> {
//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<str>) -> Perhaps<PoolCmd> {
//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<PoolCmd> {
//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<PoolCmd> {
//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<PoolCmd> {
//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<PoolCmd> {
//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<PoolCmd> {
//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<PoolCmd> {
//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<PoolCmd> {
//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<PoolCmd> {
//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<PoolCmd> {
//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<PoolCmd> {
//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<PoolCmd> {
//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<PoolCmd> {
//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<str>) -> Perhaps<PoolCmd> {
//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<PoolCmd> {
////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<PoolCmd>
//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<PoolCmd> {
////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<S>
//}
// Commands supported by [Browse]
//#[derive(Debug, Clone, PartialEq)]
//pub enum BrowseCommand {
//Begin,
//Cancel,
//Confirm,
//Select(usize),
//Chdir(PathBuf),
//Filter(Arc<str>),
//}
//impl AsRef<Arc<[MenuItem]>> 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
}