mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-07-17 07:46:57 +02:00
265 lines
11 KiB
Rust
265 lines
11 KiB
Rust
use crate::*;
|
|
|
|
/// The command-line interface descriptor.
|
|
///
|
|
/// ```
|
|
/// let cli: tek::Cli = Default::default();
|
|
///
|
|
/// use clap::CommandFactory;
|
|
/// tek::Cli::command().debug_assert();
|
|
/// ```
|
|
#[derive(Parser)]
|
|
#[command(name = "tek", version, about = Some(HEADER), long_about = Some(HEADER))]
|
|
#[derive(Debug, Default)] pub struct Cli {
|
|
/// Pre-defined configuration modes.
|
|
///
|
|
/// TODO: Replace these with scripted configurations.
|
|
#[command(subcommand)] pub action: Action,
|
|
}
|
|
|
|
/// 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 {
|
|
/// 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>,
|
|
},
|
|
/// Import media as new session.
|
|
Import,
|
|
/// Show configuration.
|
|
Config,
|
|
/// Show version.
|
|
Version,
|
|
}
|
|
|
|
/// Command-line configuration.
|
|
#[cfg(feature = "cli")]
|
|
impl Cli {
|
|
pub fn run (&self) -> Usually<()> {
|
|
self.action.run(Config::init_new(None)?)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "cli")]
|
|
impl Action {
|
|
fn run (&self, config: Config) -> Usually<()> {
|
|
use Action::*;
|
|
match self {
|
|
Version => tek_show_version(),
|
|
Config => tek_print_config(&config),
|
|
List => todo!("list sessions"),
|
|
Resume => todo!("resume session"),
|
|
New {
|
|
name, bpm, tracks, scenes, sync_lead, sync_follow,
|
|
midi_from: mf, midi_from_re: mfr, midi_to: mt, midi_to_re: mtr,
|
|
left_from: lf, right_from: rf, left_to: lt, right_to: rt, ..
|
|
} => {
|
|
let name = name.as_ref().map_or("tek", |x|x.as_str());
|
|
let jack = Jack::new(&name)?;
|
|
let mut proj = tek_project_new(
|
|
&jack, Clock::new(&jack, *bpm)?, &lf, <, &rf, &rt, &mf, &mt, &mfr, &mtr
|
|
)?;
|
|
proj.tracks_add(tracks.unwrap_or(0), None, &[], &[])?;
|
|
proj.scenes_add(scenes.unwrap_or(0))?;
|
|
//if matches!(self, Action::Status) {
|
|
//// Show status and exit
|
|
//tek_print_status(&proj);
|
|
//return Ok(())
|
|
//}
|
|
// Initialize the app state
|
|
let app = Arc::new(RwLock::new(App::new(&jack, proj, config, ":menu")));
|
|
//if matches!(self, Action::Headless) {
|
|
//// TODO: Headless mode (daemon + client over IPC, then over network...)
|
|
//println!("todo headless");
|
|
//return Ok(())
|
|
//}
|
|
let (_keyboard, _terminal) = Exit::run(|exited|tui_io(
|
|
exited.as_ref(),
|
|
&app,
|
|
Duration::from_millis(100),
|
|
Duration::from_millis(10),
|
|
std::io::stdout()
|
|
))?;
|
|
// Run the [Tui] and [Jack] threads with the [App] state.
|
|
//Tui::new(Box::new(std::io::stdout()))?.run(true, &jack.run(move|jack|{
|
|
//// Between jack init and app's first cycle:
|
|
//jack.sync_lead(*sync_lead, |mut state|{
|
|
//let clock = app.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)
|
|
//})?)?;
|
|
},
|
|
_ => todo!()
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub fn tek_project_new (
|
|
jack: &Jack<'static>,
|
|
clock: Clock,
|
|
left_from: &[impl AsRef<str>],
|
|
left_to: &[impl AsRef<str>],
|
|
right_from: &[impl AsRef<str>],
|
|
right_to: &[impl AsRef<str>],
|
|
midi_from: &[impl AsRef<str>],
|
|
midi_to: &[impl AsRef<str>],
|
|
midi_from_re: &[impl AsRef<str>],
|
|
midi_to_re: &[impl AsRef<str>],
|
|
) -> Usually<Arrangement> {
|
|
// TODO: Collect audio IO:
|
|
let empty = &[] as &[&str];
|
|
let left_froms = Connect::collect(left_from, empty, empty);
|
|
let left_tos = Connect::collect(left_to, empty, empty);
|
|
let right_froms = Connect::collect(right_from, empty, empty);
|
|
let right_tos = Connect::collect(right_to, empty, empty);
|
|
let _audio_froms = &[left_froms.as_slice(), right_froms.as_slice()];
|
|
let _audio_tos = &[left_tos.as_slice(), right_tos.as_slice()];
|
|
Ok(Arrangement::new(
|
|
jack,
|
|
None,
|
|
clock,
|
|
vec![],
|
|
vec![],
|
|
Connect::collect(&midi_from, &[] as &[&str], &midi_from_re).iter().enumerate()
|
|
.map(|(index, connect)|jack.midi_in(&format!("M/{index}"), &[connect.clone()]))
|
|
.collect::<Result<Vec<_>, _>>()?,
|
|
Connect::collect(&midi_to, &[] as &[&str], &midi_to_re).iter().enumerate()
|
|
.map(|(index, connect)|jack.midi_out(&format!("{index}/M"), &[connect.clone()]))
|
|
.collect::<Result<Vec<_>, _>>()?
|
|
))
|
|
}
|
|
|
|
pub fn tek_show_version () {
|
|
println!("todo version");
|
|
}
|
|
|
|
pub fn tek_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);
|
|
}
|
|
}
|
|
}
|
|
for (k, v) in config.modes.read().unwrap().iter() {
|
|
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!();
|
|
for (k, v) in v.modes.read().unwrap().iter() {
|
|
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 tek_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
|
|
}
|