merge cli entrypoint into main module

This commit is contained in:
🪞👃🪞 2025-01-16 11:37:30 +01:00
parent 385297c59f
commit 8e2aed58af
4 changed files with 268 additions and 242 deletions

1
Cargo.lock generated
View file

@ -1414,6 +1414,7 @@ name = "tek"
version = "0.2.0"
dependencies = [
"backtrace",
"clap",
"palette",
"rand",
"tek_jack",

View file

@ -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;

View file

@ -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"]

View file

@ -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();
}