Implement most of IAudioProcessor
Except for the process function itself.
This commit is contained in:
parent
a34293fbb4
commit
b9d38f5c39
|
@ -27,6 +27,8 @@ use crate::params::Params;
|
||||||
/// - MIDI
|
/// - MIDI
|
||||||
/// - Sidechain inputs
|
/// - Sidechain inputs
|
||||||
/// - Multiple output busses
|
/// - Multiple output busses
|
||||||
|
/// - Latency compensation
|
||||||
|
/// - Special handling for offline processing
|
||||||
/// - Storing custom state, only the parameters are saved right now
|
/// - Storing custom state, only the parameters are saved right now
|
||||||
/// - Sample accurate automation (this would be great, but sadly few hosts even support it so until
|
/// - Sample accurate automation (this would be great, but sadly few hosts even support it so until
|
||||||
/// they do we'll ignore that it's a thing)
|
/// they do we'll ignore that it's a thing)
|
||||||
|
@ -100,7 +102,7 @@ pub trait Vst3Plugin: Plugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// We only support a single main input and output bus at the moment.
|
/// We only support a single main input and output bus at the moment.
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct BusConfig {
|
pub struct BusConfig {
|
||||||
/// The number of input channels for the plugin.
|
/// The number of input channels for the plugin.
|
||||||
pub num_input_channels: u32,
|
pub num_input_channels: u32,
|
||||||
|
@ -109,7 +111,7 @@ pub struct BusConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration for (the host's) audio buffers.
|
/// Configuration for (the host's) audio buffers.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct BufferConfig {
|
pub struct BufferConfig {
|
||||||
/// The current sample rate.
|
/// The current sample rate.
|
||||||
pub sample_rate: f32,
|
pub sample_rate: f32,
|
||||||
|
@ -119,7 +121,7 @@ pub struct BufferConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indicates the current situation after the plugin has processed audio.
|
/// Indicates the current situation after the plugin has processed audio.
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum ProcessStatus {
|
pub enum ProcessStatus {
|
||||||
/// Something went wrong while processing audio.
|
/// Something went wrong while processing audio.
|
||||||
Error(&'static str),
|
Error(&'static str),
|
||||||
|
|
|
@ -24,15 +24,16 @@ use std::collections::HashMap;
|
||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
use vst3_com::base::kResultTrue;
|
||||||
use vst3_sys::base::{kInvalidArgument, kNoInterface, kResultFalse, kResultOk, tresult, TBool};
|
use vst3_sys::base::{kInvalidArgument, kNoInterface, kResultFalse, kResultOk, tresult, TBool};
|
||||||
use vst3_sys::base::{IPluginBase, IPluginFactory, IPluginFactory2, IPluginFactory3};
|
use vst3_sys::base::{IPluginBase, IPluginFactory, IPluginFactory2, IPluginFactory3};
|
||||||
use vst3_sys::vst::TChar;
|
use vst3_sys::vst::TChar;
|
||||||
use vst3_sys::vst::{IComponent, IEditController};
|
use vst3_sys::vst::{IAudioProcessor, IComponent, IEditController};
|
||||||
use vst3_sys::VST3;
|
use vst3_sys::VST3;
|
||||||
use widestring::U16CStr;
|
use widestring::U16CStr;
|
||||||
|
|
||||||
use crate::params::ParamPtr;
|
use crate::params::ParamPtr;
|
||||||
use crate::plugin::{BusConfig, Plugin, Vst3Plugin};
|
use crate::plugin::{BufferConfig, BusConfig, Plugin, ProcessStatus, Vst3Plugin};
|
||||||
use crate::wrapper::util::{hash_param_id, strlcpy, u16strlcpy};
|
use crate::wrapper::util::{hash_param_id, strlcpy, u16strlcpy};
|
||||||
|
|
||||||
// Alias needed for the VST3 attribute macro
|
// Alias needed for the VST3 attribute macro
|
||||||
|
@ -51,13 +52,15 @@ lazy_static! {
|
||||||
static ref BYPASS_PARAM_HASH: u32 = hash_param_id(BYPASS_PARAM_ID);
|
static ref BYPASS_PARAM_HASH: u32 = hash_param_id(BYPASS_PARAM_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[VST3(implements(IComponent, IEditController))]
|
#[VST3(implements(IComponent, IEditController, IAudioProcessor))]
|
||||||
pub struct Wrapper<P: Plugin> {
|
pub struct Wrapper<P: Plugin> {
|
||||||
/// The wrapped plugin instance.
|
/// The wrapped plugin instance.
|
||||||
plugin: P,
|
plugin: RefCell<P>,
|
||||||
/// Whether the plugin is currently bypassed. This is not yet integrated with the `Plugin`
|
/// Whether the plugin is currently bypassed. This is not yet integrated with the `Plugin`
|
||||||
/// trait.
|
/// trait.
|
||||||
bypass_state: Cell<bool>,
|
bypass_state: Cell<bool>,
|
||||||
|
/// The last process status returned by the plugin. This is used for tail handling.
|
||||||
|
last_process_status: Cell<ProcessStatus>,
|
||||||
|
|
||||||
/// A mapping from parameter ID hashes (obtained from the string parameter IDs) to pointers to
|
/// A mapping from parameter ID hashes (obtained from the string parameter IDs) to pointers to
|
||||||
/// parameters belonging to the plugin. As long as `plugin` does not get recreated, these
|
/// parameters belonging to the plugin. As long as `plugin` does not get recreated, these
|
||||||
|
@ -79,12 +82,13 @@ pub struct Wrapper<P: Plugin> {
|
||||||
impl<P: Plugin> Wrapper<P> {
|
impl<P: Plugin> Wrapper<P> {
|
||||||
pub fn new() -> Box<Self> {
|
pub fn new() -> Box<Self> {
|
||||||
let mut wrapper = Self::allocate(
|
let mut wrapper = Self::allocate(
|
||||||
P::default(), // plugin
|
RefCell::new(P::default()), // plugin
|
||||||
Cell::new(false), // bypass_state
|
Cell::new(false), // bypass_state
|
||||||
HashMap::new(), // param_by_hash
|
Cell::new(ProcessStatus::Normal), // last_process_status
|
||||||
Vec::new(), // param_hashes
|
HashMap::new(), // param_by_hash
|
||||||
Vec::new(), // param_defaults_normalized
|
Vec::new(), // param_hashes
|
||||||
HashMap::new(), // param_id_hashes
|
Vec::new(), // param_defaults_normalized
|
||||||
|
HashMap::new(), // param_id_hashes
|
||||||
// Some hosts, like the current version of Bitwig and Ardour at the time of writing,
|
// Some hosts, like the current version of Bitwig and Ardour at the time of writing,
|
||||||
// will try using the plugin's default not yet initialized bus arrangement. Because of
|
// will try using the plugin's default not yet initialized bus arrangement. Because of
|
||||||
// that, we'll always initialize this configuration even before the host requests a
|
// that, we'll always initialize this configuration even before the host requests a
|
||||||
|
@ -98,7 +102,7 @@ impl<P: Plugin> Wrapper<P> {
|
||||||
// This is a mapping from the parameter IDs specified by the plugin to pointers to thsoe
|
// This is a mapping from the parameter IDs specified by the plugin to pointers to thsoe
|
||||||
// parameters. Since the object returned by `params()` is pinned, these pointers are safe to
|
// parameters. Since the object returned by `params()` is pinned, these pointers are safe to
|
||||||
// dereference as long as `wrapper.plugin` is alive
|
// dereference as long as `wrapper.plugin` is alive
|
||||||
let param_map = wrapper.plugin.params().param_map();
|
let param_map = wrapper.plugin.borrow().params().param_map();
|
||||||
nih_debug_assert!(
|
nih_debug_assert!(
|
||||||
!param_map.contains_key(BYPASS_PARAM_ID),
|
!param_map.contains_key(BYPASS_PARAM_ID),
|
||||||
"The wrapper alread yadds its own bypass parameter"
|
"The wrapper alread yadds its own bypass parameter"
|
||||||
|
@ -460,6 +464,149 @@ impl<P: Plugin> IEditController for Wrapper<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<P: Plugin> IAudioProcessor for Wrapper<P> {
|
||||||
|
unsafe fn set_bus_arrangements(
|
||||||
|
&self,
|
||||||
|
inputs: *mut vst3_sys::vst::SpeakerArrangement,
|
||||||
|
num_ins: i32,
|
||||||
|
outputs: *mut vst3_sys::vst::SpeakerArrangement,
|
||||||
|
num_outs: i32,
|
||||||
|
) -> tresult {
|
||||||
|
if inputs.is_null() || outputs.is_null() {
|
||||||
|
nih_debug_assert_failure!("Null pointer passed to function");
|
||||||
|
return kInvalidArgument;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We currently only do single audio bus IO configurations
|
||||||
|
if num_ins != 1 || num_outs != 1 {
|
||||||
|
return kInvalidArgument;
|
||||||
|
}
|
||||||
|
|
||||||
|
let input_channel_map = &*inputs;
|
||||||
|
let output_channel_map = &*outputs;
|
||||||
|
let proposed_config = BusConfig {
|
||||||
|
num_input_channels: input_channel_map.count_ones(),
|
||||||
|
num_output_channels: output_channel_map.count_ones(),
|
||||||
|
};
|
||||||
|
if self.plugin.borrow().accepts_bus_config(&proposed_config) {
|
||||||
|
self.current_bus_config.replace(proposed_config);
|
||||||
|
|
||||||
|
kResultOk
|
||||||
|
} else {
|
||||||
|
kResultFalse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn get_bus_arrangement(
|
||||||
|
&self,
|
||||||
|
dir: vst3_sys::vst::BusDirection,
|
||||||
|
index: i32,
|
||||||
|
arr: *mut vst3_sys::vst::SpeakerArrangement,
|
||||||
|
) -> tresult {
|
||||||
|
if arr.is_null() {
|
||||||
|
nih_debug_assert_failure!("Null pointer passed to function");
|
||||||
|
return kInvalidArgument;
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = self.current_bus_config.borrow();
|
||||||
|
match (dir, index) {
|
||||||
|
(d, 0) if d == vst3_sys::vst::BusDirections::kInput as i32 => {
|
||||||
|
let channel_map = match config.num_input_channels {
|
||||||
|
0 => vst3_sys::vst::kEmpty,
|
||||||
|
1 => vst3_sys::vst::kMono,
|
||||||
|
2 => vst3_sys::vst::kStereo,
|
||||||
|
5 => vst3_sys::vst::k50,
|
||||||
|
6 => vst3_sys::vst::k51,
|
||||||
|
7 => vst3_sys::vst::k70Cine,
|
||||||
|
8 => vst3_sys::vst::k71Cine,
|
||||||
|
n => {
|
||||||
|
nih_debug_assert_failure!(
|
||||||
|
"No defined layout for {} channels, making something up on the spot...",
|
||||||
|
n
|
||||||
|
);
|
||||||
|
(1 << n) - 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
nih_debug_assert_eq!(config.num_input_channels, channel_map.count_ones());
|
||||||
|
*arr = channel_map;
|
||||||
|
|
||||||
|
kResultOk
|
||||||
|
}
|
||||||
|
_ => kInvalidArgument,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn can_process_sample_size(&self, symbolic_sample_size: i32) -> tresult {
|
||||||
|
if symbolic_sample_size == vst3_sys::vst::SymbolicSampleSizes::kSample32 as i32 {
|
||||||
|
kResultOk
|
||||||
|
} else {
|
||||||
|
kResultFalse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn get_latency_samples(&self) -> u32 {
|
||||||
|
// TODO: Latency compensation
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn setup_processing(&self, setup: *const vst3_sys::vst::ProcessSetup) -> tresult {
|
||||||
|
if setup.is_null() {
|
||||||
|
nih_debug_assert_failure!("Null pointer passed to function");
|
||||||
|
return kInvalidArgument;
|
||||||
|
}
|
||||||
|
|
||||||
|
// There's no special handling for offline processing at the moment
|
||||||
|
let setup = &*setup;
|
||||||
|
nih_debug_assert_eq!(
|
||||||
|
setup.symbolic_sample_size,
|
||||||
|
vst3_sys::vst::SymbolicSampleSizes::kSample32 as i32
|
||||||
|
);
|
||||||
|
|
||||||
|
let bus_config = self.current_bus_config.borrow();
|
||||||
|
let buffer_config = BufferConfig {
|
||||||
|
sample_rate: setup.sample_rate as f32,
|
||||||
|
max_buffer_size: setup.max_samples_per_block as u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
if self
|
||||||
|
.plugin
|
||||||
|
.borrow_mut()
|
||||||
|
.initialize(&bus_config, &buffer_config)
|
||||||
|
{
|
||||||
|
kResultOk
|
||||||
|
} else {
|
||||||
|
kResultFalse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn set_processing(&self, _state: TBool) -> tresult {
|
||||||
|
// Always reset the processing status when the plugin gets activated or deactivated
|
||||||
|
self.last_process_status.set(ProcessStatus::Normal);
|
||||||
|
|
||||||
|
// We don't have any special handling for suspending and resuming plugins, yet
|
||||||
|
kResultTrue
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn process(&self, data: *mut vst3_sys::vst::ProcessData) -> tresult {
|
||||||
|
if data.is_null() {
|
||||||
|
nih_debug_assert_failure!("Null pointer passed to function");
|
||||||
|
return kInvalidArgument;
|
||||||
|
}
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn get_tail_samples(&self) -> u32 {
|
||||||
|
// https://github.com/steinbergmedia/vst3_pluginterfaces/blob/2ad397ade5b51007860bedb3b01b8afd2c5f6fba/vst/ivstaudioprocessor.h#L145-L159
|
||||||
|
match self.last_process_status.get() {
|
||||||
|
ProcessStatus::Tail(samples) => samples,
|
||||||
|
ProcessStatus::KeepAlive => u32::MAX, // kInfiniteTail
|
||||||
|
_ => 0, // kNoTail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[VST3(implements(IPluginFactory, IPluginFactory2, IPluginFactory3))]
|
#[VST3(implements(IPluginFactory, IPluginFactory2, IPluginFactory3))]
|
||||||
pub struct Factory<P: Vst3Plugin> {
|
pub struct Factory<P: Vst3Plugin> {
|
||||||
/// The exposed plugin's GUID. Instead of generating this, we'll just let the programmer decide
|
/// The exposed plugin's GUID. Instead of generating this, we'll just let the programmer decide
|
||||||
|
|
Loading…
Reference in a new issue