mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
merge cli entrypoint into main module
This commit is contained in:
parent
385297c59f
commit
8e2aed58af
4 changed files with 268 additions and 242 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1414,6 +1414,7 @@ name = "tek"
|
|||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"clap",
|
||||
"palette",
|
||||
"rand",
|
||||
"tek_jack",
|
||||
|
|
|
|||
103
cli/tek.rs
103
cli/tek.rs
|
|
@ -1,109 +1,8 @@
|
|||
use tek::*;
|
||||
use clap::{self, Parser, Subcommand};
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct TekCli {
|
||||
/// Which app to initialize
|
||||
#[command(subcommand)] mode: TekMode,
|
||||
/// Name of JACK client
|
||||
#[arg(short='n', long)] name: Option<String>,
|
||||
/// Whether to attempt to become transport master
|
||||
#[arg(short='S', long, default_value_t = false)] sync_lead: bool,
|
||||
/// Whether to sync to external transport master
|
||||
#[arg(short='s', 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='t', 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>,
|
||||
}
|
||||
#[derive(Debug, Clone, Subcommand)] pub enum TekMode {
|
||||
/// A standalone transport clock.
|
||||
Clock,
|
||||
/// A MIDI sequencer.
|
||||
Sequencer,
|
||||
/// A MIDI-controlled audio sampler.
|
||||
Sampler,
|
||||
/// Sequencer and sampler together.12
|
||||
Groovebox,
|
||||
/// Multi-track MIDI sequencer.
|
||||
Arranger {
|
||||
/// Number of scenes
|
||||
#[arg(short = 'y', long, default_value_t = 1)] scenes: usize,
|
||||
/// Number of tracks
|
||||
#[arg(short = 'x', long, default_value_t = 1)] tracks: usize,
|
||||
/// Width of tracks
|
||||
#[arg(short = 'w', long, default_value_t = 9)] track_width: usize,
|
||||
},
|
||||
/// TODO: A MIDI-controlled audio mixer
|
||||
Mixer,
|
||||
/// TODO: A customizable channel strip
|
||||
Track,
|
||||
/// TODO: An audio plugin host
|
||||
Plugin,
|
||||
}
|
||||
/// Application entrypoint.
|
||||
pub fn main () -> Usually<()> {
|
||||
let cli = TekCli::parse();
|
||||
let name = cli.name.as_ref().map_or("tek", |x|x.as_str());
|
||||
//let color = ItemPalette::random();
|
||||
let jack = JackConnection::new(name)?;
|
||||
let engine = Tui::new()?;
|
||||
let empty = &[] as &[&str];
|
||||
let midi_froms = PortConnection::collect(&cli.midi_from, &cli.midi_from_re, empty);
|
||||
let midi_tos = PortConnection::collect(&cli.midi_to, &cli.midi_to_re, empty);
|
||||
let left_froms = PortConnection::collect(&cli.left_from, empty, empty);
|
||||
let left_tos = PortConnection::collect(&cli.left_to, empty, empty);
|
||||
let right_froms = PortConnection::collect(&cli.right_from, empty, empty);
|
||||
let right_tos = PortConnection::collect(&cli.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(match cli.mode {
|
||||
TekMode::Clock =>
|
||||
engine.run(&jack.activate_with(|jack|App::clock(
|
||||
jack, cli.bpm))?)?,
|
||||
TekMode::Sequencer =>
|
||||
engine.run(&jack.activate_with(|jack|App::sequencer(
|
||||
jack, cli.bpm, &midi_froms, &midi_tos))?)?,
|
||||
TekMode::Groovebox =>
|
||||
engine.run(&jack.activate_with(|jack|App::groovebox(
|
||||
jack, cli.bpm, &midi_froms, &midi_tos, &audio_froms, &audio_tos))?)?,
|
||||
TekMode::Arranger { scenes, tracks, track_width, .. } =>
|
||||
engine.run(&jack.activate_with(|jack|App::arranger(
|
||||
jack, cli.bpm, &midi_froms, &midi_tos, &audio_froms, &audio_tos,
|
||||
scenes, tracks, track_width,
|
||||
))?)?,
|
||||
//TekMode::Sampler => engine.run(&jack.activate_with(|jack|Ok(
|
||||
//SamplerTui {
|
||||
//cursor: (0, 0),
|
||||
//editing: None,
|
||||
//mode: None,
|
||||
//note_lo: 36.into(),
|
||||
//note_pt: 36.into(),
|
||||
//size: Measure::new(),
|
||||
//state: Sampler::new(jack, &"sampler", &midi_froms,
|
||||
//&[&left_froms, &right_froms],
|
||||
//&[&left_tos, &right_tos])?,
|
||||
//color,
|
||||
//}
|
||||
//))?)?,
|
||||
_ => todo!()
|
||||
})
|
||||
TekCli::parse().run()
|
||||
}
|
||||
#[test] fn verify_cli () {
|
||||
use clap::CommandFactory;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ backtrace = "0.3.72"
|
|||
palette = { version = "0.7.6", features = [ "random" ] }
|
||||
rand = "0.8.5"
|
||||
toml = "0.8.12"
|
||||
clap = { optional = true, version = "4.5.4", features = [ "derive" ] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["cli"]
|
||||
cli = ["clap"]
|
||||
|
|
|
|||
402
tek/src/lib.rs
402
tek/src/lib.rs
|
|
@ -22,7 +22,113 @@ pub use ::tek_tui::{
|
|||
Event, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers, KeyCode::{self, *},
|
||||
},
|
||||
};
|
||||
#[derive(Default, Debug)] pub struct App {
|
||||
use clap::{self, Parser, Subcommand};
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct TekCli {
|
||||
/// Which app to initialize
|
||||
#[command(subcommand)] mode: TekMode,
|
||||
/// Name of JACK client
|
||||
#[arg(short='n', long)] name: Option<String>,
|
||||
/// Whether to attempt to become transport master
|
||||
#[arg(short='S', long, default_value_t = false)] sync_lead: bool,
|
||||
/// Whether to sync to external transport master
|
||||
#[arg(short='s', 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='t', 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>,
|
||||
}
|
||||
#[derive(Debug, Clone, Subcommand)] pub enum TekMode {
|
||||
/// A standalone transport clock.
|
||||
Clock,
|
||||
/// A MIDI sequencer.
|
||||
Sequencer,
|
||||
/// A MIDI-controlled audio sampler.
|
||||
Sampler,
|
||||
/// Sequencer and sampler together.12
|
||||
Groovebox,
|
||||
/// Multi-track MIDI sequencer.
|
||||
Arranger {
|
||||
/// Number of scenes
|
||||
#[arg(short = 'y', long, default_value_t = 1)] scenes: usize,
|
||||
/// Number of tracks
|
||||
#[arg(short = 'x', long, default_value_t = 1)] tracks: usize,
|
||||
/// Width of tracks
|
||||
#[arg(short = 'w', long, default_value_t = 9)] track_width: usize,
|
||||
},
|
||||
/// TODO: A MIDI-controlled audio mixer
|
||||
Mixer,
|
||||
/// TODO: A customizable channel strip
|
||||
Track,
|
||||
/// TODO: An audio plugin host
|
||||
Plugin,
|
||||
}
|
||||
impl TekCli {
|
||||
pub fn run (&self) -> Usually<()> {
|
||||
let name = self.name.as_ref().map_or("tek", |x|x.as_str());
|
||||
//let color = ItemPalette::random();
|
||||
let jack = JackConnection::new(name)?;
|
||||
let engine = Tui::new()?;
|
||||
let empty = &[] as &[&str];
|
||||
let midi_froms = PortConnection::collect(&self.midi_from, &self.midi_from_re, empty);
|
||||
let midi_tos = PortConnection::collect(&self.midi_to, &self.midi_to_re, empty);
|
||||
let left_froms = PortConnection::collect(&self.left_from, empty, empty);
|
||||
let left_tos = PortConnection::collect(&self.left_to, empty, empty);
|
||||
let right_froms = PortConnection::collect(&self.right_from, empty, empty);
|
||||
let right_tos = PortConnection::collect(&self.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(match self.mode {
|
||||
TekMode::Clock =>
|
||||
engine.run(&jack.activate_with(|jack|Tek::new_clock(
|
||||
jack, self.bpm))?)?,
|
||||
TekMode::Sequencer =>
|
||||
engine.run(&jack.activate_with(|jack|Tek::new_sequencer(
|
||||
jack, self.bpm, &midi_froms, &midi_tos))?)?,
|
||||
TekMode::Groovebox =>
|
||||
engine.run(&jack.activate_with(|jack|Tek::new_groovebox(
|
||||
jack, self.bpm, &midi_froms, &midi_tos, &audio_froms, &audio_tos))?)?,
|
||||
TekMode::Arranger { scenes, tracks, track_width, .. } =>
|
||||
engine.run(&jack.activate_with(|jack|Tek::new_arranger(
|
||||
jack, self.bpm, &midi_froms, &midi_tos, &audio_froms, &audio_tos,
|
||||
scenes, tracks, track_width,
|
||||
))?)?,
|
||||
//TekMode::Sampler => engine.run(&jack.activate_with(|jack|Ok(
|
||||
//SamplerTui {
|
||||
//cursor: (0, 0),
|
||||
//editing: None,
|
||||
//mode: None,
|
||||
//note_lo: 36.into(),
|
||||
//note_pt: 36.into(),
|
||||
//size: Measure::new(),
|
||||
//state: Sampler::new(jack, &"sampler", &midi_froms,
|
||||
//&[&left_froms, &right_froms],
|
||||
//&[&left_tos, &right_tos])?,
|
||||
//color,
|
||||
//}
|
||||
//))?)?,
|
||||
_ => todo!()
|
||||
})
|
||||
}
|
||||
}
|
||||
#[derive(Default, Debug)] pub struct Tek {
|
||||
pub jack: Arc<RwLock<JackConnection>>,
|
||||
pub edn: String,
|
||||
pub clock: Clock,
|
||||
|
|
@ -45,18 +151,18 @@ pub use ::tek_tui::{
|
|||
pub size: Measure<TuiOut>,
|
||||
pub perf: PerfModel,
|
||||
pub compact: bool,
|
||||
pub history: Vec<AppCommand>,
|
||||
pub history: Vec<TekCommand>,
|
||||
}
|
||||
has_size!(<TuiOut>|self: App|&self.size);
|
||||
has_clock!(|self: App|self.clock);
|
||||
has_clips!(|self: App|self.pool.as_ref().expect("no clip pool").clips);
|
||||
//has_editor!(|self: App|self.editor.as_ref().expect("no editor"));
|
||||
has_jack!(|self: App|&self.jack);
|
||||
has_sampler!(|self: App|{
|
||||
has_size!(<TuiOut>|self: Tek|&self.size);
|
||||
has_clock!(|self: Tek|self.clock);
|
||||
has_clips!(|self: Tek|self.pool.as_ref().expect("no clip pool").clips);
|
||||
//has_editor!(|self: Tek|self.editor.as_ref().expect("no editor"));
|
||||
has_jack!(|self: Tek|&self.jack);
|
||||
has_sampler!(|self: Tek|{
|
||||
sampler = self.sampler;
|
||||
index = self.editor.as_ref().map(|e|e.note_point()).unwrap_or(0);
|
||||
});
|
||||
has_editor!(|self: App|{
|
||||
has_editor!(|self: Tek|{
|
||||
editor = self.editor;
|
||||
editor_w = {
|
||||
let size = self.size.w();
|
||||
|
|
@ -68,7 +174,7 @@ has_editor!(|self: App|{
|
|||
editor_h = 15;
|
||||
is_editing = self.editing.load(Relaxed);
|
||||
});
|
||||
edn_provide!(# usize: |self: App| {
|
||||
edn_provide!(# usize: |self: Tek| {
|
||||
":scene" => 0,
|
||||
":scene-next" => 0,
|
||||
":scene-prev" => 0,
|
||||
|
|
@ -76,7 +182,7 @@ edn_provide!(# usize: |self: App| {
|
|||
":track-next" => 0,
|
||||
":track-prev" => 0,
|
||||
});
|
||||
edn_view!(TuiOut: |self: App| self.size.of(EdnView::from_source(self, self.edn.as_ref())); {
|
||||
edn_view!(TuiOut: |self: Tek| self.size.of(EdnView::from_source(self, self.edn.as_ref())); {
|
||||
bool {};
|
||||
isize {};
|
||||
Color {};
|
||||
|
|
@ -99,7 +205,7 @@ edn_view!(TuiOut: |self: App| self.size.of(EdnView::from_source(self, self.edn.a
|
|||
":sample" => self.view_sample(self.is_editing()).boxed(),
|
||||
":sampler" => self.view_sampler(self.is_editing(), &self.editor).boxed(),
|
||||
":status" => self.editor.as_ref().map(|e|Bsp::e(e.clip_status(), e.edit_status())).boxed(),
|
||||
":toolbar" => ClockView::new(true, &self.clock).boxed(),
|
||||
":toolbar" => self.view_clock().boxed(),//ClockView::new(true, &self.clock).boxed(),
|
||||
":tracks" => self.row(self.w(), 3, self.track_header(), self.track_cells()).boxed(),
|
||||
":inputs" => self.row(self.w(), 3, self.input_header(), self.input_cells()).boxed(),
|
||||
":outputs" => self.row(self.w(), 3, self.output_header(), self.output_cells()).boxed(),
|
||||
|
|
@ -109,8 +215,75 @@ edn_view!(TuiOut: |self: App| self.size.of(EdnView::from_source(self, self.edn.a
|
|||
self.scene_header(),
|
||||
self.scene_cells(self.is_editing())
|
||||
)).boxed() }});
|
||||
impl App {
|
||||
pub fn clock (
|
||||
impl Tek {
|
||||
pub fn new_arranger (
|
||||
jack: &Arc<RwLock<JackConnection>>,
|
||||
bpm: Option<f64>,
|
||||
midi_froms: &[PortConnection],
|
||||
midi_tos: &[PortConnection],
|
||||
audio_froms: &[&[PortConnection];2],
|
||||
audio_tos: &[&[PortConnection];2],
|
||||
scenes: usize,
|
||||
tracks: usize,
|
||||
track_width: usize,
|
||||
) -> Usually<Self> {
|
||||
let mut arranger = Self {
|
||||
edn: include_str!("./view_arranger.edn").to_string(),
|
||||
..Self::new_groovebox(jack, bpm, midi_froms, midi_tos, audio_froms, audio_tos)?
|
||||
};
|
||||
arranger.scenes_add(scenes);
|
||||
arranger.tracks_add(tracks, track_width, &[], &[]);
|
||||
Ok(arranger)
|
||||
}
|
||||
pub fn new_groovebox (
|
||||
jack: &Arc<RwLock<JackConnection>>,
|
||||
bpm: Option<f64>,
|
||||
midi_froms: &[PortConnection],
|
||||
midi_tos: &[PortConnection],
|
||||
audio_froms: &[&[PortConnection];2],
|
||||
audio_tos: &[&[PortConnection];2],
|
||||
) -> Usually<Self> {
|
||||
let app = Self {
|
||||
edn: include_str!("./view_groovebox.edn").to_string(),
|
||||
sampler: Some(Sampler::new(
|
||||
jack,
|
||||
&"sampler",
|
||||
midi_froms,
|
||||
audio_froms,
|
||||
audio_tos
|
||||
)?),
|
||||
..Self::new_sequencer(jack, bpm, midi_froms, midi_tos)?
|
||||
};
|
||||
if let Some(sampler) = app.sampler.as_ref().unwrap().midi_in.as_ref() {
|
||||
jack.connect_ports(&app.player.as_ref().unwrap().midi_outs[0].port, &sampler.port)?;
|
||||
}
|
||||
Ok(app)
|
||||
}
|
||||
pub fn new_sequencer (
|
||||
jack: &Arc<RwLock<JackConnection>>,
|
||||
bpm: Option<f64>,
|
||||
midi_froms: &[PortConnection],
|
||||
midi_tos: &[PortConnection],
|
||||
) -> Usually<Self> {
|
||||
let clip = MidiClip::new("Clip", true, 384usize, None, Some(ItemColor::random().into()));
|
||||
let clip = Arc::new(RwLock::new(clip));
|
||||
Ok(Self {
|
||||
edn: include_str!("./view_sequencer.edn").to_string(),
|
||||
pool: Some((&clip).into()),
|
||||
editor: Some((&clip).into()),
|
||||
editing: false.into(),
|
||||
midi_buf: vec![vec![];65536],
|
||||
player: Some(MidiPlayer::new(
|
||||
&jack,
|
||||
"sequencer",
|
||||
Some(&clip),
|
||||
&midi_froms,
|
||||
&midi_tos
|
||||
)?),
|
||||
..Self::new_clock(jack, bpm)?
|
||||
})
|
||||
}
|
||||
pub fn new_clock (
|
||||
jack: &Arc<RwLock<JackConnection>>,
|
||||
bpm: Option<f64>,
|
||||
) -> Usually<Self> {
|
||||
|
|
@ -139,78 +312,28 @@ impl App {
|
|||
..Default::default()
|
||||
})
|
||||
}
|
||||
pub fn sequencer (
|
||||
jack: &Arc<RwLock<JackConnection>>,
|
||||
bpm: Option<f64>,
|
||||
midi_froms: &[PortConnection],
|
||||
midi_tos: &[PortConnection],
|
||||
) -> Usually<Self> {
|
||||
let clip = MidiClip::new("Clip", true, 384usize, None, Some(ItemColor::random().into()));
|
||||
let clip = Arc::new(RwLock::new(clip));
|
||||
Ok(Self {
|
||||
edn: include_str!("./view_sequencer.edn").to_string(),
|
||||
pool: Some((&clip).into()),
|
||||
editor: Some((&clip).into()),
|
||||
editing: false.into(),
|
||||
midi_buf: vec![vec![];65536],
|
||||
player: Some(MidiPlayer::new(
|
||||
&jack,
|
||||
"sequencer",
|
||||
Some(&clip),
|
||||
&midi_froms,
|
||||
&midi_tos
|
||||
)?),
|
||||
..Self::clock(jack, bpm)?
|
||||
})
|
||||
}
|
||||
pub fn groovebox (
|
||||
jack: &Arc<RwLock<JackConnection>>,
|
||||
bpm: Option<f64>,
|
||||
midi_froms: &[PortConnection],
|
||||
midi_tos: &[PortConnection],
|
||||
audio_froms: &[&[PortConnection];2],
|
||||
audio_tos: &[&[PortConnection];2],
|
||||
) -> Usually<Self> {
|
||||
let app = Self {
|
||||
edn: include_str!("./view_groovebox.edn").to_string(),
|
||||
sampler: Some(Sampler::new(
|
||||
jack,
|
||||
&"sampler",
|
||||
midi_froms,
|
||||
audio_froms,
|
||||
audio_tos
|
||||
)?),
|
||||
..Self::sequencer(jack, bpm, midi_froms, midi_tos)?
|
||||
};
|
||||
if let Some(sampler) = app.sampler.as_ref().unwrap().midi_in.as_ref() {
|
||||
jack.connect_ports(&app.player.as_ref().unwrap().midi_outs[0].port, &sampler.port)?;
|
||||
}
|
||||
Ok(app)
|
||||
}
|
||||
pub fn arranger (
|
||||
jack: &Arc<RwLock<JackConnection>>,
|
||||
bpm: Option<f64>,
|
||||
midi_froms: &[PortConnection],
|
||||
midi_tos: &[PortConnection],
|
||||
audio_froms: &[&[PortConnection];2],
|
||||
audio_tos: &[&[PortConnection];2],
|
||||
scenes: usize,
|
||||
tracks: usize,
|
||||
track_width: usize,
|
||||
) -> Usually<Self> {
|
||||
let mut arranger = Self {
|
||||
edn: include_str!("./view_arranger.edn").to_string(),
|
||||
..Self::groovebox(jack, bpm, midi_froms, midi_tos, audio_froms, audio_tos)?
|
||||
};
|
||||
arranger.scenes_add(scenes);
|
||||
arranger.tracks_add(tracks, track_width, &[], &[]);
|
||||
Ok(arranger)
|
||||
}
|
||||
fn compact (&self) -> bool { self.compact }
|
||||
fn is_editing (&self) -> bool { self.editing.load(Relaxed) }
|
||||
fn editor (&self) -> impl Content<TuiOut> + '_ { &self.editor }
|
||||
fn w (&self) -> u16 { self.tracks_sizes(self.is_editing(), self.editor_w()).last().map(|x|x.3 as u16).unwrap_or(0) }
|
||||
fn pool (&self) -> impl Content<TuiOut> + use<'_> { self.pool.as_ref().map(|pool|Align::e(Fixed::x(self.sidebar_w(), PoolView(self.compact(), pool)))) }
|
||||
fn view_clock (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
Outer(Style::default().fg(TuiTheme::g(0))).enclose(row!(
|
||||
OutputStats::new(self.compact, &self.clock),
|
||||
" ",
|
||||
PlayPause { compact: false, playing: self.clock.is_rolling() },
|
||||
" ",
|
||||
BeatStats::new(self.compact, &self.clock),
|
||||
))
|
||||
}
|
||||
fn pool (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
self.pool.as_ref()
|
||||
.map(|pool|Align::e(Fixed::x(self.sidebar_w(), PoolView(self.compact(), pool))))
|
||||
}
|
||||
fn w (&self) -> u16 {
|
||||
self.tracks_sizes(self.is_editing(), self.editor_w())
|
||||
.last()
|
||||
.map(|x|x.3 as u16)
|
||||
.unwrap_or(0)
|
||||
}
|
||||
fn sidebar_w (&self) -> u16 {
|
||||
let w = self.size.w();
|
||||
let w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
|
||||
|
|
@ -387,11 +510,11 @@ const KEYS_CLIP: &str = include_str!("keys_clip.edn");
|
|||
const KEYS_TRACK: &str = include_str!("keys_track.edn");
|
||||
const KEYS_SCENE: &str = include_str!("keys_scene.edn");
|
||||
const KEYS_MIX: &str = include_str!("keys_mix.edn");
|
||||
handle!(TuiIn: |self: App, input|Ok({
|
||||
handle!(TuiIn: |self: Tek, input|Ok({
|
||||
use EdnItem::*;
|
||||
let mut command: Option<AppCommand> = None;
|
||||
let mut command: Option<TekCommand> = None;
|
||||
// Handle from root keymap
|
||||
if let Some(command) = EdnKeyMapToCommand::new(KEYS_APP)?.from::<_, AppCommand>(self, input) {
|
||||
if let Some(command) = EdnKeyMapToCommand::new(KEYS_APP)?.from::<_, TekCommand>(self, input) {
|
||||
if let Some(undo) = command.execute(self)? { self.history.push(undo); }
|
||||
return Ok(Some(true))
|
||||
}
|
||||
|
|
@ -401,7 +524,7 @@ handle!(TuiIn: |self: App, input|Ok({
|
|||
Selection::Track(t) => KEYS_TRACK,
|
||||
Selection::Scene(s) => KEYS_SCENE,
|
||||
Selection::Mix => KEYS_MIX,
|
||||
})?.from::<_, AppCommand>(
|
||||
})?.from::<_, TekCommand>(
|
||||
self, input
|
||||
) {
|
||||
if let Some(undo) = command.execute(self)? { self.history.push(undo); }
|
||||
|
|
@ -409,7 +532,7 @@ handle!(TuiIn: |self: App, input|Ok({
|
|||
}
|
||||
None
|
||||
}));
|
||||
#[derive(Clone, Debug)] pub enum AppCommand {
|
||||
#[derive(Clone, Debug)] pub enum TekCommand {
|
||||
Clip(ClipCommand),
|
||||
Clock(ClockCommand),
|
||||
Color(ItemPalette),
|
||||
|
|
@ -425,7 +548,7 @@ handle!(TuiIn: |self: App, input|Ok({
|
|||
Track(TrackCommand),
|
||||
Zoom(Option<usize>),
|
||||
}
|
||||
edn_command!(AppCommand: |state: App| {
|
||||
edn_command!(TekCommand: |app: Tek| {
|
||||
("compact" [] Self::Compact(None))
|
||||
("compact" [c: bool] Self::Compact(c))
|
||||
|
||||
|
|
@ -445,47 +568,47 @@ edn_command!(AppCommand: |state: App| {
|
|||
(t, s) => Self::Select(Selection::Clip(t - 1, s - 1)),
|
||||
})
|
||||
|
||||
("clip" [a, ..b] Self::Clip(ClipCommand::from_edn(state, &a.to_ref(), b)
|
||||
("clip" [a, ..b] Self::Clip(ClipCommand::from_edn(app, &a.to_ref(), b)
|
||||
.expect("invalid command")))
|
||||
("clock" [a, ..b] Self::Clock(ClockCommand::from_edn(state.clock(), &a.to_ref(), b)
|
||||
("clock" [a, ..b] Self::Clock(ClockCommand::from_edn(app.clock(), &a.to_ref(), b)
|
||||
.expect("invalid command")))
|
||||
("editor" [a, ..b] Self::Editor(MidiEditCommand::from_edn(state.editor.as_ref().expect("no editor"), &a.to_ref(), b)
|
||||
("editor" [a, ..b] Self::Editor(MidiEditCommand::from_edn(app.editor.as_ref().expect("no editor"), &a.to_ref(), b)
|
||||
.expect("invalid command")))
|
||||
("pool" [a, ..b] Self::Pool(PoolCommand::from_edn(state.pool.as_ref().expect("no pool"), &a.to_ref(), b)
|
||||
("pool" [a, ..b] Self::Pool(PoolCommand::from_edn(app.pool.as_ref().expect("no pool"), &a.to_ref(), b)
|
||||
.expect("invalid command")))
|
||||
("sampler" [a, ..b] Self::Sampler(SamplerCommand::from_edn(state.sampler.as_ref().expect("no sampler"), &a.to_ref(), b)
|
||||
("sampler" [a, ..b] Self::Sampler(SamplerCommand::from_edn(app.sampler.as_ref().expect("no sampler"), &a.to_ref(), b)
|
||||
.expect("invalid command")))
|
||||
("scene" [a, ..b] Self::Scene(SceneCommand::from_edn(state, &a.to_ref(), b)
|
||||
("scene" [a, ..b] Self::Scene(SceneCommand::from_edn(app, &a.to_ref(), b)
|
||||
.expect("invalid command")))
|
||||
("track" [a, ..b] Self::Track(TrackCommand::from_edn(state, &a.to_ref(), b)
|
||||
("track" [a, ..b] Self::Track(TrackCommand::from_edn(app, &a.to_ref(), b)
|
||||
.expect("invalid command")))
|
||||
});
|
||||
command!(|self: AppCommand, state: App|match self {
|
||||
command!(|self: TekCommand, app: Tek|match self {
|
||||
Self::Zoom(_) => { println!("\n\rtodo: global zoom"); None },
|
||||
Self::History(delta) => { println!("\n\rtodo: undo/redo"); None },
|
||||
Self::Select(s) => { state.selected = s; None },
|
||||
Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?,
|
||||
Self::Scene(cmd) => cmd.delegate(state, Self::Scene)?,
|
||||
Self::Track(cmd) => cmd.delegate(state, Self::Track)?,
|
||||
Self::Clip(cmd) => cmd.delegate(state, Self::Clip)?,
|
||||
Self::Editor(cmd) => state.editor.as_mut()
|
||||
Self::Select(s) => { app.selected = s; None },
|
||||
Self::Clock(cmd) => cmd.delegate(app, Self::Clock)?,
|
||||
Self::Scene(cmd) => cmd.delegate(app, Self::Scene)?,
|
||||
Self::Track(cmd) => cmd.delegate(app, Self::Track)?,
|
||||
Self::Clip(cmd) => cmd.delegate(app, Self::Clip)?,
|
||||
Self::Editor(cmd) => app.editor.as_mut()
|
||||
.map(|editor|cmd.delegate(editor, Self::Editor)).transpose()?.flatten(),
|
||||
Self::Sampler(cmd) => state.sampler.as_mut()
|
||||
Self::Sampler(cmd) => app.sampler.as_mut()
|
||||
.map(|sampler|cmd.delegate(sampler, Self::Sampler)).transpose()?.flatten(),
|
||||
Self::Enqueue(clip) => state.player.as_mut()
|
||||
Self::Enqueue(clip) => app.player.as_mut()
|
||||
.map(|player|{player.enqueue_next(clip.as_ref());None}).flatten(),
|
||||
Self::Color(palette) => {
|
||||
let old = state.color;
|
||||
state.color = palette;
|
||||
let old = app.color;
|
||||
app.color = palette;
|
||||
Some(Self::Color(old))
|
||||
},
|
||||
Self::StopAll => {
|
||||
for track in 0..state.tracks.len(){state.tracks[track].player.enqueue_next(None);}
|
||||
for track in 0..app.tracks.len(){app.tracks[track].player.enqueue_next(None);}
|
||||
None
|
||||
},
|
||||
Self::Pool(cmd) => if let Some(pool) = state.pool.as_mut() {
|
||||
Self::Pool(cmd) => if let Some(pool) = app.pool.as_mut() {
|
||||
let undo = cmd.clone().delegate(pool, Self::Pool)?;
|
||||
if let Some(editor) = state.editor.as_mut() {
|
||||
if let Some(editor) = app.editor.as_mut() {
|
||||
match cmd {
|
||||
// autoselect: automatically load selected clip in editor
|
||||
// autocolor: update color in all places simultaneously
|
||||
|
|
@ -500,16 +623,16 @@ command!(|self: AppCommand, state: App|match self {
|
|||
},
|
||||
Self::Compact(compact) => match compact {
|
||||
Some(compact) => {
|
||||
if state.compact != compact {
|
||||
state.compact = compact;
|
||||
if app.compact != compact {
|
||||
app.compact = compact;
|
||||
Some(Self::Compact(Some(!compact)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
None => {
|
||||
state.compact = !state.compact;
|
||||
Some(Self::Compact(Some(!state.compact)))
|
||||
app.compact = !app.compact;
|
||||
Some(Self::Compact(Some(!app.compact)))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -555,7 +678,7 @@ impl Selection {
|
|||
}).into()
|
||||
}
|
||||
}
|
||||
impl HasSelection for App {
|
||||
impl HasSelection for Tek {
|
||||
fn selected (&self) -> &Selection { &self.selected }
|
||||
fn selected_mut (&mut self) -> &mut Selection { &mut self.selected }
|
||||
}
|
||||
|
|
@ -595,7 +718,7 @@ impl Track {
|
|||
SetZoom(usize),
|
||||
SetColor(usize, ItemPalette),
|
||||
}
|
||||
edn_command!(TrackCommand: |state: App| {
|
||||
edn_command!(TrackCommand: |app: Tek| {
|
||||
("add" [] Self::Add)
|
||||
("size" [a: usize] Self::SetSize(a.unwrap()))
|
||||
("zoom" [a: usize] Self::SetZoom(a.unwrap()))
|
||||
|
|
@ -604,18 +727,18 @@ edn_command!(TrackCommand: |state: App| {
|
|||
("stop" [a: usize] Self::Stop(a.unwrap()))
|
||||
("swap" [a: usize, b: usize] Self::Swap(a.unwrap(), b.unwrap()))
|
||||
});
|
||||
command!(|self: TrackCommand, state: App|match self {
|
||||
Self::Add => { state.track_add(None, None)?; None },
|
||||
Self::Del(index) => { state.track_del(index); None },
|
||||
Self::Stop(track) => { state.tracks[track].player.enqueue_next(None); None },
|
||||
command!(|self: TrackCommand, app: Tek|match self {
|
||||
Self::Add => { app.track_add(None, None)?; None },
|
||||
Self::Del(index) => { app.track_del(index); None },
|
||||
Self::Stop(track) => { app.tracks[track].player.enqueue_next(None); None },
|
||||
Self::SetColor(index, color) => {
|
||||
let old = state.tracks[index].color;
|
||||
state.tracks[index].color = color;
|
||||
let old = app.tracks[index].color;
|
||||
app.tracks[index].color = color;
|
||||
Some(Self::SetColor(index, old))
|
||||
},
|
||||
_ => None
|
||||
});
|
||||
impl HasTracks for App {
|
||||
impl HasTracks for Tek {
|
||||
fn midi_ins (&self) -> &Vec<JackPort<MidiIn>> { &self.midi_ins }
|
||||
fn midi_outs (&self) -> &Vec<JackPort<MidiOut>> { &self.midi_outs }
|
||||
fn tracks (&self) -> &Vec<Track> { &self.tracks }
|
||||
|
|
@ -782,7 +905,7 @@ impl Scene {
|
|||
SetColor(usize, ItemPalette),
|
||||
Enqueue(usize),
|
||||
}
|
||||
edn_command!(SceneCommand: |state: App| {
|
||||
edn_command!(SceneCommand: |app: Tek| {
|
||||
("add" [] Self::Add)
|
||||
("del" [a: usize] Self::Del(0))
|
||||
("zoom" [a: usize] Self::SetZoom(a.unwrap()))
|
||||
|
|
@ -790,23 +913,23 @@ edn_command!(SceneCommand: |state: App| {
|
|||
("enqueue" [a: usize] Self::Enqueue(a.unwrap()))
|
||||
("swap" [a: usize, b: usize] Self::Swap(a.unwrap(), b.unwrap()))
|
||||
});
|
||||
command!(|self: SceneCommand, state: App|match self {
|
||||
Self::Add => { state.scene_add(None, None)?; None }
|
||||
Self::Del(index) => { state.scene_del(index); None },
|
||||
command!(|self: SceneCommand, app: Tek|match self {
|
||||
Self::Add => { app.scene_add(None, None)?; None }
|
||||
Self::Del(index) => { app.scene_del(index); None },
|
||||
Self::SetColor(index, color) => {
|
||||
let old = state.scenes[index].color;
|
||||
state.scenes[index].color = color;
|
||||
let old = app.scenes[index].color;
|
||||
app.scenes[index].color = color;
|
||||
Some(Self::SetColor(index, old))
|
||||
},
|
||||
Self::Enqueue(scene) => {
|
||||
for track in 0..state.tracks.len() {
|
||||
state.tracks[track].player.enqueue_next(state.scenes[scene].clips[track].as_ref());
|
||||
for track in 0..app.tracks.len() {
|
||||
app.tracks[track].player.enqueue_next(app.scenes[scene].clips[track].as_ref());
|
||||
}
|
||||
None
|
||||
},
|
||||
_ => None
|
||||
});
|
||||
impl HasScenes for App {
|
||||
impl HasScenes for Tek {
|
||||
fn scenes (&self) -> &Vec<Scene> { &self.scenes }
|
||||
fn scenes_mut (&mut self) -> &mut Vec<Scene> { &mut self.scenes }
|
||||
}
|
||||
|
|
@ -879,7 +1002,7 @@ pub trait HasScenes: HasSelection + HasEditor + Send + Sync {
|
|||
SetLoop(usize, usize, bool),
|
||||
SetColor(usize, usize, ItemPalette),
|
||||
}
|
||||
edn_command!(ClipCommand: |state: App| {
|
||||
edn_command!(ClipCommand: |app: Tek| {
|
||||
("get" [a: usize ,b: usize]
|
||||
Self::Get(a.unwrap(), b.unwrap()))
|
||||
("put" [a: usize, b: usize, c: Option<Arc<RwLock<MidiClip>>>]
|
||||
|
|
@ -893,20 +1016,20 @@ edn_command!(ClipCommand: |state: App| {
|
|||
("color" [a: usize, b: usize]
|
||||
Self::SetColor(a.unwrap(), b.unwrap(), ItemPalette::random()))
|
||||
});
|
||||
command!(|self: ClipCommand, state: App|match self {
|
||||
command!(|self: ClipCommand, app: Tek|match self {
|
||||
Self::Get(track, scene) => { todo!() },
|
||||
Self::Put(track, scene, clip) => {
|
||||
let old = state.scenes[scene].clips[track].clone();
|
||||
state.scenes[scene].clips[track] = clip;
|
||||
let old = app.scenes[scene].clips[track].clone();
|
||||
app.scenes[scene].clips[track] = clip;
|
||||
Some(Self::Put(track, scene, old))
|
||||
},
|
||||
Self::Enqueue(track, scene) => {
|
||||
state.tracks[track].player.enqueue_next(state.scenes[scene].clips[track].as_ref());
|
||||
app.tracks[track].player.enqueue_next(app.scenes[scene].clips[track].as_ref());
|
||||
None
|
||||
},
|
||||
_ => None
|
||||
});
|
||||
audio!(|self: App, client, scope|{
|
||||
audio!(|self: Tek, client, scope|{
|
||||
// Start profiling cycle
|
||||
let t0 = self.perf.get_t0();
|
||||
// Update transport clock
|
||||
|
|
@ -1071,5 +1194,6 @@ impl OutputStats {
|
|||
}
|
||||
}
|
||||
#[cfg(test)] fn test_tek () {
|
||||
// TODO
|
||||
use clap::CommandFactory;
|
||||
TekCli::command().debug_assert();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue