1
0
Fork 0

Implement most of IAudioProcessor

Except for the process function itself.
This commit is contained in:
Robbert van der Helm 2022-01-28 13:40:47 +01:00
parent a34293fbb4
commit b9d38f5c39
2 changed files with 163 additions and 14 deletions

View file

@ -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),

View file

@ -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,8 +82,9 @@ 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
Cell::new(ProcessStatus::Normal), // last_process_status
HashMap::new(), // param_by_hash HashMap::new(), // param_by_hash
Vec::new(), // param_hashes Vec::new(), // param_hashes
Vec::new(), // param_defaults_normalized Vec::new(), // param_defaults_normalized
@ -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