Compare commits

..

1 commit

Author SHA1 Message Date
i do not exist
f1756f9a0e wip: compiled layouts
Some checks failed
/ build (push) Has been cancelled
2026-08-29 18:52:43 +03:00
16 changed files with 663 additions and 672 deletions

27
.gitignore vendored
View file

@ -1,19 +1,18 @@
*/target
target/*
!target/.gitkeep
perf.data*
flamegraph*.svg
vgcore*
example.mid
cov
*/cov
*.profraw
build/*
!build/README.md
!build/*.sh !build/*.sh
!build/Dockerfile.* !build/Dockerfile.*
!build/README.md
!target/.gitkeep
*.profraw
*/cov
*/target
.direnv
.misc .misc
build/* .direnv
callgrind.* callgrind.*
cov
example.mid
flamegraph*.svg
perf.data*
profile.json.gz
target/*
tracing*.* tracing*.*
vgcore*

4
Cargo.lock generated
View file

@ -3893,9 +3893,13 @@ dependencies = [
"midly", "midly",
"palette", "palette",
"parking_lot 0.12.5", "parking_lot 0.12.5",
"profiling",
"quanta", "quanta",
"rand 0.8.7", "rand 0.8.7",
"ratatui", "ratatui",
"tracing",
"tracing-flame",
"tracing-subscriber",
"unicode-width 0.2.0", "unicode-width 0.2.0",
] ]

View file

@ -51,10 +51,9 @@ proptest = { version = "^1" }
proptest-derive = { version = "^0.5.1" } proptest-derive = { version = "^0.5.1" }
[features] [features]
default = ["cli", "arranger", "sampler", "prof"] default = ["cli", "arranger", "sampler", "prof"]
prof = [] prof = ["tengri/prof"]
#prof2 = ["prof", "tengri/prof"]
hotpath = ["hotpath/hotpath"] hotpath = ["hotpath/hotpath"]
hotpath-cpu = ["hotpath/hotpath-cpu"] hotpath-cpu = ["hotpath/hotpath-cpu"]
hotpath-alloc = ["hotpath/hotpath-alloc"] hotpath-alloc = ["hotpath/hotpath-alloc"]

View file

@ -48,16 +48,12 @@ run:
run-init: run-init:
rm -rf ~/.config/tek && {{debug}} rm -rf ~/.config/tek && {{debug}}
prof +ARGS="new": prof:
CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph -F 10000 -- {{ARGS}} CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph -F 10000 -- new
tracy +ARGS="new":
{{release}} -F prof {{ARGS}}
samply +ARGS="new":
samply record target/release/tek {{ARGS}}
release := "reset && cargo run --release" release := "reset && cargo run --release --"
release +ARGS="new": release +ARGS="new":
{{release}} -- {{ARGS}} {{release}} {{ARGS}}
build-release: build-release:
time cargo build -j4 --release time cargo build -j4 --release

View file

@ -12,7 +12,6 @@
pkgs.perf pkgs.perf
pkgs.pkg-config pkgs.pkg-config
pkgs.watchexec pkgs.watchexec
pkgs.samply
]; ];
buildInputs = [ buildInputs = [
pkgs.libclang pkgs.libclang

View file

@ -1,306 +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");
}
#[cfg(not(feature = "cli"))]
fn run_new_plain (config: Config) -> Usually<()> {
let name = "tek";
tengri::Tui::run_main(Jack::new_run(name, move|jack|{
let mode = ":menu";
let title = "untitled!";
let bpm = 74.;
let clock = Clock::new(&jack, Some(bpm))?;
let tracks = [];
let scenes = [];
Ok(App::new(None, Arrangement::new(
&jack,
title.into(),
clock,
tracks.into_iter(),
scenes.into_iter(),
connect_midi_ins(&jack, &"M", &[], None)?.into_iter(),
connect_midi_outs(&jack, &"M", &[], None)?.into_iter(),
[].into_iter().chain(connect_audio_ins(&jack, &"L", &[], None)?.into_iter())
.chain(connect_audio_ins(&jack, &"R", &[], None)?.into_iter()),
[].into_iter().chain(connect_audio_outs(&jack, &"L", &[], None)?.into_iter())
.chain(connect_audio_outs(&jack, &"R", &[], None)?.into_iter()),
), config, mode))
})?)
}
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(),
{
let mut app = App::new(Some(exit), sesh.init()?, config, ":menu");
#[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<Arrangement> {
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 = Arrangement::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: &Arrangement) {
println!("Name: {:?}", &project.name);
println!("JACK: {:?}", &project.jack);
println!("Buffer: {:?}", &project.clock.chunk);
println!("Sample rate: {:?}", &project.clock.timebase.sr);
println!("MIDI PPQ: {:?}", &project.clock.timebase.ppq);
println!("Tempo: {:?}", &project.clock.timebase.bpm);
println!("Quantize: {:?}", &project.clock.quant);
println!("Launch: {:?}", &project.clock.sync);
println!("Playhead: {:?}us", &project.clock.playhead.usec);
println!("Playhead: {:?}s", &project.clock.playhead.sample);
println!("Playhead: {:?}p", &project.clock.playhead.pulse);
println!("Started: {:?}", &project.clock.started);
println!("Tracks:");
for (i, t) in project.tracks.iter().enumerate() {
println!(" Track {i}: {} {} {:?} {:?}", t.name, t.width,
&t.sequencer.play_clip, &t.sequencer.next_clip);
}
println!("Scenes:");
for (i, t) in project.scenes.iter().enumerate() {
println!(" Scene {i}: {} {:?}", &t.name, &t.clips);
}
println!("MIDI Ins: {:?}", &project.midi_ins);
println!("MIDI Outs: {:?}", &project.midi_outs);
println!("Audio Ins: {:?}", &project.audio_ins);
println!("Audio Outs: {:?}", &project.audio_outs);
// TODO git integration
// TODO dawvert integration
}
pub fn print_config (config: &Config) {
use ::ansi_term::Color::*;
println!("{:?}", config.dirs);
for (k, v) in config.views.try_read().unwrap().iter() {
println!("{} {} {}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}")), v.source);
}
for (k, v) in config.binds.try_read().unwrap().iter() {
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

@ -23,7 +23,7 @@ pub fn config_init <C: AsRef<Config>> (config: C) -> Usually<C> {
pub fn config_load <C: AsRef<Config>, L: Language> (config: C, src: L) -> Usually<C> { pub fn config_load <C: AsRef<Config>, L: Language> (config: C, src: L) -> Usually<C> {
config.as_ref().clear(); config.as_ref().clear();
config.as_ref().stamp.store(quanta::Clock::new().raw(), Relaxed); config.as_ref().stamp.store(quanta::Clock::new().raw(), Relaxed);
src.each(config, |c, s|config_load_item(c, s.trim())) src.each(config, |c, s|config_load_item(c, s))
} }
pub fn config_load_item <C: AsRef<Config>, L: Language> (config: C, src: L) -> Usually<C> { pub fn config_load_item <C: AsRef<Config>, L: Language> (config: C, src: L) -> Usually<C> {
@ -60,7 +60,7 @@ pub fn config_watch (
*config.as_ref().error.try_write().unwrap() = Some(format!("{e:?}").into()); *config.as_ref().error.try_write().unwrap() = Some(format!("{e:?}").into());
panic!("{e:?}"); panic!("{e:?}");
} else { } else {
//println!("config updated"); println!("config updated");
}, },
Err(errors) => { Err(errors) => {
panic!("{errors:?}"); panic!("{errors:?}");
@ -88,12 +88,6 @@ pub fn modes_add <'a> (modes: &Modes, expr: impl Language) -> UsuallyRef<'a, ()>
Ok(()) Ok(())
} }
impl AsMut<Mode> for Mode {
fn as_mut (&mut self) -> &mut Self {
self
}
}
/// Add a definition to the mode. /// Add a definition to the mode.
/// ///
/// Supported definitions: /// Supported definitions:
@ -105,9 +99,10 @@ impl AsMut<Mode> for Mode {
/// - ... -> view /// - ... -> view
/// ///
/// ``` /// ```
/// let mut mode: tek::Mode = tek::mode_add(tek::Mode::default(), "(name hello)").unwrap(); /// let mut mode: tek::Mode<std::sync::Arc<str>> = Default::default();
/// mode.add("(name hello)").unwrap();
/// ``` /// ```
pub fn mode_add <T: AsMut<Mode>> (mut mode: T, dsl: impl Language) -> Usually<T> { pub fn mode_add (mut mode: Mode, dsl: impl Language) -> Usually<Mode> {
Ok(if let Ok(Some(expr)) = dsl.expr() && let Ok(Some(head)) = expr.head() { Ok(if let Ok(Some(expr)) = dsl.expr() && let Ok(Some(head)) = expr.head() {
//println!("Mode::add: {head} {:?}", expr.tail()); //println!("Mode::add: {head} {:?}", expr.tail());
let tail = expr.tail()?.map(|x|x.trim()).unwrap_or(""); let tail = expr.tail()?.map(|x|x.trim()).unwrap_or("");
@ -117,23 +112,23 @@ pub fn mode_add <T: AsMut<Mode>> (mut mode: T, dsl: impl Language) -> Usually<T>
let body = tail.tail()?.ok_or("submode: missing body")?; let body = tail.tail()?.ok_or("submode: missing body")?;
let submode = Mode::default(); let submode = Mode::default();
let submode = body.each(submode, |c,s|mode_add(c,s))?; let submode = body.each(submode, |c,s|mode_add(c,s))?;
let modes = mode.as_mut().modes.clone(); let modes = mode.modes.clone();
modes.0.try_write().unwrap().insert(name.into(), Arc::new(submode)); modes.0.try_write().unwrap().insert(name.into(), Arc::new(submode));
mode mode
}, },
"keys" => { "keys" => {
tail.each(mode, |mut mode: T, expr: &str|{ tail.each(mode, |mut mode: Mode, expr: &str|{
mode.as_mut().keys.push(expr.trim().into()); mode.keys.push(expr.trim().into());
Ok(mode) Ok(mode)
})? })?
}, },
"name" => { mode.as_mut().name.push(tail.into()); mode }, "name" => { mode.name.push(tail.into()); mode },
"info" => { mode.as_mut().info.push(tail.into()); mode }, "info" => { mode.info.push(tail.into()); mode },
"view" => { mode.as_mut().view.push(View::new(tail)?.into()); mode }, "view" => { mode.view.push(View::new(tail)?.into()); mode },
_ => { mode.as_mut().view.push(View::new(tail)?.into()); mode }, _ => { mode.view.push(View::new(tail)?.into()); mode },
} }
} else if let Ok(Some(word)) = dsl.word() { } else if let Ok(Some(word)) = dsl.word() {
mode.as_mut().view.push(View::new(word)?.into()); mode.view.push(View::new(word)?.into());
mode mode
} else { } else {
return Err(format!("Mode::add: unexpected: {dsl:?}").into()); return Err(format!("Mode::add: unexpected: {dsl:?}").into());
@ -141,12 +136,12 @@ pub fn mode_add <T: AsMut<Mode>> (mut mode: T, dsl: impl Language) -> Usually<T>
} }
pub fn load_bind <'a> (binds: &Binds, expr: impl Language) -> UsuallyRef<'a, ()> { pub fn load_bind <'a> (binds: &Binds, expr: impl Language) -> UsuallyRef<'a, ()> {
//println!("\n\rload_bind: {expr:?}"); println!("\n\rload_bind: {expr:?}");
let name = expr.head()?.ok_or("bind: missing name")?; let name = expr.head()?.ok_or("bind: missing name")?;
let body = expr.tail()?.unwrap_or(""); let body = expr.tail()?.unwrap_or("");
binds.try_write().unwrap().insert(name.into(), { binds.try_write().unwrap().insert(name.into(), {
let mut map = Bind::new(); let mut map = Bind::new();
body.each((), |_, item: &str|if matches!(item.expr().head(), Ok(Some("see"))) { body.each((), |_, item: &str|if item.expr().head() == Ok(Some("see")) {
// TODO // TODO
Ok(()) Ok(())
} else if let Ok(Some(_word)) = item.expr().head().word() { } else if let Ok(Some(_word)) = item.expr().head().word() {
@ -177,15 +172,22 @@ pub fn load_bind <'a> (binds: &Binds, expr: impl Language) -> UsuallyRef<'a, ()>
/// Configuration: mode, view, and bind definitions. /// Configuration: mode, view, and bind definitions.
/// ///
/// ``` /// ```
/// let source = stringify!( /// let config = tek::Config::default();
/// (mode :menu (name Menu) /// ```
/// (info Mode selector.) (keys :axis/y :confirm) ///
/// (view (bg (g 0) (bsp/s :ports/out /// ```
/// (bsp/n :ports/in /// // Some dizzle.
/// (bg (g 30) (bsp/s (fixed/y 7 :logo) /// // What indentation to use here lol?
/// (fill :dialog/menu))))))))); /// let source = stringify!((mode :menu (name Menu)
/// let config: tek::Config = tek::modes_add(tek::Config::default(), source).unwrap(); /// (info Mode selector.) (keys :axis/y :confirm)
/// let mode: tek::Mode = config.get_mode(":menu").unwrap(); /// (view (bg (g 0) (bsp/s :ports/out
/// (bsp/n :ports/in
/// (bg (g 30) (bsp/s (fixed/y 7 :logo)
/// (fill :dialog/menu)))))))));
/// // Add this definition to the config and try to load it.
/// // A "mode" is basically a state machine
/// // with associated input and output definitions.
/// tek::Config::default().add(&source).unwrap().get_mode(":menu").unwrap();
/// ``` /// ```
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Config { pub struct Config {
@ -219,7 +221,7 @@ pub struct Modes(Arc<RwLock<BTreeMap<Arc<str>, Arc<Mode>>>>);
/// Group of view and keys definitions. /// Group of view and keys definitions.
/// ///
/// ``` /// ```
/// let mode = tek::Mode::default(); /// let mode = tek::Mode::<std::sync::Arc<str>>::default();
/// ``` /// ```
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Mode { pub struct Mode {
@ -263,9 +265,9 @@ impl View<Tui, App> {
Usually<Arc<Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync>>> Usually<Arc<Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync>>>
{ {
let source = source.as_ref(); let source = source.as_ref();
let layer = if let Ok(Some(expr)) = source.expr() { let layer = if let Some(expr) = source.expr()? {
Self::compile_expr(expr.into())? Self::compile_expr(expr.into())?
} else if let Ok(Some(word)) = source.word() { } else if let Some(word) = source.word()? {
Self::compile_word(word.into())? Self::compile_word(word.into())?
} else { } else {
return Err(format!("not word/expr:\n{source:?}").into()) return Err(format!("not word/expr:\n{source:?}").into())
@ -276,24 +278,14 @@ impl View<Tui, App> {
fn compile_expr (expr: Arc<str>) -> fn compile_expr (expr: Arc<str>) ->
Usually<Arc<Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync>>> Usually<Arc<Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync>>>
{ {
macro_rules! arg {
($src:expr, $head:expr, $index:expr, $name:expr) => {
$src.nth($index)?
.ok_or_else(||Box::<dyn Error>::from(format!(
"{}: no arg #{} ({}) in {}", $head, $index, $name, $src
)))?
};
}
Ok(Arc::new(if let Some(head) = expr.head()? && let Some(ns) = head.split('/').next() { Ok(Arc::new(if let Some(head) = expr.head()? && let Some(ns) = head.split('/').next() {
match ns { match ns {
"when" => { "when" => {
let cond = Arc::from(arg!(expr, head, 1, "condition")); let cond = Arc::from(expr.nth(1)?.ok_or_else(||Box::<dyn Error>::from("when: no arg0: condition"))?);
let cond = move|state: &App|state.namespace(&cond)?.ok_or_else(||Box::<dyn Error>::from("when: no condition value")); let cond = move|state: &App|state.namespace(&cond)?.ok_or_else(||Box::<dyn Error>::from("when: no condition value"));
let thunk = Self::compile(arg!(expr, head, 2, "content"))?; let thunk = expr.nth(2)?.ok_or_else(||Box::<dyn Error>::from("when: no arg1: content"))?;
let thunk = Self::compile(thunk)?;
Self::boxed(move|state, screen|{ Self::boxed(move|state, screen|{
when( when(
cond(state)?, cond(state)?,
@ -303,10 +295,12 @@ impl View<Tui, App> {
}, },
"either" => { "either" => {
let cond = Arc::from(arg!(expr, head, 1, "condition")); let cond = Arc::from(expr.nth(1)?.ok_or_else(||Box::<dyn Error>::from("either: no arg0: condition"))?);
let cond = move|state: &App|state.namespace(&cond)?.ok_or_else(||Box::<dyn Error>::from("either: no condition value")); let cond = move|state: &App|state.namespace(&cond)?.ok_or_else(||Box::<dyn Error>::from("either: no condition value"));
let a = Self::compile(arg!(expr, head, 2, "content A"))?; let a = expr.nth(2)?.ok_or_else(||Box::<dyn Error>::from("either: no arg1: content"))?;
let b = Self::compile(arg!(expr, head, 3, "content B"))?; let a = Self::compile(a)?;
let b = expr.nth(3)?.ok_or_else(||Box::<dyn Error>::from("either: no arg2: content"))?;
let b = Self::compile(b)?;
Self::boxed(move|state, screen|{ Self::boxed(move|state, screen|{
either( either(
cond(state)?, cond(state)?,
@ -316,7 +310,7 @@ impl View<Tui, App> {
}) })
}, },
"bsp" | "split" | "stack" => { "bsp" | "split" => {
let split = head.split('/').skip(1).next(); let split = head.split('/').skip(1).next();
let split = match split { let split = match split {
Some("n") => Split::North, Some("n") => Split::North,
@ -327,34 +321,14 @@ impl View<Tui, App> {
Some("b") => Split::Below, Some("b") => Split::Below,
_ => return Err(format!("invalid split: {split:?}").into()) _ => return Err(format!("invalid split: {split:?}").into())
}; };
let a = Self::compile(arg!(expr, head, 1, "content A"))?; let a = Self::compile(expr.nth(1)?.ok_or_else(||Box::<dyn Error>::from("either: no arg0: content"))?)?;
let b = Self::compile(arg!(expr, head, 2, "content B"))?; let b = Self::compile(expr.nth(2)?.ok_or_else(||Box::<dyn Error>::from("either: no arg1: content"))?)?;
Self::boxed(move|state, screen|split.stack( Self::boxed(move|state, screen|{
draw(|screen|a(state, screen)), split.stack(
draw(|screen|b(state, screen)), draw(|screen|a(state, screen)),
).draw(screen)) draw(|screen|b(state, screen)),
}, ).draw(screen)
})
"bar" => {
let split = head.split('/').skip(1).next();
let split = match split {
Some("n") => Split::North,
Some("s") => Split::South,
Some("e") => Split::East,
Some("w") => Split::West,
Some("a") => Split::Above,
Some("b") => Split::Below,
_ => return Err(format!("invalid split: {split:?}").into())
};
let size = Arc::from(arg!(expr, head, 1, "size"));
let size = move|state: &App|state.namespace(&size)?.ok_or_else(||Box::<dyn Error>::from("bar: no size"));
let a = Self::compile(arg!(expr, head, 2, "content A"))?;
let b = Self::compile(arg!(expr, head, 3, "content B"))?;
Self::boxed(move|state, screen|split.bar(
size(state)?,
draw(|screen|a(state, screen)),
draw(|screen|b(state, screen)),
).draw(screen))
}, },
"align" => { "align" => {
@ -373,7 +347,8 @@ impl View<Tui, App> {
Some("y") => Azimuth::Y, Some("y") => Azimuth::Y,
_ => return Err(format!("invalid azimuth: {azimuth:?}").into()) _ => return Err(format!("invalid azimuth: {azimuth:?}").into())
}; };
let thunk = Self::compile(arg!(expr, head, 1, "content"))?; let thunk = Self::compile(expr.nth(2)?
.ok_or_else(||Box::<dyn Error>::from("either: no arg1: content"))?)?;
Self::boxed(move|state, screen|{ Self::boxed(move|state, screen|{
Align( Align(
Some(azimuth), Some(azimuth),
@ -382,8 +357,9 @@ impl View<Tui, App> {
}) })
}, },
"full" | "fill" => { "full" => {
let thunk = Self::compile(arg!(expr, head, 1, "content"))?; let thunk = Self::compile(expr.nth(2)?
.ok_or_else(||Box::<dyn Error>::from("either: no arg1: content"))?)?;
match head.split('/').skip(1).next() { match head.split('/').skip(1).next() {
Some("w") | Some("x") => Self::boxed(move|state, screen|{ Some("w") | Some("x") => Self::boxed(move|state, screen|{
Full::W(draw(|screen|thunk(state, screen))).draw(screen) Full::W(draw(|screen|thunk(state, screen))).draw(screen)
@ -391,56 +367,83 @@ impl View<Tui, App> {
Some("h") | Some("y") => Self::boxed(move|state, screen|{ Some("h") | Some("y") => Self::boxed(move|state, screen|{
Full::H(draw(|screen|thunk(state, screen))).draw(screen) Full::H(draw(|screen|thunk(state, screen))).draw(screen)
}), }),
Some("wh") | Some("xy") | None => Self::boxed(move|state, screen|{ Some("wh") | Some("xy") => Self::boxed(move|state, screen|{
Full::WH(draw(|screen|thunk(state, screen))).draw(screen) Full::WH(draw(|screen|thunk(state, screen))).draw(screen)
}), }),
_ => unreachable!() _ => unreachable!()
} }
}, },
"exact" | "min" | "max" | "push" | "pull" | "pad" => { "exact" | "min" | "max" | "push" | "pull" => {
match head.split('/').skip(1).next() { match head.split('/').skip(1).next() {
Some("w") | Some("x") => { Some("w") | Some("x") => {
let value = Arc::from(arg!(expr, head, 1, "value")); let value = Arc::from(expr.nth(1)?.ok_or_else(||Box::<dyn Error>::from("{}: no arg1: value"))?);
let value = move|state: &App|state.namespace(&value); let value = move|state: &App|state.namespace(&value);
let thunk = Self::compile(arg!(expr, head, 2, "content"))?; let thunk = Self::compile(expr.nth(2)?.ok_or_else(||Box::<dyn Error>::from("either: no arg2: content"))?)?;
match ns { match ns {
"exact" => Self::boxed(move|state, screen|Exact::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), "exact" => Self::boxed(move|state, screen|Exact::W(
"push" => Self::boxed(move|state, screen|Push::X(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), draw(|screen|thunk(state, screen)), value(state)?
"pull" => Self::boxed(move|state, screen|Pull::X(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), ).draw(screen)),
"pad" => Self::boxed(move|state, screen|Pad::X(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), "push" => Self::boxed(move|state, screen|Push::X(
"min" => Self::boxed(move|state, screen|Min::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), draw(|screen|thunk(state, screen)), value(state)?
"max" => Self::boxed(move|state, screen|Max::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), ).draw(screen)),
"pull" => Self::boxed(move|state, screen|Pull::X(
draw(|screen|thunk(state, screen)), value(state)?
).draw(screen)),
"min" => Self::boxed(move|state, screen|Min::W(
draw(|screen|thunk(state, screen)), value(state)?
).draw(screen)),
"max" => Self::boxed(move|state, screen|Max::W(
draw(|screen|thunk(state, screen)), value(state)?
).draw(screen)),
_ => unreachable!() _ => unreachable!()
} }
}, },
Some("h") | Some("y") => { Some("h") | Some("y") => {
let value = Arc::from(arg!(expr, head, 1, "value")); let value = Arc::from(expr.nth(1)?.ok_or_else(||Box::<dyn Error>::from("{}: no arg1: value"))?);
let value = move|state: &App|state.namespace(&value); let value = move|state: &App|state.namespace(&value);
let thunk = Self::compile(arg!(expr, head, 2, "content"))?; let thunk = Self::compile(expr.nth(2)?.ok_or_else(||Box::<dyn Error>::from("either: no arg2: content"))?)?;
match ns { match ns {
"exact" => Self::boxed(move|state, screen|Exact::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), "exact" => Self::boxed(move|state, screen|Exact::H(
"push" => Self::boxed(move|state, screen|Push::Y(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), draw(|screen|thunk(state, screen)), value(state)?
"pull" => Self::boxed(move|state, screen|Pull::Y(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), ).draw(screen)),
"pad" => Self::boxed(move|state, screen|Pad::Y(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), "push" => Self::boxed(move|state, screen|Push::Y(
"min" => Self::boxed(move|state, screen|Min::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), draw(|screen|thunk(state, screen)), value(state)?
"max" => Self::boxed(move|state, screen|Max::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)), ).draw(screen)),
"pull" => Self::boxed(move|state, screen|Pull::Y(
draw(|screen|thunk(state, screen)), value(state)?
).draw(screen)),
"min" => Self::boxed(move|state, screen|Min::H(
draw(|screen|thunk(state, screen)), value(state)?
).draw(screen)),
"max" => Self::boxed(move|state, screen|Max::H(
draw(|screen|thunk(state, screen)), value(state)?
).draw(screen)),
_ => unreachable!() _ => unreachable!()
} }
}, },
Some("wh") | Some("xy") | None => { Some("wh") | Some("xy") => {
let value1 = Arc::from(arg!(expr, head, 1, "value")); let value1 = Arc::from(expr.nth(1)?.ok_or_else(||Box::<dyn Error>::from("{}: no arg1: value"))?);
let value1 = move|state: &App|state.namespace(&value1); let value1 = move|state: &App|state.namespace(&value1);
let value2 = Arc::from(arg!(expr, head, 2, "value")); let value2 = Arc::from(expr.nth(2)?.ok_or_else(||Box::<dyn Error>::from("{}: no arg2: value"))?);
let value2 = move|state: &App|state.namespace(&value2); let value2 = move|state: &App|state.namespace(&value2);
let thunk = Self::compile(arg!(expr, head, 3, "content"))?; let thunk = Self::compile(expr.nth(3)?.ok_or_else(||Box::<dyn Error>::from("either: no arg3: content"))?)?;
match ns { match ns {
"exact" => Self::boxed(move|state, screen|Exact::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)), "exact" => Self::boxed(move|state, screen|Exact::WH(
"push" => Self::boxed(move|state, screen|Push::XY(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)), draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?
"pull" => Self::boxed(move|state, screen|Pull::XY(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)), ).draw(screen)),
"pad" => Self::boxed(move|state, screen|Pad::XY(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)), "push" => Self::boxed(move|state, screen|Push::XY(
"min" => Self::boxed(move|state, screen|Min::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)), draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?
"max" => Self::boxed(move|state, screen|Max::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)), ).draw(screen)),
"pull" => Self::boxed(move|state, screen|Pull::XY(
draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?
).draw(screen)),
"min" => Self::boxed(move|state, screen|Min::WH(
draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?
).draw(screen)),
"max" => Self::boxed(move|state, screen|Max::WH(
draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?
).draw(screen)),
_ => unreachable!() _ => unreachable!()
} }
}, },
@ -449,31 +452,27 @@ impl View<Tui, App> {
}, },
"fg" | "bg" => { "fg" | "bg" => {
let color = Arc::from(arg!(expr, head, 1, "color")); let color = expr.nth(1)?.ok_or_else(||Box::<dyn Error>::from("{}: no arg1: color"))?;
let color = move|state: &App|state.namespace(&color); let thunk = Self::compile(expr.nth(2)?.ok_or_else(||Box::<dyn Error>::from("either: no arg2: thunk"))?)?;
let thunk = Self::compile(arg!(expr, head, 2, "content"))?; todo!()
match ns {
"fg" => Self::boxed(move|state, screen|fg(
color(state)?.unwrap_or_default(), draw(|screen|thunk(state, screen))
).draw(screen)),
"bg" => Self::boxed(move|state, screen|bg(
color(state)?.unwrap_or_default(), draw(|screen|thunk(state, screen))
).draw(screen)),
_ => unreachable!()
}
}, },
"text" => { "text" => {
let text: Arc<str> = Arc::from(expr.tail()?.unwrap_or_default()); todo!()
Self::boxed(move|_state, screen|{
text.draw(screen)
})
}, },
//"align" => Self::boxed(move|state, screen|kw_align(state, screen, expr)),
//"full" => Self::boxed(move|state, screen|kw_full(state, screen, expr)),
//"exact" => Self::boxed(move|state, screen|kw_exact(state, screen, expr)),
//"min" => Self::boxed(move|state, screen|kw_min(state, screen, expr)),
//"max" => Self::boxed(move|state, screen|kw_max(state, screen, expr)),
//"push" => Self::boxed(move|state, screen|kw_push(state, screen, expr)),
//"pull" => Self::boxed(move|state, screen|kw_pull(state, screen, expr)),
//"text" => Self::boxed(move|state, screen|kw_tui_text(state, screen, expr)),
//"fg" => Self::boxed(move|state, screen|kw_tui_fg(state, screen, expr)),
//"bg" => Self::boxed(move|state, screen|kw_tui_bg(state, screen, expr)),
_ => return Err(format!("compile_expr: unexpected: {expr:?}").into()) _ => return Err(format!("compile_expr: unexpected: {expr:?}").into())
} }
} else { } else {
return Err(format!("compile_expr: invalid expression: {expr:?}").into()) return Err(format!("compile_expr: invalid expression: {expr:?}").into())
})) }))
@ -482,10 +481,6 @@ impl View<Tui, App> {
fn compile_word (word: Arc<str>) fn compile_word (word: Arc<str>)
-> Usually<Arc<Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync>>> -> Usually<Arc<Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync>>>
{ {
macro_rules! draw {
(|$state:ident|$body:expr)=>{Self::boxed(move|$state, to|$body.draw(to))}
}
Ok(Arc::new(match word.split("/").next() { Ok(Arc::new(match word.split("/").next() {
//Some(":logo") => view_logo().draw(to), //Some(":logo") => view_logo().draw(to),
Some(":meters") => match word.split("/").skip(1).next() { Some(":meters") => match word.split("/").skip(1).next() {
@ -495,26 +490,26 @@ impl View<Tui, App> {
}, },
Some(":tracks") => match word.split("/").skip(1).next() { Some(":tracks") => match word.split("/").skip(1).next() {
None => Self::boxed(move|_, to|"TODO tracks".draw(to)), None => Self::boxed(move|_, to|"TODO tracks".draw(to)),
Some("names") => draw!(|app|app.project.view_track_names(app.color.clone())), Some("names") => Self::boxed(move|state, to|state.project.view_track_names(state.color.clone()).draw(to)),//bg(Rgb(40, 40, 40), full_w(align_w("Track Names")))),
Some("inputs") => draw!(|app|app.project.view_track_inputs(app.color.clone())), Some("inputs") => Self::boxed(move|state, to|state.project.view_track_inputs(state.color.clone()).draw(to)),//bg(Rgb(40, 40, 40), "Track Inputs".align_w().full_w()).draw(to),
Some("devices") => draw!(|app|app.project.view_track_devices(app.color.clone())), Some("devices") => Self::boxed(move|state, to|state.project.view_track_devices(state.color.clone()).draw(to)),//bg(Rgb(40, 40, 40), "Track Devices".align_w().full_w()).draw(to),
Some("outputs") => draw!(|app|app.project.view_track_outputs(app.color.clone(), 0)), Some("outputs") => Self::boxed(move|state, to|state.project.view_track_outputs(state.color.clone(), 0).draw(to)),
_ => panic!() _ => panic!()
}, },
Some(":scenes") => match word.split("/").skip(1).next() { Some(":scenes") => match word.split("/").skip(1).next() {
Some("clips") => draw!(|app|app.view_scenes_clips()), None => Self::boxed(move|state, to|state.view_scenes_clips().draw(to)),
Some("names") => draw!(|app|app.view_scenes_names()), Some("names") => Self::boxed(move|state, to|state.view_scenes_names().draw(to)),
_ => panic!() _ => panic!()
}, },
Some(":templates") => draw!(|app|view_templates(app)),
Some(":browse/title") => draw!(|app|view_browse_title(app)),
Some(":device") => draw!(|app|view_device(app)),
Some(":dialog") => Self::boxed(move|state, to|draw_dialog(to, word.split("/").skip(1), state)), Some(":dialog") => Self::boxed(move|state, to|draw_dialog(to, word.split("/").skip(1), state)),
Some(":sessions") => Self::boxed(move|_, to|view_sessions().draw(to)), Some(":templates") => Self::boxed(move|state, to|view_templates(state).draw(to)),
Some(":status") => Self::boxed(move|_, to|"TODO: Status Bar".draw(to)), Some(":sessions") => Self::boxed(move|state, to|view_sessions().draw(to)),
Some(":editor") => Self::boxed(move|_, to|"TODO Editor".draw(to)), Some(":browse/title") => Self::boxed(move|state, to|view_browse_title(state).draw(to)),
Some(":transport") => Self::boxed(move|_, to|view_transport(true, "", "", "").draw(to)), Some(":device") => Self::boxed(move|state, to|view_device(state).draw(to)),
Some(":debug") => Self::boxed(move|_, to|format!("[{:?}]", to.area()).exact_h(1).draw(to)), Some(":status") => Self::boxed(move|state, to|"TODO: Status Bar".draw(to)),
Some(":editor") => Self::boxed(move|state, to|"TODO Editor".draw(to)),
Some(":transport") => Self::boxed(move|state, to|view_transport(true, "", "", "").draw(to)),
Some(":debug") => Self::boxed(move|state, to|format!("[{:?}]", to.area()).exact_h(1).draw(to)),
Some(_) => Self::boxed(move|state, to|if let Some(view) = state.config.get_view(word.as_ref()) { Some(_) => Self::boxed(move|state, to|if let Some(view) = state.config.get_view(word.as_ref()) {
(view.render)(state, to) (view.render)(state, to)
} else { } else {
@ -637,9 +632,7 @@ mod bind {
/// An map of input events (e.g. [TuiEvent]) to [Binding]s. /// An map of input events (e.g. [TuiEvent]) to [Binding]s.
/// ///
/// ``` /// ```
/// let lang = stringify!( /// let lang = "(@x (nop)) (@y (nop) (nop))";
/// (@x (nop))
/// (@y (nop) (nop)));
/// let bind = tek::Bind::<tek::tengri::TuiEvent, std::sync::Arc<str>>::load(&lang).unwrap(); /// let bind = tek::Bind::<tek::tengri::TuiEvent, std::sync::Arc<str>>::load(&lang).unwrap();
/// assert_eq!(bind.query(&'x'.into()).map(|x|x.len()), Some(1)); /// assert_eq!(bind.query(&'x'.into()).map(|x|x.len()), Some(1));
/// //assert_eq!(bind.query(&'y'.into()).map(|x|x.len()), Some(2)); /// //assert_eq!(bind.query(&'y'.into()).map(|x|x.len()), Some(2));
@ -726,3 +719,50 @@ mod bind {
impl_debug!(Condition |self, w| { write!(w, "*") }); impl_debug!(Condition |self, w| { write!(w, "*") });
} }
pub fn print_config (config: &Config) {
use ::ansi_term::Color::*;
println!("{:?}", config.dirs);
for (k, v) in config.views.try_read().unwrap().iter() {
println!("{} {} {}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}")), v.source);
}
for (k, v) in config.binds.try_read().unwrap().iter() {
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!();
});
}

View file

@ -46,11 +46,11 @@ pub(crate) use ::{
}, },
}; };
#[cfg(feature = "prof2")] #[cfg(feature = "prof")]
pub use ::tengri::{ pub use ::tengri::{
profiling, profiling,
tracing, tracing,
tracing_tracy, tracing_flame,
tracing_subscriber tracing_subscriber
}; };

View file

@ -70,7 +70,6 @@ impl Arrangement {
midi_outs: midi_outs.collect(), midi_outs: midi_outs.collect(),
audio_ins: audio_ins.collect(), audio_ins: audio_ins.collect(),
audio_outs: audio_outs.collect(), audio_outs: audio_outs.collect(),
size_inner: Sizer(Arc::new(40.into()), Arc::new(25.into())),
clock, clock,
name, name,
..Default::default() ..Default::default()

View file

@ -50,32 +50,44 @@ pub trait ClipsView: TracksView + ScenesView {
fn view_scenes_clips (&self) -> impl Draw<Tui> { fn view_scenes_clips (&self) -> impl Draw<Tui> {
let select = self.selection(); let select = self.selection();
let editor = self.editor(); let editor = self.editor();
let size = self.clips_size();
let editing = self.is_editing(); let editing = self.is_editing();
with_clips_size(true, self.clips_size(), iter_east(move||self.tracks_with_sizes() return size.of(
.map(move|(track_index, track, _, _)|iter_south(move||self.scenes_with_sizes() above(
.map(move|(scene_index, scene, _, _)|{ fg(Green, format!("{}x{}", size.w(), size.h())).align_se().full_wh(),
let (name, theme): (Arc<str>, ItemTheme) = view_scene_name_theme(scene, track_index); iter_east(move||self.tracks_with_sizes().map(move|(
let f = theme.lightest.term; track_index, track, _, _
let (b, o) = view_scene_bg(theme, select, track_index, scene_index); )| {
let is_selected = view_scene_sel(select, track_index, scene_index, editing); iter_south(move||self.scenes_with_sizes().map(move|(
below( scene_index, scene, _, _
Outer(true, Style::default().fg(o)).full_wh(), )| {
let (name, theme): (Arc<str>, ItemTheme) = view_scene_name_theme(scene, track_index);
let f = theme.lightest.term;
let (b, o) = view_scene_bg(
theme, select, track_index, scene_index
);
let w = view_scene_w(
track, select, track_index, editor
);
let y = view_scene_y(
select, scene_index, editor
);
let is_selected = view_scene_sel(
select, track_index, scene_index, editing
);
below( below(
Outer(true, Style::default().fg(o)).full_wh(),
below( below(
fg_bg(o, b, "".full_wh()), below(
fg_bg(f, b, bold(true, name)).align_nw().full_wh(), fg_bg(o, b, "".full_wh()),
), fg_bg(f, b, bold(true, name)).align_nw().full_wh(),
when(is_selected, editor).full_wh() ),
).full_wh() when(is_selected, editor).full_wh()
).exact_wh( ).full_wh()
view_scene_w(track, select, track_index, editor), ).exact_wh(w, y)
view_scene_y(select, scene_index, editor), })).full_h().exact_w(track.width as u16)
) }))
}) ).full_wh());
)
.full_h()
.exact_w(track.width as u16))
)).align_c()
} }
} }
@ -88,11 +100,6 @@ fn view_scene_name_theme (scene: &Scene, track_index: usize) -> (Arc<str>, ItemT
} }
} }
fn with_clips_size (show: bool, size: &Sizer, content: impl Draw<Tui>) -> impl Draw<Tui> {
let wh = east!(size.w() as usize, "x", size.h() as usize);
size.of(above(when(show, fg(Green, wh).align_se().full_wh()), content))
}
fn view_scene_bg ( fn view_scene_bg (
theme: ItemTheme, select: &Selection, track_index: usize, scene_index: usize theme: ItemTheme, select: &Selection, track_index: usize, scene_index: usize
) -> (Color, Color) { ) -> (Color, Color) {

View file

@ -24,13 +24,13 @@ pub fn view_audio_outs_status (theme: ItemTheme, track: Option<&Track>) -> impl
pub fn view_ports_status <'a, T: JackPort> (theme: ItemTheme, title: &'a str, ports: &'a [T]) pub fn view_ports_status <'a, T: JackPort> (theme: ItemTheme, title: &'a str, ports: &'a [T])
-> impl Draw<Tui> + use<'a, T> -> impl Draw<Tui> + use<'a, T>
{ {
let ins = ports.len() as u16; let ins = ports.len() as u16;
let frame = Outer(true, Style::default().fg(g(96))); let frame = Outer(true, Style::default().fg(g(96)));
border(true, frame, field_v(theme, title, iter_south({ let names = iter_south(move||ports.iter().enumerate().map(|(index, port)|format!(
move||ports.iter().enumerate().map(|(index, port)|{ " {index} {}", port.port_name()
east!(" ", index, " ", port.port_name()).align_w().full_h() ).align_w().full_h()));
}) let field = field_v(theme, title, names);
})).exact_wh(20, 1 + ins)).exact_wh(20, 1 + ins) border(true, frame, field.exact_wh(20, 1 + ins)).exact_wh(20, 1 + ins)
} }
pub fn view_io_ports <'a, T: PortsSizes<'a>> ( pub fn view_io_ports <'a, T: PortsSizes<'a>> (

View file

@ -207,9 +207,13 @@ pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize +
let select = self.selection(); let select = self.selection();
let editor = self.editor(); let editor = self.editor();
let editing = self.is_editing(); let editing = self.is_editing();
iter_south(move||self.scenes_with_sizes().map(move|(index, scene, ..)|{ draw(move |to: &mut Tui|{
view_scene_name(select, editor, index, scene, editing) for (index, scene, ..) in self.scenes_with_sizes() {
})) view_scene_name(select, editor, index, scene, editing).draw(to)?;
}
Ok(Some(XYWH(1, 1, 1, 1)))
})
.exact_w(20)
} }
fn scenes_with_sizes (&self) -> impl ScenesSizes<'_> { fn scenes_with_sizes (&self) -> impl ScenesSizes<'_> {
@ -255,30 +259,6 @@ impl ScenesView for Arrangement {
} }
} }
pub fn view_scene_name <'a> (
select: &Selection,
editor: Option<&'a MidiEditor>,
index: usize,
scene: &Scene,
editing: bool
) -> impl Draw<Tui> {
let h = if select.scene() == Some(index) && let Some(_editor) = editor {
7
} else {
Scene::DEFAULT_HEIGHT as u16
};
let a = east!("·s", index, " ", fg(g(255), bold(true, &scene.name))).align_w().full_w();
let b = when(select.scene() == Some(index) && editing, south(
editor.as_ref().map(|e|e.clip_status()),
editor.as_ref().map(|e|e.edit_status())).align_nw().full_wh());
let c = if select.scene() == Some(index) {
scene.color.light.term
} else {
scene.color.base.term
};
bg(c, south(a, b).align_nw()).exact_wh(20, h)
}
pub trait HasSceneScroll: HasScenes { pub trait HasSceneScroll: HasScenes {
fn scene_scroll (&self) -> usize; fn scene_scroll (&self) -> usize;
} }
@ -294,3 +274,28 @@ impl HasSceneScroll for App {
self.project.scene_scroll() self.project.scene_scroll()
} }
} }
pub fn view_scene_name <'a> (
select: &Selection,
editor: Option<&'a MidiEditor>,
index: usize,
scene: &Scene,
editing: bool
) -> impl Draw<Tui> {
let h = if select.scene() == Some(index) && let Some(_editor) = editor {
7
} else {
Scene::DEFAULT_HEIGHT as u16
};
let a = east(format!("·s{index:02} "),
fg(g(255), bold(true, &scene.name))).align_w().full_w();
let b = when(select.scene() == Some(index) && editing, south(
editor.as_ref().map(|e|e.clip_status()),
editor.as_ref().map(|e|e.edit_status())).align_nw().full_wh());
let c = if select.scene() == Some(index) {
scene.color.light.term
} else {
scene.color.base.term
};
bg(c, south(a, b).align_nw()).exact_wh(20, h)
}

View file

@ -345,83 +345,90 @@ pub trait TracksView: HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTra
/// Draw name of each track /// Draw name of each track
fn view_track_names (&self, theme: ItemTheme) -> impl Draw<Tui> { fn view_track_names (&self, theme: ItemTheme) -> impl Draw<Tui> {
let selected = self.selection(); let selected = self.selection();
let btn1t = button_3("t", "rack ", east( east(
selected.track().map(|track|east(track, "/")), south(
self.tracks().len() button_3(
), false); "t",
let btn1s = button_3("s", "cene ", east( "rack ",
selected.scene().map(|scene|east(scene, "/")), east(
self.scenes().len() selected.track().map(|track|east(track, "/")),
), false); self.tracks().len()
let btns1 = south(btn1t, btn1s); ),
let btns2 = south( false
button_2("T", "+", false), ),
button_2("S", "+", false), button_3(
); "s",
view_track_row_section(theme, "cene ",
btns1, east(
btns2, selected.scene().map(|scene|east(scene, "/")),
bg(theme.darker.term, self.scenes().len()
iter_east(||self.tracks_with_sizes().map(|(index, track, _x1, _x2)|{ ),
let b = if selected.track() == Some(index) { false
track.color.light.term )
} else { ),
track.color.base.term west(
}; south(
bg(b, south( button_2("T", "+", false),
east!( button_2("S", "+", false),
"·t", ),
index, bg(theme.darker.term, iter_east(||self.tracks_with_sizes()
" ", .map(|(index, track, _x1, _x2)|{
fg(Rgb(255, 255, 255), bold(true, &track.name)) let b = if selected.track() == Some(index) {
) track.color.light.term
.align_nw() } else {
.full_w(), track.color.base.term
"" };
)) bg(b, south(
.exact_w(track_width(index, track)) east!(
.exact_h(2) "·t",
})))) index,
} " ",
fg(Rgb(255, 255, 255), bold(true, &track.name))
fn view_track_output_count (&self) -> impl Draw<Tui> { )
south(button_2("o", "utput", false).align_w().full_w(), .align_nw()
draw(|to: &mut Tui|{ .full_w(),
for port in self.midi_outs().iter() { ""
let _ = port.port_name().align_w().full_w().draw(to)?; ))
} .exact_w(track_width(index, track))
Ok(Some(XYWH(0, 0, 0, 0))) .exact_h(2)
})) })))
} )
)
fn view_track_output_add (&self) -> impl Draw<Tui> {
button_2("O", "+", false)
} }
/// Draw outputs per track /// Draw outputs per track
fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw<Tui> { fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw<Tui> {
view_track_row_section(theme, view_track_row_section(theme,
self.view_track_output_count(), south(button_2("o", "utput", false).align_w().full_w(),
self.view_track_output_add(), draw(|to: &mut Tui|{
bg(theme.darker.term, for port in self.midi_outs().iter() {
iter_east(move||self.tracks_with_sizes().map(move|(index, track, _x1, _x2)|{ let _ = port.port_name().align_w().full_w().draw(to)?;
}
Ok(Some(XYWH(0, 0, 0, 0)))
})),
button_2("O", "+", false),
bg(theme.darker.term, draw(|to: &mut Tui|{
for (index, track, _x1, _x2) in self.tracks_with_sizes() {
let f = Rgb(255, 255, 255); let f = Rgb(255, 255, 255);
let b = track.color.dark.term; let b = track.color.dark.term;
iter_south(move||track.sequencer.midi_outs.iter().map(move|port: &MidiOutput|{ iter_south(||track.sequencer.midi_outs.iter().map(|port: &MidiOutput|{
fg(f, bg(b, east!("·o", index, " ", port.port_name()).full_w().align_w()).exact_h(1)) fg(f, bg(b, east!(
"·o",
index,
" ",
port.port_name()
)
.full_w()
.align_w()
).exact_h(1))
})) }))
.full_h() .full_h()
.align_nw() .align_nw()
.exact_w(track_width(index, track)) .exact_w(track_width(index, track))
})))) .draw(to)?;
} }
Ok(Some(XYWH(0, 0, 0, 0)))
fn view_track_input_count (&self) -> impl Draw<Tui> { }).align_w()))
button_2("i", "nput", false)
}
fn view_track_input_add (&self) -> impl Draw<Tui> {
button_2("I", "+", false)
} }
/// Draw inputs per track /// Draw inputs per track
@ -430,12 +437,9 @@ pub trait TracksView: HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTra
for track in self.tracks().iter() { for track in self.tracks().iter() {
height = height.max(track.sequencer.midi_ins.len() as u16); height = height.max(track.sequencer.midi_ins.len() as u16);
} }
view_track_row_section(theme, view_track_row_section(theme, button_2("i", "nput", false), button_2("I", "+", false),
self.view_track_input_count(), bg(theme.darker.term, draw(move|to: &mut Tui|{
self.view_track_input_add(), for (index, track, _x1, _x2) in self.tracks_with_sizes() {
bg(theme.darker.term, iter_east(move||self
.tracks_with_sizes()
.map(move|(index, track, _x1, _x2)|{
south( south(
bg(track.color.base.term, bg(track.color.base.term,
east!( east!(
@ -443,13 +447,17 @@ pub trait TracksView: HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTra
either(track.sequencer.recording, fg(Red, "●rec "), "·rec "), either(track.sequencer.recording, fg(Red, "●rec "), "·rec "),
either(track.sequencer.overdub, fg(Yellow, "●dub "), "·dub "), either(track.sequencer.overdub, fg(Yellow, "●dub "), "·dub "),
).align_w().full_w()), ).align_w().full_w()),
iter_south(move||track.sequencer.midi_ins.iter().map(move|port|fg_bg( iter_south(||track.sequencer.midi_ins.iter().map(|port|fg_bg(Rgb(255, 255, 255), track.color.dark.term,
Rgb(255, 255, 255), east!(
track.color.dark.term, "·i",
east!("·i", index, " ", port.port_name()).align_w().full_w())))) index,
.align_nw() " ",
.exact_wh(track_width(index, track), height + 1) port.port_name()
}))).align_w()) ).align_w().full_w())))
).align_nw().exact_wh(track_width(index, track), height + 1).draw(to)?;
}
Ok(Some(XYWH(0, 0, 0, 0)))
}).align_w()))
} }
fn view_track_devices (&self, theme: ItemTheme) -> impl Draw<Tui> { fn view_track_devices (&self, theme: ItemTheme) -> impl Draw<Tui> {
@ -632,7 +640,7 @@ fn view_track_row_section <'a> (
west( west(
button_add.align_nw().exact_w(4).full_h(), button_add.align_nw().exact_w(4).full_h(),
east( east(
button.align_nw().full_h().exact_w(16), button.align_nw().full_h().exact_w(20),
content.align_c().full_wh() content.align_c().full_wh()
) )
) )

View file

@ -1,8 +1,8 @@
(view :logo (text tek)) (view :logo (text tek))
(view :browse (bsp/s (view :browse (bsp/s
(pad/xy 3 1 :browse-title) (padding 3 1 :browse-title)
(fg (g 96) browser))) (enclose (fg (g 96)) browser)))
(mode :transport (mode :transport
(name Transport) (name Transport)
@ -34,16 +34,17 @@
(mode :scene (keys :scene)) (mode :scene (keys :scene))
(mode :mix (keys :mix)) (mode :mix (keys :mix))
(view (view
(bsp/n (bsp/e :transport :status) (bsp/n (bg (g 10) (bsp/e :transport :status))
(bar/w 2 (align/ne :meters/output) (bsp/w (bg (g 20) (exact/w 4 (align/ne :meters/output)))
(bar/e 2 (align/nw :meters/input) (bsp/e (bg (g 30) (exact/w 4 (align/nw :meters/input)))
(full/xy (pad/xy 2 1 (align/c (full/xy (align/c (max/wh 80 80
(bar/s 2 :tracks/outputs (bsp/s (bg (g 40) (exact/h 4 :tracks/outputs))
(bar/s 2 :tracks/devices (bsp/s (bg (g 60) (exact/h 4 :tracks/devices))
(bar/s 2 :tracks/names (bsp/s (bg (g 50) (exact/h 2 :tracks/names))
(bar/n 2 :tracks/inputs (bsp/s (either :mode/editor
(bar/e 16 (bg (g 50) :scenes/names) (bg (g 80) (bsp/e :scenes/names :editor))
(align/c :scenes/clips)))))))))))))) (bg (g 90) :scenes))
(bg (g 70) (exact/h 4 :tracks/inputs))))))))))))))
(keys :clock (@space clock/toggle 0) (keys :clock (@space clock/toggle 0)
(@shift/space clock/toggle 0)) (@shift/space clock/toggle 0))
@ -81,29 +82,22 @@
(mode browse (keys :browse)) (mode browse (keys :browse))
(mode rename (keys :pool/rename)) (mode rename (keys :pool/rename))
(mode length (keys :pool/length)) (mode length (keys :pool/length))
(view (bsp/s (exact/h 1 :transport)
(bsp/s (exact/h 1 :transport) (bsp/n (exact/h 1 :status)
(bsp/n (exact/h 1 :status) (fill (bsp/a (fill/xy (align/e :pool)) :editor)))))
(fill/xy (bsp/a (fill/xy (align/e :pool))
:editor))))))
(mode :sampler (name Sampler) (info Sample player.) (mode :sampler (name Sampler) (info Sample player.)
(keys :sampler/directions :sampler/record :sampler/play) (keys :sampler/directions :sampler/record :sampler/play)
(view (bsp/s (exact/h 1 :transport)
(bsp/s (exact/h 1 :transport) (bsp/n (exact/h 1 :status)
(bsp/n (exact/h 1 :status) (fill :samples/grid))))
(fill/xy :samples/grid)))))
(mode :groovebox (name Groovebox) (info Sequencer with sampler.) (mode :groovebox (name Groovebox) (info Sequencer with sampler.)
(keys :clock :editor :sampler :global) (keys :clock :editor :sampler :global)
(mode browse (keys :browse)) (mode browse (keys :browse))
(mode rename (keys :pool-rename)) (mode rename (keys :pool-rename))
(mode length (keys :pool-length)) (mode length (keys :pool-length))
(view (bsp/w :meters/output (bsp/e :meters/input (bsp/w :groove/meta :groove/editor))))
(bsp/w :meters/output
(bsp/e :meters/input
(bsp/w :groove/meta
:groove/editor)))))
(view :groove/meta (fill/y (align/n (stack/s :midi-ins/status :midi-outs/status :audio-ins/status :audio-outs/status :pool)))) (view :groove/meta (fill/y (align/n (stack/s :midi-ins/status :midi-outs/status :audio-ins/status :audio-outs/status :pool))))

View file

@ -2,22 +2,242 @@
//#![feature(impl_trait_in_assoc_type)] // Used by EntriesIterator; TODO remove //#![feature(impl_trait_in_assoc_type)] // Used by EntriesIterator; TODO remove
mod deps; pub use self::deps::*; mod deps; pub use self::deps::*;
/// Banner.
pub(crate) const HEADER: &'static str = r#"
~ ~~~ ~ ~ ~~ ~ ~ ~ ~~ ~ ~ ~ ~
~ heatwave is the new darkwave ~
~ ~ ~~~ ~ ~ ~ ~ ~~~ ~~~ ~ ~~ "#;
pub fn show_version () {
println!("versions aint real man");
}
/// Command-line entrypoint. /// Command-line entrypoint.
#[allow(unused)] #[allow(unused)]
#[hotpath::main] #[hotpath::main]
fn main () -> Usually<()> { fn main () -> Usually<()> {
#[cfg(feature = "prof")] let _flame_guard = {
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
};
tengri::Tui::setup_panic(); tengri::Tui::setup_panic();
#[cfg(feature = "cli")] let outcome = Config::watched(run_with_config); #[cfg(feature = "cli")] {
#[cfg(not(feature = "cli"))] let outcome = Config::watched(run_new_plain); Config::watched(crate::cli::run_with_config)?;
if let Err(e) = outcome { }
println!("{e:#?}"); #[cfg(not(feature = "cli"))] {
std::process::exit(1); Config::watched(run_new_plain)?;
} }
Ok(()) Ok(())
} }
pub use self::cli::*; #[cfg(not(feature = "cli"))]
mod cli; fn run_new_plain (config: Config) -> Usually<()> {
let name = "tek";
tengri::Tui::run_main(Jack::new_run(name, move|jack|{
let mode = ":menu";
let title = "untitled!";
let bpm = 74.;
let clock = Clock::new(&jack, Some(bpm))?;
let tracks = [];
let scenes = [];
Ok(App::new(None, Arrangement::new(
&jack,
title.into(),
clock,
tracks.into_iter(),
scenes.into_iter(),
connect_midi_ins(&jack, &"M", &[], None)?.into_iter(),
connect_midi_outs(&jack, &"M", &[], None)?.into_iter(),
[].into_iter().chain(connect_audio_ins(&jack, &"L", &[], None)?.into_iter())
.chain(connect_audio_ins(&jack, &"R", &[], None)?.into_iter()),
[].into_iter().chain(connect_audio_outs(&jack, &"L", &[], None)?.into_iter())
.chain(connect_audio_outs(&jack, &"R", &[], None)?.into_iter()),
), config, mode))
})?)
}
//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)
//})?)?
//}
#[cfg(feature = "cli")] pub mod cli {
use crate::*;
pub fn run_with_config (config: Arc<Config>) -> Usually<()> {
Cli::parse().run(Some(config))
}
/// 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())
}
}
/// The command-line interface descriptor.
///
/// ```
/// let cli: tek::cli::Cli = Default::default();
///
/// use clap::CommandFactory;
/// tek::cli::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,
}
impl Action {
fn run (&self, config: Arc<Config>) -> 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(),
Arc::new(RwLock::new(App::new(
Some(exit),
sesh.init()?,
config,
":menu"
))))).map(|_|())?,
_ => todo!()
}
Ok(())
}
}
/// Application modes that can be passed to the mommand line interface.
///
/// ```
/// let action: tek::cli::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,
}
#[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<Arrangement> {
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 = Arrangement::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 use self::config::*; pub use self::config::*;
mod config; mod config;
@ -106,11 +326,7 @@ mod app {
/// Contains the currently edited musical arrangement /// Contains the currently edited musical arrangement
pub project: Arrangement, pub project: Arrangement,
/// Error, if any /// Error, if any
pub error: Arc<RwLock<Option<Arc<str>>>>, pub error: Arc<RwLock<Option<Arc<str>>>>
#[cfg(feature = "prof2")]
/// Tracing guard
pub guard: Option<tracing_flame::FlushGuard<std::io::BufWriter<std::fs::File>>>,
} }
impl App { impl App {
@ -118,15 +334,16 @@ mod app {
/// ///
/// ``` /// ```
/// let mut proj = tek::Arrangement::default(); /// let mut proj = tek::Arrangement::default();
/// proj.jack = tek::tengri::Jack::new(&"test_tek").expect("failed to connect to jack"); /// proj.jack = tek::tengri::Jack::new(&"test_tek").expect("failed to connect to jack");
/// let mut conf = tek::config_load(tek::Config::default(), "(mode hello)").unwrap(); /// let mut conf = std::sync::Arc::new(tek::Config::default());
/// let tek = tek::App::new(None, proj, conf.into(), "hello"); /// conf.add("(mode hello)");
/// let tek = tek::App::new(None, proj, conf, "hello");
/// ``` /// ```
pub fn new ( pub fn new (
exit: Option<Exit>, exit: Option<Exit>,
project: Arrangement, project: Arrangement,
config: Arc<Config>, config: Arc<Config>,
mode: impl AsRef<str> mode: impl AsRef<str>
) -> Self { ) -> Self {
App { App {
exit: exit.unwrap_or_default(), exit: exit.unwrap_or_default(),
@ -285,7 +502,7 @@ mod bind {
use crate::*; use crate::*;
tui_keys!(self: App, input { tui_keys!(self: App, input {
#[cfg(feature = "prof2")] profiling::scope!("App::tui_keys!"); #[cfg(feature = "prof")] profiling::scope!("App::tui_keys!");
let name = self.mode.as_ref(); let name = self.mode.as_ref();
let mode = name.and_then(|m|self.config.get_mode(m)).as_ref().map(Arc::clone); let mode = name.and_then(|m|self.config.get_mode(m)).as_ref().map(Arc::clone);
if let Some(mode) = mode { if let Some(mode) = mode {
@ -691,12 +908,12 @@ mod draw {
/// Then, every top-level form of the DSL description is rendered. /// Then, every top-level form of the DSL description is rendered.
impl Draw<Tui> for App { impl Draw<Tui> for App {
fn draw (&self, to: &mut Tui) -> Drawn<u16> { fn draw (&self, to: &mut Tui) -> Drawn<u16> {
#[cfg(feature = "prof2")] profiling::scope!("App::draw"); #[cfg(feature = "prof")] profiling::scope!("App::draw");
//self.perf.cycle(&mut |_|{ //self.perf.cycle(&mut |_|{
self.draw_error(to)?; self.draw_error(to)?;
self.draw_modes(to)?; self.draw_modes(to)?;
self.draw_debug(to)?; self.draw_debug(to)?;
#[cfg(feature = "prof2")] profiling::finish_frame!(); #[cfg(feature = "prof")] profiling::finish_frame!();
Ok(Some(to.area().into())) Ok(Some(to.area().into()))
//}) //})
} }
@ -747,7 +964,7 @@ mod draw {
format!("{}/{} {} ", format!("{}/{} {} ",
self.perf.used.load(Relaxed), self.perf.used.load(Relaxed),
self.perf.window.load(Relaxed), self.perf.window.load(Relaxed),
self.perf.clock.raw() / 10000000), self.perf.clock.raw() / 1000000000),
).align_se().draw(to) ).align_se().draw(to)
} }
} }
@ -900,3 +1117,33 @@ mod draw {
} }
} }
pub fn print_status (project: &Arrangement) {
println!("Name: {:?}", &project.name);
println!("JACK: {:?}", &project.jack);
println!("Buffer: {:?}", &project.clock.chunk);
println!("Sample rate: {:?}", &project.clock.timebase.sr);
println!("MIDI PPQ: {:?}", &project.clock.timebase.ppq);
println!("Tempo: {:?}", &project.clock.timebase.bpm);
println!("Quantize: {:?}", &project.clock.quant);
println!("Launch: {:?}", &project.clock.sync);
println!("Playhead: {:?}us", &project.clock.playhead.usec);
println!("Playhead: {:?}s", &project.clock.playhead.sample);
println!("Playhead: {:?}p", &project.clock.playhead.pulse);
println!("Started: {:?}", &project.clock.started);
println!("Tracks:");
for (i, t) in project.tracks.iter().enumerate() {
println!(" Track {i}: {} {} {:?} {:?}", t.name, t.width,
&t.sequencer.play_clip, &t.sequencer.next_clip);
}
println!("Scenes:");
for (i, t) in project.scenes.iter().enumerate() {
println!(" Scene {i}: {} {:?}", &t.name, &t.clips);
}
println!("MIDI Ins: {:?}", &project.midi_ins);
println!("MIDI Outs: {:?}", &project.midi_outs);
println!("Audio Ins: {:?}", &project.audio_ins);
println!("Audio Outs: {:?}", &project.audio_outs);
// TODO git integration
// TODO dawvert integration
}

2
tengri

@ -1 +1 @@
Subproject commit b318d498e544fa65bbf3a3ff4dabde166352b045 Subproject commit 8e7286e409ec6d4ac4382856ff93767a4758e11e