Permalink
Cannot retrieve contributors at this time
//! Plugin specific structures. | |
use std::os::raw::c_void; | |
use std::ptr; | |
use std::sync::Arc; | |
use api; | |
use api::consts::VST_MAGIC; | |
use api::{AEffect, HostCallbackProc, Supported, TimeInfo}; | |
use buffer::AudioBuffer; | |
use channels::ChannelInfo; | |
use editor::Editor; | |
use host::{self, Host}; | |
/// Plugin type. Generally either Effect or Synth. | |
/// | |
/// Other types are not necessary to build a plugin and are only useful for the host to categorize | |
/// the plugin. | |
#[repr(usize)] | |
#[derive(Clone, Copy, Debug)] | |
pub enum Category { | |
/// Unknown / not implemented | |
Unknown, | |
/// Any effect | |
Effect, | |
/// VST instrument | |
Synth, | |
/// Scope, tuner, spectrum analyser, etc. | |
Analysis, | |
/// Dynamics, etc. | |
Mastering, | |
/// Panners, etc. | |
Spacializer, | |
/// Delays and Reverbs | |
RoomFx, | |
/// Dedicated surround processor. | |
SurroundFx, | |
/// Denoiser, etc. | |
Restoration, | |
/// Offline processing. | |
OfflineProcess, | |
/// Contains other plugins. | |
Shell, | |
/// Tone generator, etc. | |
Generator, | |
} | |
impl_clike!(Category); | |
#[repr(usize)] | |
#[derive(Clone, Copy, Debug)] | |
#[doc(hidden)] | |
pub enum OpCode { | |
/// Called when plugin is initialized. | |
Initialize, | |
/// Called when plugin is being shut down. | |
Shutdown, | |
/// [value]: preset number to change to. | |
ChangePreset, | |
/// [return]: current preset number. | |
GetCurrentPresetNum, | |
/// [ptr]: char array with new preset name, limited to `consts::MAX_PRESET_NAME_LEN`. | |
SetCurrentPresetName, | |
/// [ptr]: char buffer for current preset name, limited to `consts::MAX_PRESET_NAME_LEN`. | |
GetCurrentPresetName, | |
/// [index]: parameter | |
/// [ptr]: char buffer, limited to `consts::MAX_PARAM_STR_LEN` (e.g. "db", "ms", etc) | |
GetParameterLabel, | |
/// [index]: paramter | |
/// [ptr]: char buffer, limited to `consts::MAX_PARAM_STR_LEN` (e.g. "0.5", "ROOM", etc). | |
GetParameterDisplay, | |
/// [index]: parameter | |
/// [ptr]: char buffer, limited to `consts::MAX_PARAM_STR_LEN` (e.g. "Release", "Gain") | |
GetParameterName, | |
/// Deprecated. | |
_GetVu, | |
/// [opt]: new sample rate. | |
SetSampleRate, | |
/// [value]: new maximum block size. | |
SetBlockSize, | |
/// [value]: 1 when plugin enabled, 0 when disabled. | |
StateChanged, | |
/// [ptr]: Rect** receiving pointer to editor size. | |
EditorGetRect, | |
/// [ptr]: system dependent window pointer, eg HWND on Windows. | |
EditorOpen, | |
/// Close editor. No arguments. | |
EditorClose, | |
/// Deprecated. | |
_EditorDraw, | |
/// Deprecated. | |
_EditorMouse, | |
/// Deprecated. | |
_EditorKey, | |
/// Idle call from host. | |
EditorIdle, | |
/// Deprecated. | |
_EditorTop, | |
/// Deprecated. | |
_EditorSleep, | |
/// Deprecated. | |
_EditorIdentify, | |
/// [ptr]: pointer for chunk data address (void**). | |
/// [index]: 0 for bank, 1 for program | |
GetData, | |
/// [ptr]: data (void*) | |
/// [value]: data size in bytes | |
/// [index]: 0 for bank, 1 for program | |
SetData, | |
/// [ptr]: VstEvents* TODO: Events | |
ProcessEvents, | |
/// [index]: param index | |
/// [return]: 1=true, 0=false | |
CanBeAutomated, | |
/// [index]: param index | |
/// [ptr]: parameter string | |
/// [return]: true for success | |
StringToParameter, | |
/// Deprecated. | |
_GetNumCategories, | |
/// [index]: program name | |
/// [ptr]: char buffer for name, limited to `consts::MAX_PRESET_NAME_LEN` | |
/// [return]: true for success | |
GetPresetName, | |
/// Deprecated. | |
_CopyPreset, | |
/// Deprecated. | |
_ConnectIn, | |
/// Deprecated. | |
_ConnectOut, | |
/// [index]: input index | |
/// [ptr]: `VstPinProperties` | |
/// [return]: 1 if supported | |
GetInputInfo, | |
/// [index]: output index | |
/// [ptr]: `VstPinProperties` | |
/// [return]: 1 if supported | |
GetOutputInfo, | |
/// [return]: `PluginCategory` category. | |
GetCategory, | |
/// Deprecated. | |
_GetCurrentPosition, | |
/// Deprecated. | |
_GetDestinationBuffer, | |
/// [ptr]: `VstAudioFile` array | |
/// [value]: count | |
/// [index]: start flag | |
OfflineNotify, | |
/// [ptr]: `VstOfflineTask` array | |
/// [value]: count | |
OfflinePrepare, | |
/// [ptr]: `VstOfflineTask` array | |
/// [value]: count | |
OfflineRun, | |
/// [ptr]: `VstVariableIo` | |
/// [use]: used for variable I/O processing (offline e.g. timestretching) | |
ProcessVarIo, | |
/// TODO: implement | |
/// [value]: input `*mut VstSpeakerArrangement`. | |
/// [ptr]: output `*mut VstSpeakerArrangement`. | |
SetSpeakerArrangement, | |
/// Deprecated. | |
_SetBlocksizeAndSampleRate, | |
/// Soft bypass (automatable). | |
/// [value]: 1 = bypass, 0 = nobypass. | |
SoftBypass, | |
// [ptr]: buffer for effect name, limited to `kVstMaxEffectNameLen` | |
GetEffectName, | |
/// Deprecated. | |
_GetErrorText, | |
/// [ptr]: buffer for vendor name, limited to `consts::MAX_VENDOR_STR_LEN`. | |
GetVendorName, | |
/// [ptr]: buffer for product name, limited to `consts::MAX_PRODUCT_STR_LEN`. | |
GetProductName, | |
/// [return]: vendor specific version. | |
GetVendorVersion, | |
/// no definition, vendor specific. | |
VendorSpecific, | |
/// [ptr]: "Can do" string. | |
/// [return]: 1 = yes, 0 = maybe, -1 = no. | |
CanDo, | |
/// [return]: tail size (e.g. reverb time). 0 is defualt, 1 means no tail. | |
GetTailSize, | |
/// Deprecated. | |
_Idle, | |
/// Deprecated. | |
_GetIcon, | |
/// Deprecated. | |
_SetVewPosition, | |
/// [index]: param index | |
/// [ptr]: `*mut VstParamInfo` //TODO: Implement | |
/// [return]: 1 if supported | |
GetParamInfo, | |
/// Deprecated. | |
_KeysRequired, | |
/// [return]: 2400 for vst 2.4. | |
GetApiVersion, | |
/// [index]: ASCII char. | |
/// [value]: `Key` keycode. | |
/// [opt]: `flags::modifier_key` bitmask. | |
/// [return]: 1 if used. | |
EditorKeyDown, | |
/// [index]: ASCII char. | |
/// [value]: `Key` keycode. | |
/// [opt]: `flags::modifier_key` bitmask. | |
/// [return]: 1 if used. | |
EditorKeyUp, | |
/// [value]: 0 = circular, 1 = circular relative, 2 = linear. | |
EditorSetKnobMode, | |
/// [index]: MIDI channel. | |
/// [ptr]: `*mut MidiProgramName`. //TODO: Implement | |
/// [return]: number of used programs, 0 = unsupported. | |
GetMidiProgramName, | |
/// [index]: MIDI channel. | |
/// [ptr]: `*mut MidiProgramName`. //TODO: Implement | |
/// [return]: index of current program. | |
GetCurrentMidiProgram, | |
/// [index]: MIDI channel. | |
/// [ptr]: `*mut MidiProgramCategory`. //TODO: Implement | |
/// [return]: number of used categories. | |
GetMidiProgramCategory, | |
/// [index]: MIDI channel. | |
/// [return]: 1 if `MidiProgramName` or `MidiKeyName` has changed. //TODO: Implement | |
HasMidiProgramsChanged, | |
/// [index]: MIDI channel. | |
/// [ptr]: `*mut MidiKeyName`. //TODO: Implement | |
/// [return]: 1 = supported 0 = not. | |
GetMidiKeyName, | |
/// Called before a preset is loaded. | |
BeginSetPreset, | |
/// Called after a preset is loaded. | |
EndSetPreset, | |
/// [value]: inputs `*mut VstSpeakerArrangement` //TODO: Implement | |
/// [ptr]: Outputs `*mut VstSpeakerArrangement` | |
GetSpeakerArrangement, | |
/// [ptr]: buffer for plugin name, limited to `consts::MAX_PRODUCT_STR_LEN`. | |
/// [return]: next plugin's uniqueID. | |
ShellGetNextPlugin, | |
/// No args. Called once before start of process call. This indicates that the process call | |
/// will be interrupted (e.g. Host reconfiguration or bypass when plugin doesn't support | |
/// SoftBypass) | |
StartProcess, | |
/// No arguments. Called after stop of process call. | |
StopProcess, | |
/// [value]: number of samples to process. Called in offline mode before process. | |
SetTotalSampleToProcess, | |
/// [value]: pan law `PanLaw`. //TODO: Implement | |
/// [opt]: gain. | |
SetPanLaw, | |
/// [ptr]: `*mut VstPatchChunkInfo`. //TODO: Implement | |
/// [return]: -1 = bank cant be loaded, 1 = can be loaded, 0 = unsupported. | |
BeginLoadBank, | |
/// [ptr]: `*mut VstPatchChunkInfo`. //TODO: Implement | |
/// [return]: -1 = bank cant be loaded, 1 = can be loaded, 0 = unsupported. | |
BeginLoadPreset, | |
/// [value]: 0 if 32 bit, anything else if 64 bit. | |
SetPrecision, | |
/// [return]: number of used MIDI Inputs (1-15). | |
GetNumMidiInputs, | |
/// [return]: number of used MIDI Outputs (1-15). | |
GetNumMidiOutputs, | |
} | |
impl_clike!(OpCode); | |
/// A structure representing static plugin information. | |
#[derive(Clone, Debug)] | |
pub struct Info { | |
/// Plugin Name. | |
pub name: String, | |
/// Plugin Vendor. | |
pub vendor: String, | |
/// Number of different presets. | |
pub presets: i32, | |
/// Number of parameters. | |
pub parameters: i32, | |
/// Number of inputs. | |
pub inputs: i32, | |
/// Number of outputs. | |
pub outputs: i32, | |
/// Number of MIDI input channels (1-16), or 0 for the default of 16 channels. | |
pub midi_inputs: i32, | |
/// Number of MIDI output channels (1-16), or 0 for the default of 16 channels. | |
pub midi_outputs: i32, | |
/// Unique plugin ID. Can be registered with Steinberg to prevent conflicts with other plugins. | |
/// | |
/// This ID is used to identify a plugin during save and load of a preset and project. | |
pub unique_id: i32, | |
/// Plugin version (e.g. 0001 = `v0.0.0.1`, 1283 = `v1.2.8.3`). | |
pub version: i32, | |
/// Plugin category. Possible values are found in `enums::PluginCategory`. | |
pub category: Category, | |
/// Latency of the plugin in samples. | |
/// | |
/// This reports how many samples it takes for the plugin to create an output (group delay). | |
pub initial_delay: i32, | |
/// Indicates that preset data is handled in formatless chunks. | |
/// | |
/// If false, host saves and restores plugin by reading/writing parameter data. If true, it is | |
/// up to the plugin to manage saving preset data by implementing the | |
/// `{get, load}_{preset, bank}_chunks()` methods. Default is `false`. | |
pub preset_chunks: bool, | |
/// Indicates whether this plugin can process f64 based `AudioBuffer` buffers. | |
/// | |
/// Default is `false`. | |
pub f64_precision: bool, | |
/// If this is true, the plugin will not produce sound when the input is silence. | |
/// | |
/// Default is `false`. | |
pub silent_when_stopped: bool, | |
} | |
impl Default for Info { | |
fn default() -> Info { | |
Info { | |
name: "VST".to_string(), | |
vendor: String::new(), | |
presets: 1, // default preset | |
parameters: 0, | |
inputs: 2, // Stereo in,out | |
outputs: 2, | |
midi_inputs: 0, | |
midi_outputs: 0, | |
unique_id: 0, // This must be changed. | |
version: 1, // v0.0.0.1 | |
category: Category::Effect, | |
initial_delay: 0, | |
preset_chunks: false, | |
f64_precision: false, | |
silent_when_stopped: false, | |
} | |
} | |
} | |
/// Features which are optionally supported by a plugin. These are queried by the host at run time. | |
#[derive(Debug)] | |
#[allow(missing_docs)] | |
pub enum CanDo { | |
SendEvents, | |
SendMidiEvent, | |
ReceiveEvents, | |
ReceiveMidiEvent, | |
ReceiveTimeInfo, | |
Offline, | |
MidiProgramNames, | |
Bypass, | |
ReceiveSysExEvent, | |
//Bitwig specific? | |
MidiSingleNoteTuningChange, | |
MidiKeyBasedInstrumentControl, | |
Other(String), | |
} | |
impl CanDo { | |
// TODO: implement FromStr | |
#![allow(clippy::should_implement_trait)] | |
/// Converts a string to a `CanDo` instance. Any given string that does not match the predefined | |
/// values will return a `CanDo::Other` value. | |
pub fn from_str(s: &str) -> CanDo { | |
use self::CanDo::*; | |
match s { | |
"sendVstEvents" => SendEvents, | |
"sendVstMidiEvent" => SendMidiEvent, | |
"receiveVstEvents" => ReceiveEvents, | |
"receiveVstMidiEvent" => ReceiveMidiEvent, | |
"receiveVstTimeInfo" => ReceiveTimeInfo, | |
"offline" => Offline, | |
"midiProgramNames" => MidiProgramNames, | |
"bypass" => Bypass, | |
"receiveVstSysexEvent" => ReceiveSysExEvent, | |
"midiSingleNoteTuningChange" => MidiSingleNoteTuningChange, | |
"midiKeyBasedInstrumentControl" => MidiKeyBasedInstrumentControl, | |
otherwise => Other(otherwise.to_string()), | |
} | |
} | |
} | |
impl Into<String> for CanDo { | |
fn into(self) -> String { | |
use self::CanDo::*; | |
match self { | |
SendEvents => "sendVstEvents".to_string(), | |
SendMidiEvent => "sendVstMidiEvent".to_string(), | |
ReceiveEvents => "receiveVstEvents".to_string(), | |
ReceiveMidiEvent => "receiveVstMidiEvent".to_string(), | |
ReceiveTimeInfo => "receiveVstTimeInfo".to_string(), | |
Offline => "offline".to_string(), | |
MidiProgramNames => "midiProgramNames".to_string(), | |
Bypass => "bypass".to_string(), | |
ReceiveSysExEvent => "receiveVstSysexEvent".to_string(), | |
MidiSingleNoteTuningChange => "midiSingleNoteTuningChange".to_string(), | |
MidiKeyBasedInstrumentControl => "midiKeyBasedInstrumentControl".to_string(), | |
Other(other) => other, | |
} | |
} | |
} | |
/// Must be implemented by all VST plugins. | |
/// | |
/// All methods except `new` and `get_info` provide a default implementation | |
/// which does nothing and can be safely overridden. | |
/// | |
/// At any time, a plugin is in one of two states: *suspended* or *resumed*. | |
/// While a plugin is in the *suspended* state, various processing parameters, | |
/// such as the sample rate and block size, can be changed by the host, but no | |
/// audio processing takes place. While a plugin is in the *resumed* state, | |
/// audio processing methods and parameter access methods can be called by | |
/// the host. A plugin starts in the *suspended* state and is switched between | |
/// the states by the host using the `resume` and `suspend` methods. | |
/// | |
/// Hosts call methods of the plugin on two threads: the UI thread and the | |
/// processing thread. For this reason, the plugin API is separated into two | |
/// traits: The `Plugin` trait containing setup and processing methods, and | |
/// the `PluginParameters` trait containing methods for parameter access. | |
#[allow(unused_variables)] | |
pub trait Plugin: Send { | |
/// This method must return an `Info` struct. | |
fn get_info(&self) -> Info; | |
/// Called during initialization to pass a `HostCallback` to the plugin. | |
/// | |
/// This method can be overriden to set `host` as a field in the plugin struct. | |
/// | |
/// # Example | |
/// | |
/// ``` | |
/// // ... | |
/// # extern crate vst; | |
/// # #[macro_use] extern crate log; | |
/// # use vst::plugin::{Plugin, Info}; | |
/// use vst::plugin::HostCallback; | |
/// | |
/// struct ExamplePlugin { | |
/// host: HostCallback | |
/// } | |
/// | |
/// impl Plugin for ExamplePlugin { | |
/// fn new(host: HostCallback) -> ExamplePlugin { | |
/// ExamplePlugin { | |
/// host | |
/// } | |
/// } | |
/// | |
/// fn init(&mut self) { | |
/// info!("loaded with host vst version: {}", self.host.vst_version()); | |
/// } | |
/// | |
/// // ... | |
/// # fn get_info(&self) -> Info { | |
/// # Info { | |
/// # name: "Example Plugin".to_string(), | |
/// # ..Default::default() | |
/// # } | |
/// # } | |
/// } | |
/// | |
/// # fn main() {} | |
/// ``` | |
fn new(host: HostCallback) -> Self | |
where | |
Self: Sized; | |
/// Called when plugin is fully initialized. | |
/// | |
/// This method is only called while the plugin is in the *suspended* state. | |
fn init(&mut self) { | |
trace!("Initialized vst plugin."); | |
} | |
/// Called when sample rate is changed by host. | |
/// | |
/// This method is only called while the plugin is in the *suspended* state. | |
fn set_sample_rate(&mut self, rate: f32) {} | |
/// Called when block size is changed by host. | |
/// | |
/// This method is only called while the plugin is in the *suspended* state. | |
fn set_block_size(&mut self, size: i64) {} | |
/// Called to transition the plugin into the *resumed* state. | |
fn resume(&mut self) {} | |
/// Called to transition the plugin into the *suspended* state. | |
fn suspend(&mut self) {} | |
/// Vendor specific handling. | |
fn vendor_specific(&mut self, index: i32, value: isize, ptr: *mut c_void, opt: f32) -> isize { | |
0 | |
} | |
/// Return whether plugin supports specified action. | |
/// | |
/// This method is only called while the plugin is in the *suspended* state. | |
fn can_do(&self, can_do: CanDo) -> Supported { | |
info!("Host is asking if plugin can: {:?}.", can_do); | |
Supported::Maybe | |
} | |
/// Get the tail size of plugin when it is stopped. Used in offline processing as well. | |
fn get_tail_size(&self) -> isize { | |
0 | |
} | |
/// Process an audio buffer containing `f32` values. | |
/// | |
/// # Example | |
/// ```no_run | |
/// # use vst::plugin::{HostCallback, Info, Plugin}; | |
/// # use vst::buffer::AudioBuffer; | |
/// # | |
/// # struct ExamplePlugin; | |
/// # impl Plugin for ExamplePlugin { | |
/// # fn new(_host: HostCallback) -> Self { Self } | |
/// # | |
/// # fn get_info(&self) -> Info { Default::default() } | |
/// # | |
/// // Processor that clips samples above 0.4 or below -0.4: | |
/// fn process(&mut self, buffer: &mut AudioBuffer<f32>){ | |
/// // For each input and output | |
/// for (input, output) in buffer.zip() { | |
/// // For each input sample and output sample in buffer | |
/// for (in_sample, out_sample) in input.into_iter().zip(output.into_iter()) { | |
/// *out_sample = if *in_sample > 0.4 { | |
/// 0.4 | |
/// } else if *in_sample < -0.4 { | |
/// -0.4 | |
/// } else { | |
/// *in_sample | |
/// }; | |
/// } | |
/// } | |
/// } | |
/// # } | |
/// ``` | |
/// | |
/// This method is only called while the plugin is in the *resumed* state. | |
fn process(&mut self, buffer: &mut AudioBuffer<f32>) { | |
// For each input and output | |
for (input, output) in buffer.zip() { | |
// For each input sample and output sample in buffer | |
for (in_frame, out_frame) in input.iter().zip(output.iter_mut()) { | |
*out_frame = *in_frame; | |
} | |
} | |
} | |
/// Process an audio buffer containing `f64` values. | |
/// | |
/// # Example | |
/// ```no_run | |
/// # use vst::plugin::{HostCallback, Info, Plugin}; | |
/// # use vst::buffer::AudioBuffer; | |
/// # | |
/// # struct ExamplePlugin; | |
/// # impl Plugin for ExamplePlugin { | |
/// # fn new(_host: HostCallback) -> Self { Self } | |
/// # | |
/// # fn get_info(&self) -> Info { Default::default() } | |
/// # | |
/// // Processor that clips samples above 0.4 or below -0.4: | |
/// fn process_f64(&mut self, buffer: &mut AudioBuffer<f64>){ | |
/// // For each input and output | |
/// for (input, output) in buffer.zip() { | |
/// // For each input sample and output sample in buffer | |
/// for (in_sample, out_sample) in input.into_iter().zip(output.into_iter()) { | |
/// *out_sample = if *in_sample > 0.4 { | |
/// 0.4 | |
/// } else if *in_sample < -0.4 { | |
/// -0.4 | |
/// } else { | |
/// *in_sample | |
/// }; | |
/// } | |
/// } | |
/// } | |
/// # } | |
/// ``` | |
/// | |
/// This method is only called while the plugin is in the *resumed* state. | |
fn process_f64(&mut self, buffer: &mut AudioBuffer<f64>) { | |
// For each input and output | |
for (input, output) in buffer.zip() { | |
// For each input sample and output sample in buffer | |
for (in_frame, out_frame) in input.iter().zip(output.iter_mut()) { | |
*out_frame = *in_frame; | |
} | |
} | |
} | |
/// Handle incoming events sent from the host. | |
/// | |
/// This is always called before the start of `process` or `process_f64`. | |
/// | |
/// This method is only called while the plugin is in the *resumed* state. | |
fn process_events(&mut self, events: &api::Events) {} | |
/// Get a reference to the shared parameter object. | |
fn get_parameter_object(&mut self) -> Arc<dyn PluginParameters> { | |
Arc::new(DummyPluginParameters) | |
} | |
/// Get information about an input channel. Only used by some hosts. | |
fn get_input_info(&self, input: i32) -> ChannelInfo { | |
ChannelInfo::new( | |
format!("Input channel {}", input), | |
Some(format!("In {}", input)), | |
true, | |
None, | |
) | |
} | |
/// Get information about an output channel. Only used by some hosts. | |
fn get_output_info(&self, output: i32) -> ChannelInfo { | |
ChannelInfo::new( | |
format!("Output channel {}", output), | |
Some(format!("Out {}", output)), | |
true, | |
None, | |
) | |
} | |
/// Called one time before the start of process call. | |
/// | |
/// This indicates that the process call will be interrupted (due to Host reconfiguration | |
/// or bypass state when the plug-in doesn't support softBypass). | |
/// | |
/// This method is only called while the plugin is in the *resumed* state. | |
fn start_process(&mut self) {} | |
/// Called after the stop of process call. | |
/// | |
/// This method is only called while the plugin is in the *resumed* state. | |
fn stop_process(&mut self) {} | |
/// Return handle to plugin editor if supported. | |
/// The method need only return the object on the first call. | |
/// Subsequent calls can just return `None`. | |
/// | |
/// The editor object will typically contain an `Arc` reference to the parameter | |
/// object through which it can communicate with the audio processing. | |
fn get_editor(&mut self) -> Option<Box<dyn Editor>> { | |
None | |
} | |
} | |
/// Parameter object shared between the UI and processing threads. | |
/// Since access is shared, all methods take `self` by immutable reference. | |
/// All mutation must thus be performed using thread-safe interior mutability. | |
#[allow(unused_variables)] | |
pub trait PluginParameters: Sync { | |
/// Set the current preset to the index specified by `preset`. | |
/// | |
/// This method can be called on the processing thread for automation. | |
fn change_preset(&self, preset: i32) {} | |
/// Get the current preset index. | |
fn get_preset_num(&self) -> i32 { | |
0 | |
} | |
/// Set the current preset name. | |
fn set_preset_name(&self, name: String) {} | |
/// Get the name of the preset at the index specified by `preset`. | |
fn get_preset_name(&self, preset: i32) -> String { | |
"".to_string() | |
} | |
/// Get parameter label for parameter at `index` (e.g. "db", "sec", "ms", "%"). | |
fn get_parameter_label(&self, index: i32) -> String { | |
"".to_string() | |
} | |
/// Get the parameter value for parameter at `index` (e.g. "1.0", "150", "Plate", "Off"). | |
fn get_parameter_text(&self, index: i32) -> String { | |
format!("{:.3}", self.get_parameter(index)) | |
} | |
/// Get the name of parameter at `index`. | |
fn get_parameter_name(&self, index: i32) -> String { | |
format!("Param {}", index) | |
} | |
/// Get the value of paramater at `index`. Should be value between 0.0 and 1.0. | |
fn get_parameter(&self, index: i32) -> f32 { | |
0.0 | |
} | |
/// Set the value of parameter at `index`. `value` is between 0.0 and 1.0. | |
/// | |
/// This method can be called on the processing thread for automation. | |
fn set_parameter(&self, index: i32, value: f32) {} | |
/// Return whether parameter at `index` can be automated. | |
fn can_be_automated(&self, index: i32) -> bool { | |
true | |
} | |
/// Use String as input for parameter value. Used by host to provide an editable field to | |
/// adjust a parameter value. E.g. "100" may be interpreted as 100hz for parameter. Returns if | |
/// the input string was used. | |
fn string_to_parameter(&self, index: i32, text: String) -> bool { | |
false | |
} | |
/// If `preset_chunks` is set to true in plugin info, this should return the raw chunk data for | |
/// the current preset. | |
fn get_preset_data(&self) -> Vec<u8> { | |
Vec::new() | |
} | |
/// If `preset_chunks` is set to true in plugin info, this should return the raw chunk data for | |
/// the current plugin bank. | |
fn get_bank_data(&self) -> Vec<u8> { | |
Vec::new() | |
} | |
/// If `preset_chunks` is set to true in plugin info, this should load a preset from the given | |
/// chunk data. | |
fn load_preset_data(&self, data: &[u8]) {} | |
/// If `preset_chunks` is set to true in plugin info, this should load a preset bank from the | |
/// given chunk data. | |
fn load_bank_data(&self, data: &[u8]) {} | |
} | |
struct DummyPluginParameters; | |
impl PluginParameters for DummyPluginParameters {} | |
/// A reference to the host which allows the plugin to call back and access information. | |
/// | |
/// # Panics | |
/// | |
/// All methods in this struct will panic if the `HostCallback` was constructed using | |
/// `Default::default()` rather than being set to the value passed to `Plugin::new`. | |
#[derive(Copy, Clone)] | |
pub struct HostCallback { | |
callback: Option<HostCallbackProc>, | |
effect: *mut AEffect, | |
} | |
/// `HostCallback` implements `Default` so that the plugin can implement `Default` and have a | |
/// `HostCallback` field. | |
impl Default for HostCallback { | |
fn default() -> HostCallback { | |
HostCallback { | |
callback: None, | |
effect: ptr::null_mut(), | |
} | |
} | |
} | |
unsafe impl Send for HostCallback {} | |
unsafe impl Sync for HostCallback {} | |
impl HostCallback { | |
/// Wrap callback in a function to avoid using fn pointer notation. | |
#[doc(hidden)] | |
fn callback( | |
&self, | |
effect: *mut AEffect, | |
opcode: host::OpCode, | |
index: i32, | |
value: isize, | |
ptr: *mut c_void, | |
opt: f32, | |
) -> isize { | |
let callback = self.callback.unwrap_or_else(|| panic!("Host not yet initialized.")); | |
callback(effect, opcode.into(), index, value, ptr, opt) | |
} | |
/// Check whether the plugin has been initialized. | |
#[doc(hidden)] | |
fn is_effect_valid(&self) -> bool { | |
// Check whether `effect` points to a valid AEffect struct | |
unsafe { (*self.effect).magic as i32 == VST_MAGIC } | |
} | |
/// Create a new Host structure wrapping a host callback. | |
#[doc(hidden)] | |
pub fn wrap(callback: HostCallbackProc, effect: *mut AEffect) -> HostCallback { | |
HostCallback { | |
callback: Some(callback), | |
effect, | |
} | |
} | |
/// Get the VST API version supported by the host e.g. `2400 = VST 2.4`. | |
pub fn vst_version(&self) -> i32 { | |
self.callback(self.effect, host::OpCode::Version, 0, 0, ptr::null_mut(), 0.0) as i32 | |
} | |
/// Get the callback for calling host-specific extensions | |
#[inline(always)] | |
pub fn raw_callback(&self) -> Option<HostCallbackProc> { | |
self.callback | |
} | |
/// Get the effect pointer for calling host-specific extensions | |
#[inline(always)] | |
pub fn raw_effect(&self) -> *mut AEffect { | |
self.effect | |
} | |
fn read_string(&self, opcode: host::OpCode, max: usize) -> String { | |
self.read_string_param(opcode, 0, 0, 0.0, max) | |
} | |
fn read_string_param(&self, opcode: host::OpCode, index: i32, value: isize, opt: f32, max: usize) -> String { | |
let mut buf = vec![0; max]; | |
self.callback(self.effect, 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 Host for HostCallback { | |
/// Signal the host that the value for the parameter has changed. | |
/// | |
/// Make sure to also call `begin_edit` and `end_edit` when a parameter | |
/// has been touched. This is important for the host to determine | |
/// if a user interaction is happening and the automation should be recorded. | |
fn automate(&self, index: i32, value: f32) { | |
if self.is_effect_valid() { | |
// TODO: Investigate removing this check, should be up to host | |
self.callback(self.effect, host::OpCode::Automate, index, 0, ptr::null_mut(), value); | |
} | |
} | |
/// Signal the host the start of a parameter change a gesture (mouse down on knob dragging). | |
fn begin_edit(&self, index: i32) { | |
self.callback(self.effect, host::OpCode::BeginEdit, index, 0, ptr::null_mut(), 0.0); | |
} | |
/// Signal the host the end of a parameter change gesture (mouse up after knob dragging). | |
fn end_edit(&self, index: i32) { | |
self.callback(self.effect, host::OpCode::EndEdit, index, 0, ptr::null_mut(), 0.0); | |
} | |
fn get_plugin_id(&self) -> i32 { | |
self.callback(self.effect, host::OpCode::CurrentId, 0, 0, ptr::null_mut(), 0.0) as i32 | |
} | |
fn idle(&self) { | |
self.callback(self.effect, host::OpCode::Idle, 0, 0, ptr::null_mut(), 0.0); | |
} | |
fn get_info(&self) -> (isize, String, String) { | |
use api::consts::*; | |
let version = self.callback(self.effect, host::OpCode::CurrentId, 0, 0, ptr::null_mut(), 0.0) as isize; | |
let vendor_name = self.read_string(host::OpCode::GetVendorString, MAX_VENDOR_STR_LEN); | |
let product_name = self.read_string(host::OpCode::GetProductString, MAX_PRODUCT_STR_LEN); | |
(version, vendor_name, product_name) | |
} | |
/// Send events to the host. | |
/// | |
/// This should only be called within [`process`] or [`process_f64`]. Calling `process_events` | |
/// anywhere else is undefined behaviour and may crash some hosts. | |
/// | |
/// [`process`]: trait.Plugin.html#method.process | |
/// [`process_f64`]: trait.Plugin.html#method.process_f64 | |
fn process_events(&self, events: &api::Events) { | |
self.callback( | |
self.effect, | |
host::OpCode::ProcessEvents, | |
0, | |
0, | |
events as *const _ as *mut _, | |
0.0, | |
); | |
} | |
/// Request time information from Host. | |
/// | |
/// The mask parameter is composed of the same flags which will be found in the `flags` field of `TimeInfo` when returned. | |
/// That is, if you want the host's tempo, the parameter passed to `get_time_info()` should have the `TEMPO_VALID` flag set. | |
/// This request and delivery system is important, as a request like this may cause | |
/// significant calculations at the application's end, which may take a lot of our precious time. | |
/// This obviously means you should only set those flags that are required to get the information you need. | |
/// | |
/// Also please be aware that requesting information does not necessarily mean that that information is provided in return. | |
/// Check the flags field in the `TimeInfo` structure to see if your request was actually met. | |
fn get_time_info(&self, mask: i32) -> Option<TimeInfo> { | |
let opcode = host::OpCode::GetTime; | |
let mask = mask as isize; | |
let null = ptr::null_mut(); | |
let ptr = self.callback(self.effect, opcode, 0, mask, null, 0.0); | |
match ptr { | |
0 => None, | |
ptr => Some(unsafe { *(ptr as *const TimeInfo) }), | |
} | |
} | |
/// Get block size. | |
fn get_block_size(&self) -> isize { | |
self.callback(self.effect, host::OpCode::GetBlockSize, 0, 0, ptr::null_mut(), 0.0) | |
} | |
/// Refresh UI after the plugin's parameters changed. | |
fn update_display(&self) { | |
self.callback(self.effect, host::OpCode::UpdateDisplay, 0, 0, ptr::null_mut(), 0.0); | |
} | |
} | |
#[cfg(test)] | |
mod tests { | |
use std::ptr; | |
use plugin; | |
/// Create a plugin instance. | |
/// | |
/// This is a macro to allow you to specify attributes on the created struct. | |
macro_rules! make_plugin { | |
($($attr:meta) *) => { | |
use std::os::raw::c_void; | |
use main; | |
use api::AEffect; | |
use host::{Host, OpCode}; | |
use plugin::{HostCallback, Info, Plugin}; | |
$(#[$attr]) * | |
struct TestPlugin { | |
host: HostCallback | |
} | |
impl Plugin for TestPlugin { | |
fn get_info(&self) -> Info { | |
Info { | |
name: "Test Plugin".to_string(), | |
..Default::default() | |
} | |
} | |
fn new(host: HostCallback) -> TestPlugin { | |
TestPlugin { | |
host | |
} | |
} | |
fn init(&mut self) { | |
info!("Loaded with host vst version: {}", self.host.vst_version()); | |
assert_eq!(2400, self.host.vst_version()); | |
assert_eq!(9876, self.host.get_plugin_id()); | |
// Callback will assert these. | |
self.host.begin_edit(123); | |
self.host.automate(123, 12.3); | |
self.host.end_edit(123); | |
self.host.idle(); | |
} | |
} | |
#[allow(dead_code)] | |
fn instance() -> *mut AEffect { | |
extern "C" fn host_callback( | |
_effect: *mut AEffect, | |
opcode: i32, | |
index: i32, | |
_value: isize, | |
_ptr: *mut c_void, | |
opt: f32, | |
) -> isize { | |
let opcode = OpCode::from(opcode); | |
match opcode { | |
OpCode::BeginEdit => { | |
assert_eq!(index, 123); | |
0 | |
}, | |
OpCode::Automate => { | |
assert_eq!(index, 123); | |
assert_eq!(opt, 12.3); | |
0 | |
}, | |
OpCode::EndEdit => { | |
assert_eq!(index, 123); | |
0 | |
}, | |
OpCode::Version => 2400, | |
OpCode::CurrentId => 9876, | |
OpCode::Idle => 0, | |
_ => 0 | |
} | |
} | |
main::<TestPlugin>(host_callback) | |
} | |
} | |
} | |
make_plugin!(derive(Default)); | |
#[test] | |
#[should_panic] | |
fn null_panic() { | |
make_plugin!(/* no `derive(Default)` */); | |
impl Default for TestPlugin { | |
fn default() -> TestPlugin { | |
let plugin = TestPlugin { | |
host: Default::default(), | |
}; | |
// Should panic | |
let version = plugin.host.vst_version(); | |
info!("Loaded with host vst version: {}", version); | |
plugin | |
} | |
} | |
TestPlugin::default(); | |
} | |
#[test] | |
fn host_callbacks() { | |
let aeffect = instance(); | |
(unsafe { (*aeffect).dispatcher })(aeffect, plugin::OpCode::Initialize.into(), 0, 0, ptr::null_mut(), 0.0); | |
} | |
} |