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 {
Automate = 0,
Version,
CurrentId,
Idle,
_PinConnected = 4,
_WantMidi = 6, GetTime,
ProcessEvents,
_SetTime,
_TempoAt,
_GetNumAutomatableParameters,
_GetParameterQuantization,
IOChanged,
_NeedIdle,
SizeWindow,
GetSampleRate,
GetBlockSize,
GetInputLatency,
GetOutputLatency,
_GetPreviousPlug,
_GetNextPlug,
_WillReplaceOrAccumulate,
GetCurrentProcessLevel,
GetAutomationState,
OfflineStart,
OfflineRead,
OfflineWrite,
OfflineGetCurrentPass,
OfflineGetCurrentMetaPass,
_SetOutputSampleRate,
_GetOutputSpeakerArrangement,
GetVendorString,
GetProductString,
GetVendorVersion,
VendorSpecific,
_SetIcon,
CanDo,
GetLanguage,
_OpenWindow,
_CloseWindow,
GetDirectory,
UpdateDisplay,
BeginEdit,
EndEdit,
OpenFileSelector,
CloseFileSelector,
_EditFile,
_GetChunkFile,
_GetInputSpeakerArrangement,
}
#[allow(unused_variables)]
pub trait Host {
fn automate(&self, index: i32, value: f32) {}
fn begin_edit(&self, index: i32) {}
fn end_edit(&self, index: i32) {}
fn get_plugin_id(&self) -> i32 {
0
}
fn idle(&self) {}
fn get_info(&self) -> (isize, String, String) {
(1, "vendor string".to_owned(), "product string".to_owned())
}
fn process_events(&self, events: &api::Events) {}
fn get_time_info(&self, mask: i32) -> Option<TimeInfo> {
None
}
fn get_block_size(&self) -> isize {
0
}
fn update_display(&self) {}
}
#[derive(Debug)]
pub enum PluginLoadError {
InvalidPath,
NotAPlugin,
InstanceFailed,
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 {}
pub struct PluginLoader<T: Host> {
main: PluginMain,
lib: Arc<Library>,
host: Arc<Mutex<T>>,
}
#[allow(dead_code)] 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);
}
}
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 }) }
}
impl Editor for EditorInstance {
fn size(&self) -> (i32, i32) {
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) {
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> {
pub fn load(path: &Path, host: Arc<Mutex<T>>) -> Result<PluginLoader<T>, PluginLoadError> {
unsafe {
let lib = match Library::new(path) {
Ok(l) => l,
Err(_) => return Err(PluginLoadError::InvalidPath),
};
Ok(PluginLoader {
main:
match lib.get(b"VSTPluginMain") {
Ok(s) => *s,
_ => return Err(PluginLoadError::NotAPlugin),
}
,
lib: Arc::new(lib),
host,
})
}
}
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>)
}
pub fn instance(&mut self) -> Result<PluginInstance, PluginLoadError> {
let effect = unsafe { self.call_main() };
if effect.is_null() {
return Err(PluginLoadError::InstanceFailed);
}
unsafe {
(*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;
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)
}
fn opcode(&self, opcode: plugin::OpCode) -> isize {
self.dispatch(opcode, 0, 0, ptr::null_mut(), 0.0)
}
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 {
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 {
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
}
fn get_preset_data(&self) -> Vec<u8> {
let mut ptr: *mut u8 = ptr::null_mut();
let len = self.dispatch(
plugin::OpCode::GetData,
1,
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> {
let mut ptr: *mut u8 = ptr::null_mut();
let len = self.dispatch(
plugin::OpCode::GetData,
0,
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,
);
}
}
pub struct HostBuffer<T: Float> {
inputs: Vec<*const T>,
outputs: Vec<*mut T>,
}
impl<T: Float> HostBuffer<T> {
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],
}
}
pub fn from_info(info: &Info) -> HostBuffer<T> {
HostBuffer::new(info.inputs as usize, info.outputs as usize)
}
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,
{
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");
}
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);
unsafe {
AudioBuffer::from_raw(
input_arrays.len(),
output_arrays.len(),
self.inputs.as_ptr(),
self.outputs.as_mut_ptr(),
length,
)
}
}
pub fn input_count(&self) -> usize {
self.inputs.len()
}
pub fn output_count(&self) -> usize {
self.outputs.len()
}
}
static mut LOAD_POINTER: *mut c_void = 0 as *mut c_void;
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 !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)
} else {
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 = {
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]);
}
}