diff --git a/Cargo.toml b/Cargo.toml index 06b23615..4711a5ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vst" -version = "0.3.0" +version = "0.4.0" edition = "2021" authors = [ "Marko Mijalkovic ", diff --git a/src/api.rs b/src/api.rs index 44a1a78a..8aa69780 100644 --- a/src/api.rs +++ b/src/api.rs @@ -738,6 +738,72 @@ impl Default for SmpteFrameRate { } } +/// Parameter properties. +#[repr(C)] +pub struct VstParameterProperties { + /// Float step. + pub step_float: f32, + /// Small float step. + pub small_step_float: f32, + /// Large float step. + pub large_step_float: f32, + /// Parameter label. + pub label: [u8; 64], + /// Flags found in `VstParameterFlags`. + pub flags: i32, + /// Integer minimum. + pub min_integer: i32, + /// Integer maximum. + pub max_integer: i32, + /// Integer step. + pub step_integer: i32, + /// Large integer step. + pub large_step_integer: i32, + /// Short label. + /// + /// Recommended: 6 characters + delimiter. + pub short_label: [u8; 8], + /// Index where this parameter should be displayed (starting with 0). + /// + /// Requires flag `SUPPORTS_DISPLAY_INDEX`. + pub display_index: i16, + /// Parameter category. + /// + /// 0 means no category, else category index + 1. + pub category: i16, + /// Number of parameters in category. + pub num_parameters_in_category: i16, + /// Reserved for future use (please zero). + pub reserved: i16, + /// Category label. + pub category_label: [u8; 24], + /// Reserved for future use (please zero). + pub future: [u8; 16], +} + +impl Default for VstParameterProperties { + fn default() -> Self { + Self { + step_float: 0.0, + small_step_float: 0.0, + large_step_float: 0.0, + label: [0; 64], + flags: 0, + min_integer: 0, + max_integer: 0, + step_integer: 0, + large_step_integer: 0, + short_label: [0; 8], + display_index: 0, + category: 0, + num_parameters_in_category: 0, + reserved: 0, + category_label: [0; 24], + future: [0; 16], + } + } +} + bitflags! { /// Flags for VST channels. pub struct ChannelFlags: i32 { @@ -828,6 +894,28 @@ bitflags! { } } +bitflags! { + /// Flags for VST parameters. + /// + /// Used in `VstParameterProperties`. + pub struct VstParameterFlags: i32 { + /// Parameter is a switch (on or off). + const IS_SWITCH = 1; + /// Indicates that `min_integer` and `max_integer` are valid. + const USES_INTEGER_MIN_MAX = 1 << 1; + /// Indicates that `step_float`, `small_step_float` and `large_step_float` are valid. + const USES_FLOAT_STEP = 1 << 2; + /// Indicates that `step_integer` and `large_step_integer` are valid. + const USES_INT_STEP = 1 << 3; + /// Indicates that `display_index` is valid. + const SUPPORTS_DISPLAY_INDEX = 1 << 4; + /// Indicates that `category`, `num_parameters_in_category` and `category_label` are valid. + const SUPPORTS_DISPLAY_CATEGORY = 1 << 5; + /// If the parameter value can ramp up or down. + const CAN_RAMP = 1 << 6; + } +} + #[cfg(test)] mod tests { use super::super::event; diff --git a/src/interfaces.rs b/src/interfaces.rs index 6b5261e7..20a281c5 100644 --- a/src/interfaces.rs +++ b/src/interfaces.rs @@ -6,6 +6,7 @@ use std::cell::Cell; use std::os::raw::{c_char, c_void}; use std::{mem, slice}; +use crate::internal_util::firewall; use crate::{ api::{self, consts::*, AEffect, TimeInfo}, buffer::AudioBuffer, @@ -28,6 +29,15 @@ pub extern "C" fn process_replacing( raw_inputs: *const *const f32, raw_outputs: *mut *mut f32, samples: i32, +) { + firewall(|| process_replacing_internal(effect, raw_inputs, raw_outputs, samples)); +} + +fn process_replacing_internal( + effect: *mut AEffect, + raw_inputs: *const *const f32, + raw_outputs: *mut *mut f32, + samples: i32, ) { // Handle to the VST let plugin = unsafe { (*effect).get_plugin() }; @@ -44,6 +54,16 @@ pub extern "C" fn process_replacing_f64( raw_inputs: *const *const f64, raw_outputs: *mut *mut f64, samples: i32, +) { + firewall(|| process_replacing_f64_internal(effect, raw_inputs, raw_outputs, samples)); +} + +/// VST2.4 replacing function with `f64` values. +fn process_replacing_f64_internal( + effect: *mut AEffect, + raw_inputs: *const *const f64, + raw_outputs: *mut *mut f64, + samples: i32, ) { let plugin = unsafe { (*effect).get_plugin() }; let info = unsafe { (*effect).get_info() }; @@ -55,11 +75,19 @@ pub extern "C" fn process_replacing_f64( /// VST2.4 set parameter function. pub extern "C" fn set_parameter(effect: *mut AEffect, index: i32, value: f32) { + firewall(|| set_parameter_internal(effect, index, value)); +} + +fn set_parameter_internal(effect: *mut AEffect, index: i32, value: f32) { unsafe { (*effect).get_params() }.set_parameter(index, value); } /// VST2.4 get parameter function. pub extern "C" fn get_parameter(effect: *mut AEffect, index: i32) -> f32 { + firewall(|| get_parameter_internal(effect, index)).unwrap_or(0.0) +} + +fn get_parameter_internal(effect: *mut AEffect, index: i32) -> f32 { unsafe { (*effect).get_params() }.get_parameter(index) } @@ -73,7 +101,7 @@ fn copy_string(dst: *mut c_void, src: &str, max: usize) -> isize { let dst = dst as *mut c_void; memset(dst, 0, max); - memcpy(dst, src.as_ptr() as *const c_void, min(max, src.as_bytes().len())); + memcpy(dst, src.as_ptr() as *const c_void, min(max - 1, src.as_bytes().len())); } 1 // Success @@ -88,6 +116,10 @@ pub extern "C" fn dispatch( ptr: *mut c_void, opt: f32, ) -> isize { + firewall(|| dispatch_internal(effect, opcode, index, value, ptr, opt)).unwrap_or(0) +} + +fn dispatch_internal(effect: *mut AEffect, opcode: i32, index: i32, value: isize, ptr: *mut c_void, opt: f32) -> isize { use crate::plugin::{CanDo, OpCode}; // Convert passed in opcode to enum @@ -244,7 +276,16 @@ pub extern "C" fn dispatch( } } - //OpCode::GetParamInfo => { /*TODO*/ } + Ok(OpCode::GetParamInfo) => { + if let Some(info) = get_plugin().get_parameter_info(index) { + let ptr = ptr as *mut api::VstParameterProperties; + unsafe { + *ptr = info.into(); + } + return 1; + } + } + Ok(OpCode::GetApiVersion) => return 2400, Ok(OpCode::EditorKeyDown) => { diff --git a/src/internal_util.rs b/src/internal_util.rs new file mode 100644 index 00000000..494c80b5 --- /dev/null +++ b/src/internal_util.rs @@ -0,0 +1,9 @@ +use std::panic::{catch_unwind, AssertUnwindSafe}; + +/// Should be placed around each C entry point to not crash the DAW on a panic. +pub fn firewall(f: F) -> Option +where + F: FnOnce() -> R, +{ + catch_unwind(AssertUnwindSafe(f)).ok() +} diff --git a/src/lib.rs b/src/lib.rs index b0c26de3..c2fe99b7 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -127,10 +127,12 @@ pub mod editor; pub mod event; pub mod host; mod interfaces; +mod internal_util; pub mod plugin; pub mod prelude; pub mod util; +use crate::internal_util::firewall; use api::consts::VST_MAGIC; use api::{AEffect, HostCallbackProc}; use cache::PluginCache; @@ -166,6 +168,10 @@ macro_rules! plugin_main { /// Initializes a VST plugin and returns a raw pointer to an AEffect struct. #[doc(hidden)] pub fn main(callback: HostCallbackProc) -> *mut AEffect { + firewall(|| main_internal::(callback)).unwrap_or(ptr::null_mut()) +} + +fn main_internal(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. diff --git a/src/plugin.rs b/src/plugin.rs index 8c9be409..5dd6f521 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -383,6 +383,110 @@ impl Default for Info { } } +/// A structure representing information about a specific plug-in parameter. +/// +/// # Example +/// +/// ```no_run +/// # use vst::plugin::{PluginParameterCharacter, PluginParameterInfo}; +/// let mut info = PluginParameterInfo::default(); +/// info.character = PluginParameterCharacter::Switch; +/// ``` +#[derive(Clone, Debug)] +#[non_exhaustive] +pub struct PluginParameterInfo { + /// Defines the parameter character. + pub character: PluginParameterCharacter, + /// If the parameter value can ramp up or down. + pub can_ramp: bool, +} + +impl Default for PluginParameterInfo { + fn default() -> Self { + Self { + character: PluginParameterCharacter::Continuous { steps: None }, + can_ramp: false, + } + } +} + +/// Character of a plug-in parameter. +#[derive(Clone, Debug)] +#[non_exhaustive] +pub enum PluginParameterCharacter { + /// Parameter is a switch (on or off). + Switch, + /// Parameter has a continuous value range (floating point numbers). + Continuous { + /// Step sizes. + steps: Option, + }, + /// Parameter has a discrete value range (integers). + Discrete { + /// Minimum value. + min: i32, + /// Maximum value. + max: i32, + /// Step sizes. + steps: Option, + }, +} + +/// Custom step sizes for continuous parameters. +#[derive(Clone, Debug)] +pub struct FloatSteps { + /// Normal step. + pub step: f32, + /// Small step. + pub small_step: f32, + /// Large step. + pub large_step: f32, +} + +/// Custom step sizes for discrete parameters. +#[derive(Clone, Debug)] +pub struct IntegerSteps { + /// Normal step. + pub step: i32, + /// Large step. + pub large_step: i32, +} + +impl From for api::VstParameterProperties { + fn from(info: PluginParameterInfo) -> api::VstParameterProperties { + let mut props = api::VstParameterProperties::default(); + let mut flags = api::VstParameterFlags::empty(); + if info.can_ramp { + flags |= api::VstParameterFlags::CAN_RAMP; + } + match info.character { + PluginParameterCharacter::Switch => { + flags |= api::VstParameterFlags::IS_SWITCH; + } + PluginParameterCharacter::Continuous { steps } => { + if let Some(steps) = steps { + flags |= api::VstParameterFlags::USES_FLOAT_STEP; + props.small_step_float = steps.small_step; + props.step_float = steps.step; + props.large_step_float = steps.large_step; + } + } + PluginParameterCharacter::Discrete { min, max, steps } => { + flags |= api::VstParameterFlags::USES_INTEGER_MIN_MAX; + props.min_integer = min; + props.max_integer = max; + if let Some(steps) = steps { + flags |= api::VstParameterFlags::USES_INT_STEP; + props.step_integer = steps.step; + props.large_step_integer = steps.large_step; + } + } + } + props.flags = flags.bits(); + props + } +} + /// Features which are optionally supported by a plugin. These are queried by the host at run time. #[derive(Debug)] #[allow(missing_docs)] @@ -676,6 +780,11 @@ pub trait Plugin: Send { ) } + /// Returns additional information about the parameter at the given index. + fn get_parameter_info(&self, index: i32) -> Option { + None + } + /// Called one time before the start of process call. /// /// This indicates that the process call will be interrupted (due to Host reconfiguration