mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-09-18 12:56:42 +02:00
Compare commits
6 commits
f1756f9a0e
...
d495d97516
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d495d97516 | ||
|
|
3deef0641d | ||
|
|
1082f62696 | ||
|
|
e29f8de174 | ||
|
|
30b3802b56 | ||
|
|
ab6959a84f |
16 changed files with 672 additions and 663 deletions
27
.gitignore
vendored
27
.gitignore
vendored
|
|
@ -1,18 +1,19 @@
|
||||||
*/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.*
|
||||||
.misc
|
!build/README.md
|
||||||
|
!target/.gitkeep
|
||||||
|
*.profraw
|
||||||
|
*/cov
|
||||||
|
*/target
|
||||||
.direnv
|
.direnv
|
||||||
|
.misc
|
||||||
|
build/*
|
||||||
callgrind.*
|
callgrind.*
|
||||||
|
cov
|
||||||
|
example.mid
|
||||||
|
flamegraph*.svg
|
||||||
|
perf.data*
|
||||||
|
profile.json.gz
|
||||||
|
target/*
|
||||||
tracing*.*
|
tracing*.*
|
||||||
|
vgcore*
|
||||||
|
|
|
||||||
4
Cargo.lock
generated
4
Cargo.lock
generated
|
|
@ -3893,13 +3893,9 @@ 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",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,9 +51,10 @@ 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 = ["tengri/prof"]
|
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"]
|
||||||
|
|
|
||||||
12
Justfile
12
Justfile
|
|
@ -48,12 +48,16 @@ run:
|
||||||
run-init:
|
run-init:
|
||||||
rm -rf ~/.config/tek && {{debug}}
|
rm -rf ~/.config/tek && {{debug}}
|
||||||
|
|
||||||
prof:
|
prof +ARGS="new":
|
||||||
CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph -F 10000 -- new
|
CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph -F 10000 -- {{ARGS}}
|
||||||
|
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
pkgs.perf
|
pkgs.perf
|
||||||
pkgs.pkg-config
|
pkgs.pkg-config
|
||||||
pkgs.watchexec
|
pkgs.watchexec
|
||||||
|
pkgs.samply
|
||||||
];
|
];
|
||||||
buildInputs = [
|
buildInputs = [
|
||||||
pkgs.libclang
|
pkgs.libclang
|
||||||
|
|
|
||||||
306
src/cli.rs
Normal file
306
src/cli.rs
Normal file
|
|
@ -0,0 +1,306 @@
|
||||||
|
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)
|
||||||
|
//})?)?
|
||||||
|
//}
|
||||||
334
src/config.rs
334
src/config.rs
|
|
@ -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))
|
src.each(config, |c, s|config_load_item(c, s.trim()))
|
||||||
}
|
}
|
||||||
|
|
||||||
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,6 +88,12 @@ 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:
|
||||||
|
|
@ -99,10 +105,9 @@ pub fn modes_add <'a> (modes: &Modes, expr: impl Language) -> UsuallyRef<'a, ()>
|
||||||
/// - ... -> view
|
/// - ... -> view
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// let mut mode: tek::Mode<std::sync::Arc<str>> = Default::default();
|
/// let mut mode: tek::Mode = tek::mode_add(tek::Mode::default(), "(name hello)").unwrap();
|
||||||
/// mode.add("(name hello)").unwrap();
|
|
||||||
/// ```
|
/// ```
|
||||||
pub fn mode_add (mut mode: Mode, dsl: impl Language) -> Usually<Mode> {
|
pub fn mode_add <T: AsMut<Mode>> (mut mode: T, dsl: impl Language) -> Usually<T> {
|
||||||
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("");
|
||||||
|
|
@ -112,23 +117,23 @@ pub fn mode_add (mut mode: Mode, dsl: impl Language) -> Usually<Mode> {
|
||||||
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.modes.clone();
|
let modes = mode.as_mut().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: Mode, expr: &str|{
|
tail.each(mode, |mut mode: T, expr: &str|{
|
||||||
mode.keys.push(expr.trim().into());
|
mode.as_mut().keys.push(expr.trim().into());
|
||||||
Ok(mode)
|
Ok(mode)
|
||||||
})?
|
})?
|
||||||
},
|
},
|
||||||
"name" => { mode.name.push(tail.into()); mode },
|
"name" => { mode.as_mut().name.push(tail.into()); mode },
|
||||||
"info" => { mode.info.push(tail.into()); mode },
|
"info" => { mode.as_mut().info.push(tail.into()); mode },
|
||||||
"view" => { mode.view.push(View::new(tail)?.into()); mode },
|
"view" => { mode.as_mut().view.push(View::new(tail)?.into()); mode },
|
||||||
_ => { mode.view.push(View::new(tail)?.into()); mode },
|
_ => { mode.as_mut().view.push(View::new(tail)?.into()); mode },
|
||||||
}
|
}
|
||||||
} else if let Ok(Some(word)) = dsl.word() {
|
} else if let Ok(Some(word)) = dsl.word() {
|
||||||
mode.view.push(View::new(word)?.into());
|
mode.as_mut().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());
|
||||||
|
|
@ -136,12 +141,12 @@ pub fn mode_add (mut mode: Mode, dsl: impl Language) -> Usually<Mode> {
|
||||||
}
|
}
|
||||||
|
|
||||||
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 item.expr().head() == Ok(Some("see")) {
|
body.each((), |_, item: &str|if matches!(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() {
|
||||||
|
|
@ -172,22 +177,15 @@ 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 config = tek::Config::default();
|
/// let source = stringify!(
|
||||||
/// ```
|
/// (mode :menu (name Menu)
|
||||||
///
|
/// (info Mode selector.) (keys :axis/y :confirm)
|
||||||
/// ```
|
/// (view (bg (g 0) (bsp/s :ports/out
|
||||||
/// // Some dizzle.
|
/// (bsp/n :ports/in
|
||||||
/// // What indentation to use here lol?
|
/// (bg (g 30) (bsp/s (fixed/y 7 :logo)
|
||||||
/// let source = stringify!((mode :menu (name Menu)
|
/// (fill :dialog/menu)))))))));
|
||||||
/// (info Mode selector.) (keys :axis/y :confirm)
|
/// let config: tek::Config = tek::modes_add(tek::Config::default(), source).unwrap();
|
||||||
/// (view (bg (g 0) (bsp/s :ports/out
|
/// let mode: tek::Mode = config.get_mode(":menu").unwrap();
|
||||||
/// (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 {
|
||||||
|
|
@ -221,7 +219,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::<std::sync::Arc<str>>::default();
|
/// let mode = tek::Mode::default();
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct Mode {
|
pub struct Mode {
|
||||||
|
|
@ -265,9 +263,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 Some(expr) = source.expr()? {
|
let layer = if let Ok(Some(expr)) = source.expr() {
|
||||||
Self::compile_expr(expr.into())?
|
Self::compile_expr(expr.into())?
|
||||||
} else if let Some(word) = source.word()? {
|
} else if let Ok(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())
|
||||||
|
|
@ -278,14 +276,24 @@ 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(expr.nth(1)?.ok_or_else(||Box::<dyn Error>::from("when: no arg0: condition"))?);
|
let cond = Arc::from(arg!(expr, head, 1, "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 = expr.nth(2)?.ok_or_else(||Box::<dyn Error>::from("when: no arg1: content"))?;
|
let thunk = Self::compile(arg!(expr, head, 2, "content"))?;
|
||||||
let thunk = Self::compile(thunk)?;
|
|
||||||
Self::boxed(move|state, screen|{
|
Self::boxed(move|state, screen|{
|
||||||
when(
|
when(
|
||||||
cond(state)?,
|
cond(state)?,
|
||||||
|
|
@ -295,12 +303,10 @@ impl View<Tui, App> {
|
||||||
},
|
},
|
||||||
|
|
||||||
"either" => {
|
"either" => {
|
||||||
let cond = Arc::from(expr.nth(1)?.ok_or_else(||Box::<dyn Error>::from("either: no arg0: condition"))?);
|
let cond = Arc::from(arg!(expr, head, 1, "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 = expr.nth(2)?.ok_or_else(||Box::<dyn Error>::from("either: no arg1: content"))?;
|
let a = Self::compile(arg!(expr, head, 2, "content A"))?;
|
||||||
let a = Self::compile(a)?;
|
let b = Self::compile(arg!(expr, head, 3, "content B"))?;
|
||||||
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)?,
|
||||||
|
|
@ -310,7 +316,7 @@ impl View<Tui, App> {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
"bsp" | "split" => {
|
"bsp" | "split" | "stack" => {
|
||||||
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,
|
||||||
|
|
@ -321,14 +327,34 @@ 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(expr.nth(1)?.ok_or_else(||Box::<dyn Error>::from("either: no arg0: content"))?)?;
|
let a = Self::compile(arg!(expr, head, 1, "content A"))?;
|
||||||
let b = Self::compile(expr.nth(2)?.ok_or_else(||Box::<dyn Error>::from("either: no arg1: content"))?)?;
|
let b = Self::compile(arg!(expr, head, 2, "content B"))?;
|
||||||
Self::boxed(move|state, screen|{
|
Self::boxed(move|state, screen|split.stack(
|
||||||
split.stack(
|
draw(|screen|a(state, screen)),
|
||||||
draw(|screen|a(state, screen)),
|
draw(|screen|b(state, screen)),
|
||||||
draw(|screen|b(state, screen)),
|
).draw(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" => {
|
||||||
|
|
@ -347,8 +373,7 @@ 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(expr.nth(2)?
|
let thunk = Self::compile(arg!(expr, head, 1, "content"))?;
|
||||||
.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),
|
||||||
|
|
@ -357,9 +382,8 @@ impl View<Tui, App> {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
"full" => {
|
"full" | "fill" => {
|
||||||
let thunk = Self::compile(expr.nth(2)?
|
let thunk = Self::compile(arg!(expr, head, 1, "content"))?;
|
||||||
.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)
|
||||||
|
|
@ -367,83 +391,56 @@ 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") => Self::boxed(move|state, screen|{
|
Some("wh") | Some("xy") | None => 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" => {
|
"exact" | "min" | "max" | "push" | "pull" | "pad" => {
|
||||||
match head.split('/').skip(1).next() {
|
match head.split('/').skip(1).next() {
|
||||||
Some("w") | Some("x") => {
|
Some("w") | Some("x") => {
|
||||||
let value = Arc::from(expr.nth(1)?.ok_or_else(||Box::<dyn Error>::from("{}: no arg1: value"))?);
|
let value = Arc::from(arg!(expr, head, 1, "value"));
|
||||||
let value = move|state: &App|state.namespace(&value);
|
let value = move|state: &App|state.namespace(&value);
|
||||||
let thunk = Self::compile(expr.nth(2)?.ok_or_else(||Box::<dyn Error>::from("either: no arg2: content"))?)?;
|
let thunk = Self::compile(arg!(expr, head, 2, "content"))?;
|
||||||
match ns {
|
match ns {
|
||||||
"exact" => Self::boxed(move|state, screen|Exact::W(
|
"exact" => Self::boxed(move|state, screen|Exact::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||||
draw(|screen|thunk(state, screen)), value(state)?
|
"push" => Self::boxed(move|state, screen|Push::X(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)),
|
||||||
"push" => Self::boxed(move|state, screen|Push::X(
|
"pad" => Self::boxed(move|state, screen|Pad::X(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||||
draw(|screen|thunk(state, screen)), value(state)?
|
"min" => Self::boxed(move|state, screen|Min::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||||
).draw(screen)),
|
"max" => Self::boxed(move|state, screen|Max::W(draw(|screen|thunk(state, screen)), value(state)?).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(expr.nth(1)?.ok_or_else(||Box::<dyn Error>::from("{}: no arg1: value"))?);
|
let value = Arc::from(arg!(expr, head, 1, "value"));
|
||||||
let value = move|state: &App|state.namespace(&value);
|
let value = move|state: &App|state.namespace(&value);
|
||||||
let thunk = Self::compile(expr.nth(2)?.ok_or_else(||Box::<dyn Error>::from("either: no arg2: content"))?)?;
|
let thunk = Self::compile(arg!(expr, head, 2, "content"))?;
|
||||||
match ns {
|
match ns {
|
||||||
"exact" => Self::boxed(move|state, screen|Exact::H(
|
"exact" => Self::boxed(move|state, screen|Exact::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||||
draw(|screen|thunk(state, screen)), value(state)?
|
"push" => Self::boxed(move|state, screen|Push::Y(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)),
|
||||||
"push" => Self::boxed(move|state, screen|Push::Y(
|
"pad" => Self::boxed(move|state, screen|Pad::Y(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||||
draw(|screen|thunk(state, screen)), value(state)?
|
"min" => Self::boxed(move|state, screen|Min::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||||
).draw(screen)),
|
"max" => Self::boxed(move|state, screen|Max::H(draw(|screen|thunk(state, screen)), value(state)?).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") => {
|
Some("wh") | Some("xy") | None => {
|
||||||
let value1 = Arc::from(expr.nth(1)?.ok_or_else(||Box::<dyn Error>::from("{}: no arg1: value"))?);
|
let value1 = Arc::from(arg!(expr, head, 1, "value"));
|
||||||
let value1 = move|state: &App|state.namespace(&value1);
|
let value1 = move|state: &App|state.namespace(&value1);
|
||||||
let value2 = Arc::from(expr.nth(2)?.ok_or_else(||Box::<dyn Error>::from("{}: no arg2: value"))?);
|
let value2 = Arc::from(arg!(expr, head, 2, "value"));
|
||||||
let value2 = move|state: &App|state.namespace(&value2);
|
let value2 = move|state: &App|state.namespace(&value2);
|
||||||
let thunk = Self::compile(expr.nth(3)?.ok_or_else(||Box::<dyn Error>::from("either: no arg3: content"))?)?;
|
let thunk = Self::compile(arg!(expr, head, 3, "content"))?;
|
||||||
match ns {
|
match ns {
|
||||||
"exact" => Self::boxed(move|state, screen|Exact::WH(
|
"exact" => Self::boxed(move|state, screen|Exact::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
|
||||||
draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?
|
"push" => Self::boxed(move|state, screen|Push::XY(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)),
|
||||||
"push" => Self::boxed(move|state, screen|Push::XY(
|
"pad" => Self::boxed(move|state, screen|Pad::XY(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
|
||||||
draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?
|
"min" => Self::boxed(move|state, screen|Min::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
|
||||||
).draw(screen)),
|
"max" => Self::boxed(move|state, screen|Max::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).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!()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -452,27 +449,31 @@ impl View<Tui, App> {
|
||||||
},
|
},
|
||||||
|
|
||||||
"fg" | "bg" => {
|
"fg" | "bg" => {
|
||||||
let color = expr.nth(1)?.ok_or_else(||Box::<dyn Error>::from("{}: no arg1: color"))?;
|
let color = Arc::from(arg!(expr, head, 1, "color"));
|
||||||
let thunk = Self::compile(expr.nth(2)?.ok_or_else(||Box::<dyn Error>::from("either: no arg2: thunk"))?)?;
|
let color = move|state: &App|state.namespace(&color);
|
||||||
todo!()
|
let thunk = Self::compile(arg!(expr, head, 2, "content"))?;
|
||||||
|
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" => {
|
||||||
todo!()
|
let text: Arc<str> = Arc::from(expr.tail()?.unwrap_or_default());
|
||||||
|
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())
|
||||||
}))
|
}))
|
||||||
|
|
@ -481,6 +482,10 @@ 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() {
|
||||||
|
|
@ -490,26 +495,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") => 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("names") => draw!(|app|app.project.view_track_names(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("inputs") => draw!(|app|app.project.view_track_inputs(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("devices") => draw!(|app|app.project.view_track_devices(app.color.clone())),
|
||||||
Some("outputs") => Self::boxed(move|state, to|state.project.view_track_outputs(state.color.clone(), 0).draw(to)),
|
Some("outputs") => draw!(|app|app.project.view_track_outputs(app.color.clone(), 0)),
|
||||||
_ => panic!()
|
_ => panic!()
|
||||||
},
|
},
|
||||||
Some(":scenes") => match word.split("/").skip(1).next() {
|
Some(":scenes") => match word.split("/").skip(1).next() {
|
||||||
None => Self::boxed(move|state, to|state.view_scenes_clips().draw(to)),
|
Some("clips") => draw!(|app|app.view_scenes_clips()),
|
||||||
Some("names") => Self::boxed(move|state, to|state.view_scenes_names().draw(to)),
|
Some("names") => draw!(|app|app.view_scenes_names()),
|
||||||
_ => 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(":templates") => Self::boxed(move|state, to|view_templates(state).draw(to)),
|
Some(":sessions") => Self::boxed(move|_, to|view_sessions().draw(to)),
|
||||||
Some(":sessions") => Self::boxed(move|state, to|view_sessions().draw(to)),
|
Some(":status") => Self::boxed(move|_, to|"TODO: Status Bar".draw(to)),
|
||||||
Some(":browse/title") => Self::boxed(move|state, to|view_browse_title(state).draw(to)),
|
Some(":editor") => Self::boxed(move|_, to|"TODO Editor".draw(to)),
|
||||||
Some(":device") => Self::boxed(move|state, to|view_device(state).draw(to)),
|
Some(":transport") => Self::boxed(move|_, to|view_transport(true, "", "", "").draw(to)),
|
||||||
Some(":status") => Self::boxed(move|state, to|"TODO: Status Bar".draw(to)),
|
Some(":debug") => Self::boxed(move|_, to|format!("[{:?}]", to.area()).exact_h(1).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 {
|
||||||
|
|
@ -632,7 +637,9 @@ 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 = "(@x (nop)) (@y (nop) (nop))";
|
/// let lang = stringify!(
|
||||||
|
/// (@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));
|
||||||
|
|
@ -719,50 +726,3 @@ 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!();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -46,11 +46,11 @@ pub(crate) use ::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "prof")]
|
#[cfg(feature = "prof2")]
|
||||||
pub use ::tengri::{
|
pub use ::tengri::{
|
||||||
profiling,
|
profiling,
|
||||||
tracing,
|
tracing,
|
||||||
tracing_flame,
|
tracing_tracy,
|
||||||
tracing_subscriber
|
tracing_subscriber
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@ 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()
|
||||||
|
|
|
||||||
|
|
@ -50,44 +50,32 @@ 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();
|
||||||
return size.of(
|
with_clips_size(true, self.clips_size(), iter_east(move||self.tracks_with_sizes()
|
||||||
above(
|
.map(move|(track_index, track, _, _)|iter_south(move||self.scenes_with_sizes()
|
||||||
fg(Green, format!("{}x{}", size.w(), size.h())).align_se().full_wh(),
|
.map(move|(scene_index, scene, _, _)|{
|
||||||
iter_east(move||self.tracks_with_sizes().map(move|(
|
let (name, theme): (Arc<str>, ItemTheme) = view_scene_name_theme(scene, track_index);
|
||||||
track_index, track, _, _
|
let f = theme.lightest.term;
|
||||||
)| {
|
let (b, o) = view_scene_bg(theme, select, track_index, scene_index);
|
||||||
iter_south(move||self.scenes_with_sizes().map(move|(
|
let is_selected = view_scene_sel(select, track_index, scene_index, editing);
|
||||||
scene_index, scene, _, _
|
below(
|
||||||
)| {
|
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(
|
||||||
below(
|
fg_bg(o, b, "".full_wh()),
|
||||||
fg_bg(o, b, "".full_wh()),
|
fg_bg(f, b, bold(true, name)).align_nw().full_wh(),
|
||||||
fg_bg(f, b, bold(true, name)).align_nw().full_wh(),
|
),
|
||||||
),
|
when(is_selected, editor).full_wh()
|
||||||
when(is_selected, editor).full_wh()
|
).full_wh()
|
||||||
).full_wh()
|
).exact_wh(
|
||||||
).exact_wh(w, y)
|
view_scene_w(track, select, track_index, editor),
|
||||||
})).full_h().exact_w(track.width as u16)
|
view_scene_y(select, scene_index, editor),
|
||||||
}))
|
)
|
||||||
).full_wh());
|
})
|
||||||
|
)
|
||||||
|
.full_h()
|
||||||
|
.exact_w(track.width as u16))
|
||||||
|
)).align_c()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,6 +88,11 @@ 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) {
|
||||||
|
|
|
||||||
|
|
@ -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)));
|
||||||
let names = iter_south(move||ports.iter().enumerate().map(|(index, port)|format!(
|
border(true, frame, field_v(theme, title, iter_south({
|
||||||
" {index} {}", port.port_name()
|
move||ports.iter().enumerate().map(|(index, port)|{
|
||||||
).align_w().full_h()));
|
east!(" ", index, " ", port.port_name()).align_w().full_h()
|
||||||
let field = field_v(theme, title, names);
|
})
|
||||||
border(true, frame, field.exact_wh(20, 1 + ins)).exact_wh(20, 1 + ins)
|
})).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>> (
|
||||||
|
|
|
||||||
|
|
@ -207,13 +207,9 @@ 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();
|
||||||
draw(move |to: &mut Tui|{
|
iter_south(move||self.scenes_with_sizes().map(move|(index, scene, ..)|{
|
||||||
for (index, scene, ..) in self.scenes_with_sizes() {
|
view_scene_name(select, editor, index, scene, editing)
|
||||||
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<'_> {
|
||||||
|
|
@ -259,6 +255,30 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
@ -274,28 +294,3 @@ 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)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -345,90 +345,83 @@ 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();
|
||||||
east(
|
let btn1t = button_3("t", "rack ", east(
|
||||||
south(
|
selected.track().map(|track|east(track, "/")),
|
||||||
button_3(
|
self.tracks().len()
|
||||||
"t",
|
), false);
|
||||||
"rack ",
|
let btn1s = button_3("s", "cene ", east(
|
||||||
east(
|
selected.scene().map(|scene|east(scene, "/")),
|
||||||
selected.track().map(|track|east(track, "/")),
|
self.scenes().len()
|
||||||
self.tracks().len()
|
), false);
|
||||||
),
|
let btns1 = south(btn1t, btn1s);
|
||||||
false
|
let btns2 = south(
|
||||||
),
|
button_2("T", "+", false),
|
||||||
button_3(
|
button_2("S", "+", false),
|
||||||
"s",
|
);
|
||||||
"cene ",
|
view_track_row_section(theme,
|
||||||
east(
|
btns1,
|
||||||
selected.scene().map(|scene|east(scene, "/")),
|
btns2,
|
||||||
self.scenes().len()
|
bg(theme.darker.term,
|
||||||
),
|
iter_east(||self.tracks_with_sizes().map(|(index, track, _x1, _x2)|{
|
||||||
false
|
let b = if selected.track() == Some(index) {
|
||||||
)
|
track.color.light.term
|
||||||
),
|
} else {
|
||||||
west(
|
track.color.base.term
|
||||||
south(
|
};
|
||||||
button_2("T", "+", false),
|
bg(b, south(
|
||||||
button_2("S", "+", false),
|
east!(
|
||||||
),
|
"·t",
|
||||||
bg(theme.darker.term, iter_east(||self.tracks_with_sizes()
|
index,
|
||||||
.map(|(index, track, _x1, _x2)|{
|
" ",
|
||||||
let b = if selected.track() == Some(index) {
|
fg(Rgb(255, 255, 255), bold(true, &track.name))
|
||||||
track.color.light.term
|
)
|
||||||
} else {
|
.align_nw()
|
||||||
track.color.base.term
|
.full_w(),
|
||||||
};
|
""
|
||||||
bg(b, south(
|
))
|
||||||
east!(
|
.exact_w(track_width(index, track))
|
||||||
"·t",
|
.exact_h(2)
|
||||||
index,
|
}))))
|
||||||
" ",
|
}
|
||||||
fg(Rgb(255, 255, 255), bold(true, &track.name))
|
|
||||||
)
|
fn view_track_output_count (&self) -> impl Draw<Tui> {
|
||||||
.align_nw()
|
south(button_2("o", "utput", false).align_w().full_w(),
|
||||||
.full_w(),
|
draw(|to: &mut Tui|{
|
||||||
""
|
for port in self.midi_outs().iter() {
|
||||||
))
|
let _ = port.port_name().align_w().full_w().draw(to)?;
|
||||||
.exact_w(track_width(index, track))
|
}
|
||||||
.exact_h(2)
|
Ok(Some(XYWH(0, 0, 0, 0)))
|
||||||
})))
|
}))
|
||||||
)
|
}
|
||||||
)
|
|
||||||
|
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,
|
||||||
south(button_2("o", "utput", false).align_w().full_w(),
|
self.view_track_output_count(),
|
||||||
draw(|to: &mut Tui|{
|
self.view_track_output_add(),
|
||||||
for port in self.midi_outs().iter() {
|
bg(theme.darker.term,
|
||||||
let _ = port.port_name().align_w().full_w().draw(to)?;
|
iter_east(move||self.tracks_with_sizes().map(move|(index, track, _x1, _x2)|{
|
||||||
}
|
|
||||||
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(||track.sequencer.midi_outs.iter().map(|port: &MidiOutput|{
|
iter_south(move||track.sequencer.midi_outs.iter().map(move|port: &MidiOutput|{
|
||||||
fg(f, bg(b, east!(
|
fg(f, bg(b, east!("·o", index, " ", port.port_name()).full_w().align_w()).exact_h(1))
|
||||||
"·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)))
|
|
||||||
}).align_w()))
|
fn view_track_input_count (&self) -> impl Draw<Tui> {
|
||||||
|
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
|
||||||
|
|
@ -437,9 +430,12 @@ 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, button_2("i", "nput", false), button_2("I", "+", false),
|
view_track_row_section(theme,
|
||||||
bg(theme.darker.term, draw(move|to: &mut Tui|{
|
self.view_track_input_count(),
|
||||||
for (index, track, _x1, _x2) in self.tracks_with_sizes() {
|
self.view_track_input_add(),
|
||||||
|
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!(
|
||||||
|
|
@ -447,17 +443,13 @@ 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(||track.sequencer.midi_ins.iter().map(|port|fg_bg(Rgb(255, 255, 255), track.color.dark.term,
|
iter_south(move||track.sequencer.midi_ins.iter().map(move|port|fg_bg(
|
||||||
east!(
|
Rgb(255, 255, 255),
|
||||||
"·i",
|
track.color.dark.term,
|
||||||
index,
|
east!("·i", index, " ", port.port_name()).align_w().full_w()))))
|
||||||
" ",
|
.align_nw()
|
||||||
port.port_name()
|
.exact_wh(track_width(index, track), height + 1)
|
||||||
).align_w().full_w())))
|
}))).align_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> {
|
||||||
|
|
@ -640,7 +632,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(20),
|
button.align_nw().full_h().exact_w(16),
|
||||||
content.align_c().full_wh()
|
content.align_c().full_wh()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
46
src/tek.edn
46
src/tek.edn
|
|
@ -1,8 +1,8 @@
|
||||||
(view :logo (text tek))
|
(view :logo (text tek))
|
||||||
|
|
||||||
(view :browse (bsp/s
|
(view :browse (bsp/s
|
||||||
(padding 3 1 :browse-title)
|
(pad/xy 3 1 :browse-title)
|
||||||
(enclose (fg (g 96)) browser)))
|
(fg (g 96) browser)))
|
||||||
|
|
||||||
(mode :transport
|
(mode :transport
|
||||||
(name Transport)
|
(name Transport)
|
||||||
|
|
@ -34,17 +34,16 @@
|
||||||
(mode :scene (keys :scene))
|
(mode :scene (keys :scene))
|
||||||
(mode :mix (keys :mix))
|
(mode :mix (keys :mix))
|
||||||
(view
|
(view
|
||||||
(bsp/n (bg (g 10) (bsp/e :transport :status))
|
(bsp/n (bsp/e :transport :status)
|
||||||
(bsp/w (bg (g 20) (exact/w 4 (align/ne :meters/output)))
|
(bar/w 2 (align/ne :meters/output)
|
||||||
(bsp/e (bg (g 30) (exact/w 4 (align/nw :meters/input)))
|
(bar/e 2 (align/nw :meters/input)
|
||||||
(full/xy (align/c (max/wh 80 80
|
(full/xy (pad/xy 2 1 (align/c
|
||||||
(bsp/s (bg (g 40) (exact/h 4 :tracks/outputs))
|
(bar/s 2 :tracks/outputs
|
||||||
(bsp/s (bg (g 60) (exact/h 4 :tracks/devices))
|
(bar/s 2 :tracks/devices
|
||||||
(bsp/s (bg (g 50) (exact/h 2 :tracks/names))
|
(bar/s 2 :tracks/names
|
||||||
(bsp/s (either :mode/editor
|
(bar/n 2 :tracks/inputs
|
||||||
(bg (g 80) (bsp/e :scenes/names :editor))
|
(bar/e 16 (bg (g 50) :scenes/names)
|
||||||
(bg (g 90) :scenes))
|
(align/c :scenes/clips))))))))))))))
|
||||||
(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))
|
||||||
|
|
@ -82,22 +81,29 @@
|
||||||
(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))
|
||||||
(bsp/s (exact/h 1 :transport)
|
(view
|
||||||
(bsp/n (exact/h 1 :status)
|
(bsp/s (exact/h 1 :transport)
|
||||||
(fill (bsp/a (fill/xy (align/e :pool)) :editor)))))
|
(bsp/n (exact/h 1 :status)
|
||||||
|
(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)
|
||||||
(bsp/s (exact/h 1 :transport)
|
(view
|
||||||
(bsp/n (exact/h 1 :status)
|
(bsp/s (exact/h 1 :transport)
|
||||||
(fill :samples/grid))))
|
(bsp/n (exact/h 1 :status)
|
||||||
|
(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))
|
||||||
(bsp/w :meters/output (bsp/e :meters/input (bsp/w :groove/meta :groove/editor))))
|
(view
|
||||||
|
(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))))
|
||||||
|
|
||||||
|
|
|
||||||
291
src/tek.rs
291
src/tek.rs
|
|
@ -2,242 +2,22 @@
|
||||||
//#![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")] {
|
#[cfg(feature = "cli")] let outcome = Config::watched(run_with_config);
|
||||||
Config::watched(crate::cli::run_with_config)?;
|
#[cfg(not(feature = "cli"))] let outcome = Config::watched(run_new_plain);
|
||||||
}
|
if let Err(e) = outcome {
|
||||||
#[cfg(not(feature = "cli"))] {
|
println!("{e:#?}");
|
||||||
Config::watched(run_new_plain)?;
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "cli"))]
|
pub use self::cli::*;
|
||||||
fn run_new_plain (config: Config) -> Usually<()> {
|
mod cli;
|
||||||
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;
|
||||||
|
|
@ -326,7 +106,11 @@ 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 {
|
||||||
|
|
@ -334,16 +118,15 @@ 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 = std::sync::Arc::new(tek::Config::default());
|
/// let mut conf = tek::config_load(tek::Config::default(), "(mode hello)").unwrap();
|
||||||
/// conf.add("(mode hello)");
|
/// let tek = tek::App::new(None, proj, conf.into(), "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(),
|
||||||
|
|
@ -502,7 +285,7 @@ mod bind {
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
tui_keys!(self: App, input {
|
tui_keys!(self: App, input {
|
||||||
#[cfg(feature = "prof")] profiling::scope!("App::tui_keys!");
|
#[cfg(feature = "prof2")] 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 {
|
||||||
|
|
@ -908,12 +691,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 = "prof")] profiling::scope!("App::draw");
|
#[cfg(feature = "prof2")] 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 = "prof")] profiling::finish_frame!();
|
#[cfg(feature = "prof2")] profiling::finish_frame!();
|
||||||
Ok(Some(to.area().into()))
|
Ok(Some(to.area().into()))
|
||||||
//})
|
//})
|
||||||
}
|
}
|
||||||
|
|
@ -964,7 +747,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() / 1000000000),
|
self.perf.clock.raw() / 10000000),
|
||||||
).align_se().draw(to)
|
).align_se().draw(to)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1117,33 +900,3 @@ 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
2
tengri
|
|
@ -1 +1 @@
|
||||||
Subproject commit 8e7286e409ec6d4ac4382856ff93767a4758e11e
|
Subproject commit b318d498e544fa65bbf3a3ff4dabde166352b045
|
||||||
Loading…
Add table
Add a link
Reference in a new issue