mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-08 12:46:42 +01:00
wip: multi-crate refactor
This commit is contained in:
parent
911c47fc7c
commit
e08a79b507
25 changed files with 311 additions and 265 deletions
40
crates/cli/Cargo.toml
Normal file
40
crates/cli/Cargo.toml
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[package]
|
||||
name = "tek_cli"
|
||||
edition = "2021"
|
||||
version = "0.2.0"
|
||||
|
||||
[dependencies]
|
||||
tek = { path = "../tek" }
|
||||
clap = { version = "4.5.4", features = [ "derive" ] }
|
||||
|
||||
[[bin]]
|
||||
name = "tek_arranger"
|
||||
path = "src/cli_arranger.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "tek_sequencer"
|
||||
path = "src/cli_sequencer.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "tek_groovebox"
|
||||
path = "src/cli_groovebox.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "tek_transport"
|
||||
path = "src/cli_transport.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "tek_sampler"
|
||||
path = "src/cli_sampler.rs"
|
||||
|
||||
#[[bin]]
|
||||
#name = "tek_mixer"
|
||||
#path = "src/cli_mixer.rs"
|
||||
|
||||
#[[bin]]
|
||||
#name = "tek_track"
|
||||
#path = "src/cli_track.rs"
|
||||
|
||||
#[[bin]]
|
||||
#name = "tek_plugin"
|
||||
#path = "src/cli_plugin.rs"
|
||||
136
crates/cli/src/cli_arranger.rs
Normal file
136
crates/cli/src/cli_arranger.rs
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
include!("./lib.rs");
|
||||
use tek::tui::ArrangerTui;
|
||||
|
||||
pub fn main () -> Usually<()> {
|
||||
ArrangerCli::parse().run()
|
||||
}
|
||||
|
||||
/// Launches an interactive MIDI arranger.
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct ArrangerCli {
|
||||
/// Name of JACK client
|
||||
#[arg(short, long)]
|
||||
name: Option<String>,
|
||||
|
||||
/// Whether to include a transport toolbar (default: true)
|
||||
#[arg(short, long, default_value_t = true)]
|
||||
transport: bool,
|
||||
|
||||
/// Number of tracks
|
||||
#[arg(short = 'x', long, default_value_t = 4)]
|
||||
tracks: usize,
|
||||
|
||||
/// Number of scenes
|
||||
#[arg(short, long, default_value_t = 8)]
|
||||
scenes: usize,
|
||||
|
||||
/// MIDI outs to connect each track to.
|
||||
#[arg(short='i', long)]
|
||||
midi_from: Vec<String>,
|
||||
|
||||
/// MIDI ins to connect each track to.
|
||||
#[arg(short='o', long)]
|
||||
midi_to: Vec<String>,
|
||||
}
|
||||
|
||||
impl ArrangerCli {
|
||||
/// Run the arranger TUI from CLI arguments.
|
||||
fn run (&self) -> Usually<()> {
|
||||
let mut client_name = String::from("tek_arranger");
|
||||
if let Some(name) = self.name.as_ref() {
|
||||
client_name = name.clone();
|
||||
}
|
||||
Tui::run(JackClient::new(client_name.as_str())?.activate_with(|jack|{
|
||||
let mut app = ArrangerTui::try_from(jack)?;
|
||||
let jack = jack.read().unwrap();
|
||||
app.color = ItemPalette::random();
|
||||
add_tracks(&jack, &mut app, self)?;
|
||||
add_scenes(&mut app, self.scenes)?;
|
||||
Ok(app)
|
||||
})?)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn add_tracks (jack: &JackClient, app: &mut ArrangerTui, cli: &ArrangerCli) -> Usually<()> {
|
||||
let n = cli.tracks;
|
||||
let track_color_1 = ItemColor::random();
|
||||
let track_color_2 = ItemColor::random();
|
||||
for i in 0..n {
|
||||
let track = app.track_add(None, Some(
|
||||
track_color_1.mix(track_color_2, i as f32 / n as f32).into()
|
||||
))?;
|
||||
track.width = 8;
|
||||
let name = track.name.read().unwrap();
|
||||
track.player.midi_ins.push(
|
||||
jack.register_port(&format!("{}I", &name), MidiIn::default())?
|
||||
);
|
||||
track.player.midi_outs.push(
|
||||
jack.register_port(&format!("{}O", &name), MidiOut::default())?
|
||||
);
|
||||
}
|
||||
for connection in cli.midi_from.iter() {
|
||||
let mut split = connection.split("=");
|
||||
let number = split.next().unwrap().trim();
|
||||
if let Ok(track) = number.parse::<usize>() {
|
||||
if track < 1 {
|
||||
panic!("Tracks are zero-indexed")
|
||||
}
|
||||
if track > n {
|
||||
panic!("Tried to connect track {track} or {n}. Pass -t {track} to increase number of tracks.")
|
||||
}
|
||||
if let Some(port) = split.next() {
|
||||
if let Some(port) = jack.port_by_name(port).as_ref() {
|
||||
jack.client().connect_ports(port, &app.tracks[track-1].player.midi_ins[0])?;
|
||||
} else {
|
||||
panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names.");
|
||||
}
|
||||
} else {
|
||||
panic!("No port specified for track {track}. Format is TRACK_NUMBER=PORT_NAME")
|
||||
}
|
||||
} else {
|
||||
panic!("Failed to parse track number: {number}")
|
||||
}
|
||||
}
|
||||
for connection in cli.midi_to.iter() {
|
||||
let mut split = connection.split("=");
|
||||
let number = split.next().unwrap().trim();
|
||||
if let Ok(track) = number.parse::<usize>() {
|
||||
if track < 1 {
|
||||
panic!("Tracks are zero-indexed")
|
||||
}
|
||||
if track > n {
|
||||
panic!("Tried to connect track {track} or {n}. Pass -t {track} to increase number of tracks.")
|
||||
}
|
||||
if let Some(port) = split.next() {
|
||||
if let Some(port) = jack.port_by_name(port).as_ref() {
|
||||
jack.client().connect_ports(&app.tracks[track-1].player.midi_outs[0], port)?;
|
||||
} else {
|
||||
panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names.");
|
||||
}
|
||||
} else {
|
||||
panic!("No port specified for track {track}. Format is TRACK_NUMBER=PORT_NAME")
|
||||
}
|
||||
} else {
|
||||
panic!("Failed to parse track number: {number}")
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_scenes (app: &mut ArrangerTui, n: usize) -> Usually<()> {
|
||||
let scene_color_1 = ItemColor::random();
|
||||
let scene_color_2 = ItemColor::random();
|
||||
for i in 0..n {
|
||||
let _scene = app.scene_add(None, Some(
|
||||
scene_color_1.mix(scene_color_2, i as f32 / n as f32).into()
|
||||
))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test] fn verify_arranger_cli () {
|
||||
use clap::CommandFactory;
|
||||
ArrangerCli::command().debug_assert();
|
||||
}
|
||||
22
crates/cli/src/cli_groovebox.rs
Normal file
22
crates/cli/src/cli_groovebox.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
include!("./lib.rs");
|
||||
pub fn main () -> Usually<()> { GrooveboxCli::parse().run() }
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct GrooveboxCli;
|
||||
impl GrooveboxCli {
|
||||
fn run (&self) -> Usually<()> {
|
||||
Tui::run(JackClient::new("tek_groovebox")?.activate_with(|jack|{
|
||||
let app = tek::tui::GrooveboxTui::try_from(jack)?;
|
||||
let jack = jack.read().unwrap();
|
||||
let midi_out = jack.register_port("out", MidiOut::default())?;
|
||||
let midi_in_1 = jack.register_port("in1", MidiIn::default())?;
|
||||
let midi_in_2 = jack.register_port("in2", MidiIn::default())?;
|
||||
let audio_in_1 = jack.register_port("inL", AudioIn::default())?;
|
||||
let audio_in_2 = jack.register_port("inR", AudioIn::default())?;
|
||||
let audio_out_1 = jack.register_port("out1", AudioOut::default())?;
|
||||
let audio_out_2 = jack.register_port("out2", AudioOut::default())?;
|
||||
Ok(app)
|
||||
})?)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
17
crates/cli/src/cli_sampler.rs
Normal file
17
crates/cli/src/cli_sampler.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
include!("./lib.rs");
|
||||
pub fn main () -> Usually<()> { SamplerCli::parse().run() }
|
||||
#[derive(Debug, Parser)] #[command(version, about, long_about = None)] pub struct SamplerCli {
|
||||
/// Name of JACK client
|
||||
#[arg(short, long)] name: Option<String>,
|
||||
/// Path to plugin
|
||||
#[arg(short, long)] path: Option<String>,
|
||||
}
|
||||
impl SamplerCli {
|
||||
fn run (&self) -> Usually<()> {
|
||||
Tui::run(JackClient::new("tek_sampler")?.activate_with(|x|{
|
||||
let sampler = tek::tui::SamplerTui::try_from(x)?;
|
||||
Ok(sampler)
|
||||
})?)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
71
crates/cli/src/cli_sequencer.rs
Normal file
71
crates/cli/src/cli_sequencer.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
include!("./lib.rs");
|
||||
|
||||
pub fn main () -> Usually<()> {
|
||||
SequencerCli::parse().run()
|
||||
}
|
||||
|
||||
/// Launches a single interactive MIDI sequencer.
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct SequencerCli {
|
||||
/// Name of JACK client
|
||||
#[arg(short, long)]
|
||||
name: Option<String>,
|
||||
|
||||
/// Whether to include a transport toolbar (default: true)
|
||||
#[arg(short, long, default_value_t = true)]
|
||||
transport: bool,
|
||||
|
||||
/// MIDI outs to connect to (multiple instances accepted)
|
||||
#[arg(short='i', long)]
|
||||
midi_from: Vec<String>,
|
||||
|
||||
/// MIDI ins to connect to (multiple instances accepted)
|
||||
#[arg(short='o', long)]
|
||||
midi_to: Vec<String>,
|
||||
}
|
||||
|
||||
impl SequencerCli {
|
||||
fn run (&self) -> Usually<()> {
|
||||
let name = self.name.as_deref().unwrap_or("tek_sequencer");
|
||||
Tui::run(JackClient::new(name)?.activate_with(|jack|{
|
||||
let mut app = SequencerTui::try_from(jack)?;
|
||||
let jack = jack.read().unwrap();
|
||||
let midi_in = jack.register_port("i", MidiIn::default())?;
|
||||
let midi_out = jack.register_port("o", MidiOut::default())?;
|
||||
connect_from(&jack, &midi_in, &self.midi_from)?;
|
||||
connect_to(&jack, &midi_out, &self.midi_to)?;
|
||||
app.player.midi_ins.push(midi_in);
|
||||
app.player.midi_outs.push(midi_out);
|
||||
Ok(app)
|
||||
})?)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn connect_from (jack: &JackClient, input: &Port<MidiIn>, ports: &[String]) -> Usually<()> {
|
||||
for port in ports.iter() {
|
||||
if let Some(port) = jack.port_by_name(port).as_ref() {
|
||||
jack.client().connect_ports(port, input)?;
|
||||
} else {
|
||||
panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names.");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn connect_to (jack: &JackClient, output: &Port<MidiOut>, ports: &[String]) -> Usually<()> {
|
||||
for port in ports.iter() {
|
||||
if let Some(port) = jack.port_by_name(port).as_ref() {
|
||||
jack.client().connect_ports(output, port)?;
|
||||
} else {
|
||||
panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names.");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test] fn verify_sequencer_cli () {
|
||||
use clap::CommandFactory;
|
||||
SequencerCli::command().debug_assert();
|
||||
}
|
||||
9
crates/cli/src/cli_transport.rs
Normal file
9
crates/cli/src/cli_transport.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
include!("./lib.rs");
|
||||
|
||||
/// Application entrypoint.
|
||||
pub fn main () -> Usually<()> {
|
||||
Tui::run(JackClient::new("tek_transport")?.activate_with(|jack|{
|
||||
TransportTui::try_from(jack)
|
||||
})?)?;
|
||||
Ok(())
|
||||
}
|
||||
3
crates/cli/src/lib.rs
Normal file
3
crates/cli/src/lib.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
use std::sync::Arc;
|
||||
use clap::{self, Parser};
|
||||
use tek::{*, jack::*};
|
||||
25
crates/cli/src/todo_cli_mixer.rs
Normal file
25
crates/cli/src/todo_cli_mixer.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
include!("./lib.rs");
|
||||
|
||||
pub fn main () -> Usually<()> {
|
||||
MixerCli::parse().run()
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)] #[command(version, about, long_about = None)] pub struct MixerCli {
|
||||
/// Name of JACK client
|
||||
#[arg(short, long)] name: Option<String>,
|
||||
/// Number of tracks
|
||||
#[arg(short, long)] channels: Option<usize>,
|
||||
}
|
||||
|
||||
impl MixerCli {
|
||||
fn run (&self) -> Usually<()> {
|
||||
Tui::run(JackClient::new("tek_mixer")?.activate_with(|jack|{
|
||||
let mut mixer = Mixer::new(jack, self.name.as_ref().map(|x|x.as_str()).unwrap_or("mixer"))?;
|
||||
for channel in 0..self.channels.unwrap_or(8) {
|
||||
mixer.track_add(&format!("Track {}", channel + 1), 1)?;
|
||||
}
|
||||
Ok(mixer)
|
||||
})?)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
26
crates/cli/src/todo_cli_plugin.rs
Normal file
26
crates/cli/src/todo_cli_plugin.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
include!("./lib.rs");
|
||||
|
||||
pub fn main () -> Usually<()> {
|
||||
PluginCli::parse().run()
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)] #[command(version, about, long_about = None)] pub struct PluginCli {
|
||||
/// Name of JACK client
|
||||
#[arg(short, long)] name: Option<String>,
|
||||
/// Path to plugin
|
||||
#[arg(short, long)] path: Option<String>,
|
||||
}
|
||||
|
||||
impl PluginCli {
|
||||
fn run (&self) -> Usually<()> {
|
||||
Tui::run(JackClient::new("tek_plugin")?.activate_with(|jack|{
|
||||
let mut plugin = Plugin::new_lv2(
|
||||
jack,
|
||||
self.name.as_ref().map(|x|x.as_str()).unwrap_or("mixer"),
|
||||
self.path.as_ref().expect("pass --path /to/lv2/plugin.so")
|
||||
)?;
|
||||
Ok(plugin)
|
||||
})?)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
26
crates/cli/src/todo_cli_sampler.rs
Normal file
26
crates/cli/src/todo_cli_sampler.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
include!("./lib.rs");
|
||||
|
||||
pub fn main () -> Usually<()> {
|
||||
SamplerCli::parse().run()
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)] #[command(version, about, long_about = None)] pub struct SamplerCli {
|
||||
/// Name of JACK client
|
||||
#[arg(short, long)] name: Option<String>,
|
||||
/// Path to plugin
|
||||
#[arg(short, long)] path: Option<String>,
|
||||
}
|
||||
|
||||
impl SamplerCli {
|
||||
fn run (&self) -> Usually<()> {
|
||||
Tui::run(JackClient::new("tek_sampler")?.activate_with(|jack|{
|
||||
let mut plugin = Sampler::new(
|
||||
jack,
|
||||
self.name.as_ref().map(|x|x.as_str()).unwrap_or("mixer"),
|
||||
None,
|
||||
)?;
|
||||
Ok(plugin)
|
||||
})?)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue