mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 04:06:45 +01:00
wip: refactor into crates
This commit is contained in:
parent
96e17e7f7c
commit
5ae99b4ada
87 changed files with 2281 additions and 2217 deletions
13
crates/tek_plugin/Cargo.toml
Normal file
13
crates/tek_plugin/Cargo.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "tek_plugin"
|
||||
edition = "2021"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
tek_core = { path = "../tek_core" }
|
||||
tek_jack = { path = "../tek_jack" }
|
||||
livi = "0.7.4"
|
||||
winit = { version = "0.30.4", features = [ "x11" ] }
|
||||
suil-rs = { path = "../suil" }
|
||||
vst = "0.4.0"
|
||||
#vst3 = "0.1.0"
|
||||
4
crates/tek_plugin/README.md
Normal file
4
crates/tek_plugin/README.md
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# `tek_plugin`
|
||||
|
||||
This crate allows plugins to be loaded.
|
||||
|
||||
0
crates/tek_plugin/src/bin/mod.rs
Normal file
0
crates/tek_plugin/src/bin/mod.rs
Normal file
18
crates/tek_plugin/src/lib.rs
Normal file
18
crates/tek_plugin/src/lib.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
//! Plugin (currently LV2 only; TODO other formats)
|
||||
|
||||
pub(crate) use tek_core::*;
|
||||
pub(crate) use tek_core::ratatui::prelude::*;
|
||||
pub(crate) use tek_core::crossterm::event::{KeyCode, KeyModifiers};
|
||||
pub(crate) use tek_jack::*;
|
||||
pub(crate) use tek_jack::jack::*;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
submod! {
|
||||
plugin
|
||||
lv2
|
||||
lv2_gui
|
||||
vst2
|
||||
vst3
|
||||
}
|
||||
145
crates/tek_plugin/src/lv2.rs
Normal file
145
crates/tek_plugin/src/lv2.rs
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
use super::*;
|
||||
|
||||
use ::livi::{
|
||||
World,
|
||||
Instance,
|
||||
Plugin as LiviPlugin,
|
||||
Features,
|
||||
FeaturesBuilder,
|
||||
Port,
|
||||
event::LV2AtomSequence,
|
||||
};
|
||||
|
||||
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.
|
||||
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 {
|
||||
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;
|
||||
for p in world.iter_plugins() {
|
||||
plugin = Some(p);
|
||||
break
|
||||
}
|
||||
let plugin = plugin.unwrap();
|
||||
let err = &format!("init {uri}");
|
||||
|
||||
// Instantiate
|
||||
Ok(Self {
|
||||
world,
|
||||
instance: unsafe {
|
||||
plugin
|
||||
.instantiate(features.clone(), 48000.0)
|
||||
.expect(&err)
|
||||
},
|
||||
port_list: {
|
||||
let mut port_list = vec![];
|
||||
for port in plugin.ports() {
|
||||
port_list.push(port);
|
||||
}
|
||||
port_list
|
||||
},
|
||||
plugin,
|
||||
features,
|
||||
input_buffer: Vec::with_capacity(1024),
|
||||
ui_thread: None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
0
crates/tek_plugin/src/lv2_gui.rs
Normal file
0
crates/tek_plugin/src/lv2_gui.rs
Normal file
211
crates/tek_plugin/src/plugin.rs
Normal file
211
crates/tek_plugin/src/plugin.rs
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
use crate::*;
|
||||
|
||||
pub fn handle_plugin (state: &mut Plugin, event: &AppEvent) -> Usually<bool> {
|
||||
handle_keymap(state, event, KEYMAP_PLUGIN)
|
||||
}
|
||||
|
||||
/// Key bindings for plugin device.
|
||||
pub const KEYMAP_PLUGIN: &'static [KeyBinding<Plugin>] = keymap!(Plugin {
|
||||
[Up, NONE, "/plugin/cursor_up", "move cursor up", |s: &mut Plugin|{
|
||||
s.selected = s.selected.saturating_sub(1);
|
||||
Ok(true)
|
||||
}],
|
||||
[Down, NONE, "/plugin/cursor_down", "move cursor down", |s: &mut Plugin|{
|
||||
s.selected = (s.selected + 1).min(match &s.plugin {
|
||||
Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
|
||||
_ => unimplemented!()
|
||||
});
|
||||
Ok(true)
|
||||
}],
|
||||
[PageUp, NONE, "/plugin/cursor_page_up", "move cursor up", |s: &mut Plugin|{
|
||||
s.selected = s.selected.saturating_sub(8);
|
||||
Ok(true)
|
||||
}],
|
||||
[PageDown, NONE, "/plugin/cursor_page_down", "move cursor down", |s: &mut Plugin|{
|
||||
s.selected = (s.selected + 10).min(match &s.plugin {
|
||||
Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
|
||||
_ => unimplemented!()
|
||||
});
|
||||
Ok(true)
|
||||
}],
|
||||
[Char(','), NONE, "/plugin/decrement", "decrement value", |s: &mut Plugin|{
|
||||
match s.plugin.as_mut() {
|
||||
Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
|
||||
let index = port_list[s.selected].index;
|
||||
if let Some(value) = instance.control_input(index) {
|
||||
instance.set_control_input(index, value - 0.01);
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('.'), NONE, "/plugin/decrement", "increment value", |s: &mut Plugin|{
|
||||
match s.plugin.as_mut() {
|
||||
Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
|
||||
let index = port_list[s.selected].index;
|
||||
if let Some(value) = instance.control_input(index) {
|
||||
instance.set_control_input(index, value + 0.01);
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('g'), NONE, "/plugin/gui_toggle", "toggle plugin UI", |s: &mut Plugin|{
|
||||
match s.plugin {
|
||||
Some(PluginKind::LV2(ref mut plugin)) => {
|
||||
plugin.ui_thread = Some(run_lv2_ui(LV2PluginUI::new()?)?);
|
||||
},
|
||||
Some(_) => unreachable!(),
|
||||
None => {}
|
||||
}
|
||||
Ok(true)
|
||||
}],
|
||||
});
|
||||
|
||||
/// A plugin device.
|
||||
pub struct Plugin {
|
||||
pub name: String,
|
||||
pub path: Option<String>,
|
||||
pub plugin: Option<PluginKind>,
|
||||
pub selected: usize,
|
||||
pub mapping: bool,
|
||||
pub ports: JackPorts,
|
||||
}
|
||||
render!(Plugin = render_plugin);
|
||||
handle!(Plugin = handle_plugin);
|
||||
process!(Plugin = Plugin::process);
|
||||
|
||||
/// Supported plugin formats.
|
||||
pub enum PluginKind {
|
||||
LV2(LV2Plugin),
|
||||
VST2 {
|
||||
instance: ::vst::host::PluginInstance
|
||||
},
|
||||
VST3,
|
||||
}
|
||||
|
||||
impl Plugin {
|
||||
/// Create a plugin host device.
|
||||
pub fn new (name: &str) -> Usually<Self> {
|
||||
Ok(Self {
|
||||
name: name.into(),
|
||||
path: None,
|
||||
plugin: None,
|
||||
selected: 0,
|
||||
mapping: false,
|
||||
ports: JackPorts::default()
|
||||
})
|
||||
}
|
||||
pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
||||
match self.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 self.ports.midi_ins.values() {
|
||||
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 self.ports.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(
|
||||
self.ports.audio_ins.values().map(|o|o.as_slice(scope))
|
||||
)
|
||||
.with_audio_outputs(
|
||||
self.ports.audio_outs.values_mut().map(|o|o.as_mut_slice(scope))
|
||||
);
|
||||
unsafe {
|
||||
instance.run(scope.n_frames() as usize, ports).unwrap()
|
||||
};
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
Control::Continue
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_plugin (state: &Plugin, buf: &mut Buffer, area: Rect)
|
||||
-> Usually<Rect>
|
||||
{
|
||||
let Rect { x, y, height, .. } = area;
|
||||
let mut width = 20u16;
|
||||
match &state.plugin {
|
||||
Some(PluginKind::LV2(LV2Plugin { port_list, instance, .. })) => {
|
||||
let start = state.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);
|
||||
label.blit(buf, x + 2, y + 1 + i as u16 - start as u16, if i == state.selected {
|
||||
Some(Style::default().green())
|
||||
} else {
|
||||
None
|
||||
})?;
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
draw_header(state, buf, area.x, area.y, width)?;
|
||||
Ok(Rect { width, ..area })
|
||||
}
|
||||
|
||||
fn draw_header (state: &Plugin, buf: &mut Buffer, x: u16, y: u16, w: u16) -> Usually<Rect> {
|
||||
let style = Style::default().gray();
|
||||
let label1 = format!(" {}", state.name);
|
||||
label1.blit(buf, 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()))]);
|
||||
label2.blit(buf, x + 2 + label1.len() as u16, y, Some(style.not_dim()))?;
|
||||
}
|
||||
Ok(Rect { x, y, width: w, height: 1 })
|
||||
}
|
||||
|
||||
//pub struct LV2PluginUI {
|
||||
//write: (),
|
||||
//controller: (),
|
||||
//widget: (),
|
||||
//features: (),
|
||||
//transfer: (),
|
||||
//}
|
||||
13
crates/tek_plugin/src/vst2.rs
Normal file
13
crates/tek_plugin/src/vst2.rs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
use crate::*;
|
||||
|
||||
impl ::vst::host::Host for Plugin {}
|
||||
|
||||
fn set_vst_plugin (host: &Arc<Mutex<Plugin>>, _path: &str) -> Usually<PluginKind> {
|
||||
let mut loader = ::vst::host::PluginLoader::load(
|
||||
&std::path::Path::new("/nix/store/ij3sz7nqg5l7v2dygdvzy3w6cj62bd6r-helm-0.9.0/lib/lxvst/helm.so"),
|
||||
host.clone()
|
||||
)?;
|
||||
Ok(PluginKind::VST2 {
|
||||
instance: loader.instance()?
|
||||
})
|
||||
}
|
||||
1
crates/tek_plugin/src/vst3.rs
Normal file
1
crates/tek_plugin/src/vst3.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
use crate::*;
|
||||
Loading…
Add table
Add a link
Reference in a new issue