1
0
Fork 0

Add preliminary support for auxiliary IO

The missing parts are allocating buffers for these busses, copying data
to those buffers, and adding methods to the ProcessContext to interact
with these inputs and outputs.
This commit is contained in:
Robbert van der Helm 2022-05-23 17:07:48 +02:00
parent ad661c857c
commit 596b04af0a
8 changed files with 382 additions and 115 deletions

View file

@ -102,6 +102,8 @@ for download links.
- Full support for receiving and outputting both modern polyphonic note
expression events as well as MIDI CCs, channel pressure, and pitch bend for
CLAP and VST3.
- Support for flexible dynamic buffer configurations, including multiple input
and output busses.
- A plugin bundler accessible through the
`cargo xtask bundle <package> <build_arguments>` command that automatically
detects which plugin targets your plugin exposes and creates the correct

View file

@ -103,6 +103,9 @@ impl Plugin for Gain {
const DEFAULT_NUM_INPUTS: u32 = 2;
const DEFAULT_NUM_OUTPUTS: u32 = 2;
const DEFAULT_AUX_INPUTS: Option<AuxiliaryIOConfig> = None;
const DEFAULT_AUX_OUTPUTS: Option<AuxiliaryIOConfig> = None;
const MIDI_INPUT: MidiConfig = MidiConfig::None;
// Setting this to `true` will tell the wrapper to split the buffer up into smaller blocks
// whenever there are inter-buffer parameter changes. This way no changes to the plugin are

View file

@ -18,9 +18,6 @@ use crate::param::internals::Params;
/// This is super basic, and lots of things I didn't need or want to use yet haven't been
/// implemented. Notable missing features include:
///
/// - Sidechain inputs
/// - Multiple output busses
/// - Special handling for offline processing
/// - MIDI SysEx and MIDI2 for CLAP, note expressions and MIDI1 are already supported
#[allow(unused_variables)]
pub trait Plugin: Default + Send + Sync + 'static {
@ -33,13 +30,29 @@ pub trait Plugin: Default + Send + Sync + 'static {
/// but just in case they do this should only contain decimals values and dots.
const VERSION: &'static str;
/// The default number of inputs. Some hosts like, like Bitwig and Ardour, use the defaults
/// instead of setting up the busses properly.
/// The default number of input channels. This merely serves as a default. The host will probe
/// the plugin's supported configuration using
/// [`accepts_bus_config()`][Self::accepts_bus_config()], and the selected configuration is
/// passed to [`initialize()`][Self::initialize()]. Some hosts like, like Bitwig and Ardour, use
/// the defaults instead of setting up the busses properly.
///
/// Setting this to zero causes the plugin to have no main input bus.
const DEFAULT_NUM_INPUTS: u32 = 2;
/// The default number of inputs. Some hosts like, like Bitwig and Ardour, use the defaults
/// instead of setting up the busses properly.
/// The default number of output channels. All of the same caveats mentioned for
/// `DEFAULT_NUM_INPUTS` apply here.
///
/// Setting this to zero causes the plugin to have no main output bus.
const DEFAULT_NUM_OUTPUTS: u32 = 2;
/// If set, then the plugin will have this many sidechain input busses with a default number of
/// channels. Not all hosts support more than one sidechain input bus. Negotiating the actual
/// configuration wroks the same was as with `DEFAULT_NUM_INPUTS`.
const DEFAULT_AUX_INPUTS: Option<AuxiliaryIOConfig> = None;
/// If set, then the plugin will have this many auxiliary output busses with a default number of
/// channels. Negotiating the actual configuration wroks the same was as with
/// `DEFAULT_NUM_INPUTS`.
const DEFAULT_AUX_OUTPUTS: Option<AuxiliaryIOConfig> = None;
/// Whether the plugin accepts note events, and what which events it wants to receive. If this
/// is set to [`MidiConfig::None`], then the plugin won't receive any note events.
const MIDI_INPUT: MidiConfig = MidiConfig::None;
@ -282,13 +295,26 @@ unsafe impl HasRawWindowHandle for ParentWindowHandle {
}
}
/// We only support a single main input and output bus at the moment.
/// The plugin's IO configuration.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BusConfig {
/// The number of input channels for the plugin.
pub num_input_channels: u32,
/// The number of output channels for the plugin.
pub num_output_channels: u32,
/// Any additional sidechain inputs.
pub aux_input_busses: AuxiliaryIOConfig,
/// Any additional outputs.
pub aux_output_busses: AuxiliaryIOConfig,
}
/// Configuration for auxiliary inputs or outputs on [`BusCofnig`].
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct AuxiliaryIOConfig {
/// The number of auxiliary input or output busses.
pub num_busses: u32,
/// The number of channels in each bus.
pub num_channels: u32,
}
/// Configuration for (the host's) audio buffers.

View file

@ -20,7 +20,7 @@ pub use crate::param::range::{FloatRange, IntRange};
pub use crate::param::smoothing::{Smoothable, Smoother, SmoothingStyle};
pub use crate::param::{BoolParam, FloatParam, IntParam, Param, ParamFlags};
pub use crate::plugin::{
BufferConfig, BusConfig, ClapPlugin, Editor, ParentWindowHandle, Plugin, ProcessMode,
ProcessStatus, Vst3Plugin,
AuxiliaryIOConfig, BufferConfig, BusConfig, ClapPlugin, Editor, ParentWindowHandle, Plugin,
ProcessMode, ProcessStatus, Vst3Plugin,
};
pub use crate::wrapper::state::PluginState;

View file

@ -430,12 +430,29 @@ impl<P: ClapPlugin> Wrapper<P> {
let mut supported_bus_configs = Vec::new();
for num_output_channels in [1, 2] {
for num_input_channels in [0, num_output_channels] {
let bus_config = BusConfig {
num_input_channels,
num_output_channels,
};
if plugin.accepts_bus_config(&bus_config) {
supported_bus_configs.push(bus_config);
#[allow(clippy::single_element_loop)]
for num_aux_channels in [num_output_channels] {
let bus_config = BusConfig {
num_input_channels,
num_output_channels,
// We won't support a variable number of busses until that's required, so
// we'll always use the number of auxiliary busses specified by the plugin
aux_input_busses: P::DEFAULT_AUX_INPUTS
.map(|mut aux| {
aux.num_channels = num_aux_channels;
aux
})
.unwrap_or_default(),
aux_output_busses: P::DEFAULT_AUX_OUTPUTS
.map(|mut aux| {
aux.num_channels = num_aux_channels;
aux
})
.unwrap_or_default(),
};
if plugin.accepts_bus_config(&bus_config) {
supported_bus_configs.push(bus_config);
}
}
}
}
@ -445,6 +462,8 @@ impl<P: ClapPlugin> Wrapper<P> {
let default_bus_config = BusConfig {
num_input_channels: P::DEFAULT_NUM_INPUTS,
num_output_channels: P::DEFAULT_NUM_OUTPUTS,
aux_input_busses: P::DEFAULT_AUX_INPUTS.unwrap_or_default(),
aux_output_busses: P::DEFAULT_AUX_OUTPUTS.unwrap_or_default(),
};
if !supported_bus_configs.contains(&default_bus_config)
&& plugin.accepts_bus_config(&default_bus_config)
@ -486,6 +505,8 @@ impl<P: ClapPlugin> Wrapper<P> {
current_bus_config: AtomicCell::new(BusConfig {
num_input_channels: P::DEFAULT_NUM_INPUTS,
num_output_channels: P::DEFAULT_NUM_OUTPUTS,
aux_input_busses: P::DEFAULT_AUX_INPUTS.unwrap_or_default(),
aux_output_busses: P::DEFAULT_AUX_OUTPUTS.unwrap_or_default(),
}),
current_buffer_config: AtomicCell::new(None),
current_process_mode: AtomicCell::new(ProcessMode::Realtime),
@ -1543,6 +1564,8 @@ impl<P: ClapPlugin> Wrapper<P> {
output_slices.resize_with(bus_config.num_output_channels as usize, || &mut [])
});
// TODO: Allocate auxiliary IO buffers
// Also store this for later, so we can reinitialize the plugin after restoring state
wrapper.current_buffer_config.store(Some(buffer_config));
@ -1594,6 +1617,8 @@ impl<P: ClapPlugin> Wrapper<P> {
check_null_ptr!(CLAP_PROCESS_ERROR, plugin, process);
let wrapper = &*(plugin as *const Self);
// TODO: Support auxiliary IO and optional main IO, this is still the old version that assumes main IO and nothing else
// Panic on allocations if the `assert_process_allocs` feature has been enabled, and make
// sure that FTZ is set up correctly
process_wrapper(|| {
@ -1983,18 +2008,23 @@ impl<P: ClapPlugin> Wrapper<P> {
match wrapper.supported_bus_configs.get(index as usize) {
Some(bus_config) => {
// We don't support variable auxiliary IO configs right now, so we don't need to
// specify sidechain inputs and aux outputs in these descriptions
let name = match bus_config {
BusConfig {
num_input_channels: _,
num_output_channels: 1,
..
} => String::from("Mono"),
BusConfig {
num_input_channels: _,
num_output_channels: 2,
..
} => String::from("Stereo"),
BusConfig {
num_input_channels,
num_output_channels,
..
} => format!("{num_input_channels} inputs, {num_output_channels} outputs"),
};
let input_port_type = match bus_config.num_input_channels {
@ -2013,17 +2043,16 @@ impl<P: ClapPlugin> Wrapper<P> {
let config = &mut *config;
config.id = index;
strlcpy(&mut config.name, &name);
// TODO: Currently we don't support sidechain inputs or multiple outputs
config.input_port_count = if bus_config.num_input_channels > 0 {
1
} else {
0
};
} + bus_config.aux_input_busses.num_busses;
config.output_port_count = if bus_config.num_output_channels > 0 {
1
} else {
0
};
} + bus_config.aux_output_busses.num_busses;
config.has_main_input = bus_config.num_output_channels > 0;
config.main_input_channel_count = bus_config.num_output_channels;
config.main_input_port_type = input_port_type;
@ -2070,20 +2099,28 @@ impl<P: ClapPlugin> Wrapper<P> {
}
unsafe extern "C" fn ext_audio_ports_count(plugin: *const clap_plugin, is_input: bool) -> u32 {
// TODO: Implement sidechain nputs and auxiliary outputs
check_null_ptr!(0, plugin);
let wrapper = &*(plugin as *const Self);
let bus_config = wrapper.current_bus_config.load();
match (
is_input,
bus_config.num_input_channels,
bus_config.num_output_channels,
) {
(true, 0, _) => 0,
// This should not be possible, however
(false, _, 0) => 0,
_ => 1,
if is_input {
let main_busses = if bus_config.num_input_channels > 0 {
1
} else {
0
};
let aux_busses = bus_config.aux_input_busses.num_busses;
main_busses + aux_busses
} else {
let main_busses = if bus_config.num_output_channels > 0 {
1
} else {
0
};
let aux_busses = bus_config.aux_output_busses.num_busses;
main_busses + aux_busses
}
}
@ -2096,63 +2133,80 @@ impl<P: ClapPlugin> Wrapper<P> {
check_null_ptr!(false, plugin, info);
let wrapper = &*(plugin as *const Self);
const INPUT_ID: u32 = 0;
const OUTPUT_ID: u32 = 1;
let num_input_ports = Self::ext_audio_ports_count(plugin, true);
let num_output_ports = Self::ext_audio_ports_count(plugin, false);
if (is_input && index >= num_input_ports) || (!is_input && index >= num_output_ports) {
nih_debug_assert_failure!(
"Host tried to query information for out of bounds audio port {} (input: {})",
index,
is_input
);
// Even if we don't report having ports when the number of channels are 0, might as well
// handle them here anyways in case we do need to always report them in the future
match index {
0 => {
let current_bus_config = wrapper.current_bus_config.load();
let channel_count = if is_input {
current_bus_config.num_input_channels
} else {
current_bus_config.num_output_channels
};
// When we add sidechain inputs and auxiliary outputs this would need some changing
let stable_id = if is_input { INPUT_ID } else { OUTPUT_ID };
let pair_stable_id = if is_input && current_bus_config.num_output_channels > 0 {
OUTPUT_ID
} else if !is_input && current_bus_config.num_input_channels > 0 {
INPUT_ID
} else {
CLAP_INVALID_ID
};
let port_type_name = if is_input { "Input" } else { "Output" };
let name = match channel_count {
1 => format!("Mono {port_type_name}"),
2 => format!("Stereo {port_type_name}"),
n => format!("{n} channel {port_type_name}"),
};
let port_type = match channel_count {
1 => CLAP_PORT_MONO,
2 => CLAP_PORT_STEREO,
_ => ptr::null(),
};
*info = std::mem::zeroed();
let info = &mut *info;
info.id = stable_id;
strlcpy(&mut info.name, &name);
info.flags = CLAP_AUDIO_PORT_IS_MAIN;
info.channel_count = channel_count;
info.port_type = port_type;
info.in_place_pair = pair_stable_id;
true
}
_ => {
nih_debug_assert_failure!(
"Host tried to query information for out of bounds audio port {} (input: {})",
index,
is_input
);
false
}
return false;
}
let current_bus_config = wrapper.current_bus_config.load();
let has_main_input = current_bus_config.num_input_channels > 0;
let has_main_output = current_bus_config.num_output_channels > 0;
// Whether this port is a main port or an auxiliary (sidechain) port
let is_main_port =
index == 0 && ((is_input && has_main_input) || (!is_input && has_main_output));
// We'll number the ports in a linear order from `0..num_input_ports` and
// `num_input_ports..(num_input_ports + num_output_ports)`
let stable_id = if is_input {
index
} else {
index + num_input_ports
};
let pair_stable_id = match (is_input, is_main_port) {
// Ports are named linearly with inputs coming before outputs, so this is the index of
// the first output port
(true, true) => num_input_ports,
(false, true) => 0,
(_, false) => CLAP_INVALID_ID,
};
let channel_count = match (is_input, is_main_port) {
(true, true) => current_bus_config.num_input_channels,
(false, true) => current_bus_config.num_output_channels,
(true, false) => current_bus_config.aux_input_busses.num_channels,
(false, false) => current_bus_config.aux_output_busses.num_channels,
};
let port_type_name = match (is_input, is_main_port) {
(true, true) => "Input",
(false, true) => "Output",
(true, false) => "Sidechain Input",
(false, false) => "Auxiliary Output",
};
let name = match channel_count {
1 => format!("Mono {port_type_name}"),
2 => format!("Stereo {port_type_name}"),
n => format!("{n} channel {port_type_name}"),
};
let port_type = match channel_count {
1 => CLAP_PORT_MONO,
2 => CLAP_PORT_STEREO,
_ => ptr::null(),
};
*info = std::mem::zeroed();
let info = &mut *info;
info.id = stable_id;
strlcpy(&mut info.name, &name);
info.flags = if is_main_port {
CLAP_AUDIO_PORT_IS_MAIN
} else {
0
};
info.channel_count = channel_count;
info.port_type = port_type;
info.in_place_pair = pair_stable_id;
true
}
unsafe extern "C" fn ext_event_filter_accepts(

View file

@ -16,7 +16,8 @@ use crate::context::Transport;
use crate::param::internals::{ParamPtr, Params};
use crate::param::ParamFlags;
use crate::plugin::{
BufferConfig, BusConfig, Editor, ParentWindowHandle, Plugin, ProcessMode, ProcessStatus,
AuxiliaryIOConfig, BufferConfig, BusConfig, Editor, ParentWindowHandle, Plugin, ProcessMode,
ProcessStatus,
};
use crate::util::permit_alloc;
use crate::wrapper::state::{self, PluginState};
@ -199,6 +200,9 @@ impl<P: Plugin, B: Backend> Wrapper<P, B> {
bus_config: BusConfig {
num_input_channels: config.input_channels,
num_output_channels: config.output_channels,
// TODO: Expose additional sidechain IO in the JACK backend
aux_input_busses: AuxiliaryIOConfig::default(),
aux_output_busses: AuxiliaryIOConfig::default(),
},
buffer_config: BufferConfig {
sample_rate: config.sample_rate,

View file

@ -277,6 +277,8 @@ impl<P: Vst3Plugin> WrapperInner<P> {
current_bus_config: AtomicCell::new(BusConfig {
num_input_channels: P::DEFAULT_NUM_INPUTS,
num_output_channels: P::DEFAULT_NUM_OUTPUTS,
aux_input_busses: P::DEFAULT_AUX_INPUTS.unwrap_or_default(),
aux_output_busses: P::DEFAULT_AUX_OUTPUTS.unwrap_or_default(),
}),
current_buffer_config: AtomicCell::new(None),
current_process_mode: AtomicCell::new(ProcessMode::Realtime),

View file

@ -26,7 +26,9 @@ use super::view::WrapperView;
use crate::context::Transport;
use crate::midi::{MidiConfig, NoteEvent};
use crate::param::ParamFlags;
use crate::plugin::{BufferConfig, BusConfig, ProcessMode, ProcessStatus, Vst3Plugin};
use crate::plugin::{
AuxiliaryIOConfig, BufferConfig, BusConfig, ProcessMode, ProcessStatus, Vst3Plugin,
};
use crate::util::permit_alloc;
use crate::wrapper::state;
use crate::wrapper::util::process_wrapper;
@ -74,7 +76,8 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
}
unsafe fn set_io_mode(&self, _mode: vst3_sys::vst::IoMode) -> tresult {
// This would need to integrate with the GUI, which we currently don't have
// Not quite sure what the point of this is when the processing setup also receives similar
// information
kResultOk
}
@ -83,9 +86,25 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
type_: vst3_sys::vst::MediaType,
dir: vst3_sys::vst::BusDirection,
) -> i32 {
// All plugins currently only have a single input and a single output bus
// A plugin has a main input and output bus if the default number of channels is non-zero,
// and a plugin can also have auxiliary input and output busses
match type_ {
x if x == vst3_sys::vst::MediaTypes::kAudio as i32 => 1,
x if x == vst3_sys::vst::MediaTypes::kAudio as i32
&& dir == vst3_sys::vst::BusDirections::kInput as i32 =>
{
let main_busses = if P::DEFAULT_NUM_INPUTS > 0 { 1 } else { 0 };
let aux_busses = P::DEFAULT_AUX_INPUTS.unwrap_or_default().num_busses as i32;
main_busses + aux_busses
}
x if x == vst3_sys::vst::MediaTypes::kAudio as i32
&& dir == vst3_sys::vst::BusDirections::kOutput as i32 =>
{
let main_busses = if P::DEFAULT_NUM_OUTPUTS > 0 { 1 } else { 0 };
let aux_busses = P::DEFAULT_AUX_OUTPUTS.unwrap_or_default().num_busses as i32;
main_busses + aux_busses
}
x if x == vst3_sys::vst::MediaTypes::kEvent as i32
&& dir == vst3_sys::vst::BusDirections::kInput as i32
&& P::MIDI_INPUT >= MidiConfig::Basic =>
@ -117,26 +136,71 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
let info = &mut *info;
info.media_type = vst3_sys::vst::MediaTypes::kAudio as i32;
info.bus_type = vst3_sys::vst::BusTypes::kMain as i32;
info.direction = dir;
info.flags = vst3_sys::vst::BusFlags::kDefaultActive as u32;
match (dir, index) {
(d, 0) if d == vst3_sys::vst::BusDirections::kInput as i32 => {
info.direction = vst3_sys::vst::BusDirections::kInput as i32;
info.channel_count =
self.inner.current_bus_config.load().num_input_channels as i32;
// This is fun since main IO is optional
let bus_config = self.inner.current_bus_config.load();
if dir == vst3_sys::vst::BusDirections::kInput as i32 {
let aux_inputs_only =
P::DEFAULT_NUM_INPUTS == 0 && P::DEFAULT_AUX_INPUTS.is_some();
let aux_input_start_idx = if aux_inputs_only { 0 } else { 1 };
if !aux_inputs_only && index == 0 {
info.bus_type = vst3_sys::vst::BusTypes::kMain as i32;
info.channel_count = bus_config.num_input_channels as i32;
u16strlcpy(&mut info.name, "Input");
kResultOk
} else if (aux_input_start_idx
..(aux_input_start_idx + bus_config.aux_input_busses.num_busses as i32))
.contains(&index)
{
info.bus_type = vst3_sys::vst::BusTypes::kAux as i32;
info.channel_count = bus_config.aux_input_busses.num_channels as i32;
if bus_config.aux_input_busses.num_busses <= 1 {
u16strlcpy(&mut info.name, "Sidechain Input");
} else {
u16strlcpy(
&mut info.name,
&format!("Sidechain Input {}", index - aux_input_start_idx),
);
}
kResultOk
} else {
kInvalidArgument
}
(d, 0) if d == vst3_sys::vst::BusDirections::kOutput as i32 => {
info.direction = vst3_sys::vst::BusDirections::kOutput as i32;
info.channel_count =
self.inner.current_bus_config.load().num_output_channels as i32;
} else if dir == vst3_sys::vst::BusDirections::kOutput as i32 {
let aux_outputs_only =
P::DEFAULT_NUM_OUTPUTS == 0 && P::DEFAULT_AUX_OUTPUTS.is_some();
let aux_output_start_idx = if aux_outputs_only { 0 } else { 1 };
if !aux_outputs_only && index == 0 {
info.bus_type = vst3_sys::vst::BusTypes::kMain as i32;
info.channel_count = bus_config.num_output_channels as i32;
u16strlcpy(&mut info.name, "Output");
kResultOk
} else if (aux_output_start_idx
..(aux_output_start_idx + bus_config.aux_output_busses.num_busses as i32))
.contains(&index)
{
info.bus_type = vst3_sys::vst::BusTypes::kAux as i32;
info.channel_count = bus_config.aux_output_busses.num_channels as i32;
if bus_config.aux_output_busses.num_busses <= 1 {
u16strlcpy(&mut info.name, "Sidechain Output");
} else {
u16strlcpy(
&mut info.name,
&format!("Sidechain Output {}", index - aux_output_start_idx),
);
}
kResultOk
} else {
kInvalidArgument
}
_ => kInvalidArgument,
} else {
kInvalidArgument
}
}
(t, d, 0)
@ -187,7 +251,12 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
let in_info = &*in_info;
let out_info = &mut *out_info;
match (in_info.media_type, in_info.bus_index) {
(t, 0) if t == vst3_sys::vst::MediaTypes::kAudio as i32 => {
(t, 0)
if t == vst3_sys::vst::MediaTypes::kAudio as i32
// We only have an IO pair when the plugin has both a main input and a main output
&& P::DEFAULT_NUM_INPUTS > 0
&& P::DEFAULT_NUM_OUTPUTS > 0 =>
{
out_info.media_type = vst3_sys::vst::MediaTypes::kAudio as i32;
out_info.bus_index = in_info.bus_index;
out_info.channel = in_info.channel;
@ -205,7 +274,7 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
kResultOk
}
_ => kInvalidArgument,
_ => kResultFalse,
}
}
@ -218,7 +287,32 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
) -> tresult {
// We don't need any special handling here
match (type_, dir, index) {
(t, _, 0) if t == vst3_sys::vst::MediaTypes::kAudio as i32 => kResultOk,
(t, d, _)
if t == vst3_sys::vst::MediaTypes::kAudio as i32
&& d == vst3_sys::vst::BusDirections::kInput as i32 =>
{
let main_busses = if P::DEFAULT_NUM_INPUTS > 0 { 1 } else { 0 };
let aux_busses = P::DEFAULT_AUX_INPUTS.unwrap_or_default().num_busses as i32;
if (0..main_busses + aux_busses).contains(&index) {
kResultOk
} else {
kInvalidArgument
}
}
(t, d, _)
if t == vst3_sys::vst::MediaTypes::kAudio as i32
&& d == vst3_sys::vst::BusDirections::kOutput as i32 =>
{
let main_busses = if P::DEFAULT_NUM_OUTPUTS > 0 { 1 } else { 0 };
let aux_busses = P::DEFAULT_AUX_OUTPUTS.unwrap_or_default().num_busses as i32;
if (0..main_busses + aux_busses).contains(&index) {
kResultOk
} else {
kInvalidArgument
}
}
(t, d, 0)
if t == vst3_sys::vst::MediaTypes::kEvent as i32
&& d == vst3_sys::vst::BusDirections::kInput as i32
@ -271,6 +365,8 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
.resize_with(bus_config.num_output_channels as usize, || &mut [])
});
// TODO: Initialize auxiliary IO
kResultOk
} else {
kResultFalse
@ -599,16 +695,72 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
) -> tresult {
check_null_ptr!(inputs, outputs);
// We currently only do single audio bus IO configurations
if num_ins != 1 || num_outs != 1 {
// Why are these signed integers again?
if num_ins < 0 || num_outs < 0 {
return kInvalidArgument;
}
let input_channel_map = &*inputs;
let output_channel_map = &*outputs;
// Every auxiliary input or output needs to have the same number of channels. In order to
// support plugins with no main IO but with auxiliary IO, we'll need to take that into
// account when asserting this. If that's the case, then the first bus for that direction
// will have been marked auxiliary.
let aux_inputs_only = P::DEFAULT_NUM_INPUTS == 0 && P::DEFAULT_AUX_INPUTS.is_some();
let num_input_channels = if aux_inputs_only || num_ins < 1 {
0
} else {
(*inputs).count_ones()
};
let aux_input_start_idx = if aux_inputs_only { 0 } else { 1 };
let num_aux_input_busses = (num_ins as u32).saturating_sub(aux_input_start_idx);
let num_aux_input_channels = if num_aux_input_busses == 0 {
0
} else {
(*inputs.offset(aux_input_start_idx as isize)).count_ones()
};
for i in 1..num_aux_input_busses {
if (*inputs.offset((aux_input_start_idx + i) as isize)).count_ones()
!= num_aux_input_channels
{
nih_debug_assert_failure!("Mismatching auxiliary input bus channels set by host");
return kResultFalse;
}
}
let aux_outputs_only = P::DEFAULT_NUM_OUTPUTS == 0 && P::DEFAULT_AUX_OUTPUTS.is_some();
let num_output_channels = if aux_outputs_only || num_ins < 1 {
0
} else {
(*outputs).count_ones()
};
let aux_output_start_idx = if aux_outputs_only { 0 } else { 1 };
let num_aux_output_busses = (num_ins as u32).saturating_sub(aux_output_start_idx);
let num_aux_output_channels = if num_aux_output_busses == 0 {
0
} else {
(*outputs.offset(aux_output_start_idx as isize)).count_ones()
};
for i in 1..num_aux_output_busses {
if (*outputs.offset((aux_output_start_idx + i) as isize)).count_ones()
!= num_aux_output_channels
{
nih_debug_assert_failure!("Mismatching auxiliary output bus channels set by host");
return kResultFalse;
}
}
let proposed_config = BusConfig {
num_input_channels: input_channel_map.count_ones(),
num_output_channels: output_channel_map.count_ones(),
num_input_channels,
num_output_channels,
aux_input_busses: AuxiliaryIOConfig {
num_busses: num_aux_input_busses,
num_channels: num_aux_input_channels,
},
aux_output_busses: AuxiliaryIOConfig {
num_busses: num_aux_output_busses,
num_channels: num_aux_output_channels,
},
};
if self
.inner
@ -649,13 +801,35 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
}
};
let config = self.inner.current_bus_config.load();
let num_channels = match (dir, index) {
(d, 0) if d == vst3_sys::vst::BusDirections::kInput as i32 => config.num_input_channels,
(d, 0) if d == vst3_sys::vst::BusDirections::kOutput as i32 => {
config.num_output_channels
let bus_config = self.inner.current_bus_config.load();
let num_channels = if dir == vst3_sys::vst::BusDirections::kInput as i32 {
let aux_inputs_only = P::DEFAULT_NUM_INPUTS == 0 && P::DEFAULT_AUX_INPUTS.is_some();
let aux_input_start_idx = if aux_inputs_only { 0 } else { 1 };
if !aux_inputs_only && index == 0 {
bus_config.num_input_channels
} else if (aux_input_start_idx
..(aux_input_start_idx + bus_config.aux_input_busses.num_busses as i32))
.contains(&index)
{
bus_config.aux_input_busses.num_channels
} else {
return kInvalidArgument;
}
_ => return kInvalidArgument,
} else if dir == vst3_sys::vst::BusDirections::kOutput as i32 {
let aux_outputs_only = P::DEFAULT_NUM_OUTPUTS == 0 && P::DEFAULT_AUX_OUTPUTS.is_some();
let aux_output_start_idx = if aux_outputs_only { 0 } else { 1 };
if !aux_outputs_only && index == 0 {
bus_config.num_output_channels
} else if (aux_output_start_idx
..(aux_output_start_idx + bus_config.aux_output_busses.num_busses as i32))
.contains(&index)
{
bus_config.aux_output_busses.num_channels
} else {
return kInvalidArgument;
}
} else {
return kInvalidArgument;
};
let channel_map = channel_count_to_map(num_channels);
@ -734,6 +908,8 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
unsafe fn process(&self, data: *mut vst3_sys::vst::ProcessData) -> tresult {
check_null_ptr!(data);
// TODO: Handle auxiliary IO, this still assumes main IO is always present and there's no auxiliary IO
// Panic on allocations if the `assert_process_allocs` feature has been enabled, and make
// sure that FTZ is set up correctly
process_wrapper(|| {