mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-08 04:36:45 +01:00
wip: refactor into fewer crates
This commit is contained in:
parent
c367a0444e
commit
77703d83a5
105 changed files with 64 additions and 131 deletions
962
deps/vst/src/host.rs
vendored
Normal file
962
deps/vst/src/host.rs
vendored
Normal file
|
|
@ -0,0 +1,962 @@
|
|||
//! Host specific structures.
|
||||
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
use num_traits::Float;
|
||||
|
||||
use libloading::Library;
|
||||
use std::cell::UnsafeCell;
|
||||
use std::convert::TryFrom;
|
||||
use std::error::Error;
|
||||
use std::ffi::CString;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::os::raw::c_void;
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{fmt, ptr, slice};
|
||||
|
||||
use crate::{
|
||||
api::{self, consts::*, AEffect, PluginFlags, PluginMain, Supported, TimeInfo},
|
||||
buffer::AudioBuffer,
|
||||
channels::ChannelInfo,
|
||||
editor::{Editor, Rect},
|
||||
interfaces,
|
||||
plugin::{self, Category, HostCallback, Info, Plugin, PluginParameters},
|
||||
};
|
||||
|
||||
#[repr(i32)]
|
||||
#[derive(Clone, Copy, Debug, TryFromPrimitive, IntoPrimitive)]
|
||||
#[doc(hidden)]
|
||||
pub enum OpCode {
|
||||
/// [index]: parameter index
|
||||
/// [opt]: parameter value
|
||||
Automate = 0,
|
||||
/// [return]: host vst version (e.g. 2400 for VST 2.4)
|
||||
Version,
|
||||
/// [return]: current plugin ID (useful for shell plugins to figure out which plugin to load in
|
||||
/// `VSTPluginMain()`).
|
||||
CurrentId,
|
||||
/// No arguments. Give idle time to Host application, e.g. if plug-in editor is doing mouse
|
||||
/// tracking in a modal loop.
|
||||
Idle,
|
||||
/// Deprecated.
|
||||
_PinConnected = 4,
|
||||
|
||||
/// Deprecated.
|
||||
_WantMidi = 6, // Not a typo
|
||||
/// [value]: request mask. see `VstTimeInfoFlags`
|
||||
/// [return]: `VstTimeInfo` pointer or null if not supported.
|
||||
GetTime,
|
||||
/// Inform host that the plugin has MIDI events ready to be processed. Should be called at the
|
||||
/// end of `Plugin::process`.
|
||||
/// [ptr]: `VstEvents*` the events to be processed.
|
||||
/// [return]: 1 if supported and processed OK.
|
||||
ProcessEvents,
|
||||
/// Deprecated.
|
||||
_SetTime,
|
||||
/// Deprecated.
|
||||
_TempoAt,
|
||||
/// Deprecated.
|
||||
_GetNumAutomatableParameters,
|
||||
/// Deprecated.
|
||||
_GetParameterQuantization,
|
||||
|
||||
/// Notifies the host that the input/output setup has changed. This can allow the host to check
|
||||
/// numInputs/numOutputs or call `getSpeakerArrangement()`.
|
||||
/// [return]: 1 if supported.
|
||||
IOChanged,
|
||||
|
||||
/// Deprecated.
|
||||
_NeedIdle,
|
||||
|
||||
/// Request the host to resize the plugin window.
|
||||
/// [index]: new width.
|
||||
/// [value]: new height.
|
||||
SizeWindow,
|
||||
/// [return]: the current sample rate.
|
||||
GetSampleRate,
|
||||
/// [return]: the current block size.
|
||||
GetBlockSize,
|
||||
/// [return]: the input latency in samples.
|
||||
GetInputLatency,
|
||||
/// [return]: the output latency in samples.
|
||||
GetOutputLatency,
|
||||
|
||||
/// Deprecated.
|
||||
_GetPreviousPlug,
|
||||
/// Deprecated.
|
||||
_GetNextPlug,
|
||||
/// Deprecated.
|
||||
_WillReplaceOrAccumulate,
|
||||
|
||||
/// [return]: the current process level, see `VstProcessLevels`
|
||||
GetCurrentProcessLevel,
|
||||
/// [return]: the current automation state, see `VstAutomationStates`
|
||||
GetAutomationState,
|
||||
|
||||
/// The plugin is ready to begin offline processing.
|
||||
/// [index]: number of new audio files.
|
||||
/// [value]: number of audio files.
|
||||
/// [ptr]: `AudioFile*` the host audio files. Flags can be updated from plugin.
|
||||
OfflineStart,
|
||||
/// Called by the plugin to read data.
|
||||
/// [index]: (bool)
|
||||
/// VST offline processing allows a plugin to overwrite existing files. If this value is
|
||||
/// true then the host will read the original file's samples, but if it is false it will
|
||||
/// read the samples which the plugin has written via `OfflineWrite`
|
||||
/// [value]: see `OfflineOption`
|
||||
/// [ptr]: `OfflineTask*` describing the task.
|
||||
/// [return]: 1 on success
|
||||
OfflineRead,
|
||||
/// Called by the plugin to write data.
|
||||
/// [value]: see `OfflineOption`
|
||||
/// [ptr]: `OfflineTask*` describing the task.
|
||||
OfflineWrite,
|
||||
/// Unknown. Used in offline processing.
|
||||
OfflineGetCurrentPass,
|
||||
/// Unknown. Used in offline processing.
|
||||
OfflineGetCurrentMetaPass,
|
||||
|
||||
/// Deprecated.
|
||||
_SetOutputSampleRate,
|
||||
/// Deprecated.
|
||||
_GetOutputSpeakerArrangement,
|
||||
|
||||
/// Get the vendor string.
|
||||
/// [ptr]: `char*` for vendor string, limited to `MAX_VENDOR_STR_LEN`.
|
||||
GetVendorString,
|
||||
/// Get the product string.
|
||||
/// [ptr]: `char*` for vendor string, limited to `MAX_PRODUCT_STR_LEN`.
|
||||
GetProductString,
|
||||
/// [return]: vendor-specific version
|
||||
GetVendorVersion,
|
||||
/// Vendor specific handling.
|
||||
VendorSpecific,
|
||||
|
||||
/// Deprecated.
|
||||
_SetIcon,
|
||||
|
||||
/// Check if the host supports a feature.
|
||||
/// [ptr]: `char*` can do string
|
||||
/// [return]: 1 if supported
|
||||
CanDo,
|
||||
/// Get the language of the host.
|
||||
/// [return]: `VstHostLanguage`
|
||||
GetLanguage,
|
||||
|
||||
/// Deprecated.
|
||||
_OpenWindow,
|
||||
/// Deprecated.
|
||||
_CloseWindow,
|
||||
|
||||
/// Get the current directory.
|
||||
/// [return]: `FSSpec` on OS X, `char*` otherwise
|
||||
GetDirectory,
|
||||
/// Tell the host that the plugin's parameters have changed, refresh the UI.
|
||||
///
|
||||
/// No arguments.
|
||||
UpdateDisplay,
|
||||
/// Tell the host that if needed, it should record automation data for a control.
|
||||
///
|
||||
/// Typically called when the plugin editor begins changing a control.
|
||||
///
|
||||
/// [index]: index of the control.
|
||||
/// [return]: true on success.
|
||||
BeginEdit,
|
||||
/// A control is no longer being changed.
|
||||
///
|
||||
/// Typically called after the plugin editor is done.
|
||||
///
|
||||
/// [index]: index of the control.
|
||||
/// [return]: true on success.
|
||||
EndEdit,
|
||||
/// Open the host file selector.
|
||||
/// [ptr]: `VstFileSelect*`
|
||||
/// [return]: true on success.
|
||||
OpenFileSelector,
|
||||
/// Close the host file selector.
|
||||
/// [ptr]: `VstFileSelect*`
|
||||
/// [return]: true on success.
|
||||
CloseFileSelector,
|
||||
|
||||
/// Deprecated.
|
||||
_EditFile,
|
||||
/// Deprecated.
|
||||
/// [ptr]: char[2048] or sizeof (FSSpec).
|
||||
/// [return]: 1 if supported.
|
||||
_GetChunkFile,
|
||||
/// Deprecated.
|
||||
_GetInputSpeakerArrangement,
|
||||
}
|
||||
|
||||
/// Implemented by all VST hosts.
|
||||
#[allow(unused_variables)]
|
||||
pub trait Host {
|
||||
/// Automate a parameter; the value has been changed.
|
||||
fn automate(&self, index: i32, value: f32) {}
|
||||
|
||||
/// Signal that automation of a parameter started (the knob has been touched / mouse button down).
|
||||
fn begin_edit(&self, index: i32) {}
|
||||
|
||||
/// Signal that automation of a parameter ended (the knob is no longer been touched / mouse button up).
|
||||
fn end_edit(&self, index: i32) {}
|
||||
|
||||
/// Get the plugin ID of the currently loading plugin.
|
||||
///
|
||||
/// This is only useful for shell plugins where this value will change the plugin returned.
|
||||
/// `TODO: implement shell plugins`
|
||||
fn get_plugin_id(&self) -> i32 {
|
||||
// TODO: Handle this properly
|
||||
0
|
||||
}
|
||||
|
||||
/// An idle call.
|
||||
///
|
||||
/// This is useful when the plugin is doing something such as mouse tracking in the UI.
|
||||
fn idle(&self) {}
|
||||
|
||||
/// Get vendor and product information.
|
||||
///
|
||||
/// Returns a tuple in the form of `(version, vendor_name, product_name)`.
|
||||
fn get_info(&self) -> (isize, String, String) {
|
||||
(1, "vendor string".to_owned(), "product string".to_owned())
|
||||
}
|
||||
|
||||
/// Handle incoming events from the plugin.
|
||||
fn process_events(&self, events: &api::Events) {}
|
||||
|
||||
/// Get time information.
|
||||
fn get_time_info(&self, mask: i32) -> Option<TimeInfo> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Get block size.
|
||||
fn get_block_size(&self) -> isize {
|
||||
0
|
||||
}
|
||||
|
||||
/// Refresh UI after the plugin's parameters changed.
|
||||
///
|
||||
/// Note: some hosts will call some `PluginParameters` methods from within the `update_display`
|
||||
/// call, including `get_parameter`, `get_parameter_label`, `get_parameter_name`
|
||||
/// and `get_parameter_text`.
|
||||
fn update_display(&self) {}
|
||||
}
|
||||
|
||||
/// All possible errors that can occur when loading a VST plugin.
|
||||
#[derive(Debug)]
|
||||
pub enum PluginLoadError {
|
||||
/// Could not load given path.
|
||||
InvalidPath,
|
||||
|
||||
/// Given path is not a VST plugin.
|
||||
NotAPlugin,
|
||||
|
||||
/// Failed to create an instance of this plugin.
|
||||
///
|
||||
/// This can happen for many reasons, such as if the plugin requires a different version of
|
||||
/// the VST API to be used, or due to improper licensing.
|
||||
InstanceFailed,
|
||||
|
||||
/// The API version which the plugin used is not supported by this library.
|
||||
InvalidApiVersion,
|
||||
}
|
||||
|
||||
impl fmt::Display for PluginLoadError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::PluginLoadError::*;
|
||||
let description = match self {
|
||||
InvalidPath => "Could not open the requested path",
|
||||
NotAPlugin => "The given path does not contain a VST2.4 compatible library",
|
||||
InstanceFailed => "Failed to create a plugin instance",
|
||||
InvalidApiVersion => "The plugin API version is not compatible with this library",
|
||||
};
|
||||
write!(f, "{}", description)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for PluginLoadError {}
|
||||
|
||||
/// Wrapper for an externally loaded VST plugin.
|
||||
///
|
||||
/// The only functionality this struct provides is loading plugins, which can be done via the
|
||||
/// [`load`](#method.load) method.
|
||||
pub struct PluginLoader<T: Host> {
|
||||
main: PluginMain,
|
||||
lib: Arc<Library>,
|
||||
host: Arc<Mutex<T>>,
|
||||
}
|
||||
|
||||
/// An instance of an externally loaded VST plugin.
|
||||
#[allow(dead_code)] // To keep `lib` around.
|
||||
pub struct PluginInstance {
|
||||
params: Arc<PluginParametersInstance>,
|
||||
lib: Arc<Library>,
|
||||
info: Info,
|
||||
is_editor_active: bool,
|
||||
}
|
||||
|
||||
struct PluginParametersInstance {
|
||||
effect: UnsafeCell<*mut AEffect>,
|
||||
}
|
||||
|
||||
unsafe impl Send for PluginParametersInstance {}
|
||||
unsafe impl Sync for PluginParametersInstance {}
|
||||
|
||||
impl Drop for PluginInstance {
|
||||
fn drop(&mut self) {
|
||||
self.dispatch(plugin::OpCode::Shutdown, 0, 0, ptr::null_mut(), 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// The editor of an externally loaded VST plugin.
|
||||
struct EditorInstance {
|
||||
params: Arc<PluginParametersInstance>,
|
||||
is_open: bool,
|
||||
}
|
||||
|
||||
impl EditorInstance {
|
||||
fn get_rect(&self) -> Option<Rect> {
|
||||
let mut rect: *mut Rect = std::ptr::null_mut();
|
||||
let rect_ptr: *mut *mut Rect = &mut rect;
|
||||
|
||||
let result = self
|
||||
.params
|
||||
.dispatch(plugin::OpCode::EditorGetRect, 0, 0, rect_ptr as *mut c_void, 0.0);
|
||||
|
||||
if result == 0 || rect.is_null() {
|
||||
return None;
|
||||
}
|
||||
Some(unsafe { *rect }) // TODO: Who owns rect? Who should free the memory?
|
||||
}
|
||||
}
|
||||
|
||||
impl Editor for EditorInstance {
|
||||
fn size(&self) -> (i32, i32) {
|
||||
// Assuming coordinate origins from top-left
|
||||
match self.get_rect() {
|
||||
None => (0, 0),
|
||||
Some(rect) => ((rect.right - rect.left) as i32, (rect.bottom - rect.top) as i32),
|
||||
}
|
||||
}
|
||||
|
||||
fn position(&self) -> (i32, i32) {
|
||||
// Assuming coordinate origins from top-left
|
||||
match self.get_rect() {
|
||||
None => (0, 0),
|
||||
Some(rect) => (rect.left as i32, rect.top as i32),
|
||||
}
|
||||
}
|
||||
|
||||
fn close(&mut self) {
|
||||
self.params
|
||||
.dispatch(plugin::OpCode::EditorClose, 0, 0, ptr::null_mut(), 0.0);
|
||||
self.is_open = false;
|
||||
}
|
||||
|
||||
fn open(&mut self, parent: *mut c_void) -> bool {
|
||||
let result = self.params.dispatch(plugin::OpCode::EditorOpen, 0, 0, parent, 0.0);
|
||||
|
||||
let opened = result == 1;
|
||||
if opened {
|
||||
self.is_open = true;
|
||||
}
|
||||
|
||||
opened
|
||||
}
|
||||
|
||||
fn is_open(&mut self) -> bool {
|
||||
self.is_open
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Host> PluginLoader<T> {
|
||||
/// Load a plugin at the given path with the given host.
|
||||
///
|
||||
/// Because of the possibility of multi-threading problems that can occur when using plugins,
|
||||
/// the host must be passed in via an `Arc<Mutex<T>>` object. This makes sure that even if the
|
||||
/// plugins are multi-threaded no data race issues can occur.
|
||||
///
|
||||
/// Upon success, this method returns a [`PluginLoader`](.) object which you can use to call
|
||||
/// [`instance`](#method.instance) to create a new instance of the plugin.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::path::Path;
|
||||
/// # use std::sync::{Arc, Mutex};
|
||||
/// # use vst::host::{Host, PluginLoader};
|
||||
/// # let path = Path::new(".");
|
||||
/// # struct MyHost;
|
||||
/// # impl MyHost { fn new() -> MyHost { MyHost } }
|
||||
/// # impl Host for MyHost {
|
||||
/// # fn automate(&self, _: i32, _: f32) {}
|
||||
/// # fn get_plugin_id(&self) -> i32 { 0 }
|
||||
/// # }
|
||||
/// // ...
|
||||
/// let host = Arc::new(Mutex::new(MyHost::new()));
|
||||
///
|
||||
/// let mut plugin = PluginLoader::load(path, host.clone()).unwrap();
|
||||
///
|
||||
/// let instance = plugin.instance().unwrap();
|
||||
/// // ...
|
||||
/// ```
|
||||
///
|
||||
/// # Linux/Windows
|
||||
/// * This should be a path to the library, typically ending in `.so`/`.dll`.
|
||||
/// * Possible full path: `/home/overdrivenpotato/.vst/u-he/Zebra2.64.so`
|
||||
/// * Possible full path: `C:\Program Files (x86)\VSTPlugins\iZotope Ozone 5.dll`
|
||||
///
|
||||
/// # OS X
|
||||
/// * This should point to the mach-o file within the `.vst` bundle.
|
||||
/// * Plugin: `/Library/Audio/Plug-Ins/VST/iZotope Ozone 5.vst`
|
||||
/// * Possible full path:
|
||||
/// `/Library/Audio/Plug-Ins/VST/iZotope Ozone 5.vst/Contents/MacOS/PluginHooksVST`
|
||||
pub fn load(path: &Path, host: Arc<Mutex<T>>) -> Result<PluginLoader<T>, PluginLoadError> {
|
||||
// Try loading the library at the given path
|
||||
unsafe {
|
||||
let lib = match Library::new(path) {
|
||||
Ok(l) => l,
|
||||
Err(_) => return Err(PluginLoadError::InvalidPath),
|
||||
};
|
||||
|
||||
Ok(PluginLoader {
|
||||
main:
|
||||
// Search the library for the VSTAPI entry point
|
||||
match lib.get(b"VSTPluginMain") {
|
||||
Ok(s) => *s,
|
||||
_ => return Err(PluginLoadError::NotAPlugin),
|
||||
}
|
||||
,
|
||||
lib: Arc::new(lib),
|
||||
host,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Call the VST entry point and retrieve a (possibly null) pointer.
|
||||
unsafe fn call_main(&mut self) -> *mut AEffect {
|
||||
LOAD_POINTER = Box::into_raw(Box::new(Arc::clone(&self.host))) as *mut c_void;
|
||||
(self.main)(callback_wrapper::<T>)
|
||||
}
|
||||
|
||||
/// Try to create an instance of this VST plugin.
|
||||
///
|
||||
/// If the instance is successfully created, a [`PluginInstance`](struct.PluginInstance.html)
|
||||
/// is returned. This struct implements the [`Plugin` trait](../plugin/trait.Plugin.html).
|
||||
pub fn instance(&mut self) -> Result<PluginInstance, PluginLoadError> {
|
||||
// Call the plugin main function. This also passes the plugin main function as the closure
|
||||
// could not return an error if the symbol wasn't found
|
||||
let effect = unsafe { self.call_main() };
|
||||
|
||||
if effect.is_null() {
|
||||
return Err(PluginLoadError::InstanceFailed);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
// Move the host to the heap and add it to the `AEffect` struct for future reference
|
||||
(*effect).reserved1 = Box::into_raw(Box::new(Arc::clone(&self.host))) as isize;
|
||||
}
|
||||
|
||||
let instance = PluginInstance::new(effect, Arc::clone(&self.lib));
|
||||
|
||||
let api_ver = instance.dispatch(plugin::OpCode::GetApiVersion, 0, 0, ptr::null_mut(), 0.0);
|
||||
if api_ver >= 2400 {
|
||||
Ok(instance)
|
||||
} else {
|
||||
trace!("Could not load plugin with api version {}", api_ver);
|
||||
Err(PluginLoadError::InvalidApiVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PluginInstance {
|
||||
fn new(effect: *mut AEffect, lib: Arc<Library>) -> PluginInstance {
|
||||
use plugin::OpCode as op;
|
||||
|
||||
let params = Arc::new(PluginParametersInstance {
|
||||
effect: UnsafeCell::new(effect),
|
||||
});
|
||||
let mut plug = PluginInstance {
|
||||
params,
|
||||
lib,
|
||||
info: Default::default(),
|
||||
is_editor_active: false,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let effect: &AEffect = &*effect;
|
||||
let flags = PluginFlags::from_bits_truncate(effect.flags);
|
||||
|
||||
plug.info = Info {
|
||||
name: plug.read_string(op::GetProductName, MAX_PRODUCT_STR_LEN),
|
||||
vendor: plug.read_string(op::GetVendorName, MAX_VENDOR_STR_LEN),
|
||||
|
||||
presets: effect.numPrograms,
|
||||
parameters: effect.numParams,
|
||||
inputs: effect.numInputs,
|
||||
outputs: effect.numOutputs,
|
||||
|
||||
midi_inputs: 0,
|
||||
midi_outputs: 0,
|
||||
|
||||
unique_id: effect.uniqueId,
|
||||
version: effect.version,
|
||||
|
||||
category: Category::try_from(plug.opcode(op::GetCategory)).unwrap_or(Category::Unknown),
|
||||
|
||||
initial_delay: effect.initialDelay,
|
||||
|
||||
preset_chunks: flags.intersects(PluginFlags::PROGRAM_CHUNKS),
|
||||
f64_precision: flags.intersects(PluginFlags::CAN_DOUBLE_REPLACING),
|
||||
silent_when_stopped: flags.intersects(PluginFlags::NO_SOUND_IN_STOP),
|
||||
};
|
||||
}
|
||||
|
||||
plug
|
||||
}
|
||||
}
|
||||
|
||||
trait Dispatch {
|
||||
fn get_effect(&self) -> *mut AEffect;
|
||||
|
||||
/// Send a dispatch message to the plugin.
|
||||
fn dispatch(&self, opcode: plugin::OpCode, index: i32, value: isize, ptr: *mut c_void, opt: f32) -> isize {
|
||||
let dispatcher = unsafe { (*self.get_effect()).dispatcher };
|
||||
if (dispatcher as *mut u8).is_null() {
|
||||
panic!("Plugin was not loaded correctly.");
|
||||
}
|
||||
dispatcher(self.get_effect(), opcode.into(), index, value, ptr, opt)
|
||||
}
|
||||
|
||||
/// Send a lone opcode with no parameters.
|
||||
fn opcode(&self, opcode: plugin::OpCode) -> isize {
|
||||
self.dispatch(opcode, 0, 0, ptr::null_mut(), 0.0)
|
||||
}
|
||||
|
||||
/// Like `dispatch`, except takes a `&str` to send via `ptr`.
|
||||
fn write_string(&self, opcode: plugin::OpCode, index: i32, value: isize, string: &str, opt: f32) -> isize {
|
||||
let string = CString::new(string).expect("Invalid string data");
|
||||
self.dispatch(opcode, index, value, string.as_bytes().as_ptr() as *mut c_void, opt)
|
||||
}
|
||||
|
||||
fn read_string(&self, opcode: plugin::OpCode, max: usize) -> String {
|
||||
self.read_string_param(opcode, 0, 0, 0.0, max)
|
||||
}
|
||||
|
||||
fn read_string_param(&self, opcode: plugin::OpCode, index: i32, value: isize, opt: f32, max: usize) -> String {
|
||||
let mut buf = vec![0; max];
|
||||
self.dispatch(opcode, index, value, buf.as_mut_ptr() as *mut c_void, opt);
|
||||
String::from_utf8_lossy(&buf)
|
||||
.chars()
|
||||
.take_while(|c| *c != '\0')
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch for PluginInstance {
|
||||
fn get_effect(&self) -> *mut AEffect {
|
||||
self.params.get_effect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch for PluginParametersInstance {
|
||||
fn get_effect(&self) -> *mut AEffect {
|
||||
unsafe { *self.effect.get() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for PluginInstance {
|
||||
fn get_info(&self) -> plugin::Info {
|
||||
self.info.clone()
|
||||
}
|
||||
|
||||
fn new(_host: HostCallback) -> Self {
|
||||
// Plugin::new is only called on client side and PluginInstance is only used on host side
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn init(&mut self) {
|
||||
self.opcode(plugin::OpCode::Initialize);
|
||||
}
|
||||
|
||||
fn set_sample_rate(&mut self, rate: f32) {
|
||||
self.dispatch(plugin::OpCode::SetSampleRate, 0, 0, ptr::null_mut(), rate);
|
||||
}
|
||||
|
||||
fn set_block_size(&mut self, size: i64) {
|
||||
self.dispatch(plugin::OpCode::SetBlockSize, 0, size as isize, ptr::null_mut(), 0.0);
|
||||
}
|
||||
|
||||
fn resume(&mut self) {
|
||||
self.dispatch(plugin::OpCode::StateChanged, 0, 1, ptr::null_mut(), 0.0);
|
||||
}
|
||||
|
||||
fn suspend(&mut self) {
|
||||
self.dispatch(plugin::OpCode::StateChanged, 0, 0, ptr::null_mut(), 0.0);
|
||||
}
|
||||
|
||||
fn vendor_specific(&mut self, index: i32, value: isize, ptr: *mut c_void, opt: f32) -> isize {
|
||||
self.dispatch(plugin::OpCode::VendorSpecific, index, value, ptr, opt)
|
||||
}
|
||||
|
||||
fn can_do(&self, can_do: plugin::CanDo) -> Supported {
|
||||
let s: String = can_do.into();
|
||||
Supported::from(self.write_string(plugin::OpCode::CanDo, 0, 0, &s, 0.0))
|
||||
.expect("Invalid response received when querying plugin CanDo")
|
||||
}
|
||||
|
||||
fn get_tail_size(&self) -> isize {
|
||||
self.opcode(plugin::OpCode::GetTailSize)
|
||||
}
|
||||
|
||||
fn process(&mut self, buffer: &mut AudioBuffer<f32>) {
|
||||
if buffer.input_count() < self.info.inputs as usize {
|
||||
panic!("Too few inputs in AudioBuffer");
|
||||
}
|
||||
if buffer.output_count() < self.info.outputs as usize {
|
||||
panic!("Too few outputs in AudioBuffer");
|
||||
}
|
||||
unsafe {
|
||||
((*self.get_effect()).processReplacing)(
|
||||
self.get_effect(),
|
||||
buffer.raw_inputs().as_ptr() as *const *const _,
|
||||
buffer.raw_outputs().as_mut_ptr() as *mut *mut _,
|
||||
buffer.samples() as i32,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn process_f64(&mut self, buffer: &mut AudioBuffer<f64>) {
|
||||
if buffer.input_count() < self.info.inputs as usize {
|
||||
panic!("Too few inputs in AudioBuffer");
|
||||
}
|
||||
if buffer.output_count() < self.info.outputs as usize {
|
||||
panic!("Too few outputs in AudioBuffer");
|
||||
}
|
||||
unsafe {
|
||||
((*self.get_effect()).processReplacingF64)(
|
||||
self.get_effect(),
|
||||
buffer.raw_inputs().as_ptr() as *const *const _,
|
||||
buffer.raw_outputs().as_mut_ptr() as *mut *mut _,
|
||||
buffer.samples() as i32,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn process_events(&mut self, events: &api::Events) {
|
||||
self.dispatch(plugin::OpCode::ProcessEvents, 0, 0, events as *const _ as *mut _, 0.0);
|
||||
}
|
||||
|
||||
fn get_input_info(&self, input: i32) -> ChannelInfo {
|
||||
let mut props: MaybeUninit<api::ChannelProperties> = MaybeUninit::uninit();
|
||||
let ptr = props.as_mut_ptr() as *mut c_void;
|
||||
|
||||
self.dispatch(plugin::OpCode::GetInputInfo, input, 0, ptr, 0.0);
|
||||
|
||||
ChannelInfo::from(unsafe { props.assume_init() })
|
||||
}
|
||||
|
||||
fn get_output_info(&self, output: i32) -> ChannelInfo {
|
||||
let mut props: MaybeUninit<api::ChannelProperties> = MaybeUninit::uninit();
|
||||
let ptr = props.as_mut_ptr() as *mut c_void;
|
||||
|
||||
self.dispatch(plugin::OpCode::GetOutputInfo, output, 0, ptr, 0.0);
|
||||
|
||||
ChannelInfo::from(unsafe { props.assume_init() })
|
||||
}
|
||||
|
||||
fn get_parameter_object(&mut self) -> Arc<dyn PluginParameters> {
|
||||
Arc::clone(&self.params) as Arc<dyn PluginParameters>
|
||||
}
|
||||
|
||||
fn get_editor(&mut self) -> Option<Box<dyn Editor>> {
|
||||
if self.is_editor_active {
|
||||
// An editor is already active, the caller should be using the active editor instead of
|
||||
// requesting for a new one.
|
||||
return None;
|
||||
}
|
||||
|
||||
self.is_editor_active = true;
|
||||
Some(Box::new(EditorInstance {
|
||||
params: self.params.clone(),
|
||||
is_open: false,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl PluginParameters for PluginParametersInstance {
|
||||
fn change_preset(&self, preset: i32) {
|
||||
self.dispatch(plugin::OpCode::ChangePreset, 0, preset as isize, ptr::null_mut(), 0.0);
|
||||
}
|
||||
|
||||
fn get_preset_num(&self) -> i32 {
|
||||
self.opcode(plugin::OpCode::GetCurrentPresetNum) as i32
|
||||
}
|
||||
|
||||
fn set_preset_name(&self, name: String) {
|
||||
self.write_string(plugin::OpCode::SetCurrentPresetName, 0, 0, &name, 0.0);
|
||||
}
|
||||
|
||||
fn get_preset_name(&self, preset: i32) -> String {
|
||||
self.read_string_param(plugin::OpCode::GetPresetName, preset, 0, 0.0, MAX_PRESET_NAME_LEN)
|
||||
}
|
||||
|
||||
fn get_parameter_label(&self, index: i32) -> String {
|
||||
self.read_string_param(plugin::OpCode::GetParameterLabel, index, 0, 0.0, MAX_PARAM_STR_LEN)
|
||||
}
|
||||
|
||||
fn get_parameter_text(&self, index: i32) -> String {
|
||||
self.read_string_param(plugin::OpCode::GetParameterDisplay, index, 0, 0.0, MAX_PARAM_STR_LEN)
|
||||
}
|
||||
|
||||
fn get_parameter_name(&self, index: i32) -> String {
|
||||
self.read_string_param(plugin::OpCode::GetParameterName, index, 0, 0.0, MAX_PARAM_STR_LEN)
|
||||
}
|
||||
|
||||
fn get_parameter(&self, index: i32) -> f32 {
|
||||
unsafe { ((*self.get_effect()).getParameter)(self.get_effect(), index) }
|
||||
}
|
||||
|
||||
fn set_parameter(&self, index: i32, value: f32) {
|
||||
unsafe { ((*self.get_effect()).setParameter)(self.get_effect(), index, value) }
|
||||
}
|
||||
|
||||
fn can_be_automated(&self, index: i32) -> bool {
|
||||
self.dispatch(plugin::OpCode::CanBeAutomated, index, 0, ptr::null_mut(), 0.0) > 0
|
||||
}
|
||||
|
||||
fn string_to_parameter(&self, index: i32, text: String) -> bool {
|
||||
self.write_string(plugin::OpCode::StringToParameter, index, 0, &text, 0.0) > 0
|
||||
}
|
||||
|
||||
// TODO: Editor
|
||||
|
||||
fn get_preset_data(&self) -> Vec<u8> {
|
||||
// Create a pointer that can be updated from the plugin.
|
||||
let mut ptr: *mut u8 = ptr::null_mut();
|
||||
let len = self.dispatch(
|
||||
plugin::OpCode::GetData,
|
||||
1, /*preset*/
|
||||
0,
|
||||
&mut ptr as *mut *mut u8 as *mut c_void,
|
||||
0.0,
|
||||
);
|
||||
let slice = unsafe { slice::from_raw_parts(ptr, len as usize) };
|
||||
slice.to_vec()
|
||||
}
|
||||
|
||||
fn get_bank_data(&self) -> Vec<u8> {
|
||||
// Create a pointer that can be updated from the plugin.
|
||||
let mut ptr: *mut u8 = ptr::null_mut();
|
||||
let len = self.dispatch(
|
||||
plugin::OpCode::GetData,
|
||||
0, /*bank*/
|
||||
0,
|
||||
&mut ptr as *mut *mut u8 as *mut c_void,
|
||||
0.0,
|
||||
);
|
||||
let slice = unsafe { slice::from_raw_parts(ptr, len as usize) };
|
||||
slice.to_vec()
|
||||
}
|
||||
|
||||
fn load_preset_data(&self, data: &[u8]) {
|
||||
self.dispatch(
|
||||
plugin::OpCode::SetData,
|
||||
1,
|
||||
data.len() as isize,
|
||||
data.as_ptr() as *mut c_void,
|
||||
0.0,
|
||||
);
|
||||
}
|
||||
|
||||
fn load_bank_data(&self, data: &[u8]) {
|
||||
self.dispatch(
|
||||
plugin::OpCode::SetData,
|
||||
0,
|
||||
data.len() as isize,
|
||||
data.as_ptr() as *mut c_void,
|
||||
0.0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Used for constructing `AudioBuffer` instances on the host.
|
||||
///
|
||||
/// This struct contains all necessary allocations for an `AudioBuffer` apart
|
||||
/// from the actual sample arrays. This way, the inner processing loop can
|
||||
/// be allocation free even if `AudioBuffer` instances are repeatedly created.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use vst::host::HostBuffer;
|
||||
/// # use vst::plugin::Plugin;
|
||||
/// # fn test<P: Plugin>(plugin: &mut P) {
|
||||
/// let mut host_buffer: HostBuffer<f32> = HostBuffer::new(2, 2);
|
||||
/// let inputs = vec![vec![0.0; 1000]; 2];
|
||||
/// let mut outputs = vec![vec![0.0; 1000]; 2];
|
||||
/// let mut audio_buffer = host_buffer.bind(&inputs, &mut outputs);
|
||||
/// plugin.process(&mut audio_buffer);
|
||||
/// # }
|
||||
/// ```
|
||||
pub struct HostBuffer<T: Float> {
|
||||
inputs: Vec<*const T>,
|
||||
outputs: Vec<*mut T>,
|
||||
}
|
||||
|
||||
impl<T: Float> HostBuffer<T> {
|
||||
/// Create a `HostBuffer` for a given number of input and output channels.
|
||||
pub fn new(input_count: usize, output_count: usize) -> HostBuffer<T> {
|
||||
HostBuffer {
|
||||
inputs: vec![ptr::null(); input_count],
|
||||
outputs: vec![ptr::null_mut(); output_count],
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a `HostBuffer` for the number of input and output channels
|
||||
/// specified in an `Info` struct.
|
||||
pub fn from_info(info: &Info) -> HostBuffer<T> {
|
||||
HostBuffer::new(info.inputs as usize, info.outputs as usize)
|
||||
}
|
||||
|
||||
/// Bind sample arrays to the `HostBuffer` to create an `AudioBuffer` to pass to a plugin.
|
||||
///
|
||||
/// # Panics
|
||||
/// This function will panic if more inputs or outputs are supplied than the `HostBuffer`
|
||||
/// was created for, or if the sample arrays do not all have the same length.
|
||||
pub fn bind<'a, I, O>(&'a mut self, input_arrays: &[I], output_arrays: &mut [O]) -> AudioBuffer<'a, T>
|
||||
where
|
||||
I: AsRef<[T]> + 'a,
|
||||
O: AsMut<[T]> + 'a,
|
||||
{
|
||||
// Check that number of desired inputs and outputs fit in allocation
|
||||
if input_arrays.len() > self.inputs.len() {
|
||||
panic!("Too many inputs for HostBuffer");
|
||||
}
|
||||
if output_arrays.len() > self.outputs.len() {
|
||||
panic!("Too many outputs for HostBuffer");
|
||||
}
|
||||
|
||||
// Initialize raw pointers and find common length
|
||||
let mut length = None;
|
||||
for (i, input) in input_arrays.iter().map(|r| r.as_ref()).enumerate() {
|
||||
self.inputs[i] = input.as_ptr();
|
||||
match length {
|
||||
None => length = Some(input.len()),
|
||||
Some(old_length) => {
|
||||
if input.len() != old_length {
|
||||
panic!("Mismatching lengths of input arrays");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i, output) in output_arrays.iter_mut().map(|r| r.as_mut()).enumerate() {
|
||||
self.outputs[i] = output.as_mut_ptr();
|
||||
match length {
|
||||
None => length = Some(output.len()),
|
||||
Some(old_length) => {
|
||||
if output.len() != old_length {
|
||||
panic!("Mismatching lengths of output arrays");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let length = length.unwrap_or(0);
|
||||
|
||||
// Construct AudioBuffer
|
||||
unsafe {
|
||||
AudioBuffer::from_raw(
|
||||
input_arrays.len(),
|
||||
output_arrays.len(),
|
||||
self.inputs.as_ptr(),
|
||||
self.outputs.as_mut_ptr(),
|
||||
length,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Number of input channels supported by this `HostBuffer`.
|
||||
pub fn input_count(&self) -> usize {
|
||||
self.inputs.len()
|
||||
}
|
||||
|
||||
/// Number of output channels supported by this `HostBuffer`.
|
||||
pub fn output_count(&self) -> usize {
|
||||
self.outputs.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// HACK: a pointer to store the host so that it can be accessed from the `callback_wrapper`
|
||||
/// function passed to the plugin.
|
||||
///
|
||||
/// When the plugin is being loaded, a `Box<Arc<Mutex<T>>>` is transmuted to a `*mut c_void` pointer
|
||||
/// and placed here. When the plugin calls the callback during initialization, the host refers to
|
||||
/// this pointer to get a handle to the Host. After initialization, this pointer is invalidated and
|
||||
/// the host pointer is placed into a [reserved field] in the instance `AEffect` struct.
|
||||
///
|
||||
/// The issue with this approach is that if 2 plugins are simultaneously loaded with 2 different
|
||||
/// host instances, this might fail as one host may receive a pointer to the other one. In practice
|
||||
/// this is a rare situation as you normally won't have 2 separate host instances loading at once.
|
||||
///
|
||||
/// [reserved field]: ../api/struct.AEffect.html#structfield.reserved1
|
||||
static mut LOAD_POINTER: *mut c_void = 0 as *mut c_void;
|
||||
|
||||
/// Function passed to plugin to handle dispatching host opcodes.
|
||||
extern "C" fn callback_wrapper<T: Host>(
|
||||
effect: *mut AEffect,
|
||||
opcode: i32,
|
||||
index: i32,
|
||||
value: isize,
|
||||
ptr: *mut c_void,
|
||||
opt: f32,
|
||||
) -> isize {
|
||||
unsafe {
|
||||
// If the effect pointer is not null and the host pointer is not null, the plugin has
|
||||
// already been initialized
|
||||
if !effect.is_null() && (*effect).reserved1 != 0 {
|
||||
let reserved = (*effect).reserved1 as *const Arc<Mutex<T>>;
|
||||
let host = &*reserved;
|
||||
|
||||
let host = &mut *host.lock().unwrap();
|
||||
|
||||
interfaces::host_dispatch(host, effect, opcode, index, value, ptr, opt)
|
||||
// In this case, the plugin is still undergoing initialization and so `LOAD_POINTER` is
|
||||
// dereferenced
|
||||
} else {
|
||||
// Used only during the plugin initialization
|
||||
let host = LOAD_POINTER as *const Arc<Mutex<T>>;
|
||||
let host = &*host;
|
||||
let host = &mut *host.lock().unwrap();
|
||||
|
||||
interfaces::host_dispatch(host, effect, opcode, index, value, ptr, opt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::host::HostBuffer;
|
||||
|
||||
#[test]
|
||||
fn host_buffer() {
|
||||
const LENGTH: usize = 1_000_000;
|
||||
let mut host_buffer: HostBuffer<f32> = HostBuffer::new(2, 2);
|
||||
let input_left = vec![1.0; LENGTH];
|
||||
let input_right = vec![1.0; LENGTH];
|
||||
let mut output_left = vec![0.0; LENGTH];
|
||||
let mut output_right = vec![0.0; LENGTH];
|
||||
{
|
||||
let mut audio_buffer = {
|
||||
// Slices given to `bind` need not persist, but the sample arrays do.
|
||||
let inputs = [&input_left, &input_right];
|
||||
let mut outputs = [&mut output_left, &mut output_right];
|
||||
host_buffer.bind(&inputs, &mut outputs)
|
||||
};
|
||||
for (input, output) in audio_buffer.zip() {
|
||||
for (i, o) in input.iter().zip(output) {
|
||||
*o = *i * 2.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(output_left, vec![2.0; LENGTH]);
|
||||
assert_eq!(output_right, vec![2.0; LENGTH]);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue