mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-09-18 04:46:43 +02:00
Compare commits
6 commits
f1756f9a0e
...
d495d97516
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d495d97516 | ||
|
|
3deef0641d | ||
|
|
1082f62696 | ||
|
|
e29f8de174 | ||
|
|
30b3802b56 | ||
|
|
ab6959a84f |
26 changed files with 1053 additions and 773 deletions
29
.gitignore
vendored
29
.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/Dockerfile.*
|
||||
.misc
|
||||
!build/README.md
|
||||
!target/.gitkeep
|
||||
*.profraw
|
||||
*/cov
|
||||
*/target
|
||||
.direnv
|
||||
.misc
|
||||
build/*
|
||||
callgrind.*
|
||||
tracing.*
|
||||
cov
|
||||
example.mid
|
||||
flamegraph*.svg
|
||||
perf.data*
|
||||
profile.json.gz
|
||||
target/*
|
||||
tracing*.*
|
||||
vgcore*
|
||||
|
|
|
|||
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -3892,6 +3892,7 @@ dependencies = [
|
|||
"konst",
|
||||
"midly",
|
||||
"palette",
|
||||
"parking_lot 0.12.5",
|
||||
"quanta",
|
||||
"rand 0.8.7",
|
||||
"ratatui",
|
||||
|
|
|
|||
|
|
@ -51,8 +51,10 @@ proptest = { version = "^1" }
|
|||
proptest-derive = { version = "^0.5.1" }
|
||||
|
||||
[features]
|
||||
default = ["cli", "arranger", "sampler"]
|
||||
default = ["cli", "arranger", "sampler", "prof"]
|
||||
|
||||
prof = []
|
||||
#prof2 = ["prof", "tengri/prof"]
|
||||
hotpath = ["hotpath/hotpath"]
|
||||
hotpath-cpu = ["hotpath/hotpath-cpu"]
|
||||
hotpath-alloc = ["hotpath/hotpath-alloc"]
|
||||
|
|
@ -82,7 +84,7 @@ vst3 = []
|
|||
|
||||
[profile.release]
|
||||
lto = true
|
||||
debug = "line-tables-only"
|
||||
debug = true
|
||||
|
||||
[profile.coverage]
|
||||
inherits = "test"
|
||||
|
|
|
|||
16
Justfile
16
Justfile
|
|
@ -1,6 +1,6 @@
|
|||
#export RUSTFLAGS := "--cfg procmacro2_semver_exempt -Zmacro-backtrace -Clink-arg=-fuse-ld=mold"
|
||||
export RUST_BACKTRACE := "1"
|
||||
export RUSTFLAGS := "-Zmacro-backtrace -Clink-arg=-fuse-ld=mold"
|
||||
export RUSTFLAGS := "-Zmacro-backtrace -Clink-arg=-fuse-ld=mold -Clink-arg=-Wl,--no-rosegment -Cforce-frame-pointers=yes"
|
||||
|
||||
[default]
|
||||
list:
|
||||
|
|
@ -48,12 +48,16 @@ run:
|
|||
run-init:
|
||||
rm -rf ~/.config/tek && {{debug}}
|
||||
|
||||
prof:
|
||||
CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph -- new
|
||||
prof +ARGS="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:
|
||||
{{release}}
|
||||
release := "reset && cargo run --release"
|
||||
release +ARGS="new":
|
||||
{{release}} -- {{ARGS}}
|
||||
build-release:
|
||||
time cargo build -j4 --release
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
pkgs.perf
|
||||
pkgs.pkg-config
|
||||
pkgs.watchexec
|
||||
pkgs.samply
|
||||
];
|
||||
buildInputs = [
|
||||
pkgs.libclang
|
||||
|
|
|
|||
|
|
@ -1186,3 +1186,24 @@
|
|||
//take!(ClipCommand |state: Arrangement, iter|state.selected_clip().as_ref()
|
||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
||||
|
||||
|
||||
//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.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)
|
||||
//})?)?
|
||||
//}
|
||||
|
|
|
|||
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)
|
||||
//})?)?
|
||||
//}
|
||||
490
src/config.rs
490
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> {
|
||||
config.as_ref().clear();
|
||||
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> {
|
||||
|
|
@ -57,7 +57,7 @@ pub fn config_watch (
|
|||
move|result|{
|
||||
match result {
|
||||
Ok(_events) => if let Err(e) = config_init(config.as_ref()) {
|
||||
*config.as_ref().error.write().unwrap() = Some(format!("{e:?}").into());
|
||||
*config.as_ref().error.try_write().unwrap() = Some(format!("{e:?}").into());
|
||||
panic!("{e:?}");
|
||||
} else {
|
||||
//println!("config updated");
|
||||
|
|
@ -71,7 +71,7 @@ pub fn config_watch (
|
|||
if let Some(path) = config.as_ref().get_file() {
|
||||
//println!("watching: {path:?}");
|
||||
watcher.watch(&path, RecursiveMode::NonRecursive)?;
|
||||
*config.as_ref().watch.write().unwrap() = Some(watcher);
|
||||
*config.as_ref().watch.try_write().unwrap() = Some(watcher);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("no config path").into())
|
||||
|
|
@ -83,11 +83,17 @@ pub fn modes_add <'a> (modes: &Modes, expr: impl Language) -> UsuallyRef<'a, ()>
|
|||
let name = expr.head()?.ok_or("mode: missing name")?;
|
||||
let body = expr.tail()?.ok_or("mode: missing body")?;
|
||||
let mode = Mode::default();
|
||||
let mode = body.each(mode, |c,s|mode_add(c,s))?;
|
||||
modes.0.write().unwrap().insert(name.into(), Arc::new(mode));
|
||||
let mode = body.each(mode, |c, s|mode_add(c, s))?;
|
||||
modes.0.try_write().unwrap().insert(name.into(), Arc::new(mode));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl AsMut<Mode> for Mode {
|
||||
fn as_mut (&mut self) -> &mut Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a definition to the mode.
|
||||
///
|
||||
/// Supported definitions:
|
||||
|
|
@ -99,10 +105,9 @@ pub fn modes_add <'a> (modes: &Modes, expr: impl Language) -> UsuallyRef<'a, ()>
|
|||
/// - ... -> view
|
||||
///
|
||||
/// ```
|
||||
/// let mut mode: tek::Mode<std::sync::Arc<str>> = Default::default();
|
||||
/// mode.add("(name hello)").unwrap();
|
||||
/// let mut mode: tek::Mode = tek::mode_add(tek::Mode::default(), "(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() {
|
||||
//println!("Mode::add: {head} {:?}", expr.tail());
|
||||
let tail = expr.tail()?.map(|x|x.trim()).unwrap_or("");
|
||||
|
|
@ -112,46 +117,36 @@ pub fn mode_add (mut mode: Mode, dsl: impl Language) -> Usually<Mode> {
|
|||
let body = tail.tail()?.ok_or("submode: missing body")?;
|
||||
let submode = Mode::default();
|
||||
let submode = body.each(submode, |c,s|mode_add(c,s))?;
|
||||
let modes = mode.modes.clone();
|
||||
modes.0.write().unwrap().insert(name.into(), Arc::new(submode));
|
||||
let modes = mode.as_mut().modes.clone();
|
||||
modes.0.try_write().unwrap().insert(name.into(), Arc::new(submode));
|
||||
mode
|
||||
},
|
||||
"keys" => {
|
||||
dsl.each(mode, |mut mode: Mode, expr: &str|{
|
||||
mode.keys.push(expr.trim().into());
|
||||
tail.each(mode, |mut mode: T, expr: &str|{
|
||||
mode.as_mut().keys.push(expr.trim().into());
|
||||
Ok(mode)
|
||||
})?
|
||||
},
|
||||
"name" => { mode.name.push(tail.into()); mode },
|
||||
"info" => { mode.info.push(tail.into()); mode },
|
||||
"view" => { mode.view.push(tail.into()); mode },
|
||||
_ => { mode.view.push(expr.into()); mode },
|
||||
"name" => { mode.as_mut().name.push(tail.into()); mode },
|
||||
"info" => { mode.as_mut().info.push(tail.into()); mode },
|
||||
"view" => { mode.as_mut().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() {
|
||||
mode.view.push(word.into());
|
||||
mode.as_mut().view.push(View::new(word)?.into());
|
||||
mode
|
||||
} else {
|
||||
return Err(format!("Mode::add: unexpected: {dsl:?}").into());
|
||||
})
|
||||
}
|
||||
|
||||
/// Load custom view definition.
|
||||
pub fn load_view <'a> (views: &Views, expr: impl Language) -> UsuallyRef<'a, ()> {
|
||||
let name = expr.head()?.ok_or("view: missing name")?;
|
||||
let body = expr.tail()?.ok_or("view: missing body")?;
|
||||
views.write().unwrap().insert(
|
||||
name.into(),
|
||||
body.src()?.unwrap_or_default().into()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_bind <'a> (binds: &Binds, expr: impl Language) -> UsuallyRef<'a, ()> {
|
||||
//println!("\n\rload_bind: {expr:?}");
|
||||
let name = expr.head()?.ok_or("bind: missing name")?;
|
||||
let body = expr.tail()?.unwrap_or("");
|
||||
binds.write().unwrap().insert(name.into(), {
|
||||
binds.try_write().unwrap().insert(name.into(), {
|
||||
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
|
||||
Ok(())
|
||||
} else if let Ok(Some(_word)) = item.expr().head().word() {
|
||||
|
|
@ -169,10 +164,10 @@ pub fn load_bind <'a> (binds: &Binds, expr: impl Language) -> UsuallyRef<'a, ()>
|
|||
// TODO
|
||||
return Ok(())
|
||||
} else {
|
||||
return Err(format!("Config::load_bind: invalid key: {:?}", item.expr()?.head()?).into())
|
||||
return Err(format!("load_bind: invalid key: {:?}", item.expr()?.head()?).into())
|
||||
}
|
||||
} else {
|
||||
return Err(format!("Config::load_bind: unexpected: {item:?}").into())
|
||||
return Err(format!("load_bind: unexpected: {item:?}").into())
|
||||
})?;
|
||||
map
|
||||
});
|
||||
|
|
@ -182,22 +177,15 @@ pub fn load_bind <'a> (binds: &Binds, expr: impl Language) -> UsuallyRef<'a, ()>
|
|||
/// Configuration: mode, view, and bind definitions.
|
||||
///
|
||||
/// ```
|
||||
/// let config = tek::Config::default();
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// // Some dizzle.
|
||||
/// // What indentation to use here lol?
|
||||
/// let source = stringify!((mode :menu (name Menu)
|
||||
/// (info Mode selector.) (keys :axis/y :confirm)
|
||||
/// (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();
|
||||
/// let source = stringify!(
|
||||
/// (mode :menu (name Menu)
|
||||
/// (info Mode selector.) (keys :axis/y :confirm)
|
||||
/// (view (bg (g 0) (bsp/s :ports/out
|
||||
/// (bsp/n :ports/in
|
||||
/// (bg (g 30) (bsp/s (fixed/y 7 :logo)
|
||||
/// (fill :dialog/menu)))))))));
|
||||
/// let config: tek::Config = tek::modes_add(tek::Config::default(), source).unwrap();
|
||||
/// let mode: tek::Mode = config.get_mode(":menu").unwrap();
|
||||
/// ```
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Config {
|
||||
|
|
@ -231,18 +219,321 @@ pub struct Modes(Arc<RwLock<BTreeMap<Arc<str>, Arc<Mode>>>>);
|
|||
/// Group of view and keys definitions.
|
||||
///
|
||||
/// ```
|
||||
/// let mode = tek::Mode::<std::sync::Arc<str>>::default();
|
||||
/// let mode = tek::Mode::default();
|
||||
/// ```
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Mode {
|
||||
pub path: PathBuf,
|
||||
pub name: Vec<Arc<str>>,
|
||||
pub info: Vec<Arc<str>>,
|
||||
pub view: Vec<Arc<str>>,
|
||||
pub view: Vec<Arc<View<Tui, App>>>,
|
||||
pub keys: Vec<Arc<str>>,
|
||||
pub modes: Modes,
|
||||
}
|
||||
|
||||
/// Collection of custom view definitions.
|
||||
pub type Views = Arc<RwLock<BTreeMap<Arc<str>, Arc<View<Tui, App>>>>>;
|
||||
|
||||
/// Custom view definition is a boxed closure emitting a [Draw]able from state `S`.
|
||||
pub struct View<S: Screen, T> {
|
||||
pub source: Arc<str>,
|
||||
pub render: Arc<Box<dyn Fn(&T, &mut S)->Drawn<S::Unit> + Send + Sync>>
|
||||
}
|
||||
|
||||
impl_debug!(<S: Screen, T> View<S, T> |self, w| { write!(w, "View({})", self.source) });
|
||||
|
||||
impl_display!(<S: Screen, T> View<S, T> |self, w| { write!(w, "View({})", self.source) });
|
||||
|
||||
impl View<Tui, App> {
|
||||
|
||||
pub fn new (source: impl AsRef<str>) -> Usually<Self> {
|
||||
Ok(Self {
|
||||
source: source.as_ref().into(),
|
||||
render: Self::compile(source)?
|
||||
})
|
||||
}
|
||||
|
||||
fn boxed <F: Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync + 'static> (f: F)
|
||||
-> Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync + 'static>
|
||||
{
|
||||
Box::new(f)
|
||||
}
|
||||
|
||||
fn compile (source: impl AsRef<str>) ->
|
||||
Usually<Arc<Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync>>>
|
||||
{
|
||||
let source = source.as_ref();
|
||||
let layer = if let Ok(Some(expr)) = source.expr() {
|
||||
Self::compile_expr(expr.into())?
|
||||
} else if let Ok(Some(word)) = source.word() {
|
||||
Self::compile_word(word.into())?
|
||||
} else {
|
||||
return Err(format!("not word/expr:\n{source:?}").into())
|
||||
};
|
||||
Ok(Arc::new(Box::new(move|state, screen|layer(state, screen))))
|
||||
}
|
||||
|
||||
fn compile_expr (expr: Arc<str>) ->
|
||||
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() {
|
||||
|
||||
match ns {
|
||||
|
||||
"when" => {
|
||||
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 thunk = Self::compile(arg!(expr, head, 2, "content"))?;
|
||||
Self::boxed(move|state, screen|{
|
||||
when(
|
||||
cond(state)?,
|
||||
draw(|screen|thunk(state, screen))
|
||||
).draw(screen)
|
||||
})
|
||||
},
|
||||
|
||||
"either" => {
|
||||
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 a = Self::compile(arg!(expr, head, 2, "content A"))?;
|
||||
let b = Self::compile(arg!(expr, head, 3, "content B"))?;
|
||||
Self::boxed(move|state, screen|{
|
||||
either(
|
||||
cond(state)?,
|
||||
draw(|screen|a(state, screen)),
|
||||
draw(|screen|b(state, screen)),
|
||||
).draw(screen)
|
||||
})
|
||||
},
|
||||
|
||||
"bsp" | "split" | "stack" => {
|
||||
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 a = Self::compile(arg!(expr, head, 1, "content A"))?;
|
||||
let b = Self::compile(arg!(expr, head, 2, "content B"))?;
|
||||
Self::boxed(move|state, screen|split.stack(
|
||||
draw(|screen|a(state, 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" => {
|
||||
let azimuth = head.split('/').skip(1).next();
|
||||
let azimuth = match azimuth {
|
||||
Some("n") => Azimuth::N,
|
||||
Some("s") => Azimuth::S,
|
||||
Some("e") => Azimuth::E,
|
||||
Some("w") => Azimuth::W,
|
||||
Some("ne") => Azimuth::NE,
|
||||
Some("se") => Azimuth::SE,
|
||||
Some("nw") => Azimuth::NW,
|
||||
Some("sw") => Azimuth::SW,
|
||||
Some("c") => Azimuth::C,
|
||||
Some("x") => Azimuth::X,
|
||||
Some("y") => Azimuth::Y,
|
||||
_ => return Err(format!("invalid azimuth: {azimuth:?}").into())
|
||||
};
|
||||
let thunk = Self::compile(arg!(expr, head, 1, "content"))?;
|
||||
Self::boxed(move|state, screen|{
|
||||
Align(
|
||||
Some(azimuth),
|
||||
draw(|screen|thunk(state, screen))
|
||||
).draw(screen)
|
||||
})
|
||||
},
|
||||
|
||||
"full" | "fill" => {
|
||||
let thunk = Self::compile(arg!(expr, head, 1, "content"))?;
|
||||
match head.split('/').skip(1).next() {
|
||||
Some("w") | Some("x") => Self::boxed(move|state, screen|{
|
||||
Full::W(draw(|screen|thunk(state, screen))).draw(screen)
|
||||
}),
|
||||
Some("h") | Some("y") => Self::boxed(move|state, screen|{
|
||||
Full::H(draw(|screen|thunk(state, screen))).draw(screen)
|
||||
}),
|
||||
Some("wh") | Some("xy") | None => Self::boxed(move|state, screen|{
|
||||
Full::WH(draw(|screen|thunk(state, screen))).draw(screen)
|
||||
}),
|
||||
_ => unreachable!()
|
||||
}
|
||||
},
|
||||
|
||||
"exact" | "min" | "max" | "push" | "pull" | "pad" => {
|
||||
match head.split('/').skip(1).next() {
|
||||
Some("w") | Some("x") => {
|
||||
let value = Arc::from(arg!(expr, head, 1, "value"));
|
||||
let value = move|state: &App|state.namespace(&value);
|
||||
let thunk = Self::compile(arg!(expr, head, 2, "content"))?;
|
||||
match ns {
|
||||
"exact" => Self::boxed(move|state, screen|Exact::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"push" => Self::boxed(move|state, screen|Push::X(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)),
|
||||
"pad" => Self::boxed(move|state, screen|Pad::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!()
|
||||
}
|
||||
},
|
||||
Some("h") | Some("y") => {
|
||||
let value = Arc::from(arg!(expr, head, 1, "value"));
|
||||
let value = move|state: &App|state.namespace(&value);
|
||||
let thunk = Self::compile(arg!(expr, head, 2, "content"))?;
|
||||
match ns {
|
||||
"exact" => Self::boxed(move|state, screen|Exact::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"push" => Self::boxed(move|state, screen|Push::Y(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)),
|
||||
"pad" => Self::boxed(move|state, screen|Pad::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!()
|
||||
}
|
||||
},
|
||||
Some("wh") | Some("xy") | None => {
|
||||
let value1 = Arc::from(arg!(expr, head, 1, "value"));
|
||||
let value1 = move|state: &App|state.namespace(&value1);
|
||||
let value2 = Arc::from(arg!(expr, head, 2, "value"));
|
||||
let value2 = move|state: &App|state.namespace(&value2);
|
||||
let thunk = Self::compile(arg!(expr, head, 3, "content"))?;
|
||||
match ns {
|
||||
"exact" => Self::boxed(move|state, screen|Exact::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
|
||||
"push" => Self::boxed(move|state, screen|Push::XY(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)),
|
||||
"pad" => Self::boxed(move|state, screen|Pad::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!()
|
||||
}
|
||||
},
|
||||
|
||||
"fg" | "bg" => {
|
||||
let color = Arc::from(arg!(expr, head, 1, "color"));
|
||||
let color = move|state: &App|state.namespace(&color);
|
||||
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" => {
|
||||
let text: Arc<str> = Arc::from(expr.tail()?.unwrap_or_default());
|
||||
Self::boxed(move|_state, screen|{
|
||||
text.draw(screen)
|
||||
})
|
||||
},
|
||||
|
||||
_ => return Err(format!("compile_expr: unexpected: {expr:?}").into())
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
return Err(format!("compile_expr: invalid expression: {expr:?}").into())
|
||||
}))
|
||||
}
|
||||
|
||||
fn compile_word (word: Arc<str>)
|
||||
-> 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() {
|
||||
//Some(":logo") => view_logo().draw(to),
|
||||
Some(":meters") => match word.split("/").skip(1).next() {
|
||||
Some("input") => Self::boxed(move|_, to|bg(Rgb(30, 30, 30), "Input Meters".align_s().full_h()).draw(to)),
|
||||
Some("output") => Self::boxed(move|_, to|bg(Rgb(30, 30, 30), "Output Meters".align_s().full_h()).draw(to)),
|
||||
_ => panic!()
|
||||
},
|
||||
Some(":tracks") => match word.split("/").skip(1).next() {
|
||||
None => Self::boxed(move|_, to|"TODO tracks".draw(to)),
|
||||
Some("names") => draw!(|app|app.project.view_track_names(app.color.clone())),
|
||||
Some("inputs") => draw!(|app|app.project.view_track_inputs(app.color.clone())),
|
||||
Some("devices") => draw!(|app|app.project.view_track_devices(app.color.clone())),
|
||||
Some("outputs") => draw!(|app|app.project.view_track_outputs(app.color.clone(), 0)),
|
||||
_ => panic!()
|
||||
},
|
||||
Some(":scenes") => match word.split("/").skip(1).next() {
|
||||
Some("clips") => draw!(|app|app.view_scenes_clips()),
|
||||
Some("names") => draw!(|app|app.view_scenes_names()),
|
||||
_ => 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(":sessions") => Self::boxed(move|_, to|view_sessions().draw(to)),
|
||||
Some(":status") => Self::boxed(move|_, to|"TODO: Status Bar".draw(to)),
|
||||
Some(":editor") => Self::boxed(move|_, to|"TODO Editor".draw(to)),
|
||||
Some(":transport") => Self::boxed(move|_, to|view_transport(true, "", "", "").draw(to)),
|
||||
Some(":debug") => Self::boxed(move|_, 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()) {
|
||||
(view.render)(state, to)
|
||||
} else {
|
||||
fg(Color::Rgb(128, 32, 32), format!("undefined: {word:?}")).draw(to)
|
||||
}),
|
||||
_ => unreachable!()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Load custom view definition.
|
||||
pub fn load_view <'a> (views: &Views, expr: impl Language) -> UsuallyRef<'a, ()> {
|
||||
views.try_write().unwrap().insert(
|
||||
expr.head()?.ok_or("view: missing name")?.into(),
|
||||
View::new(expr.tail()?.ok_or("view: missing body")?)?.into()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
||||
/// Default configuration directory.
|
||||
|
|
@ -308,44 +599,32 @@ impl Config {
|
|||
|
||||
/// Make this configuration empty.
|
||||
fn clear (&self) {
|
||||
*self.modes.0.write().unwrap() = Default::default();
|
||||
*self.views.write().unwrap() = Default::default();
|
||||
*self.binds.write().unwrap() = Default::default();
|
||||
*self.modes.0.try_write().unwrap() = Default::default();
|
||||
*self.views.try_write().unwrap() = Default::default();
|
||||
*self.binds.try_write().unwrap() = Default::default();
|
||||
}
|
||||
|
||||
pub fn get_view (&self, name: impl AsRef<str>) -> Option<Arc<str>> {
|
||||
self.views.read().unwrap().get(name.as_ref()).cloned()
|
||||
pub fn get_view (&self, name: impl AsRef<str>) -> Option<Arc<View<Tui, App>>> {
|
||||
self.views.try_read().unwrap().get(name.as_ref()).cloned()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub use self::view::*;
|
||||
mod view {
|
||||
use crate::*;
|
||||
/// Collection of custom view definitions.
|
||||
pub type Views = Arc<RwLock<BTreeMap<Arc<str>, Arc<str>>>>;
|
||||
}
|
||||
|
||||
pub use self::mode::*;
|
||||
mod mode {
|
||||
use crate::*;
|
||||
|
||||
impl Modes {
|
||||
/// Get a mode by name.
|
||||
pub fn get (&self, name: impl AsRef<str>) -> Option<Arc<Mode>> {
|
||||
self.0.read().unwrap().get(name.as_ref()).cloned()
|
||||
}
|
||||
/// Run something for each mode.
|
||||
pub fn for_each <T> (&self, mut ator: impl FnMut(&str, &Mode)->T) {
|
||||
for (k, v) in self.0.read().unwrap().iter() {
|
||||
let _ = ator(k.as_ref(), v.as_ref());
|
||||
}
|
||||
}
|
||||
/// Count modes.
|
||||
pub fn len (&self) -> usize {
|
||||
self.0.read().unwrap().len()
|
||||
impl Modes {
|
||||
/// Get a mode by name.
|
||||
pub fn get (&self, name: impl AsRef<str>) -> Option<Arc<Mode>> {
|
||||
self.0.try_read().unwrap().get(name.as_ref()).cloned()
|
||||
}
|
||||
/// Run something for each mode.
|
||||
pub fn for_each <T> (&self, mut ator: impl FnMut(&str, &Mode)->T) {
|
||||
for (k, v) in self.0.try_read().unwrap().iter() {
|
||||
let _ = ator(k.as_ref(), v.as_ref());
|
||||
}
|
||||
}
|
||||
/// Count modes.
|
||||
pub fn len (&self) -> usize {
|
||||
self.0.try_read().unwrap().len()
|
||||
}
|
||||
}
|
||||
|
||||
pub use self::bind::*;
|
||||
|
|
@ -358,7 +637,9 @@ mod bind {
|
|||
/// 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();
|
||||
/// assert_eq!(bind.query(&'x'.into()).map(|x|x.len()), Some(1));
|
||||
/// //assert_eq!(bind.query(&'y'.into()).map(|x|x.len()), Some(2));
|
||||
|
|
@ -445,50 +726,3 @@ mod bind {
|
|||
|
||||
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.read().unwrap().iter() {
|
||||
println!("{} {} {v}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}")));
|
||||
}
|
||||
for (k, v) in config.binds.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!();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
25
src/deps.rs
25
src/deps.rs
|
|
@ -1,8 +1,7 @@
|
|||
pub extern crate atomic_float;
|
||||
pub extern crate xdg;
|
||||
pub extern crate tengri;
|
||||
#[cfg(feature = "cli")]
|
||||
pub(crate) use ::clap::{self, Parser, Subcommand};
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) use ::{
|
||||
std::{
|
||||
|
|
@ -14,10 +13,13 @@ pub(crate) use ::{
|
|||
fs::File,
|
||||
ops::{Add, Sub, Mul, Div, Rem},
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, AtomicU64, Ordering::Relaxed}},
|
||||
sync::{Arc, atomic::{AtomicBool, AtomicUsize, AtomicU64, Ordering::Relaxed}},
|
||||
time::Duration,
|
||||
thread::{spawn, JoinHandle},
|
||||
},
|
||||
atomic_float::{
|
||||
AtomicF64
|
||||
},
|
||||
xdg::{
|
||||
BaseDirectories,
|
||||
},
|
||||
|
|
@ -35,5 +37,22 @@ pub(crate) use ::{
|
|||
prelude::{Rect, Style, Stylize, Buffer, Color::{self, *}},
|
||||
widgets::{Widget, canvas::{Canvas, Line}},
|
||||
},
|
||||
parking_lot::{
|
||||
RwLock,
|
||||
RwLockReadGuard,
|
||||
RwLockWriteGuard,
|
||||
RawRwLock,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(feature = "prof2")]
|
||||
pub use ::tengri::{
|
||||
profiling,
|
||||
tracing,
|
||||
tracing_tracy,
|
||||
tracing_subscriber
|
||||
};
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
pub(crate) use ::clap::{self, Parser, Subcommand};
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ impl Arrangement {
|
|||
midi_outs: midi_outs.collect(),
|
||||
audio_ins: audio_ins.collect(),
|
||||
audio_outs: audio_outs.collect(),
|
||||
size_inner: Sizer(Arc::new(40.into()), Arc::new(25.into())),
|
||||
clock,
|
||||
name,
|
||||
..Default::default()
|
||||
|
|
|
|||
|
|
@ -1,33 +1,10 @@
|
|||
use crate::*;
|
||||
|
||||
/// TODO: Preserve the generic passthru syntax;
|
||||
/// remove this macro (only used twice) and potentially the trait.
|
||||
#[macro_export] macro_rules! impl_has_clips {
|
||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasClips for $Struct $(<$($L),*$($T),*>)? {
|
||||
fn clips <'a> (&'a $self) -> std::sync::RwLockReadGuard<'a, ClipPool> {
|
||||
$cb.read().unwrap()
|
||||
}
|
||||
fn clips_mut <'a> (&'a $self) -> std::sync::RwLockWriteGuard<'a, ClipPool> {
|
||||
$cb.write().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! has_clip {
|
||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasMidiClip for $Struct $(<$($L),*$($T),*>)? {
|
||||
fn clip (&$self) -> Option<Arc<RwLock<MidiClip>>> { $cb }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Arrangement {
|
||||
/// Toggle looping for the active clip
|
||||
pub fn toggle_loop (&mut self) {
|
||||
if let Some(clip) = self.selected_clip() {
|
||||
clip.write().unwrap().toggle_loop()
|
||||
clip.try_write().unwrap().toggle_loop()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -45,7 +22,7 @@ impl Arrangement {
|
|||
&self, track: usize, scene: usize, color: ItemTheme
|
||||
) -> Option<ItemTheme> {
|
||||
self.scenes[scene].clips[track].as_ref().map(|clip|{
|
||||
let mut clip = clip.write().unwrap();
|
||||
let mut clip = clip.try_write().unwrap();
|
||||
let old = clip.color.clone();
|
||||
clip.color = color.clone();
|
||||
panic!("{color:?} {old:?}");
|
||||
|
|
@ -73,56 +50,49 @@ pub trait ClipsView: TracksView + ScenesView {
|
|||
fn view_scenes_clips (&self) -> impl Draw<Tui> {
|
||||
let select = self.selection();
|
||||
let editor = self.editor();
|
||||
let size = self.clips_size();
|
||||
let editing = self.is_editing();
|
||||
return size.of(
|
||||
above(
|
||||
fg(Green, format!("{}x{}", size.w(), size.h())).align_se().full_wh(),
|
||||
iter_east(move||self.tracks_with_sizes().map(move|(
|
||||
track_index, track, _, _
|
||||
)| {
|
||||
iter_south(move||self.scenes_with_sizes().map(move|(
|
||||
scene_index, scene, _, _
|
||||
)| {
|
||||
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
|
||||
);
|
||||
with_clips_size(true, self.clips_size(), iter_east(move||self.tracks_with_sizes()
|
||||
.map(move|(track_index, track, _, _)|iter_south(move||self.scenes_with_sizes()
|
||||
.map(move|(scene_index, scene, _, _)|{
|
||||
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 is_selected = view_scene_sel(select, track_index, scene_index, editing);
|
||||
below(
|
||||
Outer(true, Style::default().fg(o)).full_wh(),
|
||||
below(
|
||||
Outer(true, Style::default().fg(o)).full_wh(),
|
||||
below(
|
||||
below(
|
||||
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()
|
||||
).exact_wh(w, y)
|
||||
})).full_h().exact_w(track.width as u16)
|
||||
}))
|
||||
).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()
|
||||
).exact_wh(
|
||||
view_scene_w(track, select, track_index, editor),
|
||||
view_scene_y(select, scene_index, editor),
|
||||
)
|
||||
})
|
||||
)
|
||||
.full_h()
|
||||
.exact_w(track.width as u16))
|
||||
)).align_c()
|
||||
}
|
||||
}
|
||||
|
||||
fn view_scene_name_theme (scene: &Scene, track_index: usize) -> (Arc<str>, ItemTheme) {
|
||||
if let Some(Some(clip)) = &scene.clips.get(track_index) {
|
||||
let clip = clip.read().unwrap();
|
||||
let clip = clip.try_read().unwrap();
|
||||
(format!(" ⏹ {}", &clip.name).into(), clip.color)
|
||||
} else {
|
||||
(" ⏹ -- ".into(), ItemTheme::G[32])
|
||||
}
|
||||
}
|
||||
|
||||
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 (
|
||||
theme: ItemTheme, select: &Selection, track_index: usize, scene_index: usize
|
||||
) -> (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])
|
||||
-> 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 names = iter_south(move||ports.iter().enumerate().map(|(index, port)|format!(
|
||||
" {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)
|
||||
border(true, frame, field_v(theme, title, iter_south({
|
||||
move||ports.iter().enumerate().map(|(index, port)|{
|
||||
east!(" ", index, " ", port.port_name()).align_w().full_h()
|
||||
})
|
||||
})).exact_wh(20, 1 + ins)).exact_wh(20, 1 + ins)
|
||||
}
|
||||
|
||||
pub fn view_io_ports <'a, T: PortsSizes<'a>> (
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ impl Scene {
|
|||
/// Get pulse length of the longest clip in the scene
|
||||
pub fn pulses (&self) -> usize {
|
||||
self.clips.iter().fold(0, |a, p|{
|
||||
a.max(p.as_ref().map(|q|q.read().unwrap().length).unwrap_or(0))
|
||||
a.max(p.as_ref().map(|q|q.try_read().unwrap().length).unwrap_or(0))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ impl Scene {
|
|||
.get(track_index)
|
||||
.map(|track|{
|
||||
if let Some((_, Some(clip))) = track.sequencer().play_clip() {
|
||||
*clip.read().unwrap() == *c.read().unwrap()
|
||||
*clip.try_read().unwrap() == *c.try_read().unwrap()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
|
@ -207,13 +207,9 @@ pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize +
|
|||
let select = self.selection();
|
||||
let editor = self.editor();
|
||||
let editing = self.is_editing();
|
||||
draw(move |to: &mut Tui|{
|
||||
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)
|
||||
iter_south(move||self.scenes_with_sizes().map(move|(index, scene, ..)|{
|
||||
view_scene_name(select, editor, index, scene, editing)
|
||||
}))
|
||||
}
|
||||
|
||||
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 {
|
||||
fn scene_scroll (&self) -> usize;
|
||||
}
|
||||
|
|
@ -274,28 +294,3 @@ impl HasSceneScroll for App {
|
|||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ impl Selection {
|
|||
tracks.get(*t).map(|track|format!("T{t}: {}", &track.name)).unwrap_or_else(||"T??".into()),
|
||||
TrackClip { track, scene } => match (tracks.get(*track), scenes.get(*scene)) {
|
||||
(Some(_), Some(s)) => match s.clip(*track) {
|
||||
Some(clip) => format!("T{track} S{scene} C{}", &clip.read().unwrap().name),
|
||||
Some(clip) => format!("T{track} S{scene} C{}", &clip.try_read().unwrap().name),
|
||||
None => format!("T{track} S{scene}: Empty")
|
||||
},
|
||||
_ => format!("T{track} S{scene}: Empty"),
|
||||
|
|
|
|||
|
|
@ -345,90 +345,83 @@ pub trait TracksView: HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTra
|
|||
/// Draw name of each track
|
||||
fn view_track_names (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
||||
let selected = self.selection();
|
||||
east(
|
||||
south(
|
||||
button_3(
|
||||
"t",
|
||||
"rack ",
|
||||
east(
|
||||
selected.track().map(|track|east(track, "/")),
|
||||
self.tracks().len()
|
||||
),
|
||||
false
|
||||
),
|
||||
button_3(
|
||||
"s",
|
||||
"cene ",
|
||||
east(
|
||||
selected.scene().map(|scene|east(scene, "/")),
|
||||
self.scenes().len()
|
||||
),
|
||||
false
|
||||
)
|
||||
),
|
||||
west(
|
||||
south(
|
||||
button_2("T", "+", false),
|
||||
button_2("S", "+", false),
|
||||
),
|
||||
bg(theme.darker.term, iter_east(||self.tracks_with_sizes()
|
||||
.map(|(index, track, _x1, _x2)|{
|
||||
let b = if selected.track() == Some(index) {
|
||||
track.color.light.term
|
||||
} else {
|
||||
track.color.base.term
|
||||
};
|
||||
bg(b, south(
|
||||
east!(
|
||||
"·t",
|
||||
index,
|
||||
" ",
|
||||
fg(Rgb(255, 255, 255), bold(true, &track.name))
|
||||
)
|
||||
.align_nw()
|
||||
.full_w(),
|
||||
""
|
||||
))
|
||||
.exact_w(track_width(index, track))
|
||||
.exact_h(2)
|
||||
})))
|
||||
)
|
||||
)
|
||||
let btn1t = button_3("t", "rack ", east(
|
||||
selected.track().map(|track|east(track, "/")),
|
||||
self.tracks().len()
|
||||
), false);
|
||||
let btn1s = button_3("s", "cene ", east(
|
||||
selected.scene().map(|scene|east(scene, "/")),
|
||||
self.scenes().len()
|
||||
), false);
|
||||
let btns1 = south(btn1t, btn1s);
|
||||
let btns2 = south(
|
||||
button_2("T", "+", false),
|
||||
button_2("S", "+", false),
|
||||
);
|
||||
view_track_row_section(theme,
|
||||
btns1,
|
||||
btns2,
|
||||
bg(theme.darker.term,
|
||||
iter_east(||self.tracks_with_sizes().map(|(index, track, _x1, _x2)|{
|
||||
let b = if selected.track() == Some(index) {
|
||||
track.color.light.term
|
||||
} else {
|
||||
track.color.base.term
|
||||
};
|
||||
bg(b, south(
|
||||
east!(
|
||||
"·t",
|
||||
index,
|
||||
" ",
|
||||
fg(Rgb(255, 255, 255), bold(true, &track.name))
|
||||
)
|
||||
.align_nw()
|
||||
.full_w(),
|
||||
""
|
||||
))
|
||||
.exact_w(track_width(index, track))
|
||||
.exact_h(2)
|
||||
}))))
|
||||
}
|
||||
|
||||
fn view_track_output_count (&self) -> impl Draw<Tui> {
|
||||
south(button_2("o", "utput", false).align_w().full_w(),
|
||||
draw(|to: &mut Tui|{
|
||||
for port in self.midi_outs().iter() {
|
||||
let _ = port.port_name().align_w().full_w().draw(to)?;
|
||||
}
|
||||
Ok(Some(XYWH(0, 0, 0, 0)))
|
||||
}))
|
||||
}
|
||||
|
||||
fn view_track_output_add (&self) -> impl Draw<Tui> {
|
||||
button_2("O", "+", false)
|
||||
}
|
||||
|
||||
/// Draw outputs per track
|
||||
fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw<Tui> {
|
||||
view_track_row_section(theme,
|
||||
south(button_2("o", "utput", false).align_w().full_w(),
|
||||
draw(|to: &mut Tui|{
|
||||
for port in self.midi_outs().iter() {
|
||||
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() {
|
||||
self.view_track_output_count(),
|
||||
self.view_track_output_add(),
|
||||
bg(theme.darker.term,
|
||||
iter_east(move||self.tracks_with_sizes().map(move|(index, track, _x1, _x2)|{
|
||||
let f = Rgb(255, 255, 255);
|
||||
let b = track.color.dark.term;
|
||||
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))
|
||||
iter_south(move||track.sequencer.midi_outs.iter().map(move|port: &MidiOutput|{
|
||||
fg(f, bg(b, east!("·o", index, " ", port.port_name()).full_w().align_w()).exact_h(1))
|
||||
}))
|
||||
.full_h()
|
||||
.align_nw()
|
||||
.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
|
||||
|
|
@ -437,9 +430,12 @@ pub trait TracksView: HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTra
|
|||
for track in self.tracks().iter() {
|
||||
height = height.max(track.sequencer.midi_ins.len() as u16);
|
||||
}
|
||||
view_track_row_section(theme, button_2("i", "nput", false), button_2("I", "+", false),
|
||||
bg(theme.darker.term, draw(move|to: &mut Tui|{
|
||||
for (index, track, _x1, _x2) in self.tracks_with_sizes() {
|
||||
view_track_row_section(theme,
|
||||
self.view_track_input_count(),
|
||||
self.view_track_input_add(),
|
||||
bg(theme.darker.term, iter_east(move||self
|
||||
.tracks_with_sizes()
|
||||
.map(move|(index, track, _x1, _x2)|{
|
||||
south(
|
||||
bg(track.color.base.term,
|
||||
east!(
|
||||
|
|
@ -447,17 +443,13 @@ pub trait TracksView: HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTra
|
|||
either(track.sequencer.recording, fg(Red, "●rec "), "·rec "),
|
||||
either(track.sequencer.overdub, fg(Yellow, "●dub "), "·dub "),
|
||||
).align_w().full_w()),
|
||||
iter_south(||track.sequencer.midi_ins.iter().map(|port|fg_bg(Rgb(255, 255, 255), track.color.dark.term,
|
||||
east!(
|
||||
"·i",
|
||||
index,
|
||||
" ",
|
||||
port.port_name()
|
||||
).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()))
|
||||
iter_south(move||track.sequencer.midi_ins.iter().map(move|port|fg_bg(
|
||||
Rgb(255, 255, 255),
|
||||
track.color.dark.term,
|
||||
east!("·i", index, " ", port.port_name()).align_w().full_w()))))
|
||||
.align_nw()
|
||||
.exact_wh(track_width(index, track), height + 1)
|
||||
}))).align_w())
|
||||
}
|
||||
|
||||
fn view_track_devices (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
||||
|
|
@ -640,7 +632,7 @@ fn view_track_row_section <'a> (
|
|||
west(
|
||||
button_add.align_nw().exact_w(4).full_h(),
|
||||
east(
|
||||
button.align_nw().full_h().exact_w(20),
|
||||
button.align_nw().full_h().exact_w(16),
|
||||
content.align_c().full_wh()
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
use crate::*;
|
||||
use ::std::sync::{Arc, RwLock, atomic::AtomicUsize};
|
||||
use ::atomic_float::AtomicF64;
|
||||
|
||||
mod memo; pub use self::memo::*;
|
||||
mod moment; pub use self::moment::*;
|
||||
|
|
@ -328,11 +326,11 @@ impl Clock {
|
|||
}
|
||||
/// Is currently paused?
|
||||
pub fn is_stopped (&self) -> bool {
|
||||
self.started.read().unwrap().is_none()
|
||||
self.started.try_read().unwrap().is_none()
|
||||
}
|
||||
/// Is currently playing?
|
||||
pub fn is_rolling (&self) -> bool {
|
||||
self.started.read().unwrap().is_some()
|
||||
self.started.try_read().unwrap().is_some()
|
||||
}
|
||||
/// Update chunk size
|
||||
pub fn set_chunk (&self, n_frames: usize) {
|
||||
|
|
@ -347,7 +345,7 @@ impl Clock {
|
|||
self.global.sample.set(current_frames as f64);
|
||||
self.global.usec.set(current_usecs as f64);
|
||||
|
||||
let mut started = self.started.write().unwrap();
|
||||
let mut started = self.started.try_write().unwrap();
|
||||
|
||||
// If transport has just started or just stopped,
|
||||
// update starting point:
|
||||
|
|
@ -401,7 +399,7 @@ impl Clock {
|
|||
pub fn get_sample_offset (&self, scope: &ProcessScope, started: &Moment) -> usize{
|
||||
(scope.last_frame_time() as usize).saturating_sub(
|
||||
started.sample.get() as usize +
|
||||
self.started.read().unwrap().as_ref().unwrap().sample.get() as usize
|
||||
self.started.try_read().unwrap().as_ref().unwrap().sample.get() as usize
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ impl ClockView {
|
|||
let chunk = clock.chunk.load(Relaxed) as f64;
|
||||
let lat = chunk / rate * 1000.;
|
||||
let delta = |start: &Moment|clock.global.usec.get() - start.usec.get();
|
||||
let mut cache = cache.write().unwrap();
|
||||
let mut cache = cache.try_write().unwrap();
|
||||
|
||||
cache.buf.update(
|
||||
Some(chunk), rewrite!(buf, "{chunk}")
|
||||
|
|
@ -59,7 +59,7 @@ impl ClockView {
|
|||
}
|
||||
);
|
||||
|
||||
if let Some(now) = clock.started.read().unwrap().as_ref().map(delta) {
|
||||
if let Some(now) = clock.started.try_read().unwrap().as_ref().map(delta) {
|
||||
let pulse = clock.timebase.usecs_to_pulse(now);
|
||||
let time = now/1000000.;
|
||||
let bpm = clock.timebase.bpm.get();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{*, device::*};
|
||||
|
||||
pub fn draw_dialog <'a> (to: &mut Tui, mut frags: std::str::Split<&str>, state: &App)
|
||||
pub fn draw_dialog <'a, I: Debug + Iterator<Item = &'a str>> (to: &mut Tui, mut frags: I, state: &App)
|
||||
-> Drawn<u16>
|
||||
{
|
||||
match frags.next() {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ impl App {
|
|||
let (_index, clip) = self.pool.add_new_clip();
|
||||
// autocolor: new clip colors from scene and track color
|
||||
let color = track.color.base.mix(scene.color.base, 0.5);
|
||||
clip.write().unwrap().color = ItemColor::random_near(color, 0.2).into();
|
||||
clip.try_write().unwrap().color = ItemColor::random_near(color, 0.2).into();
|
||||
if let Some(editor) = &mut self.project.editor {
|
||||
editor.set_clip(Some(&clip));
|
||||
}
|
||||
|
|
@ -46,11 +46,11 @@ impl App {
|
|||
{
|
||||
// Remove clip from arrangement when exiting empty clip editor
|
||||
let mut swapped = None;
|
||||
if clip.read().unwrap().count_midi_messages() == 0 {
|
||||
if clip.try_read().unwrap().count_midi_messages() == 0 {
|
||||
std::mem::swap(&mut swapped, slot);
|
||||
}
|
||||
if let Some(clip) = swapped {
|
||||
self.pool.delete_clip(&clip.read().unwrap());
|
||||
self.pool.delete_clip(&clip.try_read().unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -210,7 +210,7 @@ impl MidiEditor {
|
|||
pub fn put_note (&mut self, advance: bool) {
|
||||
let mut redraw = false;
|
||||
if let Some(clip) = self.clip() {
|
||||
let mut clip = clip.write().unwrap();
|
||||
let mut clip = clip.try_write().unwrap();
|
||||
let note_start = self.get_time_pos();
|
||||
let note_pos = self.get_note_pos();
|
||||
let note_len = self.get_note_len();
|
||||
|
|
@ -236,7 +236,7 @@ impl MidiEditor {
|
|||
self.mode.redraw();
|
||||
}
|
||||
}
|
||||
fn clip_length (&self) -> usize { self.clip().as_ref().map(|p|p.read().unwrap().length).unwrap_or(1) }
|
||||
fn clip_length (&self) -> usize { self.clip().as_ref().map(|p|p.try_read().unwrap().length).unwrap_or(1) }
|
||||
fn note_length (&self) -> usize { self.get_note_len() }
|
||||
fn note_pos (&self) -> usize { self.get_note_pos() }
|
||||
fn note_pos_next (&self) -> usize { self.get_note_pos() + 1 }
|
||||
|
|
@ -269,7 +269,7 @@ impl MidiEditor {
|
|||
.0.min(self.clip_length().saturating_sub(1))
|
||||
}
|
||||
pub fn clip_status (&self) -> impl Draw<Tui> + '_ {
|
||||
let (_color, name, length, looped) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) {
|
||||
let (_color, name, length, looped) = if let Some(clip) = self.clip().as_ref().map(|p|p.try_read().unwrap()) {
|
||||
(clip.color, clip.name.clone(), clip.length, clip.looped)
|
||||
} else { (ItemTheme::G[64], String::new().into(), 0, false) };
|
||||
south!(
|
||||
|
|
@ -282,7 +282,7 @@ impl MidiEditor {
|
|||
).exact_w(20)
|
||||
}
|
||||
pub fn edit_status (&self) -> impl Draw<Tui> + '_ {
|
||||
let (_color, length) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) {
|
||||
let (_color, length) = if let Some(clip) = self.clip().as_ref().map(|p|p.try_read().unwrap()) {
|
||||
(clip.color, clip.length)
|
||||
} else { (ItemTheme::G[64], 0) };
|
||||
let time_pos = self.get_time_pos();
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ impl PianoHorizontal {
|
|||
buffer: RwLock::new(Default::default()).into(),
|
||||
point: MidiCursor::default(),
|
||||
clip: clip.cloned(),
|
||||
color: clip.as_ref().map(|p|p.read().unwrap().color).unwrap_or(ItemTheme::G[64]),
|
||||
color: clip.as_ref().map(|p|p.try_read().unwrap().color).unwrap_or(ItemTheme::G[64]),
|
||||
};
|
||||
piano.redraw();
|
||||
piano
|
||||
|
|
@ -140,7 +140,7 @@ impl PianoHorizontal {
|
|||
draw(move|to: &mut Tui|{
|
||||
let xywh = to.area().into();
|
||||
let XYWH(x0, y0, w, _h) = xywh;
|
||||
let source = buffer.read().unwrap();
|
||||
let source = buffer.try_read().unwrap();
|
||||
//if h as usize != note_axis {
|
||||
//panic!("area height mismatch: {h} <> {note_axis}");
|
||||
//}
|
||||
|
|
@ -234,7 +234,7 @@ impl PianoHorizontal {
|
|||
let xywh = to.area().into();
|
||||
let XYWH(x, y, w, _h) = xywh;
|
||||
let style = Some(Style::default().dim());
|
||||
let length = self.clip.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1);
|
||||
let length = self.clip.as_ref().map(|p|p.try_read().unwrap().length).unwrap_or(1);
|
||||
for (area_x, screen_x) in (0..w).map(|d|(d, d+x)) {
|
||||
let t = area_x as usize * self.time_zoom().load(Relaxed);
|
||||
if t < length {
|
||||
|
|
@ -276,8 +276,8 @@ impl MidiViewer for PianoHorizontal {
|
|||
(clip.length / self.range.time_zoom().load(Relaxed), 128)
|
||||
}
|
||||
fn redraw (&self) {
|
||||
*self.buffer.write().unwrap() = if let Some(clip) = self.clip.as_ref() {
|
||||
let clip = clip.read().unwrap();
|
||||
*self.buffer.try_write().unwrap() = if let Some(clip) = self.clip.as_ref() {
|
||||
let clip = clip.try_read().unwrap();
|
||||
let buf_size = self.buffer_size(&clip);
|
||||
let mut buffer = BigBuffer::from(buf_size);
|
||||
let time_zoom = self.get_time_zoom();
|
||||
|
|
@ -291,14 +291,14 @@ impl MidiViewer for PianoHorizontal {
|
|||
}
|
||||
fn set_clip (&mut self, clip: Option<&Arc<RwLock<MidiClip>>>) {
|
||||
*self.clip_mut() = clip.cloned();
|
||||
self.color = clip.map(|p|p.read().unwrap().color).unwrap_or(ItemTheme::G[64]);
|
||||
self.color = clip.map(|p|p.try_read().unwrap().color).unwrap_or(ItemTheme::G[64]);
|
||||
self.redraw();
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for PianoHorizontal {
|
||||
fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||
let buffer = self.buffer.read().unwrap();
|
||||
let buffer = self.buffer.try_read().unwrap();
|
||||
f.debug_struct("PianoHorizontal")
|
||||
.field("time_zoom", &self.range.time_zoom)
|
||||
.field("buffer", &format!("{}x{}", buffer.width, buffer.height))
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ pub trait PoolController: HasPool
|
|||
/// Delete a clip from the pool
|
||||
#[command(Delete = "delete")]
|
||||
fn delete (&mut self, index: usize) -> Perhaps<PoolCommand> {
|
||||
let clip = self.pool_mut().clips_mut().remove(index).read().unwrap().clone();
|
||||
let clip = self.pool_mut().clips_mut().remove(index).try_read().unwrap().clone();
|
||||
Ok(Some(PoolCommand::Add { index, clip }))
|
||||
}
|
||||
|
||||
|
|
@ -181,8 +181,8 @@ pub trait PoolController: HasPool
|
|||
#[command(SetName = "set-name")]
|
||||
fn clip_set_name (&mut self, index: usize, name: Arc<str>) -> Perhaps<PoolCommand> {
|
||||
let clip = &mut self.pool_mut().clips_mut()[index];
|
||||
let old_name = clip.read().unwrap().name.clone();
|
||||
clip.write().unwrap().name = name.clone();
|
||||
let old_name = clip.try_read().unwrap().name.clone();
|
||||
clip.try_write().unwrap().name = name.clone();
|
||||
Ok(Some(PoolCommand::SetName { index, name: old_name }))
|
||||
}
|
||||
|
||||
|
|
@ -190,8 +190,8 @@ pub trait PoolController: HasPool
|
|||
#[command(SetLength = "set-length")]
|
||||
fn clip_set_length (&mut self, index: usize, length: usize) -> Perhaps<PoolCommand> {
|
||||
let clip = &mut self.pool_mut().clips_mut()[index];
|
||||
let old_len = clip.read().unwrap().length;
|
||||
clip.write().unwrap().length = length;
|
||||
let old_len = clip.try_read().unwrap().length;
|
||||
clip.try_write().unwrap().length = length;
|
||||
Ok(Some(PoolCommand::SetLength { index, length: old_len }))
|
||||
}
|
||||
|
||||
|
|
@ -199,7 +199,7 @@ pub trait PoolController: HasPool
|
|||
#[command(SetColor = "set-color")]
|
||||
fn clip_set_color (&mut self, index: usize, color: ItemColor) -> Perhaps<PoolCommand> {
|
||||
let mut color = ItemTheme::from(color);
|
||||
std::mem::swap(&mut color, &mut self.pool().clips()[index].write().unwrap().color);
|
||||
std::mem::swap(&mut color, &mut self.pool().clips()[index].try_write().unwrap().color);
|
||||
Ok(Some(PoolCommand::SetColor { index, color: color.base }))
|
||||
}
|
||||
|
||||
|
|
@ -207,7 +207,7 @@ pub trait PoolController: HasPool
|
|||
#[command(CropBegin = "crop/begin")]
|
||||
fn crop_begin (&mut self) -> Perhaps<PoolCommand> {
|
||||
let index = self.pool().clip_index();
|
||||
let length = self.pool().clips()[index].read().unwrap().length;
|
||||
let length = self.pool().clips()[index].try_read().unwrap().length;
|
||||
*self.pool_mut().mode_mut() = Some(PoolMode::Length(index, length, ClipLengthFocus::Bar));
|
||||
Ok(None)
|
||||
}
|
||||
|
|
@ -228,9 +228,9 @@ pub trait PoolController: HasPool
|
|||
{
|
||||
let old_length;
|
||||
{
|
||||
let clip = self.pool().clips()[clip].clone();//.write().unwrap();
|
||||
old_length = Some(clip.read().unwrap().length);
|
||||
clip.write().unwrap().length = *length;
|
||||
let clip = self.pool().clips()[clip].clone();//.try_write().unwrap();
|
||||
old_length = Some(clip.try_read().unwrap().length);
|
||||
clip.try_write().unwrap().length = *length;
|
||||
}
|
||||
*self.pool_mut().mode_mut() = None;
|
||||
return Ok(old_length.map(|l|PoolCommand::CropSet { _length: l }))
|
||||
|
|
@ -290,7 +290,7 @@ pub trait PoolController: HasPool
|
|||
#[command(RenameBegin = "rename/begin")]
|
||||
fn rename_begin (&mut self) -> Perhaps<PoolCommand> {
|
||||
let index = self.pool().clip_index();
|
||||
let name = self.pool().clips()[index].read().unwrap().name.clone();
|
||||
let name = self.pool().clips()[index].try_read().unwrap().name.clone();
|
||||
*self.pool_mut().mode_mut() = Some(PoolMode::Rename(index, name));
|
||||
Ok(None)
|
||||
}
|
||||
|
|
@ -299,7 +299,7 @@ pub trait PoolController: HasPool
|
|||
#[command(RenameCancel = "rename/cancel")]
|
||||
fn rename_cancel (&mut self) -> Perhaps<PoolCommand> {
|
||||
if let Some(PoolMode::Rename(clip, ref mut old_name)) = self.pool_mut().mode_mut().clone() {
|
||||
self.pool().clips()[clip].write().unwrap().name = old_name.clone().into();
|
||||
self.pool().clips()[clip].try_write().unwrap().name = old_name.clone().into();
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
|
@ -319,7 +319,7 @@ pub trait PoolController: HasPool
|
|||
#[command(RenameSet = "rename/set")]
|
||||
fn rename_set (&mut self, value: Arc<str>) -> Perhaps<PoolCommand> {
|
||||
if let Some(PoolMode::Rename(clip, ref mut _old_name)) = self.pool_mut().mode_mut().clone() {
|
||||
self.pool().clips()[clip].write().unwrap().name = value.clone();
|
||||
self.pool().clips()[clip].try_write().unwrap().name = value.clone();
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
|
@ -332,7 +332,7 @@ impl_has_clips!(|self: Pool|self.clips);
|
|||
|
||||
impl_from!(Pool: |clip:&Arc<RwLock<MidiClip>>|{
|
||||
let model = Self::default();
|
||||
model.clips.write().unwrap().push(clip.clone());
|
||||
model.clips.try_write().unwrap().push(clip.clone());
|
||||
model.clip.store(1, Relaxed);
|
||||
model
|
||||
});
|
||||
|
|
@ -378,14 +378,14 @@ impl Pool {
|
|||
}
|
||||
pub fn cloned_clip (&self) -> MidiClip {
|
||||
let index = self.clip_index();
|
||||
let mut clip = self.clips()[index].read().unwrap().duplicate();
|
||||
let mut clip = self.clips()[index].try_read().unwrap().duplicate();
|
||||
clip.color = ItemTheme::random_near(clip.color, 0.25);
|
||||
clip
|
||||
}
|
||||
pub fn add_new_clip (&self) -> (usize, Arc<RwLock<MidiClip>>) {
|
||||
let clip = Arc::new(RwLock::new(self.new_clip()));
|
||||
let index = {
|
||||
let mut clips = self.clips.write().unwrap();
|
||||
let mut clips = self.clips.try_write().unwrap();
|
||||
clips.push(clip.clone());
|
||||
clips.len().saturating_sub(1)
|
||||
};
|
||||
|
|
@ -393,9 +393,9 @@ impl Pool {
|
|||
(index, clip)
|
||||
}
|
||||
pub fn delete_clip (&mut self, clip: &MidiClip) -> bool {
|
||||
let index = self.clips.read().unwrap().iter().position(|x|*x.read().unwrap()==*clip);
|
||||
let index = self.clips.try_read().unwrap().iter().position(|x|*x.try_read().unwrap()==*clip);
|
||||
if let Some(index) = index {
|
||||
self.clips.write().unwrap().remove(index);
|
||||
self.clips.try_write().unwrap().remove(index);
|
||||
return true
|
||||
}
|
||||
false
|
||||
|
|
@ -441,14 +441,14 @@ impl Pool {
|
|||
impl<'a> PoolView<'a> {
|
||||
//fn tui (&self) -> impl Draw<'_, Tui> {
|
||||
//let Self(pool) = self;
|
||||
////let color = self.1.clip().map(|c|c.read().unwrap().color).unwrap_or_else(||g(32).into());
|
||||
////let color = self.1.clip().map(|c|c.try_read().unwrap().color).unwrap_or_else(||g(32).into());
|
||||
////let on_bg = |x|x;//below(Repeat(" "), bg(color.darkest.term, x));
|
||||
////let border = |x|x;//Outer(Style::default().fg(color.dark.term).bg(color.darkest.term)).enclose(x);
|
||||
////let height = pool.clips.read().unwrap().len() as u16;
|
||||
////let height = pool.clips.try_read().unwrap().len() as u16;
|
||||
//iter(
|
||||
//||pool.clips().clone().into_iter(),
|
||||
//move|clip: Arc<RwLock<MidiClip>>, i: usize|{
|
||||
//let MidiClip { ref name, color, length, .. } = *clip.read().unwrap();
|
||||
//let MidiClip { ref name, color, length, .. } = *clip.try_read().unwrap();
|
||||
//let item_height = 1;
|
||||
//let _item_offset = i as u16 * item_height;
|
||||
//let selected = i == pool.clip_index();
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ pub trait SamplerController: HasSampler
|
|||
fn sample_play (&mut self, slot: usize) -> Perhaps<SamplerCommand> {
|
||||
let sampler = self.sampler_mut();
|
||||
if let Some(ref sample) = sampler.samples.0[slot] {
|
||||
sampler.voices.write().unwrap().push(Sample::play(sample, 0, &u7::from(128)));
|
||||
sampler.voices.try_write().unwrap().push(Sample::play(sample, 0, &u7::from(128)));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
|
@ -244,7 +244,7 @@ impl Sampler {
|
|||
/// Record from inputs to sample
|
||||
fn record_into (&mut self, scope: &ProcessScope) {
|
||||
if let Some(ref sample) = self.recording.as_ref().expect("no recording sample").1 {
|
||||
let mut sample = sample.write().unwrap();
|
||||
let mut sample = sample.try_write().unwrap();
|
||||
if sample.channels.len() != self.audio_ins.len() {
|
||||
panic!("channel count mismatch");
|
||||
}
|
||||
|
|
@ -294,10 +294,10 @@ impl Sampler {
|
|||
let Sampler { buffer, voices, output_gain, mixing_mode, .. } = self;
|
||||
let _channel_count = buffer.len();
|
||||
match mixing_mode {
|
||||
MixingMode::Summing => voices.write().unwrap().retain_mut(|voice|{
|
||||
MixingMode::Summing => voices.try_write().unwrap().retain_mut(|voice|{
|
||||
mix_summing(buffer.as_mut_slice(), *output_gain, frames, ||voice.next())
|
||||
}),
|
||||
MixingMode::Average => voices.write().unwrap().retain_mut(|voice|{
|
||||
MixingMode::Average => voices.try_write().unwrap().retain_mut(|voice|{
|
||||
mix_average(buffer.as_mut_slice(), *output_gain, frames, ||voice.next())
|
||||
}),
|
||||
}
|
||||
|
|
@ -316,7 +316,7 @@ impl Sampler {
|
|||
|
||||
fn draw_list_item (sample: &Option<Arc<RwLock<Sample>>>) -> String {
|
||||
if let Some(sample) = sample {
|
||||
let sample = sample.read().unwrap();
|
||||
let sample = sample.try_read().unwrap();
|
||||
format!("{:8}", sample.name)
|
||||
//format!("{:8} {:3} {:6}-{:6}/{:6}",
|
||||
//sample.name,
|
||||
|
|
@ -337,7 +337,7 @@ fn draw_viewer (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> + use<'_
|
|||
let XYWH(x, y, width, height) = xywh;
|
||||
let area = Rect { x, y, width, height };
|
||||
if let Some(sample) = &sample {
|
||||
let sample = sample.read().unwrap();
|
||||
let sample = sample.try_read().unwrap();
|
||||
let start = sample.start as f64;
|
||||
let end = sample.end as f64;
|
||||
let length = end - start;
|
||||
|
|
@ -400,7 +400,7 @@ fn sampler_midi_in (
|
|||
match message {
|
||||
MidiMessage::NoteOn { ref key, ref vel } => {
|
||||
if let Some(sample) = samples.get(key.as_int() as usize) {
|
||||
voices.write().unwrap().push(Sample::play(sample, time as usize, vel));
|
||||
voices.try_write().unwrap().push(Sample::play(sample, time as usize, vel));
|
||||
}
|
||||
},
|
||||
MidiMessage::Controller { controller: _, value: _ } => {
|
||||
|
|
@ -444,7 +444,7 @@ impl Iterator for Voice {
|
|||
self.after -= 1;
|
||||
return Some([0.0, 0.0])
|
||||
}
|
||||
let sample = self.sample.read().unwrap();
|
||||
let sample = self.sample.try_read().unwrap();
|
||||
if self.position < sample.end {
|
||||
let position = self.position;
|
||||
self.position += 1;
|
||||
|
|
@ -514,7 +514,7 @@ impl Sample {
|
|||
Voice {
|
||||
sample: sample.clone(),
|
||||
after,
|
||||
position: sample.read().unwrap().start,
|
||||
position: sample.try_read().unwrap().start,
|
||||
velocity: velocity.as_int() as f32 / 127.0,
|
||||
}
|
||||
}
|
||||
|
|
@ -679,8 +679,8 @@ impl SampleAdd {
|
|||
fn try_preview (&mut self) -> Usually<()> {
|
||||
if let Some(path) = self.cursor_file() {
|
||||
if let Ok(sample) = Sample::from_file(&path) {
|
||||
*self.sample.write().unwrap() = sample;
|
||||
self.voices.write().unwrap().push(
|
||||
*self.sample.try_write().unwrap() = sample;
|
||||
self.voices.try_write().unwrap().push(
|
||||
Sample::play(&self.sample, 0, &u7::from(100u8))
|
||||
);
|
||||
}
|
||||
|
|
@ -736,7 +736,7 @@ impl SampleAdd {
|
|||
}
|
||||
if let Some(path) = self.cursor_file() {
|
||||
let (end, channels) = read_sample_data(&path.to_string_lossy())?;
|
||||
let mut sample = self.sample.write().unwrap();
|
||||
let mut sample = self.sample.try_write().unwrap();
|
||||
sample.name = path.file_name().unwrap().to_string_lossy().into();
|
||||
sample.end = end;
|
||||
sample.channels = channels;
|
||||
|
|
@ -752,7 +752,7 @@ fn read_sample_data (_: &str) -> Usually<(usize, Vec<Vec<f32>>)> {
|
|||
|
||||
pub fn view_sample_info (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> + use<'_> {
|
||||
when(sample.is_some(), draw(move|to: &mut Tui|{
|
||||
let sample = sample.unwrap().read().unwrap();
|
||||
let sample = sample.unwrap().try_read().unwrap();
|
||||
let theme = sample.color;
|
||||
east!(
|
||||
field_h(theme, "Name", format!("{:<10}", sample.name.clone())),
|
||||
|
|
@ -767,7 +767,7 @@ pub fn view_sample_info (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui>
|
|||
|
||||
pub fn view_sample_info_v (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> + use<'_> {
|
||||
let a = draw(move|to: &mut Tui|{
|
||||
let sample = sample.unwrap().read().unwrap();
|
||||
let sample = sample.unwrap().try_read().unwrap();
|
||||
let theme = sample.color;
|
||||
south!(
|
||||
field_h(theme, "Name ", format!("{:<10}", sample.name.clone())) .align_w().full_w(),
|
||||
|
|
@ -791,7 +791,7 @@ pub fn view_sample_info_v (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tu
|
|||
pub fn view_sample_status (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> {
|
||||
bold(true, fg(g(224), sample
|
||||
.map(|sample|{
|
||||
let sample = sample.read().unwrap();
|
||||
let sample = sample.try_read().unwrap();
|
||||
format!("Sample {}-{}", sample.start, sample.end)
|
||||
})
|
||||
.unwrap_or_else(||"No sample".to_string())))
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ pub trait HasPlayClip: HasClock {
|
|||
fn pulses_since_start_looped (&self) -> Option<(f64, f64)> {
|
||||
if let Some((started, Some(clip))) = self.play_clip().as_ref() {
|
||||
let elapsed = self.clock().playhead.pulse.get() - started.pulse.get();
|
||||
let length = clip.read().unwrap().length.max(1); // prevent div0 on empty clip
|
||||
let length = clip.try_read().unwrap().length.max(1); // prevent div0 on empty clip
|
||||
let times = (elapsed as usize / length) as f64;
|
||||
let elapsed = (elapsed as usize % length) as f64;
|
||||
return Some((times, elapsed))
|
||||
|
|
@ -115,7 +115,7 @@ pub trait HasPlayClip: HasClock {
|
|||
|
||||
fn play_status (&self) -> impl Draw<Tui> {
|
||||
let (name, color): (Arc<str>, ItemTheme) = if let Some((_, Some(clip))) = self.play_clip() {
|
||||
let MidiClip { ref name, color, .. } = *clip.read().unwrap();
|
||||
let MidiClip { ref name, color, .. } = *clip.try_read().unwrap();
|
||||
(name.clone(), color)
|
||||
} else {
|
||||
("".into(), ItemTheme::G[64].into())
|
||||
|
|
@ -136,7 +136,7 @@ pub trait HasPlayClip: HasClock {
|
|||
let mut color = ItemTheme::G[64];
|
||||
let clock = self.clock();
|
||||
if let Some((t, Some(clip))) = self.next_clip() {
|
||||
let clip = clip.read().unwrap();
|
||||
let clip = clip.try_read().unwrap();
|
||||
name = clip.name.clone();
|
||||
color = clip.color.clone();
|
||||
time = {
|
||||
|
|
@ -150,7 +150,7 @@ pub trait HasPlayClip: HasClock {
|
|||
}
|
||||
}.into()
|
||||
} else if let Some((t, Some(clip))) = self.play_clip() {
|
||||
let clip = clip.read().unwrap();
|
||||
let clip = clip.try_read().unwrap();
|
||||
if clip.looped {
|
||||
name = clip.name.clone();
|
||||
color = clip.color.clone();
|
||||
|
|
@ -205,7 +205,7 @@ pub trait MidiRecord: MidiMonitor + HasClock + HasPlayClip {
|
|||
let _recording = self.recording();
|
||||
let timebase = self.clock().timebase().clone();
|
||||
let quant = self.clock().quant.get();
|
||||
let mut clip = clip.write().unwrap();
|
||||
let mut clip = clip.try_write().unwrap();
|
||||
let length = clip.length;
|
||||
for input in self.midi_ins_mut().iter() {
|
||||
for (sample, event, _bytes) in parse_midi_input(input.port().iter(scope)) {
|
||||
|
|
@ -232,8 +232,8 @@ pub type MidiData = Vec<Vec<MidiMessage>>;
|
|||
pub type ClipPool = Vec<Arc<RwLock<MidiClip>>>;
|
||||
|
||||
pub trait HasClips {
|
||||
fn clips <'a> (&'a self) -> std::sync::RwLockReadGuard<'a, ClipPool>;
|
||||
fn clips_mut <'a> (&'a self) -> std::sync::RwLockWriteGuard<'a, ClipPool>;
|
||||
fn clips <'a> (&'a self) -> RwLockReadGuard<'a, ClipPool>;
|
||||
fn clips_mut <'a> (&'a self) -> RwLockWriteGuard<'a, ClipPool>;
|
||||
fn add_clip (&self) -> (usize, Arc<RwLock<MidiClip>>) {
|
||||
let clip = Arc::new(RwLock::new(MidiClip::new("Clip", true, 384, None, None)));
|
||||
self.clips_mut().push(clip.clone());
|
||||
|
|
@ -241,6 +241,29 @@ pub trait HasClips {
|
|||
}
|
||||
}
|
||||
|
||||
/// TODO: Preserve the generic passthru syntax;
|
||||
/// remove this macro (only used twice) and potentially the trait.
|
||||
#[macro_export] macro_rules! impl_has_clips {
|
||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasClips for $Struct $(<$($L),*$($T),*>)? {
|
||||
fn clips <'a> (&'a $self) -> ::tengri::parking_lot::RwLockReadGuard<'a, ClipPool> {
|
||||
$cb.try_read().unwrap()
|
||||
}
|
||||
fn clips_mut <'a> (&'a $self) -> ::tengri::parking_lot::RwLockWriteGuard<'a, ClipPool> {
|
||||
$cb.try_write().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! has_clip {
|
||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasMidiClip for $Struct $(<$($L),*$($T),*>)? {
|
||||
fn clip (&$self) -> Option<Arc<RwLock<MidiClip>>> { $cb }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasMidiClip {
|
||||
fn clip (&self) -> Option<Arc<RwLock<MidiClip>>>;
|
||||
}
|
||||
|
|
@ -433,7 +456,7 @@ impl Sequencer {
|
|||
self.midi_buf[sample].push(bytes.to_vec());
|
||||
}
|
||||
// FIXME: don't lock on every event!
|
||||
update_keys(&mut notes_in.write().unwrap(), &message);
|
||||
update_keys(&mut notes_in.try_write().unwrap(), &message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -469,7 +492,7 @@ impl Sequencer {
|
|||
// If no clip is playing, prepare for switchover immediately.
|
||||
if let Some((started, clip)) = &self.play_clip {
|
||||
// Length of clip, to repeat or stop on end.
|
||||
let length = clip.as_ref().map_or(0, |p|p.read().unwrap().length);
|
||||
let length = clip.as_ref().map_or(0, |p|p.try_read().unwrap().length);
|
||||
// Index of first sample to populate.
|
||||
let offset = self.clock().get_sample_offset(scope, &started);
|
||||
// Write MIDI events from clip at sample offsets corresponding to pulses.
|
||||
|
|
@ -484,7 +507,7 @@ impl Sequencer {
|
|||
// If there's a currently playing clip, output notes from it to buffer:
|
||||
if let Some(clip) = clip {
|
||||
// Source clip from which the MIDI events will be taken.
|
||||
let clip = clip.read().unwrap();
|
||||
let clip = clip.try_read().unwrap();
|
||||
// Clip with zero length is not processed
|
||||
if clip.length > 0 {
|
||||
// Current pulse index in source clip
|
||||
|
|
@ -513,7 +536,7 @@ impl Sequencer {
|
|||
//let samples = scope.n_frames() as usize;
|
||||
if let Some((start_at, clip)) = &self.next_clip() {
|
||||
let start = start_at.sample.get() as usize;
|
||||
let sample = self.clock().started.read().unwrap()
|
||||
let sample = self.clock().started.try_read().unwrap()
|
||||
.as_ref().unwrap().sample.get() as usize;
|
||||
// If it's time to switch to the next clip:
|
||||
if start <= sample0.saturating_sub(sample) {
|
||||
|
|
|
|||
48
src/tek.edn
48
src/tek.edn
|
|
@ -1,8 +1,8 @@
|
|||
(view :logo (text tek))
|
||||
|
||||
(view :browse (bsp/s
|
||||
(padding 3 1 :browse-title)
|
||||
(enclose (fg (g 96)) browser)))
|
||||
(pad/xy 3 1 :browse-title)
|
||||
(fg (g 96) browser)))
|
||||
|
||||
(mode :transport
|
||||
(name Transport)
|
||||
|
|
@ -34,17 +34,16 @@
|
|||
(mode :scene (keys :scene))
|
||||
(mode :mix (keys :mix))
|
||||
(view
|
||||
(bsp/n (bg (g 10) (bsp/e :transport :status))
|
||||
(bsp/w (bg (g 20) (exact/x 4 (align/ne :meters/output)))
|
||||
(bsp/e (bg (g 30) (exact/x 4 (align/nw :meters/input)))
|
||||
(full/xy (align/c (max/xy 80 80
|
||||
(bsp/s (bg (g 40) (exact/y 4 :tracks/outputs))
|
||||
(bsp/s (bg (g 60) (exact/y 4 :tracks/devices))
|
||||
(bsp/s (bg (g 50) (exact/y 2 :tracks/names))
|
||||
(bsp/s (either :mode/editor
|
||||
(bg (g 80) (bsp/e :scenes/names :editor))
|
||||
(bg (g 90) :scenes))
|
||||
(bg (g 70) (exact/y 4 :tracks/inputs))))))))))))))
|
||||
(bsp/n (bsp/e :transport :status)
|
||||
(bar/w 2 (align/ne :meters/output)
|
||||
(bar/e 2 (align/nw :meters/input)
|
||||
(full/xy (pad/xy 2 1 (align/c
|
||||
(bar/s 2 :tracks/outputs
|
||||
(bar/s 2 :tracks/devices
|
||||
(bar/s 2 :tracks/names
|
||||
(bar/n 2 :tracks/inputs
|
||||
(bar/e 16 (bg (g 50) :scenes/names)
|
||||
(align/c :scenes/clips))))))))))))))
|
||||
|
||||
(keys :clock (@space clock/toggle 0)
|
||||
(@shift/space clock/toggle 0))
|
||||
|
|
@ -82,28 +81,35 @@
|
|||
(mode browse (keys :browse))
|
||||
(mode rename (keys :pool/rename))
|
||||
(mode length (keys :pool/length))
|
||||
(bsp/s (exact/y 1 :transport)
|
||||
(bsp/n (exact/y 1 :status)
|
||||
(fill (bsp/a (fill/xy (align/e :pool)) :editor)))))
|
||||
(view
|
||||
(bsp/s (exact/h 1 :transport)
|
||||
(bsp/n (exact/h 1 :status)
|
||||
(fill/xy (bsp/a (fill/xy (align/e :pool))
|
||||
:editor))))))
|
||||
|
||||
(mode :sampler (name Sampler) (info Sample player.)
|
||||
(keys :sampler/directions :sampler/record :sampler/play)
|
||||
(bsp/s (exact/y 1 :transport)
|
||||
(bsp/n (exact/y 1 :status)
|
||||
(fill :samples/grid))))
|
||||
(view
|
||||
(bsp/s (exact/h 1 :transport)
|
||||
(bsp/n (exact/h 1 :status)
|
||||
(fill/xy :samples/grid)))))
|
||||
|
||||
(mode :groovebox (name Groovebox) (info Sequencer with sampler.)
|
||||
(keys :clock :editor :sampler :global)
|
||||
(mode browse (keys :browse))
|
||||
(mode rename (keys :pool-rename))
|
||||
(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/editor (bsp/n :groove/sample :groove/sequence))
|
||||
|
||||
(view :groove/sample (exact/y :h-sample-detail (bsp/e (fill/y (exact/x 20 (align/nw :sample-status))) :sample-viewer)))
|
||||
(view :groove/sample (exact/h :h-sample-detail (bsp/e (fill/y (exact/w 20 (align/nw :sample-status))) :sample-viewer)))
|
||||
|
||||
(view :groove/sequence (bsp/e (fill/y (align/n (bsp/s :status/v :editor-status))) (bsp/e :samples/keys :editor)))
|
||||
|
||||
|
|
|
|||
382
src/tek.rs
382
src/tek.rs
|
|
@ -2,233 +2,22 @@
|
|||
//#![feature(impl_trait_in_assoc_type)] // Used by EntriesIterator; TODO remove
|
||||
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.
|
||||
#[allow(unused)]
|
||||
#[hotpath::main]
|
||||
fn main () -> Usually<()> {
|
||||
tengri::Tui::setup_panic();
|
||||
#[cfg(feature = "cli")] {
|
||||
Config::watched(crate::cli::run_with_config)?;
|
||||
}
|
||||
#[cfg(not(feature = "cli"))] {
|
||||
Config::watched(run_new_plain)?;
|
||||
#[cfg(feature = "cli")] let outcome = Config::watched(run_with_config);
|
||||
#[cfg(not(feature = "cli"))] let outcome = Config::watched(run_new_plain);
|
||||
if let Err(e) = outcome {
|
||||
println!("{e:#?}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 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.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::cli::*;
|
||||
mod cli;
|
||||
|
||||
pub use self::config::*;
|
||||
mod config;
|
||||
|
|
@ -317,7 +106,11 @@ mod app {
|
|||
/// Contains the currently edited musical arrangement
|
||||
pub project: Arrangement,
|
||||
/// 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 {
|
||||
|
|
@ -325,16 +118,15 @@ mod app {
|
|||
///
|
||||
/// ```
|
||||
/// let mut proj = tek::Arrangement::default();
|
||||
/// proj.jack = tek::tengri::Jack::new(&"test_tek").expect("failed to connect to jack");
|
||||
/// let mut conf = std::sync::Arc::new(tek::Config::default());
|
||||
/// conf.add("(mode hello)");
|
||||
/// let tek = tek::App::new(None, proj, conf, "hello");
|
||||
/// 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 tek = tek::App::new(None, proj, conf.into(), "hello");
|
||||
/// ```
|
||||
pub fn new (
|
||||
exit: Option<Exit>,
|
||||
exit: Option<Exit>,
|
||||
project: Arrangement,
|
||||
config: Arc<Config>,
|
||||
mode: impl AsRef<str>
|
||||
config: Arc<Config>,
|
||||
mode: impl AsRef<str>
|
||||
) -> Self {
|
||||
App {
|
||||
exit: exit.unwrap_or_default(),
|
||||
|
|
@ -493,12 +285,13 @@ mod bind {
|
|||
use crate::*;
|
||||
|
||||
tui_keys!(self: App, input {
|
||||
#[cfg(feature = "prof2")] profiling::scope!("App::tui_keys!");
|
||||
let name = self.mode.as_ref();
|
||||
let mode = name.and_then(|m|self.config.get_mode(m)).as_ref().map(Arc::clone);
|
||||
if let Some(mode) = mode {
|
||||
let binds = self.config.binds.clone();
|
||||
for id in mode.keys.iter() {
|
||||
if let Some(event_map) = binds.read().unwrap().get(id.as_ref())
|
||||
if let Some(event_map) = binds.try_read().unwrap().get(id.as_ref())
|
||||
&& let Some(bindings) = event_map.query(input) {
|
||||
for binding in bindings {
|
||||
for command in binding.commands.iter() {
|
||||
|
|
@ -702,7 +495,11 @@ mod device {
|
|||
}
|
||||
}
|
||||
|
||||
impl HasJack<'static> for App { fn jack (&self) -> &Jack<'static> { &self.jack } }
|
||||
impl HasJack<'static> for App {
|
||||
fn jack (&self) -> &Jack<'static> {
|
||||
&self.jack
|
||||
}
|
||||
}
|
||||
|
||||
impl_audio!(App: tek_jack_process, tek_jack_event);
|
||||
|
||||
|
|
@ -894,10 +691,12 @@ mod draw {
|
|||
/// Then, every top-level form of the DSL description is rendered.
|
||||
impl Draw<Tui> for App {
|
||||
fn draw (&self, to: &mut Tui) -> Drawn<u16> {
|
||||
#[cfg(feature = "prof2")] profiling::scope!("App::draw");
|
||||
//self.perf.cycle(&mut |_|{
|
||||
self.draw_error(to)?;
|
||||
self.draw_modes(to)?;
|
||||
//self.draw_debug(to)?;
|
||||
self.draw_debug(to)?;
|
||||
#[cfg(feature = "prof2")] profiling::finish_frame!();
|
||||
Ok(Some(to.area().into()))
|
||||
//})
|
||||
}
|
||||
|
|
@ -905,7 +704,7 @@ mod draw {
|
|||
|
||||
impl App {
|
||||
fn draw_error (&self, to: &mut Tui) -> Usually<()> {
|
||||
if let Some(e) = self.error.read().unwrap().as_ref() {
|
||||
if let Some(e) = self.error.try_read().unwrap().as_ref() {
|
||||
e.as_ref().align_c().draw(to)?;
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -914,27 +713,27 @@ mod draw {
|
|||
fn draw_modes (&self, to: &mut Tui) -> Usually<()> {
|
||||
if let Some(mode) = self.mode.as_ref().and_then(|m|self.config.get_mode(m)) {
|
||||
let mut error = false;
|
||||
for (index, dsl) in mode.view.iter().enumerate() {
|
||||
match self.interpret(to, dsl) {
|
||||
for (index, view) in mode.view.iter().enumerate() {
|
||||
match (view.render)(self, to) {
|
||||
Ok(None) => {},
|
||||
Ok(Some(XYWH(.., w, h))) => {
|
||||
self.size.0.store(w as usize, Relaxed);
|
||||
self.size.1.store(h as usize, Relaxed);
|
||||
},
|
||||
Err(e) => {
|
||||
let src = &dsl.src().unwrap_or(Some("<source error>")).unwrap_or("<no source>");
|
||||
let message = format!(
|
||||
"Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{src}",
|
||||
&mode.name
|
||||
"Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{}",
|
||||
&mode.name,
|
||||
&view.source
|
||||
);
|
||||
*self.error.write().unwrap() = Some(message.into());
|
||||
*self.error.try_write().unwrap() = Some(message.into());
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if !error {
|
||||
*self.error.write().unwrap() = None;
|
||||
*self.error.try_write().unwrap() = None;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -942,78 +741,17 @@ mod draw {
|
|||
|
||||
#[allow(unused)] fn draw_debug (&self, to: &mut Tui) -> Drawn<u16> {
|
||||
east(
|
||||
format!("{}x{} ", self.size.0.load(Relaxed), self.size.1.load(Relaxed)),
|
||||
format!("{}% {} ", self.perf.percentage().unwrap_or_default(), self.perf.clock.raw() / 1000000000),
|
||||
format!("{}x{} ",
|
||||
self.size.0.load(Relaxed),
|
||||
self.size.1.load(Relaxed)),
|
||||
format!("{}/{} {} ",
|
||||
self.perf.used.load(Relaxed),
|
||||
self.perf.window.load(Relaxed),
|
||||
self.perf.clock.raw() / 10000000),
|
||||
).align_se().draw(to)
|
||||
}
|
||||
}
|
||||
|
||||
impl Interpret<Tui, Option<XYWH<u16>>> for App {
|
||||
fn interpret <L: Language> (&self, to: &mut Tui, dsl: L) -> Drawn<u16> {
|
||||
if let Ok(Some(expr)) = dsl.expr() {
|
||||
ok_flat(expr.head()?.map(|head|{
|
||||
match head.split('/').next() {
|
||||
Some("when") => kw_when(self, to, expr),
|
||||
Some("either") => kw_either(self, to, expr),
|
||||
Some("bsp") => kw_split(self, to, expr),
|
||||
Some("split") => kw_split(self, to, expr),
|
||||
Some("align") => kw_align(self, to, expr),
|
||||
Some("full") => kw_full(self, to, expr),
|
||||
Some("exact") => kw_exact(self, to, expr),
|
||||
Some("min") => kw_min(self, to, expr),
|
||||
Some("max") => kw_max(self, to, expr),
|
||||
Some("push") => kw_push(self, to, expr),
|
||||
Some("pull") => kw_pull(self, to, expr),
|
||||
Some("text") => kw_tui_text(self, to, expr),
|
||||
Some("fg") => kw_tui_fg(self, to, expr),
|
||||
Some("bg") => kw_tui_bg(self, to, expr),
|
||||
_ => Err(format!("interpret_expr: unexpected: {expr:?}").into())
|
||||
}
|
||||
}))
|
||||
} else if let Ok(Some(word)) = dsl.word() {
|
||||
let mut frags = word.src()?.unwrap().split("/");
|
||||
match frags.next() {
|
||||
//Some(":logo") => view_logo().draw(to),
|
||||
Some(":meters") => match frags.next() {
|
||||
Some("input") => bg(Rgb(30, 30, 30), "Input Meters".align_s().full_h()).draw(to),
|
||||
Some("output") => bg(Rgb(30, 30, 30), "Output Meters".align_s().full_h()).draw(to),
|
||||
_ => panic!()
|
||||
},
|
||||
Some(":tracks") => match frags.next() {
|
||||
None => "TODO tracks".draw(to),
|
||||
Some("names") => self.project.view_track_names(self.color.clone()).draw(to),//bg(Rgb(40, 40, 40), full_w(align_w("Track Names")))),
|
||||
Some("inputs") => self.project.view_track_inputs(self.color.clone()).draw(to),//bg(Rgb(40, 40, 40), "Track Inputs".align_w().full_w()).draw(to),
|
||||
Some("devices") => self.project.view_track_devices(self.color.clone()).draw(to),//bg(Rgb(40, 40, 40), "Track Devices".align_w().full_w()).draw(to),
|
||||
Some("outputs") => self.project.view_track_outputs(self.color.clone(), 0).draw(to),
|
||||
_ => panic!()
|
||||
},
|
||||
Some(":scenes") => match frags.next() {
|
||||
None => self.view_scenes_clips().draw(to),
|
||||
Some("names") => self.view_scenes_names().draw(to),
|
||||
_ => panic!()
|
||||
},
|
||||
Some(":dialog") => draw_dialog(to, frags, self),
|
||||
Some(":templates") => view_templates(frags, self).draw(to),
|
||||
Some(":sessions") => view_sessions().draw(to),
|
||||
Some(":browse/title") => view_browse_title(self).draw(to),
|
||||
Some(":device") => view_device(self).draw(to),
|
||||
Some(":status") => "TODO: Status Bar".draw(to),
|
||||
Some(":editor") => "TODO Editor".draw(to),
|
||||
Some(":transport") => view_transport(true, "", "", "").draw(to),
|
||||
Some(":debug") => format!("[{:?}]", to.area()).exact_h(1).draw(to),
|
||||
Some(_) => if let Some(lang) = self.config.get_view(word) {
|
||||
self.interpret(to, lang)
|
||||
} else {
|
||||
fg(Color::Rgb(128, 32, 32), format!("undefined: {word:?}")).draw(to)
|
||||
},
|
||||
_ => unreachable!()
|
||||
}
|
||||
} else {
|
||||
Err(format!("not word/expr:\n{dsl:?}").into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_has!(Sizer: |self: App|self.size);
|
||||
|
||||
pub trait HasWidth {
|
||||
|
|
@ -1024,9 +762,7 @@ mod draw {
|
|||
fn width_dec (&mut self);
|
||||
}
|
||||
|
||||
pub fn view_templates <'a> (_frags: std::str::Split<&str>, state: &'a App)
|
||||
-> impl Draw<Tui> + use<'a>
|
||||
{
|
||||
pub fn view_templates <'a> (state: &'a App) -> impl Draw<Tui> + use<'a> {
|
||||
let height = (state.config.modes.len() * 2) as u16;
|
||||
draw(move |to: &mut Tui|{
|
||||
let mut index = 0;
|
||||
|
|
@ -1164,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 4172fa257776f5c6c7b406429b2244d630702458
|
||||
Subproject commit b318d498e544fa65bbf3a3ff4dabde166352b045
|
||||
Loading…
Add table
Add a link
Reference in a new issue