use num_enum::{IntoPrimitive, TryFromPrimitive};
use std::os::raw::c_void;
use std::ptr;
use std::sync::Arc;
use crate::{
api::{self, consts::VST_MAGIC, AEffect, HostCallbackProc, Supported, TimeInfo},
buffer::AudioBuffer,
channels::ChannelInfo,
editor::Editor,
host::{self, Host},
};
#[repr(isize)]
#[derive(Clone, Copy, Debug, TryFromPrimitive, IntoPrimitive)]
pub enum Category {
Unknown,
Effect,
Synth,
Analysis,
Mastering,
Spacializer,
RoomFx,
SurroundFx,
Restoration,
OfflineProcess,
Shell,
Generator,
}
#[repr(i32)]
#[derive(Clone, Copy, Debug, TryFromPrimitive, IntoPrimitive)]
#[doc(hidden)]
pub enum OpCode {
Initialize,
Shutdown,
ChangePreset,
GetCurrentPresetNum,
SetCurrentPresetName,
GetCurrentPresetName,
GetParameterLabel,
GetParameterDisplay,
GetParameterName,
_GetVu,
SetSampleRate,
SetBlockSize,
StateChanged,
EditorGetRect,
EditorOpen,
EditorClose,
_EditorDraw,
_EditorMouse,
_EditorKey,
EditorIdle,
_EditorTop,
_EditorSleep,
_EditorIdentify,
GetData,
SetData,
ProcessEvents,
CanBeAutomated,
StringToParameter,
_GetNumCategories,
GetPresetName,
_CopyPreset,
_ConnectIn,
_ConnectOut,
GetInputInfo,
GetOutputInfo,
GetCategory,
_GetCurrentPosition,
_GetDestinationBuffer,
OfflineNotify,
OfflinePrepare,
OfflineRun,
ProcessVarIo,
SetSpeakerArrangement,
_SetBlocksizeAndSampleRate,
SoftBypass,
GetEffectName,
_GetErrorText,
GetVendorName,
GetProductName,
GetVendorVersion,
VendorSpecific,
CanDo,
GetTailSize,
_Idle,
_GetIcon,
_SetVewPosition,
GetParamInfo,
_KeysRequired,
GetApiVersion,
EditorKeyDown,
EditorKeyUp,
EditorSetKnobMode,
GetMidiProgramName,
GetCurrentMidiProgram,
GetMidiProgramCategory,
HasMidiProgramsChanged,
GetMidiKeyName,
BeginSetPreset,
EndSetPreset,
GetSpeakerArrangement,
ShellGetNextPlugin,
StartProcess,
StopProcess,
SetTotalSampleToProcess,
SetPanLaw,
BeginLoadBank,
BeginLoadPreset,
SetPrecision,
GetNumMidiInputs,
GetNumMidiOutputs,
}
#[derive(Clone, Debug)]
pub struct Info {
pub name: String,
pub vendor: String,
pub presets: i32,
pub parameters: i32,
pub inputs: i32,
pub outputs: i32,
pub midi_inputs: i32,
pub midi_outputs: i32,
pub unique_id: i32,
pub version: i32,
pub category: Category,
pub initial_delay: i32,
pub preset_chunks: bool,
pub f64_precision: bool,
pub silent_when_stopped: bool,
}
impl Default for Info {
fn default() -> Info {
Info {
name: "VST".to_string(),
vendor: String::new(),
presets: 1, parameters: 0,
inputs: 2, outputs: 2,
midi_inputs: 0,
midi_outputs: 0,
unique_id: 0, version: 1,
category: Category::Effect,
initial_delay: 0,
preset_chunks: false,
f64_precision: false,
silent_when_stopped: false,
}
}
}
#[derive(Debug)]
#[allow(missing_docs)]
pub enum CanDo {
SendEvents,
SendMidiEvent,
ReceiveEvents,
ReceiveMidiEvent,
ReceiveTimeInfo,
Offline,
MidiProgramNames,
Bypass,
ReceiveSysExEvent,
MidiSingleNoteTuningChange,
MidiKeyBasedInstrumentControl,
Other(String),
}
impl CanDo {
#![allow(clippy::should_implement_trait)]
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,
}
}
}
#[cfg_attr(
not(feature = "disable_deprecation_warning"),
deprecated = "This crate has been deprecated. See https://github.com/RustAudio/vst-rs for more information."
)]
#[allow(unused_variables)]
pub trait Plugin: Send {
fn get_info(&self) -> Info;
fn new(host: HostCallback) -> Self
where
Self: Sized;
fn init(&mut self) {
trace!("Initialized vst plugin.");
}
fn set_sample_rate(&mut self, rate: f32) {}
fn set_block_size(&mut self, size: i64) {}
fn resume(&mut self) {}
fn suspend(&mut self) {}
fn vendor_specific(&mut self, index: i32, value: isize, ptr: *mut c_void, opt: f32) -> isize {
0
}
fn can_do(&self, can_do: CanDo) -> Supported {
info!("Host is asking if plugin can: {:?}.", can_do);
Supported::Maybe
}
fn get_tail_size(&self) -> isize {
0
}
fn process(&mut self, buffer: &mut AudioBuffer<f32>) {
for (input, output) in buffer.zip() {
for (in_frame, out_frame) in input.iter().zip(output.iter_mut()) {
*out_frame = *in_frame;
}
}
}
fn process_f64(&mut self, buffer: &mut AudioBuffer<f64>) {
for (input, output) in buffer.zip() {
for (in_frame, out_frame) in input.iter().zip(output.iter_mut()) {
*out_frame = *in_frame;
}
}
}
fn process_events(&mut self, events: &api::Events) {}
fn get_parameter_object(&mut self) -> Arc<dyn PluginParameters> {
Arc::new(DummyPluginParameters)
}
fn get_input_info(&self, input: i32) -> ChannelInfo {
ChannelInfo::new(
format!("Input channel {}", input),
Some(format!("In {}", input)),
true,
None,
)
}
fn get_output_info(&self, output: i32) -> ChannelInfo {
ChannelInfo::new(
format!("Output channel {}", output),
Some(format!("Out {}", output)),
true,
None,
)
}
fn start_process(&mut self) {}
fn stop_process(&mut self) {}
fn get_editor(&mut self) -> Option<Box<dyn Editor>> {
None
}
}
#[allow(unused_variables)]
pub trait PluginParameters: Sync {
fn change_preset(&self, preset: i32) {}
fn get_preset_num(&self) -> i32 {
0
}
fn set_preset_name(&self, name: String) {}
fn get_preset_name(&self, preset: i32) -> String {
"".to_string()
}
fn get_parameter_label(&self, index: i32) -> String {
"".to_string()
}
fn get_parameter_text(&self, index: i32) -> String {
format!("{:.3}", self.get_parameter(index))
}
fn get_parameter_name(&self, index: i32) -> String {
format!("Param {}", index)
}
fn get_parameter(&self, index: i32) -> f32 {
0.0
}
fn set_parameter(&self, index: i32, value: f32) {}
fn can_be_automated(&self, index: i32) -> bool {
true
}
fn string_to_parameter(&self, index: i32, text: String) -> bool {
false
}
fn get_preset_data(&self) -> Vec<u8> {
Vec::new()
}
fn get_bank_data(&self) -> Vec<u8> {
Vec::new()
}
fn load_preset_data(&self, data: &[u8]) {}
fn load_bank_data(&self, data: &[u8]) {}
}
struct DummyPluginParameters;
impl PluginParameters for DummyPluginParameters {}
#[derive(Copy, Clone)]
pub struct HostCallback {
callback: Option<HostCallbackProc>,
effect: *mut AEffect,
}
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 {
#[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)
}
#[doc(hidden)]
fn is_effect_valid(&self) -> bool {
unsafe { (*self.effect).magic as i32 == VST_MAGIC }
}
#[doc(hidden)]
pub fn wrap(callback: HostCallbackProc, effect: *mut AEffect) -> HostCallback {
HostCallback {
callback: Some(callback),
effect,
}
}
pub fn vst_version(&self) -> i32 {
self.callback(self.effect, host::OpCode::Version, 0, 0, ptr::null_mut(), 0.0) as i32
}
#[inline(always)]
pub fn raw_callback(&self) -> Option<HostCallbackProc> {
self.callback
}
#[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 {
fn automate(&self, index: i32, value: f32) {
if self.is_effect_valid() {
self.callback(self.effect, host::OpCode::Automate, index, 0, ptr::null_mut(), value);
}
}
fn begin_edit(&self, index: i32) {
self.callback(self.effect, host::OpCode::BeginEdit, index, 0, ptr::null_mut(), 0.0);
}
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)
}
fn process_events(&self, events: &api::Events) {
self.callback(
self.effect,
host::OpCode::ProcessEvents,
0,
0,
events as *const _ as *mut _,
0.0,
);
}
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) }),
}
}
fn get_block_size(&self) -> isize {
self.callback(self.effect, host::OpCode::GetBlockSize, 0, 0, ptr::null_mut(), 0.0)
}
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 crate::plugin;
macro_rules! make_plugin {
($($attr:meta) *) => {
use std::convert::TryFrom;
use std::os::raw::c_void;
use crate::main;
use crate::api::AEffect;
use crate::host::{Host, OpCode};
use crate::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());
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 {
match OpCode::try_from(opcode) {
Ok(OpCode::BeginEdit) => {
assert_eq!(index, 123);
0
},
Ok(OpCode::Automate) => {
assert_eq!(index, 123);
assert_eq!(opt, 12.3);
0
},
Ok(OpCode::EndEdit) => {
assert_eq!(index, 123);
0
},
Ok(OpCode::Version) => 2400,
Ok(OpCode::CurrentId) => 9876,
Ok(OpCode::Idle) => 0,
_ => 0
}
}
main::<TestPlugin>(host_callback)
}
}
}
make_plugin!(derive(Default));
#[test]
#[should_panic]
fn null_panic() {
make_plugin!();
impl Default for TestPlugin {
fn default() -> TestPlugin {
let plugin = TestPlugin {
host: Default::default(),
};
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);
}
}