wip: refactor into fewer crates

This commit is contained in:
🪞👃🪞 2025-05-01 17:39:29 +03:00
parent c367a0444e
commit 77703d83a5
105 changed files with 64 additions and 131 deletions

416
deps/vst/src/lib.rs vendored Executable file
View file

@ -0,0 +1,416 @@
#![warn(missing_docs)]
//! A rust implementation of the VST2.4 API.
//!
//! The VST API is multi-threaded. A VST host calls into a plugin generally from two threads -
//! the *processing* thread and the *UI* thread. The organization of this crate reflects this
//! structure to ensure that the threading assumptions of Safe Rust are fulfilled and data
//! races are avoided.
//!
//! # Plugins
//! All Plugins must implement the `Plugin` trait and `std::default::Default`.
//! The `plugin_main!` macro must also be called in order to export the necessary functions
//! for the plugin to function.
//!
//! ## `Plugin` Trait
//! All methods in this trait have a default implementation except for the `get_info` method which
//! must be implemented by the plugin. Any of the default implementations may be overridden for
//! custom functionality; the defaults do nothing on their own.
//!
//! ## `PluginParameters` Trait
//! The methods in this trait handle access to plugin parameters. Since the host may call these
//! methods concurrently with audio processing, it needs to be separate from the main `Plugin`
//! trait.
//!
//! To support parameters, a plugin must provide an implementation of the `PluginParameters`
//! trait, wrap it in an `Arc` (so it can be accessed from both threads) and
//! return a reference to it from the `get_parameter_object` method in the `Plugin`.
//!
//! ## `plugin_main!` macro
//! `plugin_main!` will export the necessary functions to create a proper VST plugin. This must be
//! called with your VST plugin struct name in order for the vst to work.
//!
//! ## Example plugin
//! A barebones VST plugin:
//!
//! ```no_run
//! #[macro_use]
//! extern crate vst;
//!
//! use vst::plugin::{HostCallback, Info, Plugin};
//!
//! struct BasicPlugin;
//!
//! impl Plugin for BasicPlugin {
//! fn new(_host: HostCallback) -> Self {
//! BasicPlugin
//! }
//!
//! fn get_info(&self) -> Info {
//! Info {
//! name: "Basic Plugin".to_string(),
//! unique_id: 1357, // Used by hosts to differentiate between plugins.
//!
//! ..Default::default()
//! }
//! }
//! }
//!
//! plugin_main!(BasicPlugin); // Important!
//! # fn main() {} // For `extern crate vst`
//! ```
//!
//! # Hosts
//!
//! ## `Host` Trait
//! All hosts must implement the [`Host` trait](host/trait.Host.html). To load a VST plugin, you
//! need to wrap your host in an `Arc<Mutex<T>>` wrapper for thread safety reasons. Along with the
//! plugin path, this can be passed to the [`PluginLoader::load`] method to create a plugin loader
//! which can spawn plugin instances.
//!
//! ## Example Host
//! ```no_run
//! extern crate vst;
//!
//! use std::sync::{Arc, Mutex};
//! use std::path::Path;
//!
//! use vst::host::{Host, PluginLoader};
//! use vst::plugin::Plugin;
//!
//! struct SampleHost;
//!
//! impl Host for SampleHost {
//! fn automate(&self, index: i32, value: f32) {
//! println!("Parameter {} had its value changed to {}", index, value);
//! }
//! }
//!
//! fn main() {
//! let host = Arc::new(Mutex::new(SampleHost));
//! let path = Path::new("/path/to/vst");
//!
//! let mut loader = PluginLoader::load(path, host.clone()).unwrap();
//! let mut instance = loader.instance().unwrap();
//!
//! println!("Loaded {}", instance.get_info().name);
//!
//! instance.init();
//! println!("Initialized instance!");
//!
//! println!("Closing instance...");
//! // Not necessary as the instance is shut down when it goes out of scope anyway.
//! // drop(instance);
//! }
//!
//! ```
//!
//! [`PluginLoader::load`]: host/struct.PluginLoader.html#method.load
//!
extern crate libc;
extern crate libloading;
extern crate num_enum;
extern crate num_traits;
#[macro_use]
extern crate log;
#[macro_use]
extern crate bitflags;
use std::ptr;
pub mod api;
pub mod buffer;
mod cache;
pub mod channels;
pub mod editor;
pub mod event;
pub mod host;
mod interfaces;
pub mod plugin;
pub mod prelude;
pub mod util;
use api::consts::VST_MAGIC;
use api::{AEffect, HostCallbackProc};
use cache::PluginCache;
use plugin::{HostCallback, Plugin};
/// Exports the necessary symbols for the plugin to be used by a VST host.
///
/// This macro takes a type which must implement the `Plugin` trait.
#[macro_export]
macro_rules! plugin_main {
($t:ty) => {
#[cfg(target_os = "macos")]
#[no_mangle]
pub extern "system" fn main_macho(callback: $crate::api::HostCallbackProc) -> *mut $crate::api::AEffect {
VSTPluginMain(callback)
}
#[cfg(target_os = "windows")]
#[allow(non_snake_case)]
#[no_mangle]
pub extern "system" fn MAIN(callback: $crate::api::HostCallbackProc) -> *mut $crate::api::AEffect {
VSTPluginMain(callback)
}
#[allow(non_snake_case)]
#[no_mangle]
pub extern "C" fn VSTPluginMain(callback: $crate::api::HostCallbackProc) -> *mut $crate::api::AEffect {
$crate::main::<$t>(callback)
}
};
}
/// Initializes a VST plugin and returns a raw pointer to an AEffect struct.
#[doc(hidden)]
pub fn main<T: Plugin>(callback: HostCallbackProc) -> *mut AEffect {
// Initialize as much of the AEffect as we can before creating the plugin.
// In particular, initialize all the function pointers, since initializing
// these to zero is undefined behavior.
let boxed_effect = Box::new(AEffect {
magic: VST_MAGIC,
dispatcher: interfaces::dispatch, // fn pointer
_process: interfaces::process_deprecated, // fn pointer
setParameter: interfaces::set_parameter, // fn pointer
getParameter: interfaces::get_parameter, // fn pointer
numPrograms: 0, // To be updated with plugin specific value.
numParams: 0, // To be updated with plugin specific value.
numInputs: 0, // To be updated with plugin specific value.
numOutputs: 0, // To be updated with plugin specific value.
flags: 0, // To be updated with plugin specific value.
reserved1: 0,
reserved2: 0,
initialDelay: 0, // To be updated with plugin specific value.
_realQualities: 0,
_offQualities: 0,
_ioRatio: 0.0,
object: ptr::null_mut(),
user: ptr::null_mut(),
uniqueId: 0, // To be updated with plugin specific value.
version: 0, // To be updated with plugin specific value.
processReplacing: interfaces::process_replacing, // fn pointer
processReplacingF64: interfaces::process_replacing_f64, //fn pointer
future: [0u8; 56],
});
let raw_effect = Box::into_raw(boxed_effect);
let host = HostCallback::wrap(callback, raw_effect);
if host.vst_version() == 0 {
// TODO: Better criteria would probably be useful here...
return ptr::null_mut();
}
trace!("Creating VST plugin instance...");
let mut plugin = T::new(host);
let info = plugin.get_info();
let params = plugin.get_parameter_object();
let editor = plugin.get_editor();
// Update AEffect in place
let effect = unsafe { &mut *raw_effect };
effect.numPrograms = info.presets;
effect.numParams = info.parameters;
effect.numInputs = info.inputs;
effect.numOutputs = info.outputs;
effect.flags = {
use api::PluginFlags;
let mut flag = PluginFlags::CAN_REPLACING;
if info.f64_precision {
flag |= PluginFlags::CAN_DOUBLE_REPLACING;
}
if editor.is_some() {
flag |= PluginFlags::HAS_EDITOR;
}
if info.preset_chunks {
flag |= PluginFlags::PROGRAM_CHUNKS;
}
if let plugin::Category::Synth = info.category {
flag |= PluginFlags::IS_SYNTH;
}
if info.silent_when_stopped {
flag |= PluginFlags::NO_SOUND_IN_STOP;
}
flag.bits()
};
effect.initialDelay = info.initial_delay;
effect.object = Box::into_raw(Box::new(Box::new(plugin) as Box<dyn Plugin>)) as *mut _;
effect.user = Box::into_raw(Box::new(PluginCache::new(&info, params, editor))) as *mut _;
effect.uniqueId = info.unique_id;
effect.version = info.version;
effect
}
#[cfg(test)]
mod tests {
use std::ptr;
use std::os::raw::c_void;
use crate::{
api::{consts::VST_MAGIC, AEffect},
interfaces,
plugin::{HostCallback, Info, Plugin},
};
struct TestPlugin;
impl Plugin for TestPlugin {
fn new(_host: HostCallback) -> Self {
TestPlugin
}
fn get_info(&self) -> Info {
Info {
name: "Test Plugin".to_string(),
vendor: "overdrivenpotato".to_string(),
presets: 1,
parameters: 1,
unique_id: 5678,
version: 1234,
initial_delay: 123,
..Default::default()
}
}
}
plugin_main!(TestPlugin);
extern "C" fn pass_callback(
_effect: *mut AEffect,
_opcode: i32,
_index: i32,
_value: isize,
_ptr: *mut c_void,
_opt: f32,
) -> isize {
1
}
extern "C" fn fail_callback(
_effect: *mut AEffect,
_opcode: i32,
_index: i32,
_value: isize,
_ptr: *mut c_void,
_opt: f32,
) -> isize {
0
}
#[cfg(target_os = "windows")]
#[test]
fn old_hosts() {
assert_eq!(MAIN(fail_callback), ptr::null_mut());
}
#[cfg(target_os = "macos")]
#[test]
fn old_hosts() {
assert_eq!(main_macho(fail_callback), ptr::null_mut());
}
#[test]
fn host_callback() {
assert_eq!(VSTPluginMain(fail_callback), ptr::null_mut());
}
#[test]
fn aeffect_created() {
let aeffect = VSTPluginMain(pass_callback);
assert!(!aeffect.is_null());
}
#[test]
fn plugin_drop() {
static mut DROP_TEST: bool = false;
impl Drop for TestPlugin {
fn drop(&mut self) {
unsafe {
DROP_TEST = true;
}
}
}
let aeffect = VSTPluginMain(pass_callback);
assert!(!aeffect.is_null());
unsafe { (*aeffect).drop_plugin() };
// Assert that the VST is shut down and dropped.
assert!(unsafe { DROP_TEST });
}
#[test]
fn plugin_no_drop() {
let aeffect = VSTPluginMain(pass_callback);
assert!(!aeffect.is_null());
// Make sure this doesn't crash.
unsafe { (*aeffect).drop_plugin() };
}
#[test]
fn plugin_deref() {
let aeffect = VSTPluginMain(pass_callback);
assert!(!aeffect.is_null());
let plugin = unsafe { (*aeffect).get_plugin() };
// Assert that deref works correctly.
assert!(plugin.get_info().name == "Test Plugin");
}
#[test]
fn aeffect_params() {
// Assert that 2 function pointers are equal.
macro_rules! assert_fn_eq {
($a:expr, $b:expr) => {
assert_eq!($a as usize, $b as usize);
};
}
let aeffect = unsafe { &mut *VSTPluginMain(pass_callback) };
assert_eq!(aeffect.magic, VST_MAGIC);
assert_fn_eq!(aeffect.dispatcher, interfaces::dispatch);
assert_fn_eq!(aeffect._process, interfaces::process_deprecated);
assert_fn_eq!(aeffect.setParameter, interfaces::set_parameter);
assert_fn_eq!(aeffect.getParameter, interfaces::get_parameter);
assert_eq!(aeffect.numPrograms, 1);
assert_eq!(aeffect.numParams, 1);
assert_eq!(aeffect.numInputs, 2);
assert_eq!(aeffect.numOutputs, 2);
assert_eq!(aeffect.reserved1, 0);
assert_eq!(aeffect.reserved2, 0);
assert_eq!(aeffect.initialDelay, 123);
assert_eq!(aeffect.uniqueId, 5678);
assert_eq!(aeffect.version, 1234);
assert_fn_eq!(aeffect.processReplacing, interfaces::process_replacing);
assert_fn_eq!(aeffect.processReplacingF64, interfaces::process_replacing_f64);
}
}