mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
wip: device: reenable lv2 support
This commit is contained in:
parent
bb325869c2
commit
6286d69824
18 changed files with 1533 additions and 393 deletions
1246
Cargo.lock
generated
1246
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -50,11 +50,11 @@ symphonia = { version = "0.5.4", features = [ "all" ] }
|
||||||
toml = { version = "0.8.12" }
|
toml = { version = "0.8.12" }
|
||||||
uuid = { version = "1.10.0", features = [ "v4" ] }
|
uuid = { version = "1.10.0", features = [ "v4" ] }
|
||||||
wavers = { version = "1.4.3" }
|
wavers = { version = "1.4.3" }
|
||||||
|
winit = { version = "0.30.4", features = [ "x11" ] }
|
||||||
#once_cell = "1.19.0"
|
#once_cell = "1.19.0"
|
||||||
#no_deadlocks = "1.3.2"
|
#no_deadlocks = "1.3.2"
|
||||||
#suil-rs = { path = "../suil" }
|
#suil-rs = { path = "../suil" }
|
||||||
#vst = "0.4.0"
|
#vst = "0.4.0"
|
||||||
#vst3 = "0.1.0"
|
#vst3 = "0.1.0"
|
||||||
#winit = { version = "0.30.4", features = [ "x11" ] }
|
|
||||||
proptest = { version = "^1" }
|
proptest = { version = "^1" }
|
||||||
proptest-derive = { version = "^0.5.1" }
|
proptest-derive = { version = "^0.5.1" }
|
||||||
|
|
|
||||||
|
|
@ -411,7 +411,7 @@ impl Tek {
|
||||||
pub(crate) fn device_add (&mut self, index: usize) -> Usually<()> {
|
pub(crate) fn device_add (&mut self, index: usize) -> Usually<()> {
|
||||||
match index {
|
match index {
|
||||||
0 => self.device_add_sampler(),
|
0 => self.device_add_sampler(),
|
||||||
1 => self.device_add_plugin(),
|
1 => self.device_add_lv2(),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -436,7 +436,7 @@ impl Tek {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn device_add_plugin (&mut self) -> Usually<()> {
|
fn device_add_lv2 (&mut self) -> Usually<()> {
|
||||||
todo!();
|
todo!();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,14 @@ uuid = { workspace = true, optional = true }
|
||||||
livi = { workspace = true, optional = true }
|
livi = { workspace = true, optional = true }
|
||||||
symphonia = { workspace = true, optional = true }
|
symphonia = { workspace = true, optional = true }
|
||||||
wavers = { workspace = true, optional = true }
|
wavers = { workspace = true, optional = true }
|
||||||
|
winit = { workspace = true, optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = [ "clock", "sequencer", "sampler" ]
|
default = [ "clock", "sequencer", "sampler", "lv2" ]
|
||||||
clock = []
|
clock = []
|
||||||
sampler = [ "symphonia", "wavers" ]
|
sampler = [ "symphonia", "wavers" ]
|
||||||
sequencer = [ "clock", "uuid" ]
|
sequencer = [ "clock", "uuid" ]
|
||||||
plugin = [] # temporary
|
lv2 = [ "livi", "winit" ]
|
||||||
lv2 = [ "livi" ]
|
|
||||||
vst2 = []
|
vst2 = []
|
||||||
vst3 = []
|
vst3 = []
|
||||||
clap = []
|
clap = []
|
||||||
|
|
|
||||||
0
crates/device/src/clap.rs
Normal file
0
crates/device/src/clap.rs
Normal file
|
|
@ -24,8 +24,20 @@ pub(crate) use ratatui::{prelude::Rect, widgets::{Widget, canvas::{Canvas, Line}
|
||||||
#[cfg(feature = "sampler")] mod sampler;
|
#[cfg(feature = "sampler")] mod sampler;
|
||||||
#[cfg(feature = "sampler")] pub use self::sampler::*;
|
#[cfg(feature = "sampler")] pub use self::sampler::*;
|
||||||
|
|
||||||
#[cfg(feature = "plugin")] mod plugin;
|
#[cfg(feature = "lv2")] mod lv2;
|
||||||
#[cfg(feature = "plugin")] pub use self::plugin::*;
|
#[cfg(feature = "lv2")] pub use self::lv2::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "sf2")] mod sf2;
|
||||||
|
#[cfg(feature = "sf2")] pub use self::sf2::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "vst2")] mod vst2;
|
||||||
|
#[cfg(feature = "vst2")] pub use self::vst2::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "vst3")] mod vst3;
|
||||||
|
#[cfg(feature = "vst3")] pub use self::vst3::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "clap")] mod clap;
|
||||||
|
#[cfg(feature = "clap")] pub use self::clap::*;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Device {
|
pub enum Device {
|
||||||
|
|
|
||||||
16
crates/device/src/lv2.rs
Normal file
16
crates/device/src/lv2.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
mod lv2_model; pub use self::lv2_model::*;
|
||||||
|
mod lv2_audio; pub use self::lv2_audio::*;
|
||||||
|
mod lv2_gui; pub use self::lv2_gui::*;
|
||||||
|
mod lv2_tui; pub use self::lv2_tui::*;
|
||||||
|
|
||||||
|
pub(self) use std::thread::JoinHandle;
|
||||||
|
|
||||||
|
pub(self) use ::livi::{
|
||||||
|
World,
|
||||||
|
Instance,
|
||||||
|
Plugin as LiviPlugin,
|
||||||
|
Features,
|
||||||
|
FeaturesBuilder,
|
||||||
|
Port as LiviPort,
|
||||||
|
event::LV2AtomSequence,
|
||||||
|
};
|
||||||
50
crates/device/src/lv2/lv2_audio.rs
Normal file
50
crates/device/src/lv2/lv2_audio.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
use crate::*;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
audio!(|self: Lv2, _client, scope|{
|
||||||
|
let Self {
|
||||||
|
midi_ins,
|
||||||
|
midi_outs,
|
||||||
|
audio_ins,
|
||||||
|
audio_outs,
|
||||||
|
lv2_features,
|
||||||
|
ref mut lv2_instance,
|
||||||
|
ref mut lv2_input_buffer,
|
||||||
|
..
|
||||||
|
} = self;
|
||||||
|
let urid = lv2_features.midi_urid();
|
||||||
|
lv2_input_buffer.clear();
|
||||||
|
for port in midi_ins.iter() {
|
||||||
|
let mut atom = ::livi::event::LV2AtomSequence::new(
|
||||||
|
&lv2_features,
|
||||||
|
scope.n_frames() as usize
|
||||||
|
);
|
||||||
|
for event in port.iter(scope) {
|
||||||
|
match event.bytes.len() {
|
||||||
|
3 => atom.push_midi_event::<3>(
|
||||||
|
event.time as i64,
|
||||||
|
urid,
|
||||||
|
&event.bytes[0..3]
|
||||||
|
).unwrap(),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lv2_input_buffer.push(atom);
|
||||||
|
}
|
||||||
|
let mut outputs = vec![];
|
||||||
|
for _ in midi_outs.iter() {
|
||||||
|
outputs.push(::livi::event::LV2AtomSequence::new(
|
||||||
|
lv2_features,
|
||||||
|
scope.n_frames() as usize
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let ports = ::livi::EmptyPortConnections::new()
|
||||||
|
.with_atom_sequence_inputs(lv2_input_buffer.iter())
|
||||||
|
.with_atom_sequence_outputs(outputs.iter_mut())
|
||||||
|
.with_audio_inputs(audio_ins.iter().map(|o|o.as_slice(scope)))
|
||||||
|
.with_audio_outputs(audio_outs.iter_mut().map(|o|o.as_mut_slice(scope)));
|
||||||
|
unsafe {
|
||||||
|
lv2_instance.run(scope.n_frames() as usize, ports).unwrap()
|
||||||
|
};
|
||||||
|
Control::Continue
|
||||||
|
});
|
||||||
|
|
@ -56,4 +56,3 @@ impl ApplicationHandler for LV2PluginUI {
|
||||||
fn lv2_ui_instantiate (kind: &str) {
|
fn lv2_ui_instantiate (kind: &str) {
|
||||||
//let host = Suil
|
//let host = Suil
|
||||||
}
|
}
|
||||||
|
|
||||||
86
crates/device/src/lv2/lv2_model.rs
Normal file
86
crates/device/src/lv2/lv2_model.rs
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
use crate::*;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// A LV2 plugin.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Lv2 {
|
||||||
|
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
||||||
|
pub jack: Jack,
|
||||||
|
pub name: Arc<str>,
|
||||||
|
pub path: Option<Arc<str>>,
|
||||||
|
pub selected: usize,
|
||||||
|
pub mapping: bool,
|
||||||
|
pub midi_ins: Vec<Port<MidiIn>>,
|
||||||
|
pub midi_outs: Vec<Port<MidiOut>>,
|
||||||
|
pub audio_ins: Vec<Port<AudioIn>>,
|
||||||
|
pub audio_outs: Vec<Port<AudioOut>>,
|
||||||
|
|
||||||
|
pub lv2_world: livi::World,
|
||||||
|
pub lv2_instance: livi::Instance,
|
||||||
|
pub lv2_plugin: livi::Plugin,
|
||||||
|
pub lv2_features: Arc<livi::Features>,
|
||||||
|
pub lv2_port_list: Vec<livi::Port>,
|
||||||
|
pub lv2_input_buffer: Vec<livi::event::LV2AtomSequence>,
|
||||||
|
pub lv2_ui_thread: Option<JoinHandle<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Lv2 {
|
||||||
|
|
||||||
|
pub fn new (
|
||||||
|
jack: &Jack,
|
||||||
|
name: &str,
|
||||||
|
uri: &str,
|
||||||
|
) -> Usually<Self> {
|
||||||
|
let lv2_world = livi::World::with_load_bundle(&uri);
|
||||||
|
let lv2_features = lv2_world.build_features(livi::FeaturesBuilder {
|
||||||
|
min_block_length: 1,
|
||||||
|
max_block_length: 65536,
|
||||||
|
});
|
||||||
|
let lv2_plugin = lv2_world.iter_plugins().nth(0)
|
||||||
|
.unwrap_or_else(||panic!("plugin not found: {uri}"));
|
||||||
|
Ok(Self {
|
||||||
|
jack: jack.clone(),
|
||||||
|
name: name.into(),
|
||||||
|
path: Some(String::from(uri).into()),
|
||||||
|
selected: 0,
|
||||||
|
mapping: false,
|
||||||
|
midi_ins: vec![],
|
||||||
|
midi_outs: vec![],
|
||||||
|
audio_ins: vec![],
|
||||||
|
audio_outs: vec![],
|
||||||
|
lv2_instance: unsafe {
|
||||||
|
lv2_plugin
|
||||||
|
.instantiate(lv2_features.clone(), 48000.0)
|
||||||
|
.expect(&format!("instantiate failed: {uri}"))
|
||||||
|
},
|
||||||
|
lv2_port_list: lv2_plugin.ports().collect::<Vec<_>>(),
|
||||||
|
lv2_input_buffer: Vec::with_capacity(Self::INPUT_BUFFER),
|
||||||
|
lv2_ui_thread: None,
|
||||||
|
lv2_world,
|
||||||
|
lv2_features,
|
||||||
|
lv2_plugin,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const INPUT_BUFFER: usize = 1024;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//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)
|
||||||
|
//}
|
||||||
129
crates/device/src/lv2/lv2_tui.rs
Normal file
129
crates/device/src/lv2/lv2_tui.rs
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
use crate::*;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
impl Content<TuiOut> for Lv2 {
|
||||||
|
fn render (&self, to: &mut TuiOut) {
|
||||||
|
let area = to.area();
|
||||||
|
let [x, y, _, height] = area;
|
||||||
|
let mut width = 20u16;
|
||||||
|
//match &self.plugin {
|
||||||
|
//Some(PluginKind::LV2(LV2Plugin { port_list, instance, .. })) => {
|
||||||
|
let start = self.selected.saturating_sub((height as usize / 2).saturating_sub(1));
|
||||||
|
let end = start + height as usize - 2;
|
||||||
|
//draw_box(buf, Rect { x, y, width, height });
|
||||||
|
for i in start..end {
|
||||||
|
if let Some(port) = self.lv2_port_list.get(i) {
|
||||||
|
let value = if let Some(value) = self.lv2_instance.control_input(port.index) {
|
||||||
|
value
|
||||||
|
} else {
|
||||||
|
port.default_value
|
||||||
|
};
|
||||||
|
//let label = &format!("C·· M·· {:25} = {value:.03}", port.name);
|
||||||
|
let label = &format!("{:25} = {value:.03}", port.name);
|
||||||
|
width = width.max(label.len() as u16 + 4);
|
||||||
|
let style = if i == self.selected {
|
||||||
|
Some(Style::default().green())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
} ;
|
||||||
|
to.blit(&label, x + 2, y + 1 + i as u16 - start as u16, style);
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//}
|
||||||
|
//_ => {}
|
||||||
|
//};
|
||||||
|
draw_header(self, to, x, y, width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_header (state: &Lv2, to: &mut TuiOut, x: u16, y: u16, w: u16) {
|
||||||
|
let style = Style::default().gray();
|
||||||
|
let label1 = format!(" {}", state.name);
|
||||||
|
to.blit(&label1, x + 1, y, Some(style.white().bold()));
|
||||||
|
if let Some(ref path) = state.path {
|
||||||
|
let label2 = format!("{}…", &path[..((w as usize - 10).min(path.len()))]);
|
||||||
|
to.blit(&label2, x + 2 + label1.len() as u16, y, Some(style.not_dim()));
|
||||||
|
}
|
||||||
|
//Ok(Rect { x, y, width: w, height: 1 })
|
||||||
|
}
|
||||||
|
|
||||||
|
//handle!(TuiIn: |self:Plugin, from|{
|
||||||
|
//match from.event() {
|
||||||
|
//kpat!(KeyCode::Up) => {
|
||||||
|
//self.selected = self.selected.saturating_sub(1);
|
||||||
|
//Ok(Some(true))
|
||||||
|
//},
|
||||||
|
//kpat!(KeyCode::Down) => {
|
||||||
|
//self.selected = (self.selected + 1).min(match &self.plugin {
|
||||||
|
//Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
|
||||||
|
//_ => unimplemented!()
|
||||||
|
//});
|
||||||
|
//Ok(Some(true))
|
||||||
|
//},
|
||||||
|
//kpat!(KeyCode::PageUp) => {
|
||||||
|
//self.selected = self.selected.saturating_sub(8);
|
||||||
|
//Ok(Some(true))
|
||||||
|
//},
|
||||||
|
//kpat!(KeyCode::PageDown) => {
|
||||||
|
//self.selected = (self.selected + 10).min(match &self.plugin {
|
||||||
|
//Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
|
||||||
|
//_ => unimplemented!()
|
||||||
|
//});
|
||||||
|
//Ok(Some(true))
|
||||||
|
//},
|
||||||
|
//kpat!(KeyCode::Char(',')) => {
|
||||||
|
//match self.plugin.as_mut() {
|
||||||
|
//Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
|
||||||
|
//let index = port_list[self.selected].index;
|
||||||
|
//if let Some(value) = instance.control_input(index) {
|
||||||
|
//instance.set_control_input(index, value - 0.01);
|
||||||
|
//}
|
||||||
|
//},
|
||||||
|
//_ => {}
|
||||||
|
//}
|
||||||
|
//Ok(Some(true))
|
||||||
|
//},
|
||||||
|
//kpat!(KeyCode::Char('.')) => {
|
||||||
|
//match self.plugin.as_mut() {
|
||||||
|
//Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
|
||||||
|
//let index = port_list[self.selected].index;
|
||||||
|
//if let Some(value) = instance.control_input(index) {
|
||||||
|
//instance.set_control_input(index, value + 0.01);
|
||||||
|
//}
|
||||||
|
//},
|
||||||
|
//_ => {}
|
||||||
|
//}
|
||||||
|
//Ok(Some(true))
|
||||||
|
//},
|
||||||
|
//kpat!(KeyCode::Char('g')) => {
|
||||||
|
//match self.plugin {
|
||||||
|
////Some(PluginKind::LV2(ref mut plugin)) => {
|
||||||
|
////plugin.ui_thread = Some(run_lv2_ui(LV2PluginUI::new()?)?);
|
||||||
|
////},
|
||||||
|
//Some(_) => unreachable!(),
|
||||||
|
//None => {}
|
||||||
|
//}
|
||||||
|
//Ok(Some(true))
|
||||||
|
//},
|
||||||
|
//_ => Ok(None)
|
||||||
|
//}
|
||||||
|
//});
|
||||||
|
|
||||||
|
//from_atom!("plugin/lv2" => |jack: &Jack, args| -> Plugin {
|
||||||
|
//let mut name = String::new();
|
||||||
|
//let mut path = String::new();
|
||||||
|
//atom!(atom in args {
|
||||||
|
//Atom::Map(map) => {
|
||||||
|
//if let Some(Atom::Str(n)) = map.get(&Atom::Key(":name")) {
|
||||||
|
//name = String::from(*n);
|
||||||
|
//}
|
||||||
|
//if let Some(Atom::Str(p)) = map.get(&Atom::Key(":path")) {
|
||||||
|
//path = String::from(*p);
|
||||||
|
//}
|
||||||
|
//},
|
||||||
|
//_ => panic!("unexpected in lv2 '{name}'"),
|
||||||
|
//});
|
||||||
|
//Plugin::new_lv2(jack, &name, &path)
|
||||||
|
//});
|
||||||
|
|
@ -1,281 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
mod lv2;
|
|
||||||
mod lv2_gui;
|
|
||||||
mod lv2_tui;
|
|
||||||
mod vst2_tui;
|
|
||||||
mod vst3_tui;
|
|
||||||
|
|
||||||
/// A plugin device.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Plugin {
|
|
||||||
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
|
||||||
pub jack: Jack,
|
|
||||||
pub name: Arc<str>,
|
|
||||||
pub path: Option<Arc<str>>,
|
|
||||||
pub plugin: Option<PluginKind>,
|
|
||||||
pub selected: usize,
|
|
||||||
pub mapping: bool,
|
|
||||||
pub midi_ins: Vec<Port<MidiIn>>,
|
|
||||||
pub midi_outs: Vec<Port<MidiOut>>,
|
|
||||||
pub audio_ins: Vec<Port<AudioIn>>,
|
|
||||||
pub audio_outs: Vec<Port<AudioOut>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Supported plugin formats.
|
|
||||||
#[derive(Default)]
|
|
||||||
pub enum PluginKind {
|
|
||||||
#[default] None,
|
|
||||||
LV2(LV2Plugin),
|
|
||||||
VST2 { instance: () /*::vst::host::PluginInstance*/ },
|
|
||||||
VST3,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for PluginKind {
|
|
||||||
fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
|
||||||
write!(f, "{}", match self {
|
|
||||||
Self::None => "(none)",
|
|
||||||
Self::LV2(_) => "LV2",
|
|
||||||
Self::VST2{..} => "VST2",
|
|
||||||
Self::VST3 => "VST3",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Plugin {
|
|
||||||
pub fn new_lv2 (
|
|
||||||
jack: &Jack,
|
|
||||||
name: &str,
|
|
||||||
path: &str,
|
|
||||||
) -> Usually<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
jack: jack.clone(),
|
|
||||||
name: name.into(),
|
|
||||||
path: Some(String::from(path).into()),
|
|
||||||
plugin: Some(PluginKind::LV2(LV2Plugin::new(path)?)),
|
|
||||||
selected: 0,
|
|
||||||
mapping: false,
|
|
||||||
midi_ins: vec![],
|
|
||||||
midi_outs: vec![],
|
|
||||||
audio_ins: vec![],
|
|
||||||
audio_outs: vec![],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PluginAudio(Arc<RwLock<Plugin>>);
|
|
||||||
from!(|model: &Arc<RwLock<Plugin>>| PluginAudio = Self(model.clone()));
|
|
||||||
audio!(|self: PluginAudio, _client, scope|{
|
|
||||||
let state = &mut*self.0.write().unwrap();
|
|
||||||
match state.plugin.as_mut() {
|
|
||||||
Some(PluginKind::LV2(LV2Plugin {
|
|
||||||
features,
|
|
||||||
ref mut instance,
|
|
||||||
ref mut input_buffer,
|
|
||||||
..
|
|
||||||
})) => {
|
|
||||||
let urid = features.midi_urid();
|
|
||||||
input_buffer.clear();
|
|
||||||
for port in state.midi_ins.iter() {
|
|
||||||
let mut atom = ::livi::event::LV2AtomSequence::new(
|
|
||||||
&features,
|
|
||||||
scope.n_frames() as usize
|
|
||||||
);
|
|
||||||
for event in port.iter(scope) {
|
|
||||||
match event.bytes.len() {
|
|
||||||
3 => atom.push_midi_event::<3>(
|
|
||||||
event.time as i64,
|
|
||||||
urid,
|
|
||||||
&event.bytes[0..3]
|
|
||||||
).unwrap(),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
input_buffer.push(atom);
|
|
||||||
}
|
|
||||||
let mut outputs = vec![];
|
|
||||||
for _ in state.midi_outs.iter() {
|
|
||||||
outputs.push(::livi::event::LV2AtomSequence::new(
|
|
||||||
features,
|
|
||||||
scope.n_frames() as usize
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let ports = ::livi::EmptyPortConnections::new()
|
|
||||||
.with_atom_sequence_inputs(input_buffer.iter())
|
|
||||||
.with_atom_sequence_outputs(outputs.iter_mut())
|
|
||||||
.with_audio_inputs(state.audio_ins.iter().map(|o|o.as_slice(scope)))
|
|
||||||
.with_audio_outputs(state.audio_outs.iter_mut().map(|o|o.as_mut_slice(scope)));
|
|
||||||
unsafe {
|
|
||||||
instance.run(scope.n_frames() as usize, ports).unwrap()
|
|
||||||
};
|
|
||||||
},
|
|
||||||
_ => todo!("only lv2 is supported")
|
|
||||||
}
|
|
||||||
Control::Continue
|
|
||||||
});
|
|
||||||
|
|
||||||
//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)
|
|
||||||
//}
|
|
||||||
|
|
||||||
impl Plugin {
|
|
||||||
/// Create a plugin host device.
|
|
||||||
pub fn new (
|
|
||||||
jack: &Jack,
|
|
||||||
name: &str,
|
|
||||||
) -> Usually<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
//_engine: Default::default(),
|
|
||||||
jack: jack.clone(),
|
|
||||||
name: name.into(),
|
|
||||||
path: None,
|
|
||||||
plugin: None,
|
|
||||||
selected: 0,
|
|
||||||
mapping: false,
|
|
||||||
audio_ins: vec![],
|
|
||||||
audio_outs: vec![],
|
|
||||||
midi_ins: vec![],
|
|
||||||
midi_outs: vec![],
|
|
||||||
//ports: JackPorts::default()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Content<TuiOut> for Plugin {
|
|
||||||
fn render (&self, to: &mut TuiOut) {
|
|
||||||
let area = to.area();
|
|
||||||
let [x, y, _, height] = area;
|
|
||||||
let mut width = 20u16;
|
|
||||||
match &self.plugin {
|
|
||||||
Some(PluginKind::LV2(LV2Plugin { port_list, instance, .. })) => {
|
|
||||||
let start = self.selected.saturating_sub((height as usize / 2).saturating_sub(1));
|
|
||||||
let end = start + height as usize - 2;
|
|
||||||
//draw_box(buf, Rect { x, y, width, height });
|
|
||||||
for i in start..end {
|
|
||||||
if let Some(port) = port_list.get(i) {
|
|
||||||
let value = if let Some(value) = instance.control_input(port.index) {
|
|
||||||
value
|
|
||||||
} else {
|
|
||||||
port.default_value
|
|
||||||
};
|
|
||||||
//let label = &format!("C·· M·· {:25} = {value:.03}", port.name);
|
|
||||||
let label = &format!("{:25} = {value:.03}", port.name);
|
|
||||||
width = width.max(label.len() as u16 + 4);
|
|
||||||
let style = if i == self.selected {
|
|
||||||
Some(Style::default().green())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
} ;
|
|
||||||
to.blit(&label, x + 2, y + 1 + i as u16 - start as u16, style);
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
draw_header(self, to, x, y, width);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_header (state: &Plugin, to: &mut TuiOut, x: u16, y: u16, w: u16) {
|
|
||||||
let style = Style::default().gray();
|
|
||||||
let label1 = format!(" {}", state.name);
|
|
||||||
to.blit(&label1, x + 1, y, Some(style.white().bold()));
|
|
||||||
if let Some(ref path) = state.path {
|
|
||||||
let label2 = format!("{}…", &path[..((w as usize - 10).min(path.len()))]);
|
|
||||||
to.blit(&label2, x + 2 + label1.len() as u16, y, Some(style.not_dim()));
|
|
||||||
}
|
|
||||||
//Ok(Rect { x, y, width: w, height: 1 })
|
|
||||||
}
|
|
||||||
|
|
||||||
//handle!(TuiIn: |self:Plugin, from|{
|
|
||||||
//match from.event() {
|
|
||||||
//kpat!(KeyCode::Up) => {
|
|
||||||
//self.selected = self.selected.saturating_sub(1);
|
|
||||||
//Ok(Some(true))
|
|
||||||
//},
|
|
||||||
//kpat!(KeyCode::Down) => {
|
|
||||||
//self.selected = (self.selected + 1).min(match &self.plugin {
|
|
||||||
//Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
|
|
||||||
//_ => unimplemented!()
|
|
||||||
//});
|
|
||||||
//Ok(Some(true))
|
|
||||||
//},
|
|
||||||
//kpat!(KeyCode::PageUp) => {
|
|
||||||
//self.selected = self.selected.saturating_sub(8);
|
|
||||||
//Ok(Some(true))
|
|
||||||
//},
|
|
||||||
//kpat!(KeyCode::PageDown) => {
|
|
||||||
//self.selected = (self.selected + 10).min(match &self.plugin {
|
|
||||||
//Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
|
|
||||||
//_ => unimplemented!()
|
|
||||||
//});
|
|
||||||
//Ok(Some(true))
|
|
||||||
//},
|
|
||||||
//kpat!(KeyCode::Char(',')) => {
|
|
||||||
//match self.plugin.as_mut() {
|
|
||||||
//Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
|
|
||||||
//let index = port_list[self.selected].index;
|
|
||||||
//if let Some(value) = instance.control_input(index) {
|
|
||||||
//instance.set_control_input(index, value - 0.01);
|
|
||||||
//}
|
|
||||||
//},
|
|
||||||
//_ => {}
|
|
||||||
//}
|
|
||||||
//Ok(Some(true))
|
|
||||||
//},
|
|
||||||
//kpat!(KeyCode::Char('.')) => {
|
|
||||||
//match self.plugin.as_mut() {
|
|
||||||
//Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
|
|
||||||
//let index = port_list[self.selected].index;
|
|
||||||
//if let Some(value) = instance.control_input(index) {
|
|
||||||
//instance.set_control_input(index, value + 0.01);
|
|
||||||
//}
|
|
||||||
//},
|
|
||||||
//_ => {}
|
|
||||||
//}
|
|
||||||
//Ok(Some(true))
|
|
||||||
//},
|
|
||||||
//kpat!(KeyCode::Char('g')) => {
|
|
||||||
//match self.plugin {
|
|
||||||
////Some(PluginKind::LV2(ref mut plugin)) => {
|
|
||||||
////plugin.ui_thread = Some(run_lv2_ui(LV2PluginUI::new()?)?);
|
|
||||||
////},
|
|
||||||
//Some(_) => unreachable!(),
|
|
||||||
//None => {}
|
|
||||||
//}
|
|
||||||
//Ok(Some(true))
|
|
||||||
//},
|
|
||||||
//_ => Ok(None)
|
|
||||||
//}
|
|
||||||
//});
|
|
||||||
|
|
||||||
//from_atom!("plugin/lv2" => |jack: &Jack, args| -> Plugin {
|
|
||||||
//let mut name = String::new();
|
|
||||||
//let mut path = String::new();
|
|
||||||
//atom!(atom in args {
|
|
||||||
//Atom::Map(map) => {
|
|
||||||
//if let Some(Atom::Str(n)) = map.get(&Atom::Key(":name")) {
|
|
||||||
//name = String::from(*n);
|
|
||||||
//}
|
|
||||||
//if let Some(Atom::Str(p)) = map.get(&Atom::Key(":path")) {
|
|
||||||
//path = String::from(*p);
|
|
||||||
//}
|
|
||||||
//},
|
|
||||||
//_ => panic!("unexpected in lv2 '{name}'"),
|
|
||||||
//});
|
|
||||||
//Plugin::new_lv2(jack, &name, &path)
|
|
||||||
//});
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// A LV2 plugin.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct LV2Plugin {
|
|
||||||
pub world: livi::World,
|
|
||||||
pub instance: livi::Instance,
|
|
||||||
pub plugin: livi::Plugin,
|
|
||||||
pub features: Arc<livi::Features>,
|
|
||||||
pub port_list: Vec<livi::Port>,
|
|
||||||
pub input_buffer: Vec<livi::event::LV2AtomSequence>,
|
|
||||||
pub ui_thread: Option<JoinHandle<()>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LV2Plugin {
|
|
||||||
const INPUT_BUFFER: usize = 1024;
|
|
||||||
pub fn new (uri: &str) -> Usually<Self> {
|
|
||||||
let world = livi::World::with_load_bundle(&uri);
|
|
||||||
let features = world
|
|
||||||
.build_features(livi::FeaturesBuilder {
|
|
||||||
min_block_length: 1,
|
|
||||||
max_block_length: 65536,
|
|
||||||
});
|
|
||||||
let plugin = world.iter_plugins().nth(0)
|
|
||||||
.unwrap_or_else(||panic!("plugin not found: {uri}"));
|
|
||||||
Ok(Self {
|
|
||||||
instance: unsafe {
|
|
||||||
plugin
|
|
||||||
.instantiate(features.clone(), 48000.0)
|
|
||||||
.expect(&format!("instantiate failed: {uri}"))
|
|
||||||
},
|
|
||||||
port_list: plugin.ports().collect::<Vec<_>>(),
|
|
||||||
input_buffer: Vec::with_capacity(Self::INPUT_BUFFER),
|
|
||||||
ui_thread: None,
|
|
||||||
world,
|
|
||||||
features,
|
|
||||||
plugin,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use ::livi::{
|
|
||||||
World,
|
|
||||||
Instance,
|
|
||||||
Plugin as LiviPlugin,
|
|
||||||
Features,
|
|
||||||
FeaturesBuilder,
|
|
||||||
Port,
|
|
||||||
event::LV2AtomSequence,
|
|
||||||
};
|
|
||||||
use std::thread::JoinHandle;
|
|
||||||
|
|
||||||
/// A LV2 plugin.
|
|
||||||
pub struct LV2Plugin {
|
|
||||||
pub world: World,
|
|
||||||
pub instance: Instance,
|
|
||||||
pub plugin: LiviPlugin,
|
|
||||||
pub features: Arc<Features>,
|
|
||||||
pub port_list: Vec<Port>,
|
|
||||||
pub input_buffer: Vec<LV2AtomSequence>,
|
|
||||||
pub ui_thread: Option<JoinHandle<()>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LV2Plugin {
|
|
||||||
const INPUT_BUFFER: usize = 1024;
|
|
||||||
pub fn new (uri: &str) -> Usually<Self> {
|
|
||||||
// Get 1st plugin at URI
|
|
||||||
let world = World::with_load_bundle(&uri);
|
|
||||||
let features = FeaturesBuilder { min_block_length: 1, max_block_length: 65536 };
|
|
||||||
let features = world.build_features(features);
|
|
||||||
let mut plugin = None;
|
|
||||||
if let Some(p) = world.iter_plugins().next() { plugin = Some(p); }
|
|
||||||
let plugin = plugin.expect("plugin not found");
|
|
||||||
let err = &format!("init {uri}");
|
|
||||||
let instance = unsafe { plugin.instantiate(features.clone(), 48000.0).expect(&err) };
|
|
||||||
let mut port_list = vec![];
|
|
||||||
for port in plugin.ports() {
|
|
||||||
port_list.push(port);
|
|
||||||
}
|
|
||||||
let input_buffer = Vec::with_capacity(Self::INPUT_BUFFER);
|
|
||||||
// Instantiate
|
|
||||||
Ok(Self {
|
|
||||||
world, instance, port_list, plugin, features, input_buffer, ui_thread: None
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
//! TODO
|
|
||||||
|
|
||||||
0
crates/device/src/sf2.rs
Normal file
0
crates/device/src/sf2.rs
Normal file
0
crates/device/src/vst3.rs
Normal file
0
crates/device/src/vst3.rs
Normal file
Loading…
Add table
Add a link
Reference in a new issue