mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
370 lines
13 KiB
Rust
370 lines
13 KiB
Rust
//! Function interfaces for VST 2.4 API.
|
|
|
|
#![doc(hidden)]
|
|
|
|
use std::cell::Cell;
|
|
use std::os::raw::{c_char, c_void};
|
|
use std::{mem, slice};
|
|
|
|
use crate::{
|
|
api::{self, consts::*, AEffect, TimeInfo},
|
|
buffer::AudioBuffer,
|
|
editor::{Key, KeyCode, KnobMode, Rect},
|
|
host::Host,
|
|
};
|
|
|
|
/// Deprecated process function.
|
|
pub extern "C" fn process_deprecated(
|
|
_effect: *mut AEffect,
|
|
_raw_inputs: *const *const f32,
|
|
_raw_outputs: *mut *mut f32,
|
|
_samples: i32,
|
|
) {
|
|
}
|
|
|
|
/// VST2.4 replacing function.
|
|
pub extern "C" fn process_replacing(
|
|
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() };
|
|
let info = unsafe { (*effect).get_info() };
|
|
let (input_count, output_count) = (info.inputs as usize, info.outputs as usize);
|
|
let mut buffer =
|
|
unsafe { AudioBuffer::from_raw(input_count, output_count, raw_inputs, raw_outputs, samples as usize) };
|
|
plugin.process(&mut buffer);
|
|
}
|
|
|
|
/// VST2.4 replacing function with `f64` values.
|
|
pub extern "C" fn process_replacing_f64(
|
|
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() };
|
|
let (input_count, output_count) = (info.inputs as usize, info.outputs as usize);
|
|
let mut buffer =
|
|
unsafe { AudioBuffer::from_raw(input_count, output_count, raw_inputs, raw_outputs, samples as usize) };
|
|
plugin.process_f64(&mut buffer);
|
|
}
|
|
|
|
/// VST2.4 set parameter function.
|
|
pub extern "C" fn set_parameter(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 {
|
|
unsafe { (*effect).get_params() }.get_parameter(index)
|
|
}
|
|
|
|
/// Copy a string into a destination buffer.
|
|
///
|
|
/// String will be cut at `max` characters.
|
|
fn copy_string(dst: *mut c_void, src: &str, max: usize) -> isize {
|
|
unsafe {
|
|
use libc::{memcpy, memset};
|
|
use std::cmp::min;
|
|
|
|
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()));
|
|
}
|
|
|
|
1 // Success
|
|
}
|
|
|
|
/// VST2.4 dispatch function. This function handles dispatching all opcodes to the VST plugin.
|
|
pub extern "C" fn dispatch(
|
|
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
|
|
let opcode = OpCode::try_from(opcode);
|
|
// Only query plugin or editor when needed to avoid creating multiple
|
|
// concurrent mutable references to the same object.
|
|
let get_plugin = || unsafe { (*effect).get_plugin() };
|
|
let get_editor = || unsafe { (*effect).get_editor() };
|
|
let params = unsafe { (*effect).get_params() };
|
|
|
|
match opcode {
|
|
Ok(OpCode::Initialize) => get_plugin().init(),
|
|
Ok(OpCode::Shutdown) => unsafe {
|
|
(*effect).drop_plugin();
|
|
drop(Box::from_raw(effect))
|
|
},
|
|
|
|
Ok(OpCode::ChangePreset) => params.change_preset(value as i32),
|
|
Ok(OpCode::GetCurrentPresetNum) => return params.get_preset_num() as isize,
|
|
Ok(OpCode::SetCurrentPresetName) => params.set_preset_name(read_string(ptr)),
|
|
Ok(OpCode::GetCurrentPresetName) => {
|
|
let num = params.get_preset_num();
|
|
return copy_string(ptr, ¶ms.get_preset_name(num), MAX_PRESET_NAME_LEN);
|
|
}
|
|
|
|
Ok(OpCode::GetParameterLabel) => {
|
|
return copy_string(ptr, ¶ms.get_parameter_label(index), MAX_PARAM_STR_LEN)
|
|
}
|
|
Ok(OpCode::GetParameterDisplay) => {
|
|
return copy_string(ptr, ¶ms.get_parameter_text(index), MAX_PARAM_STR_LEN)
|
|
}
|
|
Ok(OpCode::GetParameterName) => return copy_string(ptr, ¶ms.get_parameter_name(index), MAX_PARAM_STR_LEN),
|
|
|
|
Ok(OpCode::SetSampleRate) => get_plugin().set_sample_rate(opt),
|
|
Ok(OpCode::SetBlockSize) => get_plugin().set_block_size(value as i64),
|
|
Ok(OpCode::StateChanged) => {
|
|
if value == 1 {
|
|
get_plugin().resume();
|
|
} else {
|
|
get_plugin().suspend();
|
|
}
|
|
}
|
|
|
|
Ok(OpCode::EditorGetRect) => {
|
|
if let Some(ref mut editor) = get_editor() {
|
|
let size = editor.size();
|
|
let pos = editor.position();
|
|
|
|
unsafe {
|
|
// Given a Rect** structure
|
|
// TODO: Investigate whether we are given a valid Rect** pointer already
|
|
*(ptr as *mut *mut c_void) = Box::into_raw(Box::new(Rect {
|
|
left: pos.0 as i16, // x coord of position
|
|
top: pos.1 as i16, // y coord of position
|
|
right: (pos.0 + size.0) as i16, // x coord of pos + x coord of size
|
|
bottom: (pos.1 + size.1) as i16, // y coord of pos + y coord of size
|
|
})) as *mut _; // TODO: free memory
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
Ok(OpCode::EditorOpen) => {
|
|
if let Some(ref mut editor) = get_editor() {
|
|
// `ptr` is a window handle to the parent window.
|
|
// See the documentation for `Editor::open` for details.
|
|
if editor.open(ptr) {
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
Ok(OpCode::EditorClose) => {
|
|
if let Some(ref mut editor) = get_editor() {
|
|
editor.close();
|
|
}
|
|
}
|
|
|
|
Ok(OpCode::EditorIdle) => {
|
|
if let Some(ref mut editor) = get_editor() {
|
|
editor.idle();
|
|
}
|
|
}
|
|
|
|
Ok(OpCode::GetData) => {
|
|
let mut chunks = if index == 0 {
|
|
params.get_bank_data()
|
|
} else {
|
|
params.get_preset_data()
|
|
};
|
|
|
|
chunks.shrink_to_fit();
|
|
let len = chunks.len() as isize; // eventually we should be using ffi::size_t
|
|
|
|
unsafe {
|
|
*(ptr as *mut *mut c_void) = chunks.as_ptr() as *mut c_void;
|
|
}
|
|
|
|
mem::forget(chunks);
|
|
return len;
|
|
}
|
|
Ok(OpCode::SetData) => {
|
|
let chunks = unsafe { slice::from_raw_parts(ptr as *mut u8, value as usize) };
|
|
|
|
if index == 0 {
|
|
params.load_bank_data(chunks);
|
|
} else {
|
|
params.load_preset_data(chunks);
|
|
}
|
|
}
|
|
|
|
Ok(OpCode::ProcessEvents) => {
|
|
get_plugin().process_events(unsafe { &*(ptr as *const api::Events) });
|
|
}
|
|
Ok(OpCode::CanBeAutomated) => return params.can_be_automated(index) as isize,
|
|
Ok(OpCode::StringToParameter) => return params.string_to_parameter(index, read_string(ptr)) as isize,
|
|
|
|
Ok(OpCode::GetPresetName) => return copy_string(ptr, ¶ms.get_preset_name(index), MAX_PRESET_NAME_LEN),
|
|
|
|
Ok(OpCode::GetInputInfo) => {
|
|
if index >= 0 && index < get_plugin().get_info().inputs {
|
|
unsafe {
|
|
let ptr = ptr as *mut api::ChannelProperties;
|
|
*ptr = get_plugin().get_input_info(index).into();
|
|
}
|
|
}
|
|
}
|
|
Ok(OpCode::GetOutputInfo) => {
|
|
if index >= 0 && index < get_plugin().get_info().outputs {
|
|
unsafe {
|
|
let ptr = ptr as *mut api::ChannelProperties;
|
|
*ptr = get_plugin().get_output_info(index).into();
|
|
}
|
|
}
|
|
}
|
|
Ok(OpCode::GetCategory) => {
|
|
return get_plugin().get_info().category.into();
|
|
}
|
|
|
|
Ok(OpCode::GetEffectName) => return copy_string(ptr, &get_plugin().get_info().name, MAX_VENDOR_STR_LEN),
|
|
|
|
Ok(OpCode::GetVendorName) => return copy_string(ptr, &get_plugin().get_info().vendor, MAX_VENDOR_STR_LEN),
|
|
Ok(OpCode::GetProductName) => return copy_string(ptr, &get_plugin().get_info().name, MAX_PRODUCT_STR_LEN),
|
|
Ok(OpCode::GetVendorVersion) => return get_plugin().get_info().version as isize,
|
|
Ok(OpCode::VendorSpecific) => return get_plugin().vendor_specific(index, value, ptr, opt),
|
|
Ok(OpCode::CanDo) => {
|
|
let can_do = CanDo::from_str(&read_string(ptr));
|
|
return get_plugin().can_do(can_do).into();
|
|
}
|
|
Ok(OpCode::GetTailSize) => {
|
|
if get_plugin().get_tail_size() == 0 {
|
|
return 1;
|
|
} else {
|
|
return get_plugin().get_tail_size();
|
|
}
|
|
}
|
|
|
|
//OpCode::GetParamInfo => { /*TODO*/ }
|
|
Ok(OpCode::GetApiVersion) => return 2400,
|
|
|
|
Ok(OpCode::EditorKeyDown) => {
|
|
if let Some(ref mut editor) = get_editor() {
|
|
if let Ok(key) = Key::try_from(value) {
|
|
editor.key_down(KeyCode {
|
|
character: index as u8 as char,
|
|
key,
|
|
modifier: opt.to_bits() as u8,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
Ok(OpCode::EditorKeyUp) => {
|
|
if let Some(ref mut editor) = get_editor() {
|
|
if let Ok(key) = Key::try_from(value) {
|
|
editor.key_up(KeyCode {
|
|
character: index as u8 as char,
|
|
key,
|
|
modifier: opt.to_bits() as u8,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
Ok(OpCode::EditorSetKnobMode) => {
|
|
if let Some(ref mut editor) = get_editor() {
|
|
if let Ok(knob_mode) = KnobMode::try_from(value) {
|
|
editor.set_knob_mode(knob_mode);
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(OpCode::StartProcess) => get_plugin().start_process(),
|
|
Ok(OpCode::StopProcess) => get_plugin().stop_process(),
|
|
|
|
Ok(OpCode::GetNumMidiInputs) => return unsafe { (*effect).get_info() }.midi_inputs as isize,
|
|
Ok(OpCode::GetNumMidiOutputs) => return unsafe { (*effect).get_info() }.midi_outputs as isize,
|
|
|
|
_ => {
|
|
debug!("Unimplemented opcode ({:?})", opcode);
|
|
trace!(
|
|
"Arguments; index: {}, value: {}, ptr: {:?}, opt: {}",
|
|
index,
|
|
value,
|
|
ptr,
|
|
opt
|
|
);
|
|
}
|
|
}
|
|
|
|
0
|
|
}
|
|
|
|
pub fn host_dispatch(
|
|
host: &mut dyn Host,
|
|
effect: *mut AEffect,
|
|
opcode: i32,
|
|
index: i32,
|
|
value: isize,
|
|
ptr: *mut c_void,
|
|
opt: f32,
|
|
) -> isize {
|
|
use crate::host::OpCode;
|
|
|
|
let opcode = OpCode::try_from(opcode);
|
|
match opcode {
|
|
Ok(OpCode::Version) => return 2400,
|
|
Ok(OpCode::Automate) => host.automate(index, opt),
|
|
Ok(OpCode::BeginEdit) => host.begin_edit(index),
|
|
Ok(OpCode::EndEdit) => host.end_edit(index),
|
|
|
|
Ok(OpCode::Idle) => host.idle(),
|
|
|
|
// ...
|
|
Ok(OpCode::CanDo) => {
|
|
info!("Plugin is asking if host can: {}.", read_string(ptr));
|
|
}
|
|
|
|
Ok(OpCode::GetVendorVersion) => return host.get_info().0,
|
|
Ok(OpCode::GetVendorString) => return copy_string(ptr, &host.get_info().1, MAX_VENDOR_STR_LEN),
|
|
Ok(OpCode::GetProductString) => return copy_string(ptr, &host.get_info().2, MAX_PRODUCT_STR_LEN),
|
|
Ok(OpCode::ProcessEvents) => {
|
|
host.process_events(unsafe { &*(ptr as *const api::Events) });
|
|
}
|
|
|
|
Ok(OpCode::GetTime) => {
|
|
return match host.get_time_info(value as i32) {
|
|
None => 0,
|
|
Some(result) => {
|
|
thread_local! {
|
|
static TIME_INFO: Cell<TimeInfo> =
|
|
Cell::new(TimeInfo::default());
|
|
}
|
|
TIME_INFO.with(|time_info| {
|
|
(*time_info).set(result);
|
|
time_info.as_ptr() as isize
|
|
})
|
|
}
|
|
};
|
|
}
|
|
Ok(OpCode::GetBlockSize) => return host.get_block_size(),
|
|
|
|
_ => {
|
|
trace!("VST: Got unimplemented host opcode ({:?})", opcode);
|
|
trace!(
|
|
"Arguments; effect: {:?}, index: {}, value: {}, ptr: {:?}, opt: {}",
|
|
effect,
|
|
index,
|
|
value,
|
|
ptr,
|
|
opt
|
|
);
|
|
}
|
|
}
|
|
0
|
|
}
|
|
|
|
// Read a string from the `ptr` buffer
|
|
fn read_string(ptr: *mut c_void) -> String {
|
|
use std::ffi::CStr;
|
|
|
|
String::from_utf8_lossy(unsafe { CStr::from_ptr(ptr as *mut c_char).to_bytes() }).into_owned()
|
|
}
|