wip: multi-crate refactor

This commit is contained in:
🪞👃🪞 2024-12-27 14:46:35 +01:00
parent 911c47fc7c
commit e08a79b507
25 changed files with 311 additions and 265 deletions

40
crates/cli/Cargo.toml Normal file
View 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"

View file

@ -1,6 +1,5 @@
#![allow(unused)]
#![allow(clippy::unit_arg)]
include!("../lib.rs");
include!("./lib.rs");
use tek::tui::ArrangerTui;
pub fn main () -> Usually<()> {
ArrangerCli::parse().run()

View 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(())
}
}

View file

@ -1,21 +1,15 @@
#![allow(unused)]
#![allow(clippy::unit_arg)]
include!("../lib.rs");
pub fn main () -> Usually<()> {
SamplerCli::parse().run()
}
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 = SamplerTui::try_from(x)?;
let sampler = tek::tui::SamplerTui::try_from(x)?;
Ok(sampler)
})?)?;
Ok(())

View file

@ -1,6 +1,5 @@
#![allow(unused)]
#![allow(clippy::unit_arg)]
include!("../lib.rs");
include!("./lib.rs");
pub fn main () -> Usually<()> {
SequencerCli::parse().run()
}

View file

@ -1,6 +1,5 @@
#![allow(unused)]
#![allow(clippy::unit_arg)]
include!("../lib.rs");
include!("./lib.rs");
/// Application entrypoint.
pub fn main () -> Usually<()> {
Tui::run(JackClient::new("tek_transport")?.activate_with(|jack|{

3
crates/cli/src/lib.rs Normal file
View file

@ -0,0 +1,3 @@
use std::sync::Arc;
use clap::{self, Parser};
use tek::{*, jack::*};

View file

@ -1,4 +1,4 @@
use crate::*;
include!("./lib.rs");
pub fn main () -> Usually<()> {
MixerCli::parse().run()

View file

@ -1,4 +1,4 @@
use crate::*;
include!("./lib.rs");
pub fn main () -> Usually<()> {
PluginCli::parse().run()

View file

@ -1,4 +1,4 @@
use crate::*;
include!("./lib.rs");
pub fn main () -> Usually<()> {
SamplerCli::parse().run()

8
crates/edn/Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "tek_edn"
edition = "2021"
version = "0.2.0"
[dependencies]
tek = { path = "../tek" }
clojure-reader = "0.1.0"

View file

@ -1,6 +1,7 @@
#![allow(unused)]
use tek::{*, jack::*};
use std::sync::{Arc, RwLock};
use std::collections::BTreeMap;
use crate::*;
pub use clojure_reader::edn::Edn;
//pub use clojure_reader::{edn::{read, Edn}, error::Error as EdnError};
@ -107,6 +108,81 @@ from_edn!(|(jack, dir) = (&Arc<RwLock<JackClient>>, &str), "sample", args| -> Mi
}))))
});
impl LV2Plugin {
pub fn from_edn <'e> (jack: &Arc<RwLock<JackClient>>, args: &[Edn<'e>]) -> Usually<Plugin> {
let mut name = String::new();
let mut path = String::new();
edn!(edn in args {
Edn::Map(map) => {
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
name = String::from(*n);
}
if let Some(Edn::Str(p)) = map.get(&Edn::Key(":path")) {
path = String::from(*p);
}
},
_ => panic!("unexpected in lv2 '{name}'"),
});
Plugin::new_lv2(jack, &name, &path)
}
}
impl MixerTrack {
const SYM_NAME: &'static str = ":name";
const SYM_GAIN: &'static str = ":gain";
const SYM_SAMPLER: &'static str = "sampler";
const SYM_LV2: &'static str = "lv2";
pub fn from_edn <'a, 'e> (jack: &Arc<RwLock<JackClient>>, args: &[Edn<'e>]) -> Usually<Self> {
let mut _gain = 0.0f64;
let mut track = MixerTrack {
name: String::new(),
audio_ins: vec![],
audio_outs: vec![],
devices: vec![],
};
edn!(edn in args {
Edn::Map(map) => {
if let Some(Edn::Str(n)) = map.get(&Edn::Key(Self::SYM_NAME)) {
track.name = n.to_string();
}
if let Some(Edn::Double(g)) = map.get(&Edn::Key(Self::SYM_GAIN)) {
_gain = f64::from(*g);
}
},
Edn::List(args) => match args.get(0) {
// Add a sampler device to the track
Some(Edn::Symbol(Self::SYM_SAMPLER)) => {
track.devices.push(
Box::new(Sampler::from_edn(jack, &args[1..])?) as Box<dyn MixerTrackDevice>
);
panic!(
"unsupported in track {}: {:?}; tek_mixer not compiled with feature \"sampler\"",
&track.name,
args.get(0).unwrap()
)
},
// Add a LV2 plugin to the track.
Some(Edn::Symbol(Self::SYM_LV2)) => {
track.devices.push(
Box::new(LV2Plugin::from_edn(jack, &args[1..])?) as Box<dyn MixerTrackDevice>
);
panic!(
"unsupported in track {}: {:?}; tek_mixer not compiled with feature \"plugin\"",
&track.name,
args.get(0).unwrap()
)
},
None =>
panic!("empty list track {}", &track.name),
_ =>
panic!("unexpected in track {}: {:?}", &track.name, args.get(0).unwrap())
},
_ => {}
});
Ok(track)
}
}
//impl ArrangerScene {
////TODO

View file

@ -1,11 +1,12 @@
[package]
name = "suil-rs"
name = "tek_suil"
version = "0.1.0"
edition = "2021"
[dependencies]
gtk = "0.18.1"
livi = "0.7.4"
#winit = { version = "0.30.4", features = [ "x11" ] }
[build-dependencies]
bindgen = "0.69.4"

View file

@ -9,7 +9,6 @@ version = "0.2.0"
atomic_float = "1.0.0"
backtrace = "0.3.72"
better-panic = "0.3.0"
clap = { version = "4.5.4", features = [ "derive" ] }
clojure-reader = "0.1.0"
crossterm = "0.27"
jack = "0.13"
@ -29,37 +28,4 @@ wavers = "1.4.3"
#winit = { version = "0.30.4", features = [ "x11" ] }
[lib]
name = "tek_lib"
path = "src/lib.rs"
[[bin]]
name = "tek_arranger"
path = "src/cli/cli_arranger.rs"
[[bin]]
name = "tek_sequencer"
path = "src/cli/cli_sequencer.rs"
[[bin]]
name = "tek_groovebox"
path = "src/cli/cli_groovebox.rs"
[[bin]]
name = "tek_transport"
path = "src/cli/cli_transport.rs"
[[bin]]
name = "tek_sampler"
path = "src/cli/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"

View file

@ -1,3 +1,9 @@
pub(crate) mod audio_in;
pub(crate) mod audio_out;
pub(crate) mod sampler; pub(crate) use sampler::*;
use crate::*;
mod audio_in;
mod audio_out;
mod sampler;
pub(crate) use sampler::*;
pub use self::sampler::{Sampler, Sample, Voice};

View file

@ -16,62 +16,6 @@ pub struct MixerTrack {
//impl MixerTrackDevice for LV2Plugin {}
impl MixerTrack {
const SYM_NAME: &'static str = ":name";
const SYM_GAIN: &'static str = ":gain";
const SYM_SAMPLER: &'static str = "sampler";
const SYM_LV2: &'static str = "lv2";
pub fn from_edn <'a, 'e> (jack: &Arc<RwLock<JackClient>>, args: &[Edn<'e>]) -> Usually<Self> {
let mut _gain = 0.0f64;
let mut track = MixerTrack {
name: String::new(),
audio_ins: vec![],
audio_outs: vec![],
devices: vec![],
};
edn!(edn in args {
Edn::Map(map) => {
if let Some(Edn::Str(n)) = map.get(&Edn::Key(Self::SYM_NAME)) {
track.name = n.to_string();
}
if let Some(Edn::Double(g)) = map.get(&Edn::Key(Self::SYM_GAIN)) {
_gain = f64::from(*g);
}
},
Edn::List(args) => match args.get(0) {
// Add a sampler device to the track
Some(Edn::Symbol(Self::SYM_SAMPLER)) => {
track.devices.push(
Box::new(Sampler::from_edn(jack, &args[1..])?) as Box<dyn MixerTrackDevice>
);
panic!(
"unsupported in track {}: {:?}; tek_mixer not compiled with feature \"sampler\"",
&track.name,
args.get(0).unwrap()
)
},
// Add a LV2 plugin to the track.
Some(Edn::Symbol(Self::SYM_LV2)) => {
track.devices.push(
Box::new(LV2Plugin::from_edn(jack, &args[1..])?) as Box<dyn MixerTrackDevice>
);
panic!(
"unsupported in track {}: {:?}; tek_mixer not compiled with feature \"plugin\"",
&track.name,
args.get(0).unwrap()
)
},
None =>
panic!("empty list track {}", &track.name),
_ =>
panic!("unexpected in track {}: {:?}", &track.name, args.get(0).unwrap())
},
_ => {}
});
Ok(track)
}
}
pub trait MixerTrackDevice: Debug + Send + Sync {
fn boxed (self) -> Box<dyn MixerTrackDevice> where Self: Sized + 'static {
Box::new(self)

View file

@ -1,27 +0,0 @@
#![allow(unused)]
#![allow(clippy::unit_arg)]
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 = GrooveboxTui::try_from(jack)?;
let midi_out = jack.read().unwrap().register_port("out", MidiOut::default())?;
let midi_in_1 = jack.read().unwrap().register_port("in1", MidiIn::default())?;
let midi_in_2 = jack.read().unwrap().register_port("in2", MidiIn::default())?;
let audio_in_1 = jack.read().unwrap().register_port("inL", AudioIn::default())?;
let audio_in_2 = jack.read().unwrap().register_port("inR", AudioIn::default())?;
let audio_out_1 = jack.read().unwrap().register_port("out1", AudioOut::default())?;
let audio_out_2 = jack.read().unwrap().register_port("out2", AudioOut::default())?;
Ok(app)
})?)?;
Ok(())
}
}

View file

@ -1,10 +1,7 @@
use crate::*;
pub use ::jack as libjack;
pub(crate) mod activate; #[allow(unused)] pub(crate) use self::activate::*;
pub(crate) mod audio; pub(crate) use self::audio::*;
pub(crate) mod client; pub(crate) use self::client::*;
pub(crate) mod jack_event; pub(crate) use self::jack_event::*;
pub(crate) mod ports; pub(crate) use self::ports::*;
pub(crate) use ::jack::{
pub use ::jack::{
contrib::ClosureProcessHandler, NotificationHandler,
Client, AsyncClient, ClientOptions, ClientStatus,
ProcessScope, Control, CycleTimes, Frames,
@ -12,6 +9,20 @@ pub(crate) use ::jack::{
Transport, TransportState, MidiIter, MidiWriter, RawMidi,
};
pub mod activate;
pub(crate) use self::activate::*;
pub use self::activate::JackActivate;
pub mod client;
pub(crate) use self::client::*;
pub use self::client::JackClient;
pub mod jack_event;
pub(crate) use self::jack_event::*;
pub mod ports;
pub(crate) use self::ports::*;
/// Implement [TryFrom<&Arc<RwLock<JackClient>>>]: create app state from wrapped JACK handle.
#[macro_export] macro_rules! from_jack {
(|$jack:ident|$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)? $cb:expr) => {
@ -24,6 +35,83 @@ pub(crate) use ::jack::{
};
}
/// Implement [Audio]: provide JACK callbacks.
#[macro_export] macro_rules! audio {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? {
#[inline] fn process (&mut $self, $c: &Client, $s: &ProcessScope) -> Control { $cb }
}
}
}
/// Trait for thing that has a JACK process callback.
pub trait Audio: Send + Sync {
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
Control::Continue
}
fn callback (
state: &Arc<RwLock<Self>>, client: &Client, scope: &ProcessScope
) -> Control where Self: Sized {
if let Ok(mut state) = state.write() {
state.process(client, scope)
} else {
Control::Quit
}
}
}
/// Trait for things that wrap a JACK client.
pub trait AudioEngine {
fn transport (&self) -> Transport {
self.client().transport()
}
fn port_by_name (&self, name: &str) -> Option<Port<Unowned>> {
self.client().port_by_name(name)
}
fn register_port <PS: PortSpec> (&self, name: &str, spec: PS) -> Usually<Port<PS>> {
Ok(self.client().register_port(name, spec)?)
}
fn client (&self) -> &Client;
fn activate (
self,
process: impl FnMut(&Arc<RwLock<Self>>, &Client, &ProcessScope) -> Control + Send + 'static
) -> Usually<Arc<RwLock<Self>>> where Self: Send + Sync + 'static;
fn thread_init (&self, _: &Client) {}
unsafe fn shutdown (&mut self, _status: ClientStatus, _reason: &str) {}
fn freewheel (&mut self, _: &Client, _enabled: bool) {}
fn client_registration (&mut self, _: &Client, _name: &str, _reg: bool) {}
fn port_registration (&mut self, _: &Client, _id: PortId, _reg: bool) {}
fn ports_connected (&mut self, _: &Client, _a: PortId, _b: PortId, _are: bool) {}
fn sample_rate (&mut self, _: &Client, _frames: Frames) -> Control {
Control::Continue
}
fn port_rename (&mut self, _: &Client, _id: PortId, _old: &str, _new: &str) -> Control {
Control::Continue
}
fn graph_reorder (&mut self, _: &Client) -> Control {
Control::Continue
}
fn xrun (&mut self, _: &Client) -> Control {
Control::Continue
}
}
////////////////////////////////////////////////////////////////////////////////////
///// A [AudioComponent] bound to a JACK client and a set of ports.

View file

@ -1,78 +0,0 @@
use crate::*;
/// Implement [Audio]: provide JACK callbacks.
#[macro_export] macro_rules! audio {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? {
#[inline] fn process (&mut $self, $c: &Client, $s: &ProcessScope) -> Control { $cb }
}
}
}
/// Trait for thing that has a JACK process callback.
pub trait Audio: Send + Sync {
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
Control::Continue
}
fn callback (
state: &Arc<RwLock<Self>>, client: &Client, scope: &ProcessScope
) -> Control where Self: Sized {
if let Ok(mut state) = state.write() {
state.process(client, scope)
} else {
Control::Quit
}
}
}
/// Trait for things that wrap a JACK client.
pub trait AudioEngine {
fn transport (&self) -> Transport {
self.client().transport()
}
fn port_by_name (&self, name: &str) -> Option<Port<Unowned>> {
self.client().port_by_name(name)
}
fn register_port <PS: PortSpec> (&self, name: &str, spec: PS) -> Usually<Port<PS>> {
Ok(self.client().register_port(name, spec)?)
}
fn client (&self) -> &Client;
fn activate (
self,
process: impl FnMut(&Arc<RwLock<Self>>, &Client, &ProcessScope) -> Control + Send + 'static
) -> Usually<Arc<RwLock<Self>>> where Self: Send + Sync + 'static;
fn thread_init (&self, _: &Client) {}
unsafe fn shutdown (&mut self, _status: ClientStatus, _reason: &str) {}
fn freewheel (&mut self, _: &Client, _enabled: bool) {}
fn client_registration (&mut self, _: &Client, _name: &str, _reg: bool) {}
fn port_registration (&mut self, _: &Client, _id: PortId, _reg: bool) {}
fn ports_connected (&mut self, _: &Client, _a: PortId, _b: PortId, _are: bool) {}
fn sample_rate (&mut self, _: &Client, _frames: Frames) -> Control {
Control::Continue
}
fn port_rename (&mut self, _: &Client, _id: PortId, _old: &str, _new: &str) -> Control {
Control::Continue
}
fn graph_reorder (&mut self, _: &Client) -> Control {
Control::Continue
}
fn xrun (&mut self, _: &Client) -> Control {
Control::Continue
}
}

View file

@ -1,16 +1,22 @@
const FOO: () = ();
#![allow(unused)]
#![allow(clippy::unit_arg)]
pub mod core; pub use self::core::*;
pub mod time; pub(crate) use self::time::*;
pub mod space; pub(crate) use self::space::*;
pub mod tui; pub(crate) use self::tui::*;
pub mod edn;
pub mod jack; pub(crate) use self::jack::*;
pub mod midi; pub(crate) use self::midi::*;
pub mod audio; pub(crate) use self::audio::*;
//pub mod plugin; pub(crate) use self::plugin::*;
pub(crate) use clap::{self, Parser};
pub mod tui; pub(crate) use self::tui::*;
pub use tui::{Tui, TransportTui, SequencerTui, SamplerTui, GrooveboxTui, ArrangerTui};
pub mod jack; pub(crate) use self::jack::*;
pub use jack::JackClient;
pub mod midi; pub(crate) use self::midi::*;
pub mod audio; pub(crate) use self::audio::*;
pub use audio::{Sampler, Sample, Voice};
//pub mod plugin; pub(crate) use self::plugin::*;
pub use ::better_panic;
pub(crate) use better_panic::{Settings, Verbosity};
@ -44,13 +50,12 @@ pub(crate) use ratatui::{
backend::{Backend, CrosstermBackend, ClearType}
};
pub use ::midly;
pub use ::midly::{self, num::u7};
pub(crate) use ::midly::{
Smf,
MidiMessage,
TrackEventKind,
live::LiveEvent,
num::u7
};
pub use ::palette;

View file

@ -40,22 +40,3 @@ impl LV2Plugin {
})
}
}
impl LV2Plugin {
pub fn from_edn <'e> (jack: &Arc<RwLock<JackClient>>, args: &[Edn<'e>]) -> Usually<Plugin> {
let mut name = String::new();
let mut path = String::new();
edn!(edn in args {
Edn::Map(map) => {
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
name = String::from(*n);
}
if let Some(Edn::Str(p)) = map.get(&Edn::Key(":path")) {
path = String::from(*p);
}
},
_ => panic!("unexpected in lv2 '{name}'"),
});
Plugin::new_lv2(jack, &name, &path)
}
}

View file

@ -15,10 +15,19 @@ mod tui_border; pub(crate) use self::tui_border::*;
////////////////////////////////////////////////////////
mod app_transport; #[allow(unused)] pub(crate) use self::app_transport::*;
pub use self::app_transport::TransportTui;
mod app_sequencer; #[allow(unused)] pub(crate) use self::app_sequencer::*;
pub use self::app_sequencer::SequencerTui;
mod app_sampler; #[allow(unused)] pub(crate) use self::app_sampler::*;
pub use self::app_sampler::SamplerTui;
mod app_groovebox; #[allow(unused)] pub(crate) use self::app_groovebox::*;
pub use self::app_groovebox::GrooveboxTui;
mod app_arranger; #[allow(unused)] pub(crate) use self::app_arranger::*;
pub use self::app_arranger::ArrangerTui;
///////////////////////////////////////////////////////

View file

@ -13,7 +13,6 @@ use symphonia::{
},
default::get_codecs,
};
pub struct SamplerTui {
pub state: Sampler,
pub cursor: (usize, usize),