mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-10-08 18:56:42 +02:00
axis config: bring failure under test
This commit is contained in:
parent
5aaa0f3e4e
commit
a355e482c7
16 changed files with 1328 additions and 1149 deletions
707
src/tek.rs
707
src/tek.rs
|
|
@ -1,5 +1,43 @@
|
|||
#![allow(clippy::unit_arg)]
|
||||
|
||||
impl_as_ref!(Config: |self: App|self.config.config.as_ref());
|
||||
impl_as_mut!(Option<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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue