mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
wip: optional dependency from mixer to sampler and plugin
This commit is contained in:
parent
a819e042c7
commit
6dd4caeb42
18 changed files with 330 additions and 316 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
|
@ -2539,6 +2539,7 @@ dependencies = [
|
||||||
"clojure-reader",
|
"clojure-reader",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"midly",
|
"midly",
|
||||||
|
"once_cell",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"toml",
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
@ -2558,6 +2559,8 @@ dependencies = [
|
||||||
"tek_chain",
|
"tek_chain",
|
||||||
"tek_core",
|
"tek_core",
|
||||||
"tek_jack",
|
"tek_jack",
|
||||||
|
"tek_plugin",
|
||||||
|
"tek_sampler",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ tek_sampler = { path = "../tek_sampler" }
|
||||||
tek_sequencer = { path = "../tek_sequencer" }
|
tek_sequencer = { path = "../tek_sequencer" }
|
||||||
tek_timer = { path = "../tek_timer" }
|
tek_timer = { path = "../tek_timer" }
|
||||||
tek_chain = { path = "../tek_chain" }
|
tek_chain = { path = "../tek_chain" }
|
||||||
tek_mixer = { path = "../tek_mixer" }
|
tek_mixer = { path = "../tek_mixer", features = ["standalone_devices"] }
|
||||||
#jack = "0.10"
|
#jack = "0.10"
|
||||||
#crossterm = "0.27"
|
#crossterm = "0.27"
|
||||||
#ratatui = { version = "0.26.3", features = [ "unstable-widget-ref", "underline-color" ] }
|
#ratatui = { version = "0.26.3", features = [ "unstable-widget-ref", "underline-color" ] }
|
||||||
|
|
@ -32,7 +32,6 @@ tek_mixer = { path = "../tek_mixer" }
|
||||||
#fraction = "0.15.3"
|
#fraction = "0.15.3"
|
||||||
#rlsf = "0.2.1"
|
#rlsf = "0.2.1"
|
||||||
#r8brain-rs = "0.3.5"
|
#r8brain-rs = "0.3.5"
|
||||||
#once_cell = "1.19.0"
|
|
||||||
|
|
||||||
#symphonia = { version = "0.5.4", features = [ "all" ] }
|
#symphonia = { version = "0.5.4", features = [ "all" ] }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ pub const KEYMAP_GLOBAL: &'static [KeyBinding<App>] = keymap!(App {
|
||||||
/// Generic key bindings for views that support focus.
|
/// Generic key bindings for views that support focus.
|
||||||
pub const KEYMAP_FOCUS: &'static [KeyBinding<App>] = keymap!(App {
|
pub const KEYMAP_FOCUS: &'static [KeyBinding<App>] = keymap!(App {
|
||||||
[Char(';'), NONE, "command", "open command palette", |_: &mut App| {
|
[Char(';'), NONE, "command", "open command palette", |_: &mut App| {
|
||||||
*MODAL.lock().unwrap() = Some(Box::new(crate::devices::help::HelpModal::new()));
|
*MODAL.lock().unwrap() = Some(Box::new(HelpModal::new()));
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
[Tab, NONE, "focus_next", "focus next area", focus_next],
|
[Tab, NONE, "focus_next", "focus next area", focus_next],
|
||||||
|
|
|
||||||
|
|
@ -14,26 +14,15 @@
|
||||||
//! * [LV2Plugin::load_edn]
|
//! * [LV2Plugin::load_edn]
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use clojure_reader::{edn::{read, Edn}, error::Error as EdnError};
|
|
||||||
|
|
||||||
/// EDN parsing helper.
|
|
||||||
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),* })
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
|
|
||||||
pub fn from_edn (src: &str) -> Usually<Self> {
|
pub fn from_edn (src: &str) -> Usually<Self> {
|
||||||
let mut app = Self::new()?;
|
let mut app = Self::new()?;
|
||||||
app.load_edn(src)?;
|
app.load_edn(src)?;
|
||||||
Ok(app)
|
Ok(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_edn (&mut self, mut src: &str) -> Usually<&mut Self> {
|
pub fn load_edn (&mut self, mut src: &str) -> Usually<&mut Self> {
|
||||||
loop {
|
loop {
|
||||||
match read(src) {
|
match read(src) {
|
||||||
|
|
@ -55,6 +44,7 @@ impl App {
|
||||||
}
|
}
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_edn_one <'e> (&mut self, edn: Edn<'e>) -> Usually<()> {
|
fn load_edn_one <'e> (&mut self, edn: Edn<'e>) -> Usually<()> {
|
||||||
match edn {
|
match edn {
|
||||||
Edn::List(items) => {
|
Edn::List(items) => {
|
||||||
|
|
@ -69,10 +59,10 @@ impl App {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(Edn::Symbol("scene")) => {
|
Some(Edn::Symbol("scene")) => {
|
||||||
tek_sequencer::Scene::load_edn(self, &items[1..])?;
|
tek_sequencer::Scene::from_edn(self, &items[1..])?;
|
||||||
},
|
},
|
||||||
Some(Edn::Symbol("track")) => {
|
Some(Edn::Symbol("track")) => {
|
||||||
tek_mixer::Track::load_edn(self, &items[1..])?;
|
tek_mixer::MixerTrack::from_edn(self, &items[1..])?;
|
||||||
},
|
},
|
||||||
Some(Edn::Symbol("midi-in")) => {
|
Some(Edn::Symbol("midi-in")) => {
|
||||||
self.midi_ins = items[1..].iter().map(|x|match x {
|
self.midi_ins = items[1..].iter().map(|x|match x {
|
||||||
|
|
@ -105,62 +95,5 @@ impl App {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl Phrase {
|
|
||||||
fn load_edn <'e> (ppq: usize, args: &[Edn<'e>]) -> Usually<Self> {
|
|
||||||
let mut phrase = Self::default();
|
|
||||||
let mut name = String::new();
|
|
||||||
let mut beats = 0usize;
|
|
||||||
let mut steps = 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::Int(b)) = map.get(&Edn::Key(":beats")) {
|
|
||||||
beats = *b as usize;
|
|
||||||
phrase.length = ppq * beats;
|
|
||||||
for _ in phrase.notes.len()..phrase.length {
|
|
||||||
phrase.notes.push(Vec::with_capacity(16))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(Edn::Int(s)) = map.get(&Edn::Key(":steps")) {
|
|
||||||
steps = *s as usize;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Edn::List(args) => {
|
|
||||||
let time = (match args.get(0) {
|
|
||||||
Some(Edn::Key(text)) => text[1..].parse::<f64>()?,
|
|
||||||
Some(Edn::Int(i)) => *i as f64,
|
|
||||||
Some(Edn::Double(f)) => f64::from(*f),
|
|
||||||
_ => panic!("unexpected in phrase '{name}': {:?}", args.get(0)),
|
|
||||||
} * beats as f64 * ppq as f64 / steps as f64) as usize;
|
|
||||||
for edn in args[1..].iter() {
|
|
||||||
match edn {
|
|
||||||
Edn::List(args) => if let (
|
|
||||||
Some(Edn::Int(key)),
|
|
||||||
Some(Edn::Int(vel)),
|
|
||||||
) = (
|
|
||||||
args.get(0),
|
|
||||||
args.get(1),
|
|
||||||
) {
|
|
||||||
let (key, vel) = (
|
|
||||||
u7::from((*key as u8).min(127)),
|
|
||||||
u7::from((*vel as u8).min(127)),
|
|
||||||
);
|
|
||||||
phrase.notes[time].push(MidiMessage::NoteOn { key, vel })
|
|
||||||
} else {
|
|
||||||
panic!("unexpected list in phrase '{name}'")
|
|
||||||
},
|
|
||||||
_ => panic!("unexpected in phrase '{name}': {edn:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => panic!("unexpected in phrase '{name}': {edn:?}"),
|
|
||||||
});
|
|
||||||
phrase.name = name;
|
|
||||||
Ok(phrase)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ render!(SetupModal |self, buf, area| {
|
||||||
Ok(area)
|
Ok(area)
|
||||||
});
|
});
|
||||||
handle!(SetupModal |self, e| {
|
handle!(SetupModal |self, e| {
|
||||||
if let AppEvent::Input(::crossterm::event::Event::Key(KeyEvent {
|
if let AppEvent::Input(Event::Key(KeyEvent {
|
||||||
code: KeyCode::Enter,
|
code: KeyCode::Enter,
|
||||||
..
|
..
|
||||||
})) = e {
|
})) = e {
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,4 @@ better-panic = "0.3.0"
|
||||||
midly = "0.5"
|
midly = "0.5"
|
||||||
clap = { version = "4.5.4", features = [ "derive" ] }
|
clap = { version = "4.5.4", features = [ "derive" ] }
|
||||||
clojure-reader = "0.1.0"
|
clojure-reader = "0.1.0"
|
||||||
|
once_cell = "1.19.0"
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ pub use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers};
|
||||||
pub use ratatui::prelude::{Rect, Style, Color, Buffer};
|
pub use ratatui::prelude::{Rect, Style, Color, Buffer};
|
||||||
pub use ratatui::style::Stylize;
|
pub use ratatui::style::Stylize;
|
||||||
pub use clojure_reader::{edn::{read, Edn}, error::Error as EdnError};
|
pub use clojure_reader::{edn::{read, Edn}, error::Error as EdnError};
|
||||||
|
pub use once_cell::sync::Lazy;
|
||||||
|
|
||||||
pub(crate) use std::error::Error;
|
pub(crate) use std::error::Error;
|
||||||
pub(crate) use std::io::{stdout};
|
pub(crate) use std::io::{stdout};
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,11 @@ version = "0.1.0"
|
||||||
tek_core = { path = "../tek_core" }
|
tek_core = { path = "../tek_core" }
|
||||||
tek_jack = { path = "../tek_jack" }
|
tek_jack = { path = "../tek_jack" }
|
||||||
tek_chain = { path = "../tek_chain" }
|
tek_chain = { path = "../tek_chain" }
|
||||||
|
tek_sampler = { path = "../tek_sampler", optional = true }
|
||||||
|
tek_plugin = { path = "../tek_plugin", optional = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
standalone_devices = [ "tek_sampler", "tek_plugin" ]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
|
||||||
|
|
@ -23,18 +23,14 @@ impl Mixer {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
selected_column: 0,
|
selected_column: 0,
|
||||||
selected_track: 1,
|
selected_track: 1,
|
||||||
tracks: vec![
|
tracks: vec![],
|
||||||
MixerTrack::new(&client, 1, "Mono 1")?,
|
|
||||||
MixerTrack::new(&client, 1, "Mono 2")?,
|
|
||||||
MixerTrack::new(&client, 2, "Stereo 1")?,
|
|
||||||
MixerTrack::new(&client, 2, "Stereo 2")?,
|
|
||||||
MixerTrack::new(&client, 2, "Stereo 3")?,
|
|
||||||
MixerTrack::new(&client, 2, "Bus 1")?,
|
|
||||||
MixerTrack::new(&client, 2, "Bus 2")?,
|
|
||||||
MixerTrack::new(&client, 2, "Mix")?,
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
pub fn add_track (&mut self, name: &str, channels: usize) -> Usually<&mut Self> {
|
||||||
|
let track = MixerTrack::new(name, channels)?;
|
||||||
|
self.tracks.push(track);
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process (
|
fn process (
|
||||||
|
|
|
||||||
|
|
@ -1,94 +1,124 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use tek_core::edn;
|
use tek_core::edn;
|
||||||
|
#[cfg(feature = "standalone_devices")]
|
||||||
|
use tek_sampler::*;
|
||||||
|
#[cfg(feature = "standalone_devices")]
|
||||||
|
use tek_plugin::*;
|
||||||
|
|
||||||
/// TODO: A track in the mixer. (Integrate with [crate::model::Track]?)
|
/// A track in the mixer.
|
||||||
pub struct MixerTrack {
|
pub struct MixerTrack {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub channels: u8,
|
pub ports: JackPorts,
|
||||||
pub input_ports: Vec<Port<AudioIn>>,
|
pub devices: Vec<JackDevice>,
|
||||||
pub pre_gain_meter: f64,
|
//pub channels: u8,
|
||||||
pub gain: f64,
|
//pub input_ports: Vec<Port<AudioIn>>,
|
||||||
pub insert_ports: Vec<Port<AudioOut>>,
|
//pub pre_gain_meter: f64,
|
||||||
pub return_ports: Vec<Port<AudioIn>>,
|
//pub gain: f64,
|
||||||
pub post_gain_meter: f64,
|
//pub insert_ports: Vec<Port<AudioOut>>,
|
||||||
pub post_insert_meter: f64,
|
//pub return_ports: Vec<Port<AudioIn>>,
|
||||||
pub level: f64,
|
//pub post_gain_meter: f64,
|
||||||
pub pan: f64,
|
//pub post_insert_meter: f64,
|
||||||
pub output_ports: Vec<Port<AudioOut>>,
|
//pub level: f64,
|
||||||
pub post_fader_meter: f64,
|
//pub pan: f64,
|
||||||
pub route: String,
|
//pub output_ports: Vec<Port<AudioOut>>,
|
||||||
|
//pub post_fader_meter: f64,
|
||||||
|
//pub route: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MixerTrack {
|
impl MixerTrack {
|
||||||
pub fn new (jack: &Client, channels: u8, name: &str) -> Usually<Self> {
|
|
||||||
let mut input_ports = vec![];
|
|
||||||
let mut insert_ports = vec![];
|
|
||||||
let mut return_ports = vec![];
|
|
||||||
let mut output_ports = vec![];
|
|
||||||
for channel in 1..=channels {
|
|
||||||
input_ports.push(jack.register_port(&format!("{name} [input {channel}]"), AudioIn::default())?);
|
|
||||||
output_ports.push(jack.register_port(&format!("{name} [out {channel}]"), AudioOut::default())?);
|
|
||||||
let insert_port = jack.register_port(&format!("{name} [pre {channel}]"), AudioOut::default())?;
|
|
||||||
let return_port = jack.register_port(&format!("{name} [insert {channel}]"), AudioIn::default())?;
|
|
||||||
jack.connect_ports(&insert_port, &return_port)?;
|
|
||||||
insert_ports.push(insert_port);
|
|
||||||
return_ports.push(return_port);
|
|
||||||
}
|
|
||||||
Ok(Self {
|
|
||||||
name: name.into(),
|
|
||||||
channels,
|
|
||||||
input_ports,
|
|
||||||
pre_gain_meter: 0.0,
|
|
||||||
gain: 0.0,
|
|
||||||
post_gain_meter: 0.0,
|
|
||||||
insert_ports,
|
|
||||||
return_ports,
|
|
||||||
post_insert_meter: 0.0,
|
|
||||||
level: 0.0,
|
|
||||||
pan: 0.0,
|
|
||||||
post_fader_meter: 0.0,
|
|
||||||
route: "---".into(),
|
|
||||||
output_ports,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MixerTrack {
|
const SYM_NAME: &'static str = ":name";
|
||||||
fn load_edn <'a, 'e> (app: &'a mut App, args: &[Edn<'e>]) -> Usually<&'a mut Self> {
|
const SYM_GAIN: &'static str = ":gain";
|
||||||
let ppq = app.transport.ppq();
|
const SYM_SAMPLER: &'static str = "sampler";
|
||||||
let mut name = None;
|
const SYM_LV2: &'static str = "lv2";
|
||||||
|
|
||||||
|
pub fn from_edn <'a, 'e> (args: &[Edn<'e>]) -> Usually<Self> {
|
||||||
let mut _gain = 0.0f64;
|
let mut _gain = 0.0f64;
|
||||||
|
let mut track = MixerTrack::new("", 0)?;
|
||||||
|
#[allow(unused_mut)]
|
||||||
let mut devices: Vec<JackDevice> = vec![];
|
let mut devices: Vec<JackDevice> = vec![];
|
||||||
edn!(edn in args {
|
edn!(edn in args {
|
||||||
Edn::Map(map) => {
|
Edn::Map(map) => {
|
||||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
|
if let Some(Edn::Str(n)) = map.get(&Edn::Key(Self::SYM_NAME)) {
|
||||||
name = Some(*n);
|
track.name = n.to_string();
|
||||||
}
|
}
|
||||||
if let Some(Edn::Double(g)) = map.get(&Edn::Key(":gain")) {
|
if let Some(Edn::Double(g)) = map.get(&Edn::Key(Self::SYM_GAIN)) {
|
||||||
_gain = f64::from(*g)
|
_gain = f64::from(*g);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Edn::List(args) => match args.get(0) {
|
Edn::List(args) => match args.get(0) {
|
||||||
Some(Edn::Symbol("sampler")) => {
|
// Add a sampler device to the track
|
||||||
devices.push(Sampler::load_edn(&args[1..])?)
|
Some(Edn::Symbol(Self::SYM_SAMPLER)) => {
|
||||||
|
#[cfg(feature = "standalone_devices")]
|
||||||
|
devices.push(Sampler::from_edn(&args[1..])?);
|
||||||
|
#[cfg(not(feature = "standalone_devices"))]
|
||||||
|
panic!(
|
||||||
|
"unsupported in track {}: {:?}; tek_mixer not compiled with feature \"sampler\"",
|
||||||
|
&track.name,
|
||||||
|
args.get(0).unwrap()
|
||||||
|
)
|
||||||
},
|
},
|
||||||
Some(Edn::Symbol("lv2")) => {
|
// Add a LV2 plugin to the track.
|
||||||
devices.push(LV2Plugin::load_edn(&args[1..])?)
|
Some(Edn::Symbol(Self::SYM_LV2)) => {
|
||||||
|
#[cfg(feature = "standalone_devices")]
|
||||||
|
devices.push(LV2Plugin::from_edn(&args[1..])?);
|
||||||
|
#[cfg(not(feature = "standalone_devices"))]
|
||||||
|
panic!(
|
||||||
|
"unsupported in track {}: {:?}; tek_mixer not compiled with feature \"plugin\"",
|
||||||
|
&track.name,
|
||||||
|
args.get(0).unwrap()
|
||||||
|
)
|
||||||
},
|
},
|
||||||
None => panic!("empty list track {}",
|
None =>
|
||||||
name.unwrap_or("")
|
panic!("empty list track {}", &track.name),
|
||||||
),
|
_ =>
|
||||||
_ => panic!("unexpected in track {}: {:?}",
|
panic!("unexpected in track {}: {:?}", &track.name, args.get(0).unwrap())
|
||||||
name.unwrap_or(""),
|
|
||||||
args.get(0).unwrap()
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
});
|
});
|
||||||
let track = app.arranger.track_add(name)?;
|
for device in devices {
|
||||||
for device in devices { track.add_device(device)?; }
|
track.add_device(device);
|
||||||
|
}
|
||||||
Ok(track)
|
Ok(track)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new (name: &str, channels: usize) -> Usually<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
name: name.into(),
|
||||||
|
ports: JackPorts::default(),
|
||||||
|
devices: vec![],
|
||||||
|
//channels,
|
||||||
|
//input_ports,
|
||||||
|
//pre_gain_meter: 0.0,
|
||||||
|
//gain: 0.0,
|
||||||
|
//post_gain_meter: 0.0,
|
||||||
|
//insert_ports,
|
||||||
|
//return_ports,
|
||||||
|
//post_insert_meter: 0.0,
|
||||||
|
//level: 0.0,
|
||||||
|
//pan: 0.0,
|
||||||
|
//post_fader_meter: 0.0,
|
||||||
|
//route: "---".into(),
|
||||||
|
//output_ports,
|
||||||
|
})
|
||||||
|
//let mut input_ports = vec![];
|
||||||
|
//let mut insert_ports = vec![];
|
||||||
|
//let mut return_ports = vec![];
|
||||||
|
//let mut output_ports = vec![];
|
||||||
|
//for channel in 1..=channels {
|
||||||
|
//input_ports.push(jack.register_port(&format!("{name} [input {channel}]"), AudioIn::default())?);
|
||||||
|
//output_ports.push(jack.register_port(&format!("{name} [out {channel}]"), AudioOut::default())?);
|
||||||
|
//let insert_port = jack.register_port(&format!("{name} [pre {channel}]"), AudioOut::default())?;
|
||||||
|
//let return_port = jack.register_port(&format!("{name} [insert {channel}]"), AudioIn::default())?;
|
||||||
|
//jack.connect_ports(&insert_port, &return_port)?;
|
||||||
|
//insert_ports.push(insert_port);
|
||||||
|
//return_ports.push(return_port);
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_device (&mut self, device: JackDevice) {
|
||||||
|
self.devices.push(device);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//impl<W: Write> Input<TUI<W>, bool> for Mixer {
|
//impl<W: Write> Input<TUI<W>, bool> for Mixer {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use ::livi::{
|
use ::livi::{
|
||||||
World,
|
World,
|
||||||
Instance,
|
Instance,
|
||||||
|
|
@ -9,33 +8,7 @@ use ::livi::{
|
||||||
Port,
|
Port,
|
||||||
event::LV2AtomSequence,
|
event::LV2AtomSequence,
|
||||||
};
|
};
|
||||||
|
use std::thread::JoinHandle;
|
||||||
use ::winit::{
|
|
||||||
application::ApplicationHandler,
|
|
||||||
event::WindowEvent,
|
|
||||||
event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
|
|
||||||
window::{Window, WindowId},
|
|
||||||
platform::x11::EventLoopBuilderExtX11
|
|
||||||
};
|
|
||||||
|
|
||||||
use ::suil_rs::{self};
|
|
||||||
|
|
||||||
use std::thread::{spawn, JoinHandle};
|
|
||||||
|
|
||||||
impl Plugin {
|
|
||||||
pub fn lv2 (name: &str, path: &str) -> Usually<JackDevice> {
|
|
||||||
let plugin = LV2Plugin::new(path)?;
|
|
||||||
jack_from_lv2(name, &plugin.plugin)?
|
|
||||||
.run(|ports|Box::new(Self {
|
|
||||||
name: name.into(),
|
|
||||||
path: Some(String::from(path)),
|
|
||||||
plugin: Some(PluginKind::LV2(plugin)),
|
|
||||||
selected: 0,
|
|
||||||
mapping: false,
|
|
||||||
ports
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A LV2 plugin.
|
/// A LV2 plugin.
|
||||||
pub struct LV2Plugin {
|
pub struct LV2Plugin {
|
||||||
|
|
@ -49,6 +22,22 @@ pub struct LV2Plugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LV2Plugin {
|
impl LV2Plugin {
|
||||||
|
pub fn from_edn <'e> (args: &[Edn<'e>]) -> Usually<JackDevice> {
|
||||||
|
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(&name, &path)
|
||||||
|
}
|
||||||
pub fn new (uri: &str) -> Usually<Self> {
|
pub fn new (uri: &str) -> Usually<Self> {
|
||||||
// Get 1st plugin at URI
|
// Get 1st plugin at URI
|
||||||
let world = World::with_load_bundle(&uri);
|
let world = World::with_load_bundle(&uri);
|
||||||
|
|
@ -84,62 +73,3 @@ impl LV2Plugin {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_lv2_ui (mut ui: LV2PluginUI) -> Usually<JoinHandle<()>> {
|
|
||||||
Ok(spawn(move||{
|
|
||||||
let event_loop = EventLoop::builder().with_x11().with_any_thread(true).build().unwrap();
|
|
||||||
event_loop.set_control_flow(ControlFlow::Wait);
|
|
||||||
event_loop.run_app(&mut ui).unwrap()
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A LV2 plugin's X11 UI.
|
|
||||||
pub struct LV2PluginUI {
|
|
||||||
pub window: Option<Window>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LV2PluginUI {
|
|
||||||
pub fn new () -> Usually<Self> {
|
|
||||||
Ok(Self { window: None })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ApplicationHandler for LV2PluginUI {
|
|
||||||
fn resumed (&mut self, event_loop: &ActiveEventLoop) {
|
|
||||||
self.window = Some(event_loop.create_window(Window::default_attributes()).unwrap());
|
|
||||||
}
|
|
||||||
fn window_event (&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
|
|
||||||
match event {
|
|
||||||
WindowEvent::CloseRequested => {
|
|
||||||
self.window.as_ref().unwrap().set_visible(false);
|
|
||||||
event_loop.exit();
|
|
||||||
},
|
|
||||||
WindowEvent::RedrawRequested => {
|
|
||||||
self.window.as_ref().unwrap().request_redraw();
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lv2_ui_instantiate (kind: &str) {
|
|
||||||
//let host = Suil
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn jack_from_lv2 (name: &str, plugin: &::livi::Plugin) -> Usually<Jack> {
|
|
||||||
let counts = plugin.port_counts();
|
|
||||||
let mut jack = Jack::new(name)?;
|
|
||||||
for i in 0..counts.atom_sequence_inputs {
|
|
||||||
jack = jack.midi_in(&format!("midi-in-{i}"))
|
|
||||||
}
|
|
||||||
for i in 0..counts.atom_sequence_outputs {
|
|
||||||
jack = jack.midi_out(&format!("midi-out-{i}"));
|
|
||||||
}
|
|
||||||
for i in 0..counts.audio_inputs {
|
|
||||||
jack = jack.audio_in(&format!("audio-in-{i}"));
|
|
||||||
}
|
|
||||||
for i in 0..counts.audio_outputs {
|
|
||||||
jack = jack.audio_out(&format!("audio-out-{i}"));
|
|
||||||
}
|
|
||||||
Ok(jack)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
impl LV2Plugin {
|
|
||||||
fn load_edn <'e> (args: &[Edn<'e>]) -> Usually<JackDevice> {
|
|
||||||
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::lv2(&name, &path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,4 +1,12 @@
|
||||||
|
use crate::*;
|
||||||
|
use std::thread::{spawn, JoinHandle};
|
||||||
|
use ::winit::{
|
||||||
|
application::ApplicationHandler,
|
||||||
|
event::WindowEvent,
|
||||||
|
event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
|
||||||
|
window::{Window, WindowId},
|
||||||
|
platform::x11::EventLoopBuilderExtX11
|
||||||
|
};
|
||||||
|
|
||||||
//pub struct LV2PluginUI {
|
//pub struct LV2PluginUI {
|
||||||
//write: (),
|
//write: (),
|
||||||
|
|
@ -7,3 +15,62 @@
|
||||||
//features: (),
|
//features: (),
|
||||||
//transfer: (),
|
//transfer: (),
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
pub fn run_lv2_ui (mut ui: LV2PluginUI) -> Usually<JoinHandle<()>> {
|
||||||
|
Ok(spawn(move||{
|
||||||
|
let event_loop = EventLoop::builder().with_x11().with_any_thread(true).build().unwrap();
|
||||||
|
event_loop.set_control_flow(ControlFlow::Wait);
|
||||||
|
event_loop.run_app(&mut ui).unwrap()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A LV2 plugin's X11 UI.
|
||||||
|
pub struct LV2PluginUI {
|
||||||
|
pub window: Option<Window>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LV2PluginUI {
|
||||||
|
pub fn new () -> Usually<Self> {
|
||||||
|
Ok(Self { window: None })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApplicationHandler for LV2PluginUI {
|
||||||
|
fn resumed (&mut self, event_loop: &ActiveEventLoop) {
|
||||||
|
self.window = Some(event_loop.create_window(Window::default_attributes()).unwrap());
|
||||||
|
}
|
||||||
|
fn window_event (&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
|
||||||
|
match event {
|
||||||
|
WindowEvent::CloseRequested => {
|
||||||
|
self.window.as_ref().unwrap().set_visible(false);
|
||||||
|
event_loop.exit();
|
||||||
|
},
|
||||||
|
WindowEvent::RedrawRequested => {
|
||||||
|
self.window.as_ref().unwrap().request_redraw();
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lv2_ui_instantiate (kind: &str) {
|
||||||
|
//let host = Suil
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn jack_from_lv2 (name: &str, plugin: &::livi::Plugin) -> Usually<Jack> {
|
||||||
|
let counts = plugin.port_counts();
|
||||||
|
let mut jack = Jack::new(name)?;
|
||||||
|
for i in 0..counts.atom_sequence_inputs {
|
||||||
|
jack = jack.midi_in(&format!("midi-in-{i}"))
|
||||||
|
}
|
||||||
|
for i in 0..counts.atom_sequence_outputs {
|
||||||
|
jack = jack.midi_out(&format!("midi-out-{i}"));
|
||||||
|
}
|
||||||
|
for i in 0..counts.audio_inputs {
|
||||||
|
jack = jack.audio_in(&format!("audio-in-{i}"));
|
||||||
|
}
|
||||||
|
for i in 0..counts.audio_outputs {
|
||||||
|
jack = jack.audio_out(&format!("audio-out-{i}"));
|
||||||
|
}
|
||||||
|
Ok(jack)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,18 @@ pub enum PluginKind {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Plugin {
|
impl Plugin {
|
||||||
|
pub fn new_lv2 (name: &str, path: &str) -> Usually<JackDevice> {
|
||||||
|
let plugin = LV2Plugin::new(path)?;
|
||||||
|
jack_from_lv2(name, &plugin.plugin)?
|
||||||
|
.run(|ports|Box::new(Self {
|
||||||
|
name: name.into(),
|
||||||
|
path: Some(String::from(path)),
|
||||||
|
plugin: Some(PluginKind::LV2(plugin)),
|
||||||
|
selected: 0,
|
||||||
|
mapping: false,
|
||||||
|
ports
|
||||||
|
}))
|
||||||
|
}
|
||||||
/// Create a plugin host device.
|
/// Create a plugin host device.
|
||||||
pub fn new (name: &str) -> Usually<Self> {
|
pub fn new (name: &str) -> Usually<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,31 @@ pub struct Sample {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sample {
|
impl Sample {
|
||||||
|
pub fn from_edn <'e> (dir: &str, args: &[Edn<'e>]) -> Usually<(Option<u7>, Arc<RwLock<Self>>)> {
|
||||||
|
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) = read_sample_data(&format!("{dir}/{file}"))?;
|
||||||
|
Ok((midi, Arc::new(RwLock::new(Self::new(&name, start, end, data)))))
|
||||||
|
}
|
||||||
pub fn new (name: &str, start: usize, end: usize, channels: Vec<Vec<f32>>) -> Self {
|
pub fn new (name: &str, start: usize, end: usize, channels: Vec<Vec<f32>>) -> Self {
|
||||||
Self { name: name.to_string(), start, end, channels, rate: None }
|
Self { name: name.to_string(), start, end, channels, rate: None }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,8 +98,36 @@ pub const KEYMAP_SAMPLER: &'static [KeyBinding<Sampler>] = keymap!(Sampler {
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
impl Sampler {
|
impl Sampler {
|
||||||
|
pub fn from_edn <'e> (args: &[Edn<'e>]) -> Usually<JackDevice> {
|
||||||
|
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.get(0) {
|
||||||
|
Some(Edn::Symbol("sample")) => {
|
||||||
|
let (midi, sample) = Sample::from_edn(&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:?}")
|
||||||
|
});
|
||||||
|
Self::new(&name, Some(samples))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new (name: &str, mapped: Option<BTreeMap<u7, Arc<RwLock<Sample>>>>) -> Usually<JackDevice> {
|
pub fn new (name: &str, mapped: Option<BTreeMap<u7, Arc<RwLock<Sample>>>>) -> Usually<JackDevice> {
|
||||||
Jack::new(name)?
|
Jack::new(name)?
|
||||||
.midi_in("midi")
|
.midi_in("midi")
|
||||||
|
|
|
||||||
|
|
@ -1,60 +1,7 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
impl Sampler {
|
impl Sampler {
|
||||||
fn load_edn <'e> (args: &[Edn<'e>]) -> Usually<JackDevice> {
|
|
||||||
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.get(0) {
|
|
||||||
Some(Edn::Symbol("sample")) => {
|
|
||||||
let (midi, sample) = Sample::load_edn(&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:?}")
|
|
||||||
});
|
|
||||||
Self::new(&name, Some(samples))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sample {
|
impl Sample {
|
||||||
fn load_edn <'e> (dir: &str, args: &[Edn<'e>]) -> Usually<(Option<u7>, Arc<RwLock<Self>>)> {
|
|
||||||
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) = read_sample_data(&format!("{dir}/{file}"))?;
|
|
||||||
Ok((midi, Arc::new(RwLock::new(Self::new(&name, start, end, data)))))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,3 +56,60 @@ impl Phrase {
|
||||||
Ok(phrase)
|
Ok(phrase)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//impl Phrase {
|
||||||
|
//fn from_edn <'e> (ppq: usize, args: &[Edn<'e>]) -> Usually<Self> {
|
||||||
|
//let mut phrase = Self::default();
|
||||||
|
//let mut name = String::new();
|
||||||
|
//let mut beats = 0usize;
|
||||||
|
//let mut steps = 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::Int(b)) = map.get(&Edn::Key(":beats")) {
|
||||||
|
//beats = *b as usize;
|
||||||
|
//phrase.length = ppq * beats;
|
||||||
|
//for _ in phrase.notes.len()..phrase.length {
|
||||||
|
//phrase.notes.push(Vec::with_capacity(16))
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//if let Some(Edn::Int(s)) = map.get(&Edn::Key(":steps")) {
|
||||||
|
//steps = *s as usize;
|
||||||
|
//}
|
||||||
|
//},
|
||||||
|
//Edn::List(args) => {
|
||||||
|
//let time = (match args.get(0) {
|
||||||
|
//Some(Edn::Key(text)) => text[1..].parse::<f64>()?,
|
||||||
|
//Some(Edn::Int(i)) => *i as f64,
|
||||||
|
//Some(Edn::Double(f)) => f64::from(*f),
|
||||||
|
//_ => panic!("unexpected in phrase '{name}': {:?}", args.get(0)),
|
||||||
|
//} * beats as f64 * ppq as f64 / steps as f64) as usize;
|
||||||
|
//for edn in args[1..].iter() {
|
||||||
|
//match edn {
|
||||||
|
//Edn::List(args) => if let (
|
||||||
|
//Some(Edn::Int(key)),
|
||||||
|
//Some(Edn::Int(vel)),
|
||||||
|
//) = (
|
||||||
|
//args.get(0),
|
||||||
|
//args.get(1),
|
||||||
|
//) {
|
||||||
|
//let (key, vel) = (
|
||||||
|
//u7::from((*key as u8).min(127)),
|
||||||
|
//u7::from((*vel as u8).min(127)),
|
||||||
|
//);
|
||||||
|
//phrase.notes[time].push(MidiMessage::NoteOn { key, vel })
|
||||||
|
//} else {
|
||||||
|
//panic!("unexpected list in phrase '{name}'")
|
||||||
|
//},
|
||||||
|
//_ => panic!("unexpected in phrase '{name}': {edn:?}")
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//},
|
||||||
|
//_ => panic!("unexpected in phrase '{name}': {edn:?}"),
|
||||||
|
//});
|
||||||
|
//phrase.name = name;
|
||||||
|
//Ok(phrase)
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue