mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-02-01 16:46:41 +01:00
wip: multi-crate refactor
This commit is contained in:
parent
911c47fc7c
commit
e08a79b507
25 changed files with 311 additions and 265 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,137 +0,0 @@
|
|||
#![allow(unused)]
|
||||
#![allow(clippy::unit_arg)]
|
||||
include!("../lib.rs");
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
#![allow(unused)]
|
||||
#![allow(clippy::unit_arg)]
|
||||
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)?;
|
||||
Ok(sampler)
|
||||
})?)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
#![allow(unused)]
|
||||
#![allow(clippy::unit_arg)]
|
||||
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();
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
#![allow(unused)]
|
||||
#![allow(clippy::unit_arg)]
|
||||
include!("../lib.rs");
|
||||
/// Application entrypoint.
|
||||
pub fn main () -> Usually<()> {
|
||||
Tui::run(JackClient::new("tek_transport")?.activate_with(|jack|{
|
||||
TransportTui::try_from(jack)
|
||||
})?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
#![allow(unused)]
|
||||
|
||||
use crate::*;
|
||||
pub use clojure_reader::edn::Edn;
|
||||
//pub use clojure_reader::{edn::{read, Edn}, error::Error as EdnError};
|
||||
|
||||
pub trait FromEdn<C>: Sized {
|
||||
const ID: &'static str;
|
||||
fn from_edn (context: C, expr: &[Edn<'_>]) -> Usually<Self>;
|
||||
}
|
||||
|
||||
/// Implements the [FromEdn] trait.
|
||||
#[macro_export] macro_rules! from_edn {
|
||||
(|$context:pat = $Context:ty, $id:expr, $args:ident| -> $T:ty $body:block) => {
|
||||
impl FromEdn<$Context> for $T {
|
||||
const ID: &'static str = $id;
|
||||
fn from_edn <'e> ($context: $Context, $args: &[Edn<'e>]) -> Usually<Self> {
|
||||
$body
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// EDN parsing helper.
|
||||
#[macro_export] macro_rules! edn {
|
||||
($edn:ident { $($pat:pat => $expr:expr),* $(,)? }) => {
|
||||
match $edn { $($pat => $expr),* }
|
||||
};
|
||||
($edn:ident in $args:ident { $($pat:pat => $expr:expr),* $(,)? }) => {
|
||||
for $edn in $args {
|
||||
edn!($edn { $($pat => $expr),* })
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
from_edn!(|jack = &Arc<RwLock<JackClient>>, "sampler", args| -> crate::Sampler {
|
||||
let mut name = String::new();
|
||||
let mut dir = String::new();
|
||||
let mut samples = BTreeMap::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(n)) = map.get(&Edn::Key(":dir")) {
|
||||
dir = String::from(*n);
|
||||
}
|
||||
},
|
||||
Edn::List(args) => match args.first() {
|
||||
Some(Edn::Symbol("sample")) => {
|
||||
let (midi, sample) = MidiSample::from_edn((jack, &dir), &args[1..])?;
|
||||
if let Some(midi) = midi {
|
||||
samples.insert(midi, sample);
|
||||
} else {
|
||||
panic!("sample without midi binding: {}", sample.read().unwrap().name);
|
||||
}
|
||||
},
|
||||
_ => panic!("unexpected in sampler {name}: {args:?}")
|
||||
},
|
||||
_ => panic!("unexpected in sampler {name}: {edn:?}")
|
||||
});
|
||||
let midi_in = jack.read().unwrap().client().register_port("in", MidiIn::default())?;
|
||||
Ok(Self {
|
||||
jack: jack.clone(),
|
||||
mapped: samples,
|
||||
unmapped: Default::default(),
|
||||
voices: Default::default(),
|
||||
buffer: Default::default(),
|
||||
audio_outs: vec![],
|
||||
output_gain: 0.,
|
||||
midi_in,
|
||||
name,
|
||||
})
|
||||
});
|
||||
|
||||
type MidiSample = (Option<u7>, Arc<RwLock<crate::Sample>>);
|
||||
|
||||
from_edn!(|(jack, dir) = (&Arc<RwLock<JackClient>>, &str), "sample", args| -> MidiSample {
|
||||
let mut name = String::new();
|
||||
let mut file = String::new();
|
||||
let mut midi = None;
|
||||
let mut start = 0usize;
|
||||
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(f)) = map.get(&Edn::Key(":file")) {
|
||||
file = String::from(*f);
|
||||
}
|
||||
if let Some(Edn::Int(i)) = map.get(&Edn::Key(":start")) {
|
||||
start = *i as usize;
|
||||
}
|
||||
if let Some(Edn::Int(m)) = map.get(&Edn::Key(":midi")) {
|
||||
midi = Some(u7::from(*m as u8));
|
||||
}
|
||||
},
|
||||
_ => panic!("unexpected in sample {name}"),
|
||||
});
|
||||
let (end, data) = Sample::read_data(&format!("{dir}/{file}"))?;
|
||||
Ok((midi, Arc::new(RwLock::new(crate::Sample {
|
||||
name,
|
||||
start,
|
||||
end,
|
||||
channels: data,
|
||||
rate: None
|
||||
}))))
|
||||
});
|
||||
|
||||
//impl ArrangerScene {
|
||||
|
||||
////TODO
|
||||
////pub fn from_edn <'a, 'e> (args: &[Edn<'e>]) -> Usually<Self> {
|
||||
////let mut name = None;
|
||||
////let mut clips = vec![];
|
||||
////edn!(edn in args {
|
||||
////Edn::Map(map) => {
|
||||
////let key = map.get(&Edn::Key(":name"));
|
||||
////if let Some(Edn::Str(n)) = key {
|
||||
////name = Some(*n);
|
||||
////} else {
|
||||
////panic!("unexpected key in scene '{name:?}': {key:?}")
|
||||
////}
|
||||
////},
|
||||
////Edn::Symbol("_") => {
|
||||
////clips.push(None);
|
||||
////},
|
||||
////Edn::Int(i) => {
|
||||
////clips.push(Some(*i as usize));
|
||||
////},
|
||||
////_ => panic!("unexpected in scene '{name:?}': {edn:?}")
|
||||
////});
|
||||
////Ok(ArrangerScene {
|
||||
////name: Arc::new(name.unwrap_or("").to_string().into()),
|
||||
////color: ItemColor::random(),
|
||||
////clips,
|
||||
////})
|
||||
////}
|
||||
//}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ use symphonia::{
|
|||
},
|
||||
default::get_codecs,
|
||||
};
|
||||
|
||||
pub struct SamplerTui {
|
||||
pub state: Sampler,
|
||||
pub cursor: (usize, usize),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue