1
0
Fork 0

Completely reword the audio IO layout system

Instead of a VST3-style polling function to test if a plugin supports a
certain layout, the plugin now explicitly enumerates the supported
layouts. This aligns better with non-VST3 plugin formats.
This commit is contained in:
Robbert van der Helm 2023-02-20 16:57:32 +01:00
parent 36c48157db
commit e8fd18ab80
32 changed files with 972 additions and 838 deletions

View file

@ -6,6 +6,29 @@ new and what's changed, this document lists all breaking changes in reverse
chronological order. If a new feature did not require any changes to existing chronological order. If a new feature did not require any changes to existing
code then it will not be listed here. code then it will not be listed here.
## [2023-02-20]
- The way audio IO layouts are configured has changed completely to align better
with NIH-plug's current and future supported plugin API backends. Rather than
defining a default layout and allowing the host/backend to change the channel
counts by polling the `Plugin::accepts_bus_config()` function, the plugin now
explicitly enumerates all supported audio IO layouts in a declarative fashion.
This change gives the plugin more options for defining alternative audio port
layouts including layouts with variable numbers of channels and ports, while
simultaneously removing ambiguities and behavior that was previously governed
by heuristics.
All types surrounding bus layouts and port names have changed slightly to
accommodate this change. Take a look at the updated examples for more details
on how this works. The `Plugin::AUDIO_IO_LAYOUTS` field's documentation also
contains an example for how to initialize the layouts slice.
- As a result of the above change, NIH-plug's standalones no longer have
`--input` and `--output` command line arguments to change the number of input
and output channels. Instead, they now have an `--audio-layout` option that
lets the user select an audio layout from the list of available layouts by
index. `--audio-layout=help` can be used to list those layouts.
## [2023-02-01] ## [2023-02-01]
- The `Vst3Plugin::VST3_CATEGORIES` string constant has been replaced by a - The `Vst3Plugin::VST3_CATEGORIES` string constant has been replaced by a

View file

@ -144,8 +144,8 @@ Scroll down for more information on the underlying plugin framework.
- MIDI SysEx is also supported. Plugins can define their own structs or sum - MIDI SysEx is also supported. Plugins can define their own structs or sum
types to wrap around those messages so they don't need to interact with raw types to wrap around those messages so they don't need to interact with raw
byte buffers in the process function. byte buffers in the process function.
- Support for flexible dynamic buffer configurations, including multiple input - Support for flexible dynamic buffer configurations, including variable numbers
and output busses. of input and output ports.
- A plugin bundler accessible through the - A plugin bundler accessible through the
`cargo xtask bundle <package> <build_arguments>` command that automatically `cargo xtask bundle <package> <build_arguments>` command that automatically
detects which plugin targets your plugin exposes and creates the correct detects which plugin targets your plugin exposes and creates the correct

View file

@ -24,6 +24,8 @@ mod envelope;
/// recording buffer's size. /// recording buffer's size.
pub const MAX_OCTAVE_SHIFT: u32 = 2; pub const MAX_OCTAVE_SHIFT: u32 = 2;
/// The number of channels supported by the plugin. We'll only do stereo for now.
const NUM_CHANNELS: u32 = 2;
/// The maximum size of an audio block. We'll split up the audio in blocks and render smoothed /// The maximum size of an audio block. We'll split up the audio in blocks and render smoothed
/// values to buffers since these values may need to be reused for multiple voices. /// values to buffers since these values may need to be reused for multiple voices.
const MAX_BLOCK_SIZE: usize = 64; const MAX_BLOCK_SIZE: usize = 64;
@ -181,8 +183,12 @@ impl Plugin for BuffrGlitch {
const VERSION: &'static str = env!("CARGO_PKG_VERSION"); const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const DEFAULT_INPUT_CHANNELS: u32 = 2; // We'll only do stereo for now
const DEFAULT_OUTPUT_CHANNELS: u32 = 2; const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[AudioIOLayout {
main_input_channels: NonZeroU32::new(NUM_CHANNELS),
main_output_channels: NonZeroU32::new(NUM_CHANNELS),
..AudioIOLayout::const_default()
}];
const MIDI_INPUT: MidiConfig = MidiConfig::Basic; const MIDI_INPUT: MidiConfig = MidiConfig::Basic;
@ -193,23 +199,21 @@ impl Plugin for BuffrGlitch {
self.params.clone() self.params.clone()
} }
fn accepts_bus_config(&self, config: &BusConfig) -> bool {
// We'll only do stereo for now
config.num_input_channels == config.num_output_channels && config.num_input_channels == 2
}
fn initialize( fn initialize(
&mut self, &mut self,
bus_config: &BusConfig, audio_io_layout: &AudioIOLayout,
buffer_config: &BufferConfig, buffer_config: &BufferConfig,
_context: &mut impl InitContext<Self>, _context: &mut impl InitContext<Self>,
) -> bool { ) -> bool {
let num_output_channels = audio_io_layout
.main_output_channels
.expect("Plugin does not have a main output")
.get() as usize;
self.sample_rate = buffer_config.sample_rate; self.sample_rate = buffer_config.sample_rate;
for voice in &mut self.voices { for voice in &mut self.voices {
voice.buffer.resize( voice
bus_config.num_input_channels as usize, .buffer
buffer_config.sample_rate, .resize(num_output_channels, buffer_config.sample_rate);
);
} }
true true
@ -443,6 +447,7 @@ impl Vst3Plugin for BuffrGlitch {
const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = &[ const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = &[
Vst3SubCategory::Fx, Vst3SubCategory::Fx,
Vst3SubCategory::Synth, Vst3SubCategory::Synth,
Vst3SubCategory::Stereo,
Vst3SubCategory::Custom("Glitch"), Vst3SubCategory::Custom("Glitch"),
]; ];
} }

View file

@ -301,8 +301,12 @@ impl Plugin for Crisp {
const VERSION: &'static str = env!("CARGO_PKG_VERSION"); const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const DEFAULT_INPUT_CHANNELS: u32 = NUM_CHANNELS; // We'll add a SIMD version in a bit which only supports stereo
const DEFAULT_OUTPUT_CHANNELS: u32 = NUM_CHANNELS; const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[AudioIOLayout {
main_input_channels: NonZeroU32::new(NUM_CHANNELS),
main_output_channels: NonZeroU32::new(NUM_CHANNELS),
..AudioIOLayout::const_default()
}];
const SAMPLE_ACCURATE_AUTOMATION: bool = true; const SAMPLE_ACCURATE_AUTOMATION: bool = true;
@ -317,20 +321,12 @@ impl Plugin for Crisp {
editor::create(self.params.clone(), self.params.editor_state.clone()) editor::create(self.params.clone(), self.params.editor_state.clone())
} }
fn accepts_bus_config(&self, config: &BusConfig) -> bool {
// We'll add a SIMD version in a bit which only supports stereo
config.num_input_channels == config.num_output_channels
&& config.num_input_channels == NUM_CHANNELS
}
fn initialize( fn initialize(
&mut self, &mut self,
bus_config: &BusConfig, _audio_io_layout: &AudioIOLayout,
buffer_config: &BufferConfig, buffer_config: &BufferConfig,
_context: &mut impl InitContext<Self>, _context: &mut impl InitContext<Self>,
) -> bool { ) -> bool {
nih_debug_assert_eq!(bus_config.num_input_channels, NUM_CHANNELS);
nih_debug_assert_eq!(bus_config.num_output_channels, NUM_CHANNELS);
self.sample_rate = buffer_config.sample_rate; self.sample_rate = buffer_config.sample_rate;
// The filter coefficients need to be reinitialized when loading a patch // The filter coefficients need to be reinitialized when loading a patch
@ -500,7 +496,6 @@ impl ClapPlugin for Crisp {
const CLAP_FEATURES: &'static [ClapFeature] = &[ const CLAP_FEATURES: &'static [ClapFeature] = &[
ClapFeature::AudioEffect, ClapFeature::AudioEffect,
ClapFeature::Stereo, ClapFeature::Stereo,
ClapFeature::Mono,
ClapFeature::Distortion, ClapFeature::Distortion,
]; ];
} }
@ -511,6 +506,7 @@ impl Vst3Plugin for Crisp {
Vst3SubCategory::Fx, Vst3SubCategory::Fx,
Vst3SubCategory::Filter, Vst3SubCategory::Filter,
Vst3SubCategory::Distortion, Vst3SubCategory::Distortion,
Vst3SubCategory::Stereo,
]; ];
} }

View file

@ -166,22 +166,24 @@ impl Plugin for Crossover {
const VERSION: &'static str = env!("CARGO_PKG_VERSION"); const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const DEFAULT_INPUT_CHANNELS: u32 = NUM_CHANNELS; const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[AudioIOLayout {
const DEFAULT_OUTPUT_CHANNELS: u32 = NUM_CHANNELS; main_input_channels: NonZeroU32::new(NUM_CHANNELS),
main_output_channels: NonZeroU32::new(NUM_CHANNELS),
const DEFAULT_AUX_OUTPUTS: Option<AuxiliaryIOConfig> = Some(AuxiliaryIOConfig { aux_input_ports: &[],
// Two to five of these busses will be used at a time // Two to five of these ports will be used at a time
num_busses: 5, aux_output_ports: &[new_nonzero_u32(NUM_CHANNELS); 5],
num_channels: NUM_CHANNELS,
});
const PORT_NAMES: PortNames = PortNames { names: PortNames {
main_input: None, layout: Some("Up to five bands"),
// We won't output any sound here
main_output: Some("The Void"), main_input: None,
aux_inputs: None, // We won't output any sound here
aux_outputs: Some(&["Band 1", "Band 2", "Band 3", "Band 4", "Band 5"]), main_output: Some("The Void"),
}; aux_inputs: &[],
aux_outputs: &["Band 1", "Band 2", "Band 3", "Band 4", "Band 5"],
},
}];
type SysExMessage = (); type SysExMessage = ();
type BackgroundTask = (); type BackgroundTask = ();
@ -190,16 +192,9 @@ impl Plugin for Crossover {
self.params.clone() self.params.clone()
} }
fn accepts_bus_config(&self, config: &BusConfig) -> bool {
// Only do stereo
config.num_input_channels == NUM_CHANNELS
&& config.num_output_channels == NUM_CHANNELS
&& config.aux_output_busses.num_channels == NUM_CHANNELS
}
fn initialize( fn initialize(
&mut self, &mut self,
_bus_config: &BusConfig, _audio_io_layout: &AudioIOLayout,
buffer_config: &BufferConfig, buffer_config: &BufferConfig,
context: &mut impl InitContext<Self>, context: &mut impl InitContext<Self>,
) -> bool { ) -> bool {

View file

@ -34,6 +34,8 @@ mod filter;
mod params; mod params;
mod spectrum; mod spectrum;
/// The number of channels we support. Hardcoded to simplify the SIMD version.
const NUM_CHANNELS: u32 = 2;
/// The maximum number of samples to iterate over at a time. /// The maximum number of samples to iterate over at a time.
const MAX_BLOCK_SIZE: usize = 64; const MAX_BLOCK_SIZE: usize = 64;
@ -81,8 +83,7 @@ impl Default for Diopser {
let bypass_smoother = Arc::new(Smoother::new(SmoothingStyle::Linear(10.0))); let bypass_smoother = Arc::new(Smoother::new(SmoothingStyle::Linear(10.0)));
// We only do stereo right now so this is simple // We only do stereo right now so this is simple
let (spectrum_input, spectrum_output) = let (spectrum_input, spectrum_output) = SpectrumInput::new(NUM_CHANNELS as usize);
SpectrumInput::new(Self::DEFAULT_OUTPUT_CHANNELS as usize);
Self { Self {
params: Arc::new(DiopserParams::new( params: Arc::new(DiopserParams::new(
@ -113,8 +114,12 @@ impl Plugin for Diopser {
const VERSION: &'static str = env!("CARGO_PKG_VERSION"); const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const DEFAULT_INPUT_CHANNELS: u32 = 2; // The SIMD version only supports stereo
const DEFAULT_OUTPUT_CHANNELS: u32 = 2; const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[AudioIOLayout {
main_input_channels: NonZeroU32::new(NUM_CHANNELS),
main_output_channels: NonZeroU32::new(NUM_CHANNELS),
..AudioIOLayout::const_default()
}];
const SAMPLE_ACCURATE_AUTOMATION: bool = true; const SAMPLE_ACCURATE_AUTOMATION: bool = true;
@ -151,14 +156,9 @@ impl Plugin for Diopser {
} }
} }
fn accepts_bus_config(&self, config: &BusConfig) -> bool {
// The SIMD version only supports stereo
config.num_input_channels == config.num_output_channels && config.num_input_channels == 2
}
fn initialize( fn initialize(
&mut self, &mut self,
_bus_config: &BusConfig, _audio_io_layout: &AudioIOLayout,
buffer_config: &BufferConfig, buffer_config: &BufferConfig,
_context: &mut impl InitContext<Self>, _context: &mut impl InitContext<Self>,
) -> bool { ) -> bool {
@ -355,8 +355,11 @@ impl ClapPlugin for Diopser {
impl Vst3Plugin for Diopser { impl Vst3Plugin for Diopser {
const VST3_CLASS_ID: [u8; 16] = *b"DiopserPlugRvdH."; const VST3_CLASS_ID: [u8; 16] = *b"DiopserPlugRvdH.";
const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = &[
&[Vst3SubCategory::Fx, Vst3SubCategory::Filter]; Vst3SubCategory::Fx,
Vst3SubCategory::Filter,
Vst3SubCategory::Stereo,
];
} }
nih_export_clap!(Diopser); nih_export_clap!(Diopser);

View file

@ -120,11 +120,27 @@ impl Plugin for Gain {
const VERSION: &'static str = env!("CARGO_PKG_VERSION"); const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const DEFAULT_INPUT_CHANNELS: u32 = 2; // The first audio IO layout is used as the default. The other layouts may be selected either
const DEFAULT_OUTPUT_CHANNELS: u32 = 2; // explicitly or automatically by the host or the user depending on the plugin API/backend.
const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[
AudioIOLayout {
main_input_channels: NonZeroU32::new(2),
main_output_channels: NonZeroU32::new(2),
const DEFAULT_AUX_INPUTS: Option<AuxiliaryIOConfig> = None; aux_input_ports: &[],
const DEFAULT_AUX_OUTPUTS: Option<AuxiliaryIOConfig> = None; aux_output_ports: &[],
// Individual ports and the layout as a whole can be named here. By default these names
// are generated as needed. This layout will be called 'Stereo', while the other one is
// given the name 'Mono' based no the number of input and output channels.
names: PortNames::const_default(),
},
AudioIOLayout {
main_input_channels: NonZeroU32::new(1),
main_output_channels: NonZeroU32::new(1),
..AudioIOLayout::const_default()
},
];
const MIDI_INPUT: MidiConfig = MidiConfig::None; const MIDI_INPUT: MidiConfig = MidiConfig::None;
// Setting this to `true` will tell the wrapper to split the buffer up into smaller blocks // Setting this to `true` will tell the wrapper to split the buffer up into smaller blocks
@ -147,11 +163,6 @@ impl Plugin for Gain {
self.params.clone() self.params.clone()
} }
fn accepts_bus_config(&self, config: &BusConfig) -> bool {
// This works with any symmetrical IO layout
config.num_input_channels == config.num_output_channels && config.num_input_channels > 0
}
// This plugin doesn't need any special initialization, but if you need to do anything expensive // This plugin doesn't need any special initialization, but if you need to do anything expensive
// then this would be the place. State is kept around when the host reconfigures the // then this would be the place. State is kept around when the host reconfigures the
// plugin. If we do need special initialization, we could implement the `initialize()` and/or // plugin. If we do need special initialization, we could implement the `initialize()` and/or

View file

@ -78,8 +78,18 @@ impl Plugin for Gain {
const VERSION: &'static str = env!("CARGO_PKG_VERSION"); const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const DEFAULT_INPUT_CHANNELS: u32 = 2; const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[
const DEFAULT_OUTPUT_CHANNELS: u32 = 2; AudioIOLayout {
main_input_channels: NonZeroU32::new(2),
main_output_channels: NonZeroU32::new(2),
..AudioIOLayout::const_default()
},
AudioIOLayout {
main_input_channels: NonZeroU32::new(1),
main_output_channels: NonZeroU32::new(1),
..AudioIOLayout::const_default()
},
];
const SAMPLE_ACCURATE_AUTOMATION: bool = true; const SAMPLE_ACCURATE_AUTOMATION: bool = true;
@ -153,14 +163,9 @@ impl Plugin for Gain {
) )
} }
fn accepts_bus_config(&self, config: &BusConfig) -> bool {
// This works with any symmetrical IO layout
config.num_input_channels == config.num_output_channels && config.num_input_channels > 0
}
fn initialize( fn initialize(
&mut self, &mut self,
_bus_config: &BusConfig, _audio_io_layout: &AudioIOLayout,
buffer_config: &BufferConfig, buffer_config: &BufferConfig,
_context: &mut impl InitContext<Self>, _context: &mut impl InitContext<Self>,
) -> bool { ) -> bool {

View file

@ -75,8 +75,18 @@ impl Plugin for Gain {
const VERSION: &'static str = env!("CARGO_PKG_VERSION"); const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const DEFAULT_INPUT_CHANNELS: u32 = 2; const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[
const DEFAULT_OUTPUT_CHANNELS: u32 = 2; AudioIOLayout {
main_input_channels: NonZeroU32::new(2),
main_output_channels: NonZeroU32::new(2),
..AudioIOLayout::const_default()
},
AudioIOLayout {
main_input_channels: NonZeroU32::new(1),
main_output_channels: NonZeroU32::new(1),
..AudioIOLayout::const_default()
},
];
const SAMPLE_ACCURATE_AUTOMATION: bool = true; const SAMPLE_ACCURATE_AUTOMATION: bool = true;
@ -95,14 +105,9 @@ impl Plugin for Gain {
) )
} }
fn accepts_bus_config(&self, config: &BusConfig) -> bool {
// This works with any symmetrical IO layout
config.num_input_channels == config.num_output_channels && config.num_input_channels > 0
}
fn initialize( fn initialize(
&mut self, &mut self,
_bus_config: &BusConfig, _audio_io_layout: &AudioIOLayout,
buffer_config: &BufferConfig, buffer_config: &BufferConfig,
_context: &mut impl InitContext<Self>, _context: &mut impl InitContext<Self>,
) -> bool { ) -> bool {

View file

@ -74,8 +74,18 @@ impl Plugin for Gain {
const VERSION: &'static str = env!("CARGO_PKG_VERSION"); const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const DEFAULT_INPUT_CHANNELS: u32 = 2; const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[
const DEFAULT_OUTPUT_CHANNELS: u32 = 2; AudioIOLayout {
main_input_channels: NonZeroU32::new(2),
main_output_channels: NonZeroU32::new(2),
..AudioIOLayout::const_default()
},
AudioIOLayout {
main_input_channels: NonZeroU32::new(1),
main_output_channels: NonZeroU32::new(1),
..AudioIOLayout::const_default()
},
];
const SAMPLE_ACCURATE_AUTOMATION: bool = true; const SAMPLE_ACCURATE_AUTOMATION: bool = true;
@ -94,14 +104,9 @@ impl Plugin for Gain {
) )
} }
fn accepts_bus_config(&self, config: &BusConfig) -> bool {
// This works with any symmetrical IO layout
config.num_input_channels == config.num_output_channels && config.num_input_channels > 0
}
fn initialize( fn initialize(
&mut self, &mut self,
_bus_config: &BusConfig, _audio_io_layout: &AudioIOLayout,
buffer_config: &BufferConfig, buffer_config: &BufferConfig,
_context: &mut impl InitContext<Self>, _context: &mut impl InitContext<Self>,
) -> bool { ) -> bool {

View file

@ -26,8 +26,8 @@ impl Plugin for MidiInverter {
const VERSION: &'static str = env!("CARGO_PKG_VERSION"); const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const DEFAULT_INPUT_CHANNELS: u32 = 0; // This plugin doesn't have any audio IO
const DEFAULT_OUTPUT_CHANNELS: u32 = 0; const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[];
const MIDI_INPUT: MidiConfig = MidiConfig::MidiCCs; const MIDI_INPUT: MidiConfig = MidiConfig::MidiCCs;
const MIDI_OUTPUT: MidiConfig = MidiConfig::MidiCCs; const MIDI_OUTPUT: MidiConfig = MidiConfig::MidiCCs;

View file

@ -148,8 +148,11 @@ impl Plugin for PolyModSynth {
const VERSION: &'static str = env!("CARGO_PKG_VERSION"); const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const DEFAULT_INPUT_CHANNELS: u32 = 2; const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[AudioIOLayout {
const DEFAULT_OUTPUT_CHANNELS: u32 = 2; main_input_channels: NonZeroU32::new(2),
main_output_channels: NonZeroU32::new(2),
..AudioIOLayout::const_default()
}];
// We won't need any MIDI CCs here, we just want notes and polyphonic modulation // We won't need any MIDI CCs here, we just want notes and polyphonic modulation
const MIDI_INPUT: MidiConfig = MidiConfig::Basic; const MIDI_INPUT: MidiConfig = MidiConfig::Basic;
@ -612,8 +615,11 @@ impl ClapPlugin for PolyModSynth {
// modulation // modulation
impl Vst3Plugin for PolyModSynth { impl Vst3Plugin for PolyModSynth {
const VST3_CLASS_ID: [u8; 16] = *b"PolyM0dSynth1337"; const VST3_CLASS_ID: [u8; 16] = *b"PolyM0dSynth1337";
const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = &[
&[Vst3SubCategory::Instrument, Vst3SubCategory::Synth]; Vst3SubCategory::Instrument,
Vst3SubCategory::Synth,
Vst3SubCategory::Stereo,
];
} }
nih_export_clap!(PolyModSynth); nih_export_clap!(PolyModSynth);

View file

@ -105,8 +105,19 @@ impl Plugin for Sine {
const VERSION: &'static str = env!("CARGO_PKG_VERSION"); const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const DEFAULT_INPUT_CHANNELS: u32 = 0; const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[
const DEFAULT_OUTPUT_CHANNELS: u32 = 2; AudioIOLayout {
// This is also the default and can be omitted here
main_input_channels: None,
main_output_channels: NonZeroU32::new(2),
..AudioIOLayout::const_default()
},
AudioIOLayout {
main_input_channels: None,
main_output_channels: NonZeroU32::new(1),
..AudioIOLayout::const_default()
},
];
const MIDI_INPUT: MidiConfig = MidiConfig::Basic; const MIDI_INPUT: MidiConfig = MidiConfig::Basic;
const SAMPLE_ACCURATE_AUTOMATION: bool = true; const SAMPLE_ACCURATE_AUTOMATION: bool = true;
@ -118,14 +129,9 @@ impl Plugin for Sine {
self.params.clone() self.params.clone()
} }
fn accepts_bus_config(&self, config: &BusConfig) -> bool {
// This can output to any number of channels, but it doesn't take any audio inputs
config.num_input_channels == 0 && config.num_output_channels > 0
}
fn initialize( fn initialize(
&mut self, &mut self,
_bus_config: &BusConfig, _audio_io_layout: &AudioIOLayout,
buffer_config: &BufferConfig, buffer_config: &BufferConfig,
_context: &mut impl InitContext<Self>, _context: &mut impl InitContext<Self>,
) -> bool { ) -> bool {

View file

@ -90,8 +90,12 @@ impl Plugin for Stft {
const VERSION: &'static str = env!("CARGO_PKG_VERSION"); const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const DEFAULT_INPUT_CHANNELS: u32 = 2; // We'll only do stereo for simplicity's sake
const DEFAULT_OUTPUT_CHANNELS: u32 = 2; const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[AudioIOLayout {
main_input_channels: NonZeroU32::new(2),
main_output_channels: NonZeroU32::new(2),
..AudioIOLayout::const_default()
}];
const SAMPLE_ACCURATE_AUTOMATION: bool = true; const SAMPLE_ACCURATE_AUTOMATION: bool = true;
@ -102,14 +106,9 @@ impl Plugin for Stft {
self.params.clone() self.params.clone()
} }
fn accepts_bus_config(&self, config: &BusConfig) -> bool {
// We'll only do stereo for simplicity's sake
config.num_input_channels == config.num_output_channels && config.num_input_channels == 2
}
fn initialize( fn initialize(
&mut self, &mut self,
_bus_config: &BusConfig, _audio_io_layout: &AudioIOLayout,
_buffer_config: &BufferConfig, _buffer_config: &BufferConfig,
context: &mut impl InitContext<Self>, context: &mut impl InitContext<Self>,
) -> bool { ) -> bool {
@ -172,15 +171,17 @@ impl ClapPlugin for Stft {
const CLAP_FEATURES: &'static [ClapFeature] = &[ const CLAP_FEATURES: &'static [ClapFeature] = &[
ClapFeature::AudioEffect, ClapFeature::AudioEffect,
ClapFeature::Stereo, ClapFeature::Stereo,
ClapFeature::Mono,
ClapFeature::Utility, ClapFeature::Utility,
]; ];
} }
impl Vst3Plugin for Stft { impl Vst3Plugin for Stft {
const VST3_CLASS_ID: [u8; 16] = *b"StftMoistestPlug"; const VST3_CLASS_ID: [u8; 16] = *b"StftMoistestPlug";
const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = &[
&[Vst3SubCategory::Fx, Vst3SubCategory::Tools]; Vst3SubCategory::Fx,
Vst3SubCategory::Tools,
Vst3SubCategory::Stereo,
];
} }
nih_export_clap!(Stft); nih_export_clap!(Stft);

View file

@ -51,8 +51,8 @@ impl Plugin for SysEx {
const VERSION: &'static str = env!("CARGO_PKG_VERSION"); const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const DEFAULT_INPUT_CHANNELS: u32 = 0; // This plugin doesn't have any audio IO
const DEFAULT_OUTPUT_CHANNELS: u32 = 0; const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[];
const SAMPLE_ACCURATE_AUTOMATION: bool = true; const SAMPLE_ACCURATE_AUTOMATION: bool = true;

View file

@ -120,8 +120,18 @@ impl Plugin for LoudnessWarWinner {
const VERSION: &'static str = env!("CARGO_PKG_VERSION"); const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const DEFAULT_INPUT_CHANNELS: u32 = 2; const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[
const DEFAULT_OUTPUT_CHANNELS: u32 = 2; AudioIOLayout {
main_input_channels: NonZeroU32::new(2),
main_output_channels: NonZeroU32::new(2),
..AudioIOLayout::const_default()
},
AudioIOLayout {
main_input_channels: NonZeroU32::new(1),
main_output_channels: NonZeroU32::new(1),
..AudioIOLayout::const_default()
},
];
type SysExMessage = (); type SysExMessage = ();
type BackgroundTask = (); type BackgroundTask = ();
@ -130,22 +140,20 @@ impl Plugin for LoudnessWarWinner {
self.params.clone() self.params.clone()
} }
fn accepts_bus_config(&self, config: &BusConfig) -> bool {
config.num_input_channels == config.num_output_channels && config.num_input_channels > 0
}
fn initialize( fn initialize(
&mut self, &mut self,
bus_config: &BusConfig, audio_io_layout: &AudioIOLayout,
buffer_config: &BufferConfig, buffer_config: &BufferConfig,
_context: &mut impl InitContext<Self>, _context: &mut impl InitContext<Self>,
) -> bool { ) -> bool {
self.sample_rate = buffer_config.sample_rate; self.sample_rate = buffer_config.sample_rate;
self.bp_filters.resize( let num_output_channels = audio_io_layout
bus_config.num_output_channels as usize, .main_output_channels
[filter::Biquad::default(); 4], .expect("Plugin does not have a main output")
); .get() as usize;
self.bp_filters
.resize(num_output_channels, [filter::Biquad::default(); 4]);
self.update_bp_filters(); self.update_bp_filters();
self.silence_fadeout_start_samples = self.silence_fadeout_start_samples =

View file

@ -165,8 +165,12 @@ impl Plugin for PubertySimulator {
const VERSION: &'static str = env!("CARGO_PKG_VERSION"); const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const DEFAULT_INPUT_CHANNELS: u32 = 2; // We'll only do stereo for simplicity's sake
const DEFAULT_OUTPUT_CHANNELS: u32 = 2; const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[AudioIOLayout {
main_input_channels: NonZeroU32::new(2),
main_output_channels: NonZeroU32::new(2),
..AudioIOLayout::const_default()
}];
type SysExMessage = (); type SysExMessage = ();
type BackgroundTask = (); type BackgroundTask = ();
@ -175,14 +179,9 @@ impl Plugin for PubertySimulator {
self.params.clone() self.params.clone()
} }
fn accepts_bus_config(&self, config: &BusConfig) -> bool {
// We'll only do stereo for simplicity's sake
config.num_input_channels == config.num_output_channels && config.num_input_channels == 2
}
fn initialize( fn initialize(
&mut self, &mut self,
_bus_config: &BusConfig, _audio_io_layout: &AudioIOLayout,
_buffer_config: &BufferConfig, _buffer_config: &BufferConfig,
context: &mut impl InitContext<Self>, context: &mut impl InitContext<Self>,
) -> bool { ) -> bool {
@ -431,8 +430,11 @@ impl ClapPlugin for PubertySimulator {
impl Vst3Plugin for PubertySimulator { impl Vst3Plugin for PubertySimulator {
const VST3_CLASS_ID: [u8; 16] = *b"PubertySim..RvdH"; const VST3_CLASS_ID: [u8; 16] = *b"PubertySim..RvdH";
const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = &[
&[Vst3SubCategory::Fx, Vst3SubCategory::PitchShift]; Vst3SubCategory::Fx,
Vst3SubCategory::PitchShift,
Vst3SubCategory::Stereo,
];
} }
nih_export_clap!(PubertySimulator); nih_export_clap!(PubertySimulator);

View file

@ -155,8 +155,18 @@ impl Plugin for SafetyLimiter {
const VERSION: &'static str = env!("CARGO_PKG_VERSION"); const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const DEFAULT_INPUT_CHANNELS: u32 = 2; const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[
const DEFAULT_OUTPUT_CHANNELS: u32 = 2; AudioIOLayout {
main_input_channels: NonZeroU32::new(2),
main_output_channels: NonZeroU32::new(2),
..AudioIOLayout::const_default()
},
AudioIOLayout {
main_input_channels: NonZeroU32::new(1),
main_output_channels: NonZeroU32::new(1),
..AudioIOLayout::const_default()
},
];
type SysExMessage = (); type SysExMessage = ();
type BackgroundTask = (); type BackgroundTask = ();
@ -165,13 +175,9 @@ impl Plugin for SafetyLimiter {
self.params.clone() self.params.clone()
} }
fn accepts_bus_config(&self, config: &BusConfig) -> bool {
config.num_input_channels == config.num_output_channels
}
fn initialize( fn initialize(
&mut self, &mut self,
_bus_config: &BusConfig, _audio_io_layout: &AudioIOLayout,
buffer_config: &BufferConfig, buffer_config: &BufferConfig,
_context: &mut impl InitContext<Self>, _context: &mut impl InitContext<Self>,
) -> bool { ) -> bool {

View file

@ -132,10 +132,7 @@ impl Default for SpectralCompressor {
fn default() -> Self { fn default() -> Self {
// Changing any of the compressor threshold or ratio parameters will set an atomic flag in // Changing any of the compressor threshold or ratio parameters will set an atomic flag in
// this object that causes the compressor thresholds and ratios to be recalcualted // this object that causes the compressor thresholds and ratios to be recalcualted
let compressor_bank = compressor_bank::CompressorBank::new( let compressor_bank = compressor_bank::CompressorBank::new(2, MAX_WINDOW_SIZE);
Self::DEFAULT_OUTPUT_CHANNELS as usize,
MAX_WINDOW_SIZE,
);
SpectralCompressor { SpectralCompressor {
params: Arc::new(SpectralCompressorParams::new(&compressor_bank)), params: Arc::new(SpectralCompressorParams::new(&compressor_bank)),
@ -149,7 +146,7 @@ impl Default for SpectralCompressor {
}, },
// These three will be set to the correct values in the initialize function // These three will be set to the correct values in the initialize function
stft: util::StftHelper::new(Self::DEFAULT_OUTPUT_CHANNELS as usize, MAX_WINDOW_SIZE, 0), stft: util::StftHelper::new(2, MAX_WINDOW_SIZE, 0),
window_function: Vec::with_capacity(MAX_WINDOW_SIZE), window_function: Vec::with_capacity(MAX_WINDOW_SIZE),
dry_wet_mixer: dry_wet_mixer::DryWetMixer::new(0, 0, 0), dry_wet_mixer: dry_wet_mixer::DryWetMixer::new(0, 0, 0),
compressor_bank, compressor_bank,
@ -256,12 +253,24 @@ impl Plugin for SpectralCompressor {
const VERSION: &'static str = env!("CARGO_PKG_VERSION"); const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const DEFAULT_INPUT_CHANNELS: u32 = 2; const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[
const DEFAULT_OUTPUT_CHANNELS: u32 = 2; AudioIOLayout {
const DEFAULT_AUX_INPUTS: Option<AuxiliaryIOConfig> = Some(AuxiliaryIOConfig { main_input_channels: NonZeroU32::new(2),
num_busses: 1, main_output_channels: NonZeroU32::new(2),
num_channels: 2,
}); aux_input_ports: &[new_nonzero_u32(2)],
..AudioIOLayout::const_default()
},
AudioIOLayout {
main_input_channels: NonZeroU32::new(1),
main_output_channels: NonZeroU32::new(1),
aux_input_ports: &[new_nonzero_u32(1)],
..AudioIOLayout::const_default()
},
];
const SAMPLE_ACCURATE_AUTOMATION: bool = true; const SAMPLE_ACCURATE_AUTOMATION: bool = true;
@ -276,35 +285,31 @@ impl Plugin for SpectralCompressor {
editor::create(self.params.clone(), self.editor_state.clone()) editor::create(self.params.clone(), self.editor_state.clone())
} }
fn accepts_bus_config(&self, config: &BusConfig) -> bool {
// We can support any channel layout as long as the number of channels is consistent
config.num_input_channels == config.num_output_channels
&& config.num_input_channels > 0
&& config.aux_input_busses.num_busses == 1
&& config.aux_input_busses.num_channels == config.num_input_channels
}
fn initialize( fn initialize(
&mut self, &mut self,
bus_config: &BusConfig, audio_io_layout: &AudioIOLayout,
buffer_config: &BufferConfig, buffer_config: &BufferConfig,
context: &mut impl InitContext<Self>, context: &mut impl InitContext<Self>,
) -> bool { ) -> bool {
// Needed to update the compressors later // Needed to update the compressors later
self.buffer_config = *buffer_config; self.buffer_config = *buffer_config;
// This plugin can accept any number of channels, so we need to resize channel-dependent // This plugin can accept a variable number of audio channels, so we need to resize
// data structures accordinly // channel-dependent data structures accordingly
if self.stft.num_channels() != bus_config.num_output_channels as usize { let num_output_channels = audio_io_layout
.main_output_channels
.expect("Plugin does not have a main output")
.get() as usize;
if self.stft.num_channels() != num_output_channels {
self.stft = util::StftHelper::new(self.stft.num_channels(), MAX_WINDOW_SIZE, 0); self.stft = util::StftHelper::new(self.stft.num_channels(), MAX_WINDOW_SIZE, 0);
} }
self.dry_wet_mixer.resize( self.dry_wet_mixer.resize(
bus_config.num_output_channels as usize, num_output_channels,
buffer_config.max_buffer_size as usize, buffer_config.max_buffer_size as usize,
MAX_WINDOW_SIZE, MAX_WINDOW_SIZE,
); );
self.compressor_bank self.compressor_bank
.update_capacity(bus_config.num_output_channels as usize, MAX_WINDOW_SIZE); .update_capacity(num_output_channels, MAX_WINDOW_SIZE);
// Planning with RustFFT is very fast, but it will still allocate we we'll plan all of the // Planning with RustFFT is very fast, but it will still allocate we we'll plan all of the
// FFTs we might need in advance // FFTs we might need in advance
@ -572,6 +577,7 @@ impl ClapPlugin for SpectralCompressor {
const CLAP_FEATURES: &'static [ClapFeature] = &[ const CLAP_FEATURES: &'static [ClapFeature] = &[
ClapFeature::AudioEffect, ClapFeature::AudioEffect,
ClapFeature::Stereo, ClapFeature::Stereo,
ClapFeature::Mono,
ClapFeature::PhaseVocoder, ClapFeature::PhaseVocoder,
ClapFeature::Compressor, ClapFeature::Compressor,
ClapFeature::Custom("nih:spectral"), ClapFeature::Custom("nih:spectral"),

View file

@ -1,55 +1,77 @@
//! Types and definitions surrounding a plugin's audio bus setup. //! Types and definitions surrounding a plugin's audio IO setup.
use std::num::NonZeroU32;
use crate::buffer::Buffer; use crate::buffer::Buffer;
/// The plugin's IO configuration. /// A description of a plugin's audio IO configuration. The [`Plugin`][crate::prelude::Plugin]
#[derive(Debug, Clone, Copy, PartialEq, Eq)] /// defines a list of supported audio IO configs, with the first one acting as the default layout.
pub struct BusConfig { /// Depending on the plugin API, the host may pick a different configuration from the list and use
/// The number of input channels for the plugin. /// that instead. The final chosen configuration is passed as an argument to the
pub num_input_channels: u32, /// [`Plugin::initialize()`][crate::prelude::Plugin::initialize] function so the plugin can allocate
/// The number of output channels for the plugin. /// its data structures based on the number of audio channels it needs to process.
pub num_output_channels: u32, #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
/// Any additional sidechain inputs. pub struct AudioIOLayout {
pub aux_input_busses: AuxiliaryIOConfig, /// The number of main input channels for the plugin, if it has a main input port. This can be
/// Any additional outputs. /// set to `None` if the plugin does not have one.
pub aux_output_busses: AuxiliaryIOConfig, pub main_input_channels: Option<NonZeroU32>,
/// The number of main output channels for the plugin, if it has a main output port. This can be
/// set to `None` if the plugin does not have one.
pub main_output_channels: Option<NonZeroU32>,
/// The plugin's additional sidechain inputs, if it has any. Use the [`new_nonzero_u32()`]
/// function to construct these values until const `Option::unwrap()` gets stabilized
/// (<https://github.com/rust-lang/rust/issues/67441>).
pub aux_input_ports: &'static [NonZeroU32],
/// The plugin's additional outputs, if it has any. Use the [`new_nonzero_u32()`] function to
/// construct these values until const `Option::unwrap()` gets stabilized
/// (<https://github.com/rust-lang/rust/issues/67441>).
pub aux_output_ports: &'static [NonZeroU32],
/// Optional names for the audio ports. Defining these can be useful for plugins with multiple
/// output and input ports.
pub names: PortNames,
} }
/// Configuration for auxiliary inputs or outputs on [`BusConfig`]. /// Construct a `NonZeroU32` value at compile time. Equivalent to `NonZeroU32::new(n).unwrap()`.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub const fn new_nonzero_u32(n: u32) -> NonZeroU32 {
pub struct AuxiliaryIOConfig { match NonZeroU32::new(n) {
/// The number of auxiliary input or output busses. Some(n) => n,
pub num_busses: u32, None => panic!("'new_nonzero_u32()' called with a zero value"),
/// The number of channels in each bus. }
pub num_channels: u32,
} }
/// Contains auxiliary (sidechain) input and output buffers for a process call. /// Contains auxiliary (sidechain) input and output buffers for a process call.
pub struct AuxiliaryBuffers<'a> { pub struct AuxiliaryBuffers<'a> {
/// All auxiliary (sidechain) inputs defined for this plugin. The data in these buffers can /// Buffers for all auxiliary (sidechain) inputs defined for this plugin. The data in these
/// safely be overwritten. Auxiliary inputs can be defined by setting /// buffers can safely be overwritten. Auxiliary inputs can be defined using the
/// [`Plugin::DEFAULT_AUX_INPUTS`][`crate::prelude::Plugin::DEFAULT_AUX_INPUTS`]. /// [`AudioIOLayout::aux_input_ports`] field.
pub inputs: &'a mut [Buffer<'a>], pub inputs: &'a mut [Buffer<'a>],
/// Get all auxiliary outputs defined for this plugin. Auxiliary outputs can be defined by /// Buffers for all auxiliary outputs defined for this plugin. Auxiliary outputs can be defined using the
/// setting [`Plugin::DEFAULT_AUX_OUTPUTS`][`crate::prelude::Plugin::DEFAULT_AUX_OUTPUTS`]. /// [`AudioIOLayout::aux_output_ports`] field.
pub outputs: &'a mut [Buffer<'a>], pub outputs: &'a mut [Buffer<'a>],
} }
/// Contains names for the main input and output ports as well as for all of the auxiliary input and /// Contains names for the ports defined in an `AudioIOLayout`. Setting these is optional, but it
/// output ports. Setting these is optional, but it makes working with multi-output plugins much /// makes working with multi-output plugins much more convenient.
/// more convenient. ///
#[derive(Debug, Default, Clone, PartialEq, Eq)] /// All of these names should start with a capital letter to be consistent with automatically
/// generated names.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PortNames { pub struct PortNames {
/// The name for the audio IO layout as a whole. Useful when a plugin has multiple distinct
/// layouts. Will be generated if not set.
pub layout: Option<&'static str>,
/// The name for the main input port. Will be generated if not set. /// The name for the main input port. Will be generated if not set.
pub main_input: Option<&'static str>, pub main_input: Option<&'static str>,
/// The name for the main output port. Will be generated if not set. /// The name for the main output port. Will be generated if not set.
pub main_output: Option<&'static str>, pub main_output: Option<&'static str>,
/// Names for auxiliary (sidechain) input ports. Will be generated if not set or if this slice /// Names for auxiliary (sidechain) input ports. Will be generated if not set or if this slice
/// does not contain enough names. /// does not contain enough names.
pub aux_inputs: Option<&'static [&'static str]>, pub aux_inputs: &'static [&'static str],
/// Names for auxiliary output ports. Will be generated if not set or if this slice does not /// Names for auxiliary output ports. Will be generated if not set or if this slice does not
/// contain enough names. /// contain enough names.
pub aux_outputs: Option<&'static [&'static str]>, pub aux_outputs: &'static [&'static str],
} }
/// Configuration for (the host's) audio buffers. /// Configuration for (the host's) audio buffers.
@ -82,3 +104,101 @@ pub enum ProcessMode {
/// processed. /// processed.
Offline, Offline,
} }
impl AudioIOLayout {
/// [`AudioIOLayout::default()`], but as a const function. Used when initializing
/// `Plugin::AUDIO_IO_LAYOUTS`. (<https://github.com/rust-lang/rust/issues/67792>)
pub const fn const_default() -> Self {
Self {
main_input_channels: None,
main_output_channels: None,
aux_input_ports: &[],
aux_output_ports: &[],
names: PortNames::const_default(),
}
}
/// A descriptive name for the layout. This is taken from `PortNames::layout` if set. Otherwise
/// it is generated based on the layout.
pub fn name(&self) -> String {
if let Some(name) = self.names.layout {
return name.to_owned();
}
// If the name is not set then we'll try to come up with something descriptive
match (
self.main_input_channels
.map(NonZeroU32::get)
.unwrap_or_default(),
self.main_output_channels
.map(NonZeroU32::get)
.unwrap_or_default(),
self.aux_input_ports.len(),
self.aux_output_ports.len(),
) {
(0, 0, 0, 0) => String::from("Empty"),
(_, 1, 0, _) | (1, 0, _, _) => String::from("Mono"),
(_, 2, 0, _) | (2, 0, _, _) => String::from("Stereo"),
(_, 1, _, _) => String::from("Mono with sidechain"),
(_, 2, _, _) => String::from("Stereo with sidechain"),
// These probably, hopefully won't occur
(i, o, 0, 0) => format!("{i} inputs, {o} outputs"),
(i, o, _, 0) => format!("{i} inputs, {o} outputs, with sidechain"),
// And these don't make much sense, suggestions for something better are welcome
(i, o, 0, aux_o) => format!("{i} inputs, {o}*{} outputs", aux_o + 1),
(i, o, aux_i, aux_o) => format!("{i}*{} inputs, {o}*{} outputs", aux_i + 1, aux_o + 1),
}
}
/// The name for the main input port. Either generated or taken from the `names` field.
pub fn main_input_name(&self) -> String {
self.names.main_input.unwrap_or("Input").to_owned()
}
/// The name for the main output port. Either generated or taken from the `names` field.
pub fn main_output_name(&self) -> String {
self.names.main_input.unwrap_or("Output").to_owned()
}
/// The name for the auxiliary input port with the given index. Either generated or taken from
/// the `names` field.
pub fn aux_input_name(&self, idx: usize) -> Option<String> {
if idx >= self.aux_input_ports.len() {
None
} else {
match self.names.aux_inputs.get(idx) {
Some(name) => Some(String::from(*name)),
None if self.aux_input_ports.len() == 1 => Some(String::from("Sidechain Input")),
None => Some(format!("Sidechain Input {}", idx + 1)),
}
}
}
/// The name for the auxiliary output port with the given index. Either generated or taken from
/// the `names` field.
pub fn aux_output_name(&self, idx: usize) -> Option<String> {
if idx >= self.aux_output_ports.len() {
None
} else {
match self.names.aux_outputs.get(idx) {
Some(name) => Some(String::from(*name)),
None if self.aux_output_ports.len() == 1 => Some(String::from("Auxiliary Output")),
None => Some(format!("Auxiliary Output {}", idx + 1)),
}
}
}
}
impl PortNames {
/// [`PortNames::default()`], but as a const function. Used when initializing
/// `Plugin::AUDIO_IO_LAYOUTS`. (<https://github.com/rust-lang/rust/issues/67792>)
pub const fn const_default() -> Self {
Self {
layout: None,
main_input: None,
main_output: None,
aux_inputs: &[],
aux_outputs: &[],
}
}
}

View file

@ -36,11 +36,10 @@
//! 1. When the host loads the plugin, your plugin object will be instantiated using its //! 1. When the host loads the plugin, your plugin object will be instantiated using its
//! [`Default`] implementation. The plugin should refrain from performing expensive //! [`Default`] implementation. The plugin should refrain from performing expensive
//! calculations or IO at this point. //! calculations or IO at this point.
//! 2. The host or the plugin wrapper will call //! 2. The host will select an audio IO layout from
//! [`Plugin::accepts_bus_config()`][prelude::Plugin::accepts_bus_config()] several times with //! [`Plugin::AUDIO_IO_LAYOUTS`][prelude::Plugin::AUDIO_IO_LAYOUTS]. The first layout is always
//! different IO configurations to poll whether your plugin supports certain IO configurations. //! used as the default one, and should reflect the plugin's most commonly used configuration.
//! The plugin should not do any work at this point and just reply with boolean whether it //! Usually this is a stereo layout.
//! supports the configuration or not.
//! 3. After that, [`Plugin::initialize()`][prelude::Plugin::initialize()] will be called with the //! 3. After that, [`Plugin::initialize()`][prelude::Plugin::initialize()] will be called with the
//! the selected IO configuration and the audio buffer settings. Here you should allocate any //! the selected IO configuration and the audio buffer settings. Here you should allocate any
//! data structures you need or precompute data that depends on the sample rate or maximum //! data structures you need or precompute data that depends on the sample rate or maximum

View file

@ -2,7 +2,7 @@
use std::sync::Arc; use std::sync::Arc;
use crate::audio_setup::{AuxiliaryBuffers, AuxiliaryIOConfig, BufferConfig, BusConfig, PortNames}; use crate::audio_setup::{AudioIOLayout, AuxiliaryBuffers, BufferConfig};
use crate::buffer::Buffer; use crate::buffer::Buffer;
use crate::context::gui::AsyncExecutor; use crate::context::gui::AsyncExecutor;
use crate::context::init::InitContext; use crate::context::init::InitContext;
@ -48,38 +48,33 @@ pub trait Plugin: Default + Send + 'static {
/// but just in case they do this should only contain decimals values and dots. /// but just in case they do this should only contain decimals values and dots.
const VERSION: &'static str; const VERSION: &'static str;
/// The default number of input channels. This merely serves as a default. The host will probe /// The plugin's supported audio IO layouts. The first config will be used as the default config
/// the plugin's supported configuration using /// if the host doesn't or can't select an alternative configuration. Because of that it's
/// [`accepts_bus_config()`][Self::accepts_bus_config()], and the selected configuration is /// recommended to begin this slice with a stereo layout. For maximum compatibility with the
/// passed to [`initialize()`][Self::initialize()]. Some hosts like, like Bitwig and Ardour, use /// different plugin formats this default layout should also include all of the plugin's
/// the defaults instead of setting up the busses properly. /// auxiliary input and output ports, if the plugin has any. If the slice is empty, then the
/// plugin will not have any audio IO.
/// ///
/// Setting this to zero causes the plugin to have no main input bus. /// Both [`AudioIOLayout`] and [`PortNames`][crate::prelude::PortNames] have `.const_default()`
const DEFAULT_INPUT_CHANNELS: u32 = 2; /// functions for compile-time equivalents to `Default::default()`:
/// The default number of output channels. All of the same caveats mentioned for
/// `DEFAULT_INPUT_CHANNELS` apply here.
/// ///
/// Setting this to zero causes the plugin to have no main output bus. /// ```
const DEFAULT_OUTPUT_CHANNELS: u32 = 2; /// # use nih_plug::prelude::*;
/// const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[AudioIOLayout {
/// If set, then the plugin will have this many sidechain input busses with a default number of /// main_input_channels: NonZeroU32::new(2),
/// channels. Not all hosts support more than one sidechain input bus. Negotiating the actual /// main_output_channels: NonZeroU32::new(2),
/// configuration works the same was as with `DEFAULT_INPUT_CHANNELS`. ///
const DEFAULT_AUX_INPUTS: Option<AuxiliaryIOConfig> = None; /// aux_input_ports: &[new_nonzero_u32(2)],
/// If set, then the plugin will have this many auxiliary output busses with a default number of ///
/// channels. Negotiating the actual configuration works the same was as with /// ..AudioIOLayout::const_default()
/// `DEFAULT_INPUT_CHANNELS`. /// }];
const DEFAULT_AUX_OUTPUTS: Option<AuxiliaryIOConfig> = None; /// ```
///
/// Optional names for the main and auxiliary input and output ports. Will be generated if not /// # Note
/// set. This is mostly useful to give descriptive names to the outputs for multi-output ///
/// plugins. /// Some plugin hosts, like Ableton Live, don't support MIDI-only plugins and may refuse to load
const PORT_NAMES: PortNames = PortNames { /// plugins with no main output or with zero main output channels.
main_input: None, const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout];
main_output: None,
aux_inputs: None,
aux_outputs: None,
};
/// Whether the plugin accepts note events, and what which events it wants to receive. If this /// 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. /// is set to [`MidiConfig::None`], then the plugin won't receive any note events.
@ -152,30 +147,28 @@ pub trait Plugin: Default + Send + 'static {
// The following functions follow the lifetime of the plugin. // The following functions follow the lifetime of the plugin.
// //
/// Whether the plugin supports a bus config. This only acts as a check, and the plugin /// Initialize the plugin for the given audio IO configuration. From this point onwards the
/// shouldn't do anything beyond returning true or false. /// audio IO layouts and the buffer sizes are fixed until this function is called again.
fn accepts_bus_config(&self, config: &BusConfig) -> bool {
config.num_input_channels == Self::DEFAULT_INPUT_CHANNELS
&& config.num_output_channels == Self::DEFAULT_OUTPUT_CHANNELS
}
/// Initialize the plugin for the given bus and buffer configurations. These configurations will
/// not change until this function is called again, so feel free to copy these objects to your
/// plugin's object. If the plugin is being restored from an old state, then that state will
/// have already been restored at this point. If based on those parameters (or for any reason
/// whatsoever) the plugin needs to introduce latency, then you can do so here using the process
/// context. Depending on how the host restores plugin state, this function may also be called
/// twice in rapid succession. If the plugin fails to initialize for whatever reason, then this
/// should return `false`.
/// ///
/// Before this point, the plugin should not have done any expensive initialization. Please /// Before this point, the plugin should not have done any expensive initialization. Please
/// don't be that plugin that takes twenty seconds to scan. /// don't be that plugin that takes twenty seconds to scan.
/// ///
/// After this function [`reset()`][Self::reset()] will always be called. If you need to clear /// After this function [`reset()`][Self::reset()] will always be called. If you need to clear
/// state, such as filters or envelopes, then you should do so in that function instead. /// state, such as filters or envelopes, then you should do so in that function instead.
///
/// - If you need to access this information in your process function, then you can copy the
/// values to your plugin instance's object.
/// - If the plugin is being restored from an old state,
/// then that state will have already been restored at this point.
/// - If based on those parameters (or for any reason whatsoever) the plugin needs to introduce
/// latency, then you can do so here using the process context.
/// - Depending on how the host restores plugin state, this function may be called multiple
/// times in rapid succession. It may thus be useful to check if the initialization work for
/// the current bufffer and audio IO configurations has already been performed first.
/// - If the plugin fails to initialize for whatever reason, then this should return `false`.
fn initialize( fn initialize(
&mut self, &mut self,
bus_config: &BusConfig, audio_io_layout: &AudioIOLayout,
buffer_config: &BufferConfig, buffer_config: &BufferConfig,
context: &mut impl InitContext<Self>, context: &mut impl InitContext<Self>,
) -> bool { ) -> bool {

View file

@ -1,3 +1,6 @@
// Used in [`AudioIOLayout`]
pub use std::num::NonZeroU32;
// Re-export the macros, derive macros are already re-exported from their respective modules // Re-export the macros, derive macros are already re-exported from their respective modules
pub use crate::debug::*; pub use crate::debug::*;
@ -11,7 +14,7 @@ pub use crate::formatters;
pub use crate::util; pub use crate::util;
pub use crate::audio_setup::{ pub use crate::audio_setup::{
AuxiliaryBuffers, AuxiliaryIOConfig, BufferConfig, BusConfig, PortNames, ProcessMode, new_nonzero_u32, AudioIOLayout, AuxiliaryBuffers, BufferConfig, PortNames, ProcessMode,
}; };
pub use crate::buffer::Buffer; pub use crate::buffer::Buffer;
pub use crate::context::gui::{AsyncExecutor, GuiContext, ParamSetter}; pub use crate::context::gui::{AsyncExecutor, GuiContext, ParamSetter};

View file

@ -68,6 +68,7 @@ use std::cmp;
use std::collections::{HashMap, HashSet, VecDeque}; use std::collections::{HashMap, HashSet, VecDeque};
use std::ffi::{c_void, CStr}; use std::ffi::{c_void, CStr};
use std::mem; use std::mem;
use std::num::NonZeroU32;
use std::os::raw::c_char; use std::os::raw::c_char;
use std::ptr; use std::ptr;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
@ -78,7 +79,7 @@ use std::time::Duration;
use super::context::{WrapperGuiContext, WrapperInitContext, WrapperProcessContext}; use super::context::{WrapperGuiContext, WrapperInitContext, WrapperProcessContext};
use super::descriptor::PluginDescriptor; use super::descriptor::PluginDescriptor;
use super::util::ClapPtr; use super::util::ClapPtr;
use crate::audio_setup::{AuxiliaryBuffers, BufferConfig, BusConfig, ProcessMode}; use crate::audio_setup::{AudioIOLayout, AuxiliaryBuffers, BufferConfig, ProcessMode};
use crate::buffer::Buffer; use crate::buffer::Buffer;
use crate::context::gui::AsyncExecutor; use crate::context::gui::AsyncExecutor;
use crate::context::process::Transport; use crate::context::process::Transport;
@ -127,8 +128,8 @@ pub struct Wrapper<P: ClapPlugin> {
is_processing: AtomicBool, is_processing: AtomicBool,
/// The current IO configuration, modified through the `clap_plugin_audio_ports_config` /// The current IO configuration, modified through the `clap_plugin_audio_ports_config`
/// extension. /// extension. Initialized to the plugin's first audio IO configuration.
current_bus_config: AtomicCell<BusConfig>, current_audio_io_layout: AtomicCell<AudioIOLayout>,
/// The current buffer configuration, containing the sample rate and the maximum block size. /// The current buffer configuration, containing the sample rate and the maximum block size.
/// Will be set in `clap_plugin::activate()`. /// Will be set in `clap_plugin::activate()`.
current_buffer_config: AtomicCell<Option<BufferConfig>>, current_buffer_config: AtomicCell<Option<BufferConfig>>,
@ -181,14 +182,6 @@ pub struct Wrapper<P: ClapPlugin> {
host_callback: ClapPtr<clap_host>, host_callback: ClapPtr<clap_host>,
clap_plugin_audio_ports_config: clap_plugin_audio_ports_config, clap_plugin_audio_ports_config: clap_plugin_audio_ports_config,
/// During initialization we'll ask `P` which bus configurations it supports. The host can then
/// use the audio ports config extension to choose a configuration. Right now we only query mono
/// and stereo configurations, with and without inputs, as well as the plugin's default input
/// and output channel counts if that does not match one of those configurations (to do the
/// least surprising thing).
///
/// TODO: Support surround setups once a plugin needs that
supported_bus_configs: Vec<BusConfig>,
// The main `clap_plugin` vtable. A pointer to this `Wrapper<P>` instance is stored in the // The main `clap_plugin` vtable. A pointer to this `Wrapper<P>` instance is stored in the
// `plugin_data` field. This pointer is set after creating the `Arc<Wrapper<P>>`. // `plugin_data` field. This pointer is set after creating the `Arc<Wrapper<P>>`.
@ -538,52 +531,6 @@ impl<P: ClapPlugin> Wrapper<P> {
} }
} }
// Query all sensible bus configurations supported by the plugin. We don't do surround or
// anything beyond stereo right now.
let mut supported_bus_configs = Vec::new();
for num_output_channels in [1, 2] {
for num_input_channels in [0, num_output_channels] {
#[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);
}
}
}
}
// In the off chance that the default config specified by the plugin is not in the above
// list, we'll try that as well.
let default_bus_config = BusConfig {
num_input_channels: P::DEFAULT_INPUT_CHANNELS,
num_output_channels: P::DEFAULT_OUTPUT_CHANNELS,
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)
{
supported_bus_configs.push(default_bus_config);
}
let wrapper = Self { let wrapper = Self {
this: AtomicRefCell::new(Weak::new()), this: AtomicRefCell::new(Weak::new()),
@ -596,12 +543,9 @@ impl<P: ClapPlugin> Wrapper<P> {
editor_scaling_factor: AtomicF32::new(1.0), editor_scaling_factor: AtomicF32::new(1.0),
is_processing: AtomicBool::new(false), is_processing: AtomicBool::new(false),
current_bus_config: AtomicCell::new(BusConfig { current_audio_io_layout: AtomicCell::new(
num_input_channels: P::DEFAULT_INPUT_CHANNELS, P::AUDIO_IO_LAYOUTS.first().copied().unwrap_or_default(),
num_output_channels: P::DEFAULT_OUTPUT_CHANNELS, ),
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_buffer_config: AtomicCell::new(None),
current_process_mode: AtomicCell::new(ProcessMode::Realtime), current_process_mode: AtomicCell::new(ProcessMode::Realtime),
input_events: AtomicRefCell::new(VecDeque::with_capacity(512)), input_events: AtomicRefCell::new(VecDeque::with_capacity(512)),
@ -643,7 +587,6 @@ impl<P: ClapPlugin> Wrapper<P> {
get: Some(Self::ext_audio_ports_config_get), get: Some(Self::ext_audio_ports_config_get),
select: Some(Self::ext_audio_ports_config_select), select: Some(Self::ext_audio_ports_config_select),
}, },
supported_bus_configs,
clap_plugin_audio_ports: clap_plugin_audio_ports { clap_plugin_audio_ports: clap_plugin_audio_ports {
count: Some(Self::ext_audio_ports_count), count: Some(Self::ext_audio_ports_count),
@ -1748,12 +1691,12 @@ impl<P: ClapPlugin> Wrapper<P> {
); );
} }
let bus_config = self.current_bus_config.load(); let audio_io_layout = self.current_audio_io_layout.load();
if let Some(buffer_config) = self.current_buffer_config.load() { if let Some(buffer_config) = self.current_buffer_config.load() {
// NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks // NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks
let mut init_context = self.make_init_context(); let mut init_context = self.make_init_context();
let mut plugin = self.plugin.lock(); let mut plugin = self.plugin.lock();
plugin.initialize(&bus_config, &buffer_config, &mut init_context); plugin.initialize(&audio_io_layout, &buffer_config, &mut init_context);
process_wrapper(|| plugin.reset()); process_wrapper(|| plugin.reset());
} }
@ -1841,7 +1784,7 @@ impl<P: ClapPlugin> Wrapper<P> {
check_null_ptr!(false, plugin, (*plugin).plugin_data); check_null_ptr!(false, plugin, (*plugin).plugin_data);
let wrapper = &*((*plugin).plugin_data as *const Self); let wrapper = &*((*plugin).plugin_data as *const Self);
let bus_config = wrapper.current_bus_config.load(); let audio_io_layout = wrapper.current_audio_io_layout.load();
let buffer_config = BufferConfig { let buffer_config = BufferConfig {
sample_rate: sample_rate as f32, sample_rate: sample_rate as f32,
min_buffer_size: Some(min_frames_count), min_buffer_size: Some(min_frames_count),
@ -1857,7 +1800,7 @@ impl<P: ClapPlugin> Wrapper<P> {
// NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks // NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks
let mut init_context = wrapper.make_init_context(); let mut init_context = wrapper.make_init_context();
let mut plugin = wrapper.plugin.lock(); let mut plugin = wrapper.plugin.lock();
if plugin.initialize(&bus_config, &buffer_config, &mut init_context) { if plugin.initialize(&audio_io_layout, &buffer_config, &mut init_context) {
// NOTE: `Plugin::reset()` is called in `clap_plugin::start_processing()` instead of in // NOTE: `Plugin::reset()` is called in `clap_plugin::start_processing()` instead of in
// this function // this function
@ -1867,7 +1810,13 @@ impl<P: ClapPlugin> Wrapper<P> {
.output_buffer .output_buffer
.borrow_mut() .borrow_mut()
.set_slices(0, |output_slices| { .set_slices(0, |output_slices| {
output_slices.resize_with(bus_config.num_output_channels as usize, || &mut []); output_slices.resize_with(
audio_io_layout
.main_output_channels
.map(NonZeroU32::get)
.unwrap_or_default() as usize,
|| &mut [],
);
// All slices must have the same length, so if the number of output channels has // All slices must have the same length, so if the number of output channels has
// changed since the last call then we should make sure to clear any old // changed since the last call then we should make sure to clear any old
// (dangling) slices to be consistent // (dangling) slices to be consistent
@ -1878,43 +1827,34 @@ impl<P: ClapPlugin> Wrapper<P> {
// inputs. The slices will be assigned in the process function as this object may have // inputs. The slices will be assigned in the process function as this object may have
// been moved before then. // been moved before then.
let mut aux_input_storage = wrapper.aux_input_storage.borrow_mut(); let mut aux_input_storage = wrapper.aux_input_storage.borrow_mut();
aux_input_storage let mut aux_input_buffers = wrapper.aux_input_buffers.borrow_mut();
.resize_with(bus_config.aux_input_busses.num_busses as usize, Vec::new); aux_input_storage.resize_with(audio_io_layout.aux_input_ports.len(), Vec::new);
for bus_storage in aux_input_storage.iter_mut() { aux_input_buffers.resize_with(audio_io_layout.aux_input_ports.len(), Buffer::default);
bus_storage for ((buffer_storage, buffer), num_channels) in aux_input_storage
.resize_with(bus_config.aux_input_busses.num_channels as usize, Vec::new); .iter_mut()
for channel_storage in bus_storage { .zip(aux_input_buffers.iter_mut())
.zip(audio_io_layout.aux_input_ports.iter())
{
buffer_storage.resize_with(num_channels.get() as usize, Vec::new);
for channel_storage in buffer_storage {
channel_storage.resize(max_frames_count as usize, 0.0); channel_storage.resize(max_frames_count as usize, 0.0);
} }
}
let mut aux_input_buffers = wrapper.aux_input_buffers.borrow_mut();
aux_input_buffers.resize_with(
bus_config.aux_input_busses.num_busses as usize,
Buffer::default,
);
for buffer in aux_input_buffers.iter_mut() {
buffer.set_slices(0, |channel_slices| { buffer.set_slices(0, |channel_slices| {
channel_slices channel_slices.resize_with(num_channels.get() as usize, || &mut []);
.resize_with(bus_config.aux_input_busses.num_channels as usize, || {
&mut []
});
channel_slices.fill_with(|| &mut []); channel_slices.fill_with(|| &mut []);
}); });
} }
// And the same thing for the output buffers // And the same thing for the output buffers
let mut aux_output_buffers = wrapper.aux_output_buffers.borrow_mut(); let mut aux_output_buffers = wrapper.aux_output_buffers.borrow_mut();
aux_output_buffers.resize_with( aux_output_buffers.resize_with(audio_io_layout.aux_output_ports.len(), Buffer::default);
bus_config.aux_output_busses.num_busses as usize, for (buffer, num_channels) in aux_output_buffers
Buffer::default, .iter_mut()
); .zip(audio_io_layout.aux_output_ports.iter())
for buffer in aux_output_buffers.iter_mut() { {
buffer.set_slices(0, |channel_slices| { buffer.set_slices(0, |channel_slices| {
channel_slices channel_slices.resize_with(num_channels.get() as usize, || &mut []);
.resize_with(bus_config.aux_output_busses.num_channels as usize, || {
&mut []
});
channel_slices.fill_with(|| &mut []); channel_slices.fill_with(|| &mut []);
}); });
} }
@ -1984,14 +1924,18 @@ impl<P: ClapPlugin> Wrapper<P> {
// Before doing anything, clear out any auxiliary outputs since they may contain // Before doing anything, clear out any auxiliary outputs since they may contain
// uninitialized data when the host assumes that we'll always write something there // uninitialized data when the host assumes that we'll always write something there
let current_bus_config = wrapper.current_bus_config.load(); let current_audio_io_layout = wrapper.current_audio_io_layout.load();
let has_main_input = current_bus_config.num_input_channels > 0; let (aux_input_start_idx, aux_output_start_idx) = {
let has_main_output = current_bus_config.num_output_channels > 0; let has_main_input = current_audio_io_layout.main_input_channels.is_some();
let has_main_output = current_audio_io_layout.main_output_channels.is_some();
(
if has_main_input { 1 } else { 0 },
if has_main_output { 1 } else { 0 },
)
};
if process.audio_outputs_count > 0 && !process.audio_outputs.is_null() { if process.audio_outputs_count > 0 && !process.audio_outputs.is_null() {
for output_idx in for output_idx in aux_output_start_idx..process.audio_outputs_count as usize {
if has_main_output { 1 } else { 0 }..process.audio_outputs_count as isize let host_output = process.audio_outputs.add(output_idx);
{
let host_output = process.audio_outputs.offset(output_idx);
if !(*host_output).data32.is_null() { if !(*host_output).data32.is_null() {
for channel_idx in 0..(*host_output).channel_count as isize { for channel_idx in 0..(*host_output).channel_count as isize {
ptr::write_bytes( ptr::write_bytes(
@ -2160,13 +2104,9 @@ impl<P: ClapPlugin> Wrapper<P> {
.zip(aux_input_buffers.iter_mut()) .zip(aux_input_buffers.iter_mut())
.enumerate() .enumerate()
{ {
let host_input_idx = if has_main_input { let host_input_idx = auxiliary_input_idx + aux_input_start_idx;
auxiliary_input_idx as isize + 1 let host_input = process.audio_inputs.add(host_input_idx);
} else { if host_input_idx >= process.audio_inputs_count as usize
auxiliary_input_idx as isize
};
let host_input = process.audio_inputs.offset(host_input_idx);
if host_input_idx >= process.audio_inputs_count as isize
|| process.audio_inputs.is_null() || process.audio_inputs.is_null()
|| (*host_input).data32.is_null() || (*host_input).data32.is_null()
// Would only happen if the user configured zero channels for the // Would only happen if the user configured zero channels for the
@ -2174,7 +2114,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|| storage.is_empty() || storage.is_empty()
|| (*host_input).channel_count != buffer.channels() as u32 || (*host_input).channel_count != buffer.channels() as u32
{ {
nih_debug_assert!(host_input_idx < process.audio_inputs_count as isize); nih_debug_assert!(host_input_idx < process.audio_inputs_count as usize);
nih_debug_assert!(!process.audio_inputs.is_null()); nih_debug_assert!(!process.audio_inputs.is_null());
nih_debug_assert!(!(*host_input).data32.is_null()); nih_debug_assert!(!(*host_input).data32.is_null());
nih_debug_assert!(!storage.is_empty()); nih_debug_assert!(!storage.is_empty());
@ -2217,18 +2157,14 @@ impl<P: ClapPlugin> Wrapper<P> {
// And the same thing for auxiliary output buffers // And the same thing for auxiliary output buffers
let mut aux_output_buffers = wrapper.aux_output_buffers.borrow_mut(); let mut aux_output_buffers = wrapper.aux_output_buffers.borrow_mut();
for (auxiliary_output_idx, buffer) in aux_output_buffers.iter_mut().enumerate() { for (auxiliary_output_idx, buffer) in aux_output_buffers.iter_mut().enumerate() {
let host_output_idx = if has_main_output { let host_output_idx = auxiliary_output_idx + aux_output_start_idx;
auxiliary_output_idx as isize + 1 let host_output = process.audio_outputs.add(host_output_idx);
} else { if host_output_idx >= process.audio_outputs_count as usize
auxiliary_output_idx as isize
};
let host_output = process.audio_outputs.offset(host_output_idx);
if host_output_idx >= process.audio_outputs_count as isize
|| process.audio_outputs.is_null() || process.audio_outputs.is_null()
|| (*host_output).data32.is_null() || (*host_output).data32.is_null()
|| buffer.channels() == 0 || buffer.channels() == 0
{ {
nih_debug_assert!(host_output_idx < process.audio_outputs_count as isize); nih_debug_assert!(host_output_idx < process.audio_outputs_count as usize);
nih_debug_assert!(!process.audio_outputs.is_null()); nih_debug_assert!(!process.audio_outputs.is_null());
nih_debug_assert!(!(*host_output).data32.is_null()); nih_debug_assert!(!(*host_output).data32.is_null());
@ -2402,14 +2338,16 @@ impl<P: ClapPlugin> Wrapper<P> {
// NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks // NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks
let mut init_context = wrapper.make_init_context(); let mut init_context = wrapper.make_init_context();
let bus_config = wrapper.current_bus_config.load(); let audio_io_layout = wrapper.current_audio_io_layout.load();
let buffer_config = wrapper.current_buffer_config.load().unwrap(); let buffer_config = wrapper.current_buffer_config.load().unwrap();
let mut plugin = wrapper.plugin.lock(); let mut plugin = wrapper.plugin.lock();
// FIXME: This is obviously not realtime-safe, but loading presets without doing // FIXME: This is obviously not realtime-safe, but loading presets without doing
// this could lead to inconsistencies. It's the plugin's responsibility to // this could lead to inconsistencies. It's the plugin's responsibility to
// not perform any realtime-unsafe work when the initialize function is // not perform any realtime-unsafe work when the initialize function is
// called a second time if it supports runtime preset loading. // called a second time if it supports runtime preset loading.
permit_alloc(|| plugin.initialize(&bus_config, &buffer_config, &mut init_context)); permit_alloc(|| {
plugin.initialize(&audio_io_layout, &buffer_config, &mut init_context)
});
plugin.reset(); plugin.reset();
// Reinitialize the plugin after loading state so it can respond to the new parameter values // Reinitialize the plugin after loading state so it can respond to the new parameter values
@ -2481,9 +2419,8 @@ impl<P: ClapPlugin> Wrapper<P> {
unsafe extern "C" fn ext_audio_ports_config_count(plugin: *const clap_plugin) -> u32 { unsafe extern "C" fn ext_audio_ports_config_count(plugin: *const clap_plugin) -> u32 {
check_null_ptr!(0, plugin, (*plugin).plugin_data); check_null_ptr!(0, plugin, (*plugin).plugin_data);
let wrapper = &*((*plugin).plugin_data as *const Self);
wrapper.supported_bus_configs.len() as u32 P::AUDIO_IO_LAYOUTS.len() as u32
} }
unsafe extern "C" fn ext_audio_ports_config_get( unsafe extern "C" fn ext_audio_ports_config_get(
@ -2492,37 +2429,24 @@ impl<P: ClapPlugin> Wrapper<P> {
config: *mut clap_audio_ports_config, config: *mut clap_audio_ports_config,
) -> bool { ) -> bool {
check_null_ptr!(false, plugin, (*plugin).plugin_data, config); check_null_ptr!(false, plugin, (*plugin).plugin_data, config);
let wrapper = &*((*plugin).plugin_data as *const Self);
match wrapper.supported_bus_configs.get(index as usize) { // This function directly maps to `P::AUDIO_IO_LAYOUTS`, and we thus also don't need to
Some(bus_config) => { // access the `wrapper` instance
// We don't support variable auxiliary IO configs right now, so we don't need to match P::AUDIO_IO_LAYOUTS.get(index as usize) {
// specify sidechain inputs and aux outputs in these descriptions Some(audio_io_layout) => {
let name = match bus_config { let name = audio_io_layout.name();
BusConfig {
num_input_channels: _, let main_input_channels = audio_io_layout.main_input_channels.map(NonZeroU32::get);
num_output_channels: 1, let main_output_channels =
.. audio_io_layout.main_output_channels.map(NonZeroU32::get);
} => String::from("Mono"), let input_port_type = match main_input_channels {
BusConfig { Some(1) => CLAP_PORT_MONO.as_ptr(),
num_input_channels: _, Some(2) => CLAP_PORT_STEREO.as_ptr(),
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 {
1 => CLAP_PORT_MONO.as_ptr(),
2 => CLAP_PORT_STEREO.as_ptr(),
_ => ptr::null(), _ => ptr::null(),
}; };
let output_port_type = match bus_config.num_output_channels { let output_port_type = match main_output_channels {
1 => CLAP_PORT_MONO.as_ptr(), Some(1) => CLAP_PORT_MONO.as_ptr(),
2 => CLAP_PORT_STEREO.as_ptr(), Some(2) => CLAP_PORT_STEREO.as_ptr(),
_ => ptr::null(), _ => ptr::null(),
}; };
@ -2531,21 +2455,17 @@ impl<P: ClapPlugin> Wrapper<P> {
let config = &mut *config; let config = &mut *config;
config.id = index; config.id = index;
strlcpy(&mut config.name, &name); strlcpy(&mut config.name, &name);
config.input_port_count = if bus_config.num_input_channels > 0 { config.input_port_count = (if main_input_channels.is_some() { 1 } else { 0 }
1 + audio_io_layout.aux_input_ports.len())
} else { as u32;
0 config.output_port_count = (if main_output_channels.is_some() { 1 } else { 0 }
} + bus_config.aux_input_busses.num_busses; + audio_io_layout.aux_output_ports.len())
config.output_port_count = if bus_config.num_output_channels > 0 { as u32;
1 config.has_main_input = main_input_channels.is_some();
} else { config.main_input_channel_count = main_input_channels.unwrap_or_default();
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; config.main_input_port_type = input_port_type;
config.has_main_output = bus_config.num_output_channels > 0; config.has_main_output = main_output_channels.is_some();
config.main_output_channel_count = bus_config.num_output_channels; config.main_output_channel_count = main_output_channels.unwrap_or_default();
config.main_output_port_type = output_port_type; config.main_output_port_type = output_port_type;
true true
@ -2569,9 +2489,9 @@ impl<P: ClapPlugin> Wrapper<P> {
let wrapper = &*((*plugin).plugin_data as *const Self); let wrapper = &*((*plugin).plugin_data as *const Self);
// We use the vector indices for the config ID // We use the vector indices for the config ID
match wrapper.supported_bus_configs.get(config_id as usize) { match P::AUDIO_IO_LAYOUTS.get(config_id as usize) {
Some(bus_config) => { Some(audio_io_layout) => {
wrapper.current_bus_config.store(*bus_config); wrapper.current_audio_io_layout.store(*audio_io_layout);
true true
} }
@ -2590,25 +2510,25 @@ impl<P: ClapPlugin> Wrapper<P> {
check_null_ptr!(0, plugin, (*plugin).plugin_data); check_null_ptr!(0, plugin, (*plugin).plugin_data);
let wrapper = &*((*plugin).plugin_data as *const Self); let wrapper = &*((*plugin).plugin_data as *const Self);
let bus_config = wrapper.current_bus_config.load(); let audio_io_layout = wrapper.current_audio_io_layout.load();
if is_input { if is_input {
let main_busses = if bus_config.num_input_channels > 0 { let main_ports = if audio_io_layout.main_input_channels.is_some() {
1 1
} else { } else {
0 0
}; };
let aux_busses = bus_config.aux_input_busses.num_busses; let aux_ports = audio_io_layout.aux_input_ports.len();
main_busses + aux_busses (main_ports + aux_ports) as u32
} else { } else {
let main_busses = if bus_config.num_output_channels > 0 { let main_ports = if audio_io_layout.main_output_channels.is_some() {
1 1
} else { } else {
0 0
}; };
let aux_busses = bus_config.aux_output_busses.num_busses; let aux_ports = audio_io_layout.aux_output_ports.len();
main_busses + aux_busses (main_ports + aux_ports) as u32
} }
} }
@ -2633,9 +2553,9 @@ impl<P: ClapPlugin> Wrapper<P> {
return false; return false;
} }
let current_bus_config = wrapper.current_bus_config.load(); let current_audio_io_layout = wrapper.current_audio_io_layout.load();
let has_main_input = current_bus_config.num_input_channels > 0; let has_main_input = current_audio_io_layout.main_input_channels.is_some();
let has_main_output = current_bus_config.num_output_channels > 0; let has_main_output = current_audio_io_layout.main_output_channels.is_some();
// Whether this port is a main port or an auxiliary (sidechain) port // Whether this port is a main port or an auxiliary (sidechain) port
let is_main_port = let is_main_port =
@ -2656,11 +2576,22 @@ impl<P: ClapPlugin> Wrapper<P> {
_ => CLAP_INVALID_ID, _ => CLAP_INVALID_ID,
}; };
let channel_count = match (is_input, is_main_port) { let channel_count = match (index, is_input) {
(true, true) => current_bus_config.num_input_channels, (0, true) if has_main_input => {
(false, true) => current_bus_config.num_output_channels, current_audio_io_layout.main_input_channels.unwrap().get()
(true, false) => current_bus_config.aux_input_busses.num_channels, }
(false, false) => current_bus_config.aux_output_busses.num_channels, (0, false) if has_main_output => {
current_audio_io_layout.main_output_channels.unwrap().get()
}
// `index` is off by one for the auxiliary ports if the plugin has a main port
(n, true) if has_main_input => {
current_audio_io_layout.aux_input_ports[n as usize - 1].get()
}
(n, false) if has_main_output => {
current_audio_io_layout.aux_output_ports[n as usize - 1].get()
}
(n, true) => current_audio_io_layout.aux_input_ports[n as usize].get(),
(n, false) => current_audio_io_layout.aux_output_ports[n as usize].get(),
}; };
let port_type = match channel_count { let port_type = match channel_count {
@ -2674,46 +2605,25 @@ impl<P: ClapPlugin> Wrapper<P> {
let info = &mut *info; let info = &mut *info;
info.id = stable_id; info.id = stable_id;
match (is_input, is_main_port) { match (is_input, is_main_port) {
(true, true) => strlcpy(&mut info.name, P::PORT_NAMES.main_input.unwrap_or("Input")), (true, true) => strlcpy(&mut info.name, &current_audio_io_layout.main_input_name()),
(false, true) => strlcpy( (false, true) => strlcpy(&mut info.name, &current_audio_io_layout.main_output_name()),
&mut info.name,
P::PORT_NAMES.main_output.unwrap_or("Output"),
),
(true, false) => { (true, false) => {
let aux_input_idx = if has_main_input { index - 1 } else { index }; let aux_input_idx = if has_main_input { index - 1 } else { index } as usize;
let custom_port_name = P::PORT_NAMES strlcpy(
.aux_inputs &mut info.name,
.map(|names| names[aux_input_idx as usize]); &current_audio_io_layout
if current_bus_config.aux_input_busses.num_busses <= 1 { .aux_input_name(aux_input_idx)
strlcpy( .expect("Out of bounds auxiliary input port"),
&mut info.name, );
custom_port_name.unwrap_or("Sidechain Input"),
);
} else {
strlcpy(
&mut info.name,
custom_port_name
.unwrap_or(&format!("Sidechain Input {}", aux_input_idx + 1)),
);
}
} }
(false, false) => { (false, false) => {
let aux_output_idx = if has_main_output { index - 1 } else { index }; let aux_output_idx = if has_main_output { index - 1 } else { index } as usize;
let custom_port_name = P::PORT_NAMES strlcpy(
.aux_outputs &mut info.name,
.map(|names| names[aux_output_idx as usize]); &current_audio_io_layout
if current_bus_config.aux_output_busses.num_busses <= 1 { .aux_output_name(aux_output_idx)
strlcpy( .expect("Out of bounds auxiliary output port"),
&mut info.name, );
custom_port_name.unwrap_or("Auxiliary Output"),
);
} else {
strlcpy(
&mut info.name,
custom_port_name
.unwrap_or(&format!("Auxiliary Output {}", aux_output_idx + 1)),
);
}
} }
}; };
info.flags = if is_main_port { info.flags = if is_main_port {
@ -3301,12 +3211,12 @@ impl<P: ClapPlugin> Wrapper<P> {
return false; return false;
} }
let bus_config = wrapper.current_bus_config.load(); let audio_io_layout = wrapper.current_audio_io_layout.load();
if let Some(buffer_config) = wrapper.current_buffer_config.load() { if let Some(buffer_config) = wrapper.current_buffer_config.load() {
// NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks // NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks
let mut init_context = wrapper.make_init_context(); let mut init_context = wrapper.make_init_context();
let mut plugin = wrapper.plugin.lock(); let mut plugin = wrapper.plugin.lock();
plugin.initialize(&bus_config, &buffer_config, &mut init_context); plugin.initialize(&audio_io_layout, &buffer_config, &mut init_context);
// TODO: This also goes for the VST3 version, but should we call reset here? Won't the // TODO: This also goes for the VST3 version, but should we call reset here? Won't the
// host always restart playback? Check this with a couple of hosts and remove the // host always restart playback? Check this with a couple of hosts and remove the
// duplicate reset if it's not needed. // duplicate reset if it's not needed.

View file

@ -196,15 +196,6 @@ fn run_wrapper<P: Plugin, B: Backend<P>>(backend: B, config: WrapperConfig) -> b
fn print_error(error: WrapperError) { fn print_error(error: WrapperError) {
match error { match error {
WrapperError::IncompatibleConfig {
input_channels,
output_channels,
} => {
nih_error!(
"The plugin does not support the {input_channels} channel input and \
{output_channels} channel output configuration",
);
}
WrapperError::InitializationFailed => { WrapperError::InitializationFailed => {
nih_error!("The plugin failed to initialize"); nih_error!("The plugin failed to initialize");
} }

View file

@ -1,3 +1,5 @@
use std::num::NonZeroU32;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use cpal::{ use cpal::{
traits::*, Device, InputCallbackInfo, OutputCallbackInfo, Sample, SampleFormat, Stream, traits::*, Device, InputCallbackInfo, OutputCallbackInfo, Sample, SampleFormat, Stream,
@ -8,7 +10,7 @@ use rtrb::RingBuffer;
use super::super::config::WrapperConfig; use super::super::config::WrapperConfig;
use super::Backend; use super::Backend;
use crate::audio_setup::{AuxiliaryIOConfig, BusConfig}; use crate::audio_setup::AudioIOLayout;
use crate::buffer::Buffer; use crate::buffer::Buffer;
use crate::context::process::Transport; use crate::context::process::Transport;
use crate::midi::{MidiConfig, PluginNoteEvent}; use crate::midi::{MidiConfig, PluginNoteEvent};
@ -17,7 +19,7 @@ use crate::plugin::Plugin;
/// Uses CPAL for audio and midir for MIDI. /// Uses CPAL for audio and midir for MIDI.
pub struct Cpal { pub struct Cpal {
config: WrapperConfig, config: WrapperConfig,
bus_config: BusConfig, audio_io_layout: AudioIOLayout,
input: Option<(Device, StreamConfig, SampleFormat)>, input: Option<(Device, StreamConfig, SampleFormat)>,
@ -136,9 +138,10 @@ impl Cpal {
/// Initialize the backend with the specified host. Returns an error if this failed for whatever /// Initialize the backend with the specified host. Returns an error if this failed for whatever
/// reason. /// reason.
pub fn new<P: Plugin>(config: WrapperConfig, cpal_host_id: cpal::HostId) -> Result<Self> { pub fn new<P: Plugin>(config: WrapperConfig, cpal_host_id: cpal::HostId) -> Result<Self> {
let audio_io_layout = config.audio_io_layout_or_exit::<P>();
let host = cpal::host_from_id(cpal_host_id).context("The Audio API is unavailable")?; let host = cpal::host_from_id(cpal_host_id).context("The Audio API is unavailable")?;
if config.input_device.is_none() && P::DEFAULT_INPUT_CHANNELS > 0 { if config.input_device.is_none() && audio_io_layout.main_input_channels.is_some() {
nih_log!( nih_log!(
"Audio inputs are not connected automatically to prevent feedback. Use the \ "Audio inputs are not connected automatically to prevent feedback. Use the \
'--input-device' option to choose an input device." '--input-device' option to choose an input device."
@ -192,16 +195,12 @@ impl Cpal {
.context("No default audio output device available")?, .context("No default audio output device available")?,
}; };
let bus_config = BusConfig {
num_input_channels: config.input_channels.unwrap_or(P::DEFAULT_INPUT_CHANNELS),
num_output_channels: config.output_channels.unwrap_or(P::DEFAULT_OUTPUT_CHANNELS),
// TODO: Support these in the standalone
aux_input_busses: AuxiliaryIOConfig::default(),
aux_output_busses: AuxiliaryIOConfig::default(),
};
let requested_sample_rate = cpal::SampleRate(config.sample_rate as u32); let requested_sample_rate = cpal::SampleRate(config.sample_rate as u32);
let requested_buffer_size = cpal::BufferSize::Fixed(config.period_size); let requested_buffer_size = cpal::BufferSize::Fixed(config.period_size);
let num_input_channels = audio_io_layout
.main_input_channels
.map(NonZeroU32::get)
.unwrap_or_default() as usize;
let input = input_device let input = input_device
.map(|device| -> Result<(Device, StreamConfig, SampleFormat)> { .map(|device| -> Result<(Device, StreamConfig, SampleFormat)> {
let input_configs: Vec<_> = device let input_configs: Vec<_> = device
@ -209,7 +208,7 @@ impl Cpal {
.context("Could not get supported audio input configurations")? .context("Could not get supported audio input configurations")?
.filter(|c| match c.buffer_size() { .filter(|c| match c.buffer_size() {
cpal::SupportedBufferSize::Range { min, max } => { cpal::SupportedBufferSize::Range { min, max } => {
c.channels() as u32 == bus_config.num_input_channels c.channels() as usize == num_input_channels
&& (c.min_sample_rate()..=c.max_sample_rate()) && (c.min_sample_rate()..=c.max_sample_rate())
.contains(&requested_sample_rate) .contains(&requested_sample_rate)
&& (min..=max).contains(&&config.period_size) && (min..=max).contains(&&config.period_size)
@ -227,7 +226,7 @@ impl Cpal {
format!( format!(
"The audio input device does not support {} audio channels at a \ "The audio input device does not support {} audio channels at a \
sample rate of {} Hz and a period size of {} samples", sample rate of {} Hz and a period size of {} samples",
bus_config.num_input_channels, config.sample_rate, config.period_size, num_input_channels, config.sample_rate, config.period_size,
) )
})?; })?;
@ -243,12 +242,16 @@ impl Cpal {
}) })
.transpose()?; .transpose()?;
let num_output_channels = audio_io_layout
.main_output_channels
.map(NonZeroU32::get)
.unwrap_or_default() as usize;
let output_configs: Vec<_> = output_device let output_configs: Vec<_> = output_device
.supported_output_configs() .supported_output_configs()
.context("Could not get supported audio output configurations")? .context("Could not get supported audio output configurations")?
.filter(|c| match c.buffer_size() { .filter(|c| match c.buffer_size() {
cpal::SupportedBufferSize::Range { min, max } => { cpal::SupportedBufferSize::Range { min, max } => {
c.channels() as u32 == bus_config.num_output_channels c.channels() as usize == num_output_channels
&& (c.min_sample_rate()..=c.max_sample_rate()) && (c.min_sample_rate()..=c.max_sample_rate())
.contains(&requested_sample_rate) .contains(&requested_sample_rate)
&& (min..=max).contains(&&config.period_size) && (min..=max).contains(&&config.period_size)
@ -265,7 +268,7 @@ impl Cpal {
format!( format!(
"The audio output device does not support {} audio channels at a sample rate \ "The audio output device does not support {} audio channels at a sample rate \
of {} Hz and a period size of {} samples", of {} Hz and a period size of {} samples",
bus_config.num_output_channels, config.sample_rate, config.period_size, num_output_channels, config.sample_rate, config.period_size,
) )
})?; })?;
let output_config = StreamConfig { let output_config = StreamConfig {
@ -282,7 +285,7 @@ impl Cpal {
Ok(Cpal { Ok(Cpal {
config, config,
bus_config, audio_io_layout,
input, input,
@ -328,12 +331,15 @@ impl Cpal {
// We'll receive interlaced input samples from CPAL. These need to converted to deinterlaced // We'll receive interlaced input samples from CPAL. These need to converted to deinterlaced
// channels, processed, and then copied those back to an interlaced buffer for the output. // channels, processed, and then copied those back to an interlaced buffer for the output.
// This needs to be wrapped in a struct like this and boxed because the `channels` vectors // This needs to be wrapped in a struct like this and boxed because the `channels` vectors
// need to live just as long as `buffer` when they get moved into the closure. FIXME: This // need to live just as long as `buffer` when they get moved into the closure.
// is pretty nasty, come up with a cleaner alternative // FIXME: This is pretty nasty, come up with a cleaner alternative
let mut channels = vec![ let num_output_channels = self
vec![0.0f32; self.config.period_size as usize]; .audio_io_layout
self.bus_config.num_output_channels as usize .main_output_channels
]; .map(NonZeroU32::get)
.unwrap_or_default() as usize;
let mut channels =
vec![vec![0.0f32; self.config.period_size as usize]; num_output_channels];
let mut buffer = Buffer::default(); let mut buffer = Buffer::default();
unsafe { unsafe {
buffer.set_slices(0, |output_slices| { buffer.set_slices(0, |output_slices| {

View file

@ -1,8 +1,9 @@
use std::num::NonZeroU32;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use super::super::config::WrapperConfig; use super::super::config::WrapperConfig;
use super::Backend; use super::Backend;
use crate::audio_setup::{AuxiliaryIOConfig, BusConfig}; use crate::audio_setup::AudioIOLayout;
use crate::buffer::Buffer; use crate::buffer::Buffer;
use crate::context::process::Transport; use crate::context::process::Transport;
use crate::midi::PluginNoteEvent; use crate::midi::PluginNoteEvent;
@ -13,7 +14,7 @@ use crate::plugin::Plugin;
/// useful for testing plugin GUIs. /// useful for testing plugin GUIs.
pub struct Dummy { pub struct Dummy {
config: WrapperConfig, config: WrapperConfig,
bus_config: BusConfig, audio_io_layout: AudioIOLayout,
} }
impl<P: Plugin> Backend<P> for Dummy { impl<P: Plugin> Backend<P> for Dummy {
@ -33,10 +34,13 @@ impl<P: Plugin> Backend<P> for Dummy {
let interval = let interval =
Duration::from_secs_f32(self.config.period_size as f32 / self.config.sample_rate); Duration::from_secs_f32(self.config.period_size as f32 / self.config.sample_rate);
let mut channels = vec![ let num_output_channels = self
vec![0.0f32; self.config.period_size as usize]; .audio_io_layout
self.bus_config.num_output_channels as usize .main_output_channels
]; .map(NonZeroU32::get)
.unwrap_or_default() as usize;
let mut channels =
vec![vec![0.0f32; self.config.period_size as usize]; num_output_channels];
let mut buffer = Buffer::default(); let mut buffer = Buffer::default();
unsafe { unsafe {
buffer.set_slices(self.config.period_size as usize, |output_slices| { buffer.set_slices(self.config.period_size as usize, |output_slices| {
@ -81,13 +85,7 @@ impl<P: Plugin> Backend<P> for Dummy {
impl Dummy { impl Dummy {
pub fn new<P: Plugin>(config: WrapperConfig) -> Self { pub fn new<P: Plugin>(config: WrapperConfig) -> Self {
Self { Self {
bus_config: BusConfig { audio_io_layout: config.audio_io_layout_or_exit::<P>(),
num_input_channels: config.input_channels.unwrap_or(P::DEFAULT_INPUT_CHANNELS),
num_output_channels: config.output_channels.unwrap_or(P::DEFAULT_OUTPUT_CHANNELS),
// TODO: Support these in the standalone
aux_input_busses: AuxiliaryIOConfig::default(),
aux_output_busses: AuxiliaryIOConfig::default(),
},
config, config,
} }
} }

View file

@ -1,4 +1,5 @@
use std::borrow::Borrow; use std::borrow::Borrow;
use std::num::NonZeroU32;
use std::sync::Arc; use std::sync::Arc;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
@ -18,6 +19,9 @@ use crate::plugin::Plugin;
use crate::wrapper::util::{clamp_input_event_timing, clamp_output_event_timing}; use crate::wrapper::util::{clamp_input_event_timing, clamp_output_event_timing};
/// Uses JACK audio and MIDI. /// Uses JACK audio and MIDI.
//
// TODO: Use the bus names
// TODO: Support auxiliary inputs and outputs
pub struct Jack { pub struct Jack {
config: WrapperConfig, config: WrapperConfig,
/// The JACK client, wrapped in an option since it needs to be transformed into an `AsyncClient` /// The JACK client, wrapped in an option since it needs to be transformed into an `AsyncClient`
@ -193,13 +197,14 @@ impl Jack {
/// generic argument is to get the name for the client, and to know whether or not the /// generic argument is to get the name for the client, and to know whether or not the
/// standalone should expose JACK MIDI ports. /// standalone should expose JACK MIDI ports.
pub fn new<P: Plugin>(config: WrapperConfig) -> Result<Self> { pub fn new<P: Plugin>(config: WrapperConfig) -> Result<Self> {
let audio_io_layout = config.audio_io_layout_or_exit::<P>();
let (client, status) = Client::new(P::NAME, ClientOptions::NO_START_SERVER) let (client, status) = Client::new(P::NAME, ClientOptions::NO_START_SERVER)
.context("Error while initializing the JACK client")?; .context("Error while initializing the JACK client")?;
if !status.is_empty() { if !status.is_empty() {
anyhow::bail!("The JACK server returned an error: {status:?}"); anyhow::bail!("The JACK server returned an error: {status:?}");
} }
if config.connect_jack_inputs.is_none() && P::DEFAULT_INPUT_CHANNELS > 0 { if config.connect_jack_inputs.is_none() && audio_io_layout.main_input_channels.is_some() {
nih_log!( nih_log!(
"Audio inputs are not connected automatically to prevent feedback. Use the \ "Audio inputs are not connected automatically to prevent feedback. Use the \
'--connect-jack-inputs' option to connect the input ports." '--connect-jack-inputs' option to connect the input ports."
@ -207,7 +212,10 @@ impl Jack {
} }
let mut inputs = Vec::new(); let mut inputs = Vec::new();
let num_input_channels = config.input_channels.unwrap_or(P::DEFAULT_INPUT_CHANNELS); let num_input_channels = audio_io_layout
.main_input_channels
.map(NonZeroU32::get)
.unwrap_or_default() as usize;
for port_no in 1..num_input_channels + 1 { for port_no in 1..num_input_channels + 1 {
inputs.push(client.register_port(&format!("input_{port_no}"), AudioIn)?); inputs.push(client.register_port(&format!("input_{port_no}"), AudioIn)?);
} }
@ -216,7 +224,10 @@ impl Jack {
// no. So the connections are made just after activating the client in the `run()` function // no. So the connections are made just after activating the client in the `run()` function
// above. // above.
let mut outputs = Vec::new(); let mut outputs = Vec::new();
let num_output_channels = config.output_channels.unwrap_or(P::DEFAULT_OUTPUT_CHANNELS); let num_output_channels = audio_io_layout
.main_output_channels
.map(NonZeroU32::get)
.unwrap_or_default() as usize;
for port_no in 1..num_output_channels + 1 { for port_no in 1..num_output_channels + 1 {
outputs.push(client.register_port(&format!("output_{port_no}"), AudioOut)?); outputs.push(client.register_port(&format!("output_{port_no}"), AudioOut)?);
} }

View file

@ -1,4 +1,8 @@
use clap::{Parser, ValueEnum}; use clap::{Parser, ValueEnum};
use std::num::NonZeroU32;
use crate::audio_setup::AudioIOLayout;
use crate::plugin::Plugin;
/// Configuration for a standalone plugin that would normally be provided by the DAW. /// Configuration for a standalone plugin that would normally be provided by the DAW.
#[derive(Debug, Clone, Parser)] #[derive(Debug, Clone, Parser)]
@ -22,15 +26,14 @@ pub struct WrapperConfig {
#[clap(value_parser, long)] #[clap(value_parser, long)]
pub output_device: Option<String>, pub output_device: Option<String>,
// These will default to the plugin's default input and output channel count. We could set the /// The audio layout to use. Defaults to the first layout.
// default value here to match those, but that would require a custom Args+FromArgMatches ///
// implementation and access to the `Plugin` type. /// Specifying an empty argument or other invalid value will list all available audio layouts.
/// The number of input channels. //
#[clap(value_parser, short = 'i', long)] // NOTE: This takes a `String` instead of a `usize` so we can list the layouts when the argument
pub input_channels: Option<u32>, // is invalid
/// The number of output channels. #[clap(value_parser, short = 'l', long)]
#[clap(value_parser, short = 'o', long)] pub audio_layout: Option<String>,
pub output_channels: Option<u32>,
/// The audio backend's sample rate. /// The audio backend's sample rate.
/// ///
/// This setting is ignored when using the JACK backend. /// This setting is ignored when using the JACK backend.
@ -104,3 +107,67 @@ pub enum BackendType {
/// Does not playback or receive any audio or MIDI. /// Does not playback or receive any audio or MIDI.
Dummy, Dummy,
} }
impl WrapperConfig {
/// Get the audio IO layout for a plugin based on this configuration. Exits the application if
/// the IO layout could not be parsed from the config. This doesn't return a `Result` to be able to differentiate between backend-specific errors and config parsing errors.
pub fn audio_io_layout_or_exit<P: Plugin>(&self) -> AudioIOLayout {
// The layouts are one-indexed here
match &self.audio_layout {
Some(audio_layout) if !P::AUDIO_IO_LAYOUTS.is_empty() => {
match audio_layout.parse::<usize>() {
Ok(n) if n >= 1 && n - 1 < P::AUDIO_IO_LAYOUTS.len() => {
P::AUDIO_IO_LAYOUTS[n - 1]
}
_ => {
// This is made to be consistent with how audio input and output devices are
// listed in the CPAL backend
let mut layouts_str = String::new();
for (idx, layout) in P::AUDIO_IO_LAYOUTS.iter().enumerate() {
let num_input_channels = layout
.main_input_channels
.map(NonZeroU32::get)
.unwrap_or_default();
let num_output_channels = layout
.main_output_channels
.map(NonZeroU32::get)
.unwrap_or_default();
layouts_str.push_str(&format!(
"\n{}: {} ({} input {}, {} output {}{}{})",
idx + 1,
layout.name(),
num_input_channels,
if num_input_channels == 1 {
"channel"
} else {
"channels"
},
num_output_channels,
if num_output_channels == 1 {
"channel"
} else {
"channels"
},
if layout.aux_input_ports.is_empty() {
String::new()
} else {
format!("{} sidechain inputs", layout.aux_input_ports.len())
},
if layout.aux_output_ports.is_empty() {
String::new()
} else {
format!("{} sidechain outputs", layout.aux_output_ports.len())
},
))
}
nih_log!("The available audio layouts are:{layouts_str}");
std::process::exit(1);
}
}
}
_ => P::AUDIO_IO_LAYOUTS.first().copied().unwrap_or_default(),
}
}
}

View file

@ -14,7 +14,7 @@ use super::backend::Backend;
use super::config::WrapperConfig; use super::config::WrapperConfig;
use super::context::{WrapperGuiContext, WrapperInitContext, WrapperProcessContext}; use super::context::{WrapperGuiContext, WrapperInitContext, WrapperProcessContext};
use crate::audio_setup::AuxiliaryBuffers; use crate::audio_setup::AuxiliaryBuffers;
use crate::audio_setup::{AuxiliaryIOConfig, BufferConfig, BusConfig, ProcessMode}; use crate::audio_setup::{AudioIOLayout, BufferConfig, ProcessMode};
use crate::context::gui::AsyncExecutor; use crate::context::gui::AsyncExecutor;
use crate::context::process::Transport; use crate::context::process::Transport;
use crate::editor::{Editor, ParentWindowHandle}; use crate::editor::{Editor, ParentWindowHandle};
@ -67,7 +67,7 @@ pub struct Wrapper<P: Plugin, B: Backend<P>> {
param_id_to_ptr: HashMap<String, ParamPtr>, param_id_to_ptr: HashMap<String, ParamPtr>,
/// The bus and buffer configurations are static for the standalone target. /// The bus and buffer configurations are static for the standalone target.
bus_config: BusConfig, audio_io_layout: AudioIOLayout,
buffer_config: BufferConfig, buffer_config: BufferConfig,
/// Parameter changes that have been output by the GUI that have not yet been set in the plugin. /// Parameter changes that have been output by the GUI that have not yet been set in the plugin.
@ -106,11 +106,6 @@ pub enum Task<P: Plugin> {
/// Errors that may arise while initializing the wrapped plugins. /// Errors that may arise while initializing the wrapped plugins.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum WrapperError { pub enum WrapperError {
/// The plugin does not accept the IO configuration from the config.
IncompatibleConfig {
input_channels: u32,
output_channels: u32,
},
/// The plugin returned `false` during initialization. /// The plugin returned `false` during initialization.
InitializationFailed, InitializationFailed,
} }
@ -178,6 +173,11 @@ impl<P: Plugin, B: Backend<P>> Wrapper<P, B> {
/// Instantiate a new instance of the standalone wrapper. Returns an error if the plugin does /// Instantiate a new instance of the standalone wrapper. Returns an error if the plugin does
/// not accept the IO configuration from the wrapper config. /// not accept the IO configuration from the wrapper config.
pub fn new(backend: B, config: WrapperConfig) -> Result<Arc<Self>, WrapperError> { pub fn new(backend: B, config: WrapperConfig) -> Result<Arc<Self>, WrapperError> {
// The backend has already queried this, so this will never cause the program to exit
// TODO: Do the validation and parsing in the argument parser so this value can be stored on
// the config itself. Right now clap doesn't support this.
let audio_io_layout = config.audio_io_layout_or_exit::<P>();
let plugin = P::default(); let plugin = P::default();
let task_executor = Mutex::new(plugin.task_executor()); let task_executor = Mutex::new(plugin.task_executor());
let params = plugin.params(); let params = plugin.params();
@ -214,11 +214,11 @@ impl<P: Plugin, B: Backend<P>> Wrapper<P, B> {
} }
// TODO: Sidechain inputs and auxiliary outputs // TODO: Sidechain inputs and auxiliary outputs
if P::DEFAULT_AUX_INPUTS.is_some() { if !audio_io_layout.aux_input_ports.is_empty() {
nih_log!("Sidechain inputs are not yet supported in this standalone version"); nih_warn!("Sidechain inputs are not yet supported in this standalone version");
} }
if P::DEFAULT_AUX_OUTPUTS.is_some() { if !audio_io_layout.aux_output_ports.is_empty() {
nih_log!("Auxiliary outputs are not yet supported in this standalone version"); nih_warn!("Auxiliary outputs are not yet supported in this standalone version");
} }
let wrapper = Arc::new(Wrapper { let wrapper = Arc::new(Wrapper {
@ -242,13 +242,7 @@ impl<P: Plugin, B: Backend<P>> Wrapper<P, B> {
.map(|(param_id, param_ptr, _)| (param_id, param_ptr)) .map(|(param_id, param_ptr, _)| (param_id, param_ptr))
.collect(), .collect(),
bus_config: BusConfig { audio_io_layout,
num_input_channels: config.input_channels.unwrap_or(P::DEFAULT_INPUT_CHANNELS),
num_output_channels: config.output_channels.unwrap_or(P::DEFAULT_OUTPUT_CHANNELS),
// TODO: Expose additional sidechain IO in the JACK backend
aux_input_busses: AuxiliaryIOConfig::default(),
aux_output_busses: AuxiliaryIOConfig::default(),
},
buffer_config: BufferConfig { buffer_config: BufferConfig {
sample_rate: config.sample_rate, sample_rate: config.sample_rate,
min_buffer_size: None, min_buffer_size: None,
@ -289,24 +283,15 @@ impl<P: Plugin, B: Backend<P>> Wrapper<P, B> {
}) })
.map(|editor| Arc::new(Mutex::new(editor))); .map(|editor| Arc::new(Mutex::new(editor)));
// Right now the IO configuration is fixed in the standalone target, so if the plugin cannot // Before initializing the plugin, make sure all smoothers are set the the default values
// work with this then we cannot initialize the plugin at all. for param in wrapper.param_id_to_ptr.values() {
unsafe { param.update_smoother(wrapper.buffer_config.sample_rate, true) };
}
{ {
let mut plugin = wrapper.plugin.lock(); let mut plugin = wrapper.plugin.lock();
if !plugin.accepts_bus_config(&wrapper.bus_config) {
return Err(WrapperError::IncompatibleConfig {
input_channels: wrapper.bus_config.num_input_channels,
output_channels: wrapper.bus_config.num_output_channels,
});
}
// Before initializing the plugin, make sure all smoothers are set the the default values
for param in wrapper.param_id_to_ptr.values() {
unsafe { param.update_smoother(wrapper.buffer_config.sample_rate, true) };
}
if !plugin.initialize( if !plugin.initialize(
&wrapper.bus_config, &wrapper.audio_io_layout,
&wrapper.buffer_config, &wrapper.buffer_config,
&mut wrapper.make_init_context(), &mut wrapper.make_init_context(),
) { ) {
@ -558,7 +543,7 @@ impl<P: Plugin, B: Backend<P>> Wrapper<P, B> {
// runtime preset loading. // runtime preset loading.
permit_alloc(|| { permit_alloc(|| {
plugin.initialize( plugin.initialize(
&self.bus_config, &self.audio_io_layout,
&self.buffer_config, &self.buffer_config,
&mut self.make_init_context(), &mut self.make_init_context(),
) )

View file

@ -14,7 +14,7 @@ use super::note_expressions::NoteExpressionController;
use super::param_units::ParamUnits; use super::param_units::ParamUnits;
use super::util::{ObjectPtr, VstPtr, VST3_MIDI_PARAMS_END, VST3_MIDI_PARAMS_START}; use super::util::{ObjectPtr, VstPtr, VST3_MIDI_PARAMS_END, VST3_MIDI_PARAMS_START};
use super::view::WrapperView; use super::view::WrapperView;
use crate::audio_setup::{BufferConfig, BusConfig, ProcessMode}; use crate::audio_setup::{AudioIOLayout, BufferConfig, ProcessMode};
use crate::buffer::Buffer; use crate::buffer::Buffer;
use crate::context::gui::AsyncExecutor; use crate::context::gui::AsyncExecutor;
use crate::context::process::Transport; use crate::context::process::Transport;
@ -66,8 +66,11 @@ pub(crate) struct WrapperInner<P: Vst3Plugin> {
/// Whether the plugin is currently processing audio. In other words, the last state /// Whether the plugin is currently processing audio. In other words, the last state
/// `IAudioProcessor::setActive()` has been called with. /// `IAudioProcessor::setActive()` has been called with.
pub is_processing: AtomicBool, pub is_processing: AtomicBool,
/// The current bus configuration, modified through `IAudioProcessor::setBusArrangements()`. /// The current audio IO layout. Modified through `IAudioProcessor::setBusArrangements()` after
pub current_bus_config: AtomicCell<BusConfig>, /// matching the proposed bus arrangement to one of the supported ones. The plugin's first audio
/// IO layout is chosen as the default. Because of the way VST3 works it's not possible to
/// change the number of busses from that default, only the channel counts can change.
pub current_audio_io_layout: AtomicCell<AudioIOLayout>,
/// The current buffer configuration, containing the sample rate and the maximum block size. /// The current buffer configuration, containing the sample rate and the maximum block size.
/// Will be set in `IAudioProcessor::setupProcessing()`. /// Will be set in `IAudioProcessor::setupProcessing()`.
pub current_buffer_config: AtomicCell<Option<BufferConfig>>, pub current_buffer_config: AtomicCell<Option<BufferConfig>>,
@ -305,12 +308,9 @@ impl<P: Vst3Plugin> WrapperInner<P> {
// 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
// channel layout. // channel layout.
current_bus_config: AtomicCell::new(BusConfig { current_audio_io_layout: AtomicCell::new(
num_input_channels: P::DEFAULT_INPUT_CHANNELS, P::AUDIO_IO_LAYOUTS.first().copied().unwrap_or_default(),
num_output_channels: P::DEFAULT_OUTPUT_CHANNELS, ),
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_buffer_config: AtomicCell::new(None),
current_process_mode: AtomicCell::new(ProcessMode::Realtime), current_process_mode: AtomicCell::new(ProcessMode::Realtime),
last_process_status: AtomicCell::new(ProcessStatus::Normal), last_process_status: AtomicCell::new(ProcessStatus::Normal),
@ -517,12 +517,12 @@ impl<P: Vst3Plugin> WrapperInner<P> {
); );
} }
let bus_config = self.current_bus_config.load(); let audio_io_layout = self.current_audio_io_layout.load();
if let Some(buffer_config) = self.current_buffer_config.load() { if let Some(buffer_config) = self.current_buffer_config.load() {
// NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks // NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks
let mut init_context = self.make_init_context(); let mut init_context = self.make_init_context();
let mut plugin = self.plugin.lock(); let mut plugin = self.plugin.lock();
plugin.initialize(&bus_config, &buffer_config, &mut init_context); plugin.initialize(&audio_io_layout, &buffer_config, &mut init_context);
process_wrapper(|| plugin.reset()); process_wrapper(|| plugin.reset());
} }

View file

@ -2,6 +2,7 @@ use std::borrow::Borrow;
use std::cmp; use std::cmp;
use std::ffi::c_void; use std::ffi::c_void;
use std::mem::{self, MaybeUninit}; use std::mem::{self, MaybeUninit};
use std::num::NonZeroU32;
use std::ptr; use std::ptr;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::Arc; use std::sync::Arc;
@ -26,9 +27,7 @@ use super::util::{
}; };
use super::util::{VST3_MIDI_CHANNELS, VST3_MIDI_PARAMS_END}; use super::util::{VST3_MIDI_CHANNELS, VST3_MIDI_PARAMS_END};
use super::view::WrapperView; use super::view::WrapperView;
use crate::audio_setup::{ use crate::audio_setup::{AuxiliaryBuffers, BufferConfig, ProcessMode};
AuxiliaryBuffers, AuxiliaryIOConfig, BufferConfig, BusConfig, ProcessMode,
};
use crate::buffer::Buffer; use crate::buffer::Buffer;
use crate::context::process::Transport; use crate::context::process::Transport;
use crate::midi::sysex::SysExMessage; use crate::midi::sysex::SysExMessage;
@ -89,10 +88,7 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
type_: vst3_sys::vst::MediaType, type_: vst3_sys::vst::MediaType,
dir: vst3_sys::vst::BusDirection, dir: vst3_sys::vst::BusDirection,
) -> i32 { ) -> i32 {
// HACK: Bitwig will not call the process function at all if the plugin does not have any let current_audio_io_layout = self.inner.current_audio_io_layout.load();
// audio IO, so we'll add a zero channel output to work around this if that is the
// case
let no_main_audio_io = P::DEFAULT_INPUT_CHANNELS == 0 && P::DEFAULT_OUTPUT_CHANNELS == 0;
// A plugin has a main input and output bus if the default number of channels is non-zero, // 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 // and a plugin can also have auxiliary input and output busses
@ -100,22 +96,25 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
x if x == vst3_sys::vst::MediaTypes::kAudio as i32 x if x == vst3_sys::vst::MediaTypes::kAudio as i32
&& dir == vst3_sys::vst::BusDirections::kInput as i32 => && dir == vst3_sys::vst::BusDirections::kInput as i32 =>
{ {
let main_busses = if P::DEFAULT_INPUT_CHANNELS > 0 { 1 } else { 0 }; let main_busses = if current_audio_io_layout.main_input_channels.is_some() {
let aux_busses = P::DEFAULT_AUX_INPUTS.unwrap_or_default().num_busses as i32; 1
} else {
0
};
let aux_busses = current_audio_io_layout.aux_input_ports.len() as i32;
main_busses + aux_busses main_busses + aux_busses
} }
x if x == vst3_sys::vst::MediaTypes::kAudio as i32 x if x == vst3_sys::vst::MediaTypes::kAudio as i32
&& dir == vst3_sys::vst::BusDirections::kOutput as i32 => && dir == vst3_sys::vst::BusDirections::kOutput as i32 =>
{ {
let main_busses = if P::DEFAULT_OUTPUT_CHANNELS > 0 { 1 } else { 0 }; // HACK: Bitwig will not call the process function at all if the plugin does not have any
let aux_busses = P::DEFAULT_AUX_OUTPUTS.unwrap_or_default().num_busses as i32; // audio IO, so we'll add a zero channel output to work around this if that is the
// case
let main_busses = 1;
let aux_busses = current_audio_io_layout.aux_output_ports.len() as i32;
if no_main_audio_io { main_busses + aux_busses
1
} else {
main_busses + aux_busses
}
} }
x if x == vst3_sys::vst::MediaTypes::kEvent as i32 x if x == vst3_sys::vst::MediaTypes::kEvent as i32
&& dir == vst3_sys::vst::BusDirections::kInput as i32 && dir == vst3_sys::vst::BusDirections::kInput as i32
@ -142,13 +141,13 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
) -> tresult { ) -> tresult {
check_null_ptr!(info); check_null_ptr!(info);
// HACK: Bitwig will not call the process function at all if the plugin does not have any let current_audio_io_layout = self.inner.current_audio_io_layout.load();
// audio IO, so we'll add a zero channel output to work around this if that is the
// case
let no_main_audio_io = P::DEFAULT_INPUT_CHANNELS == 0 && P::DEFAULT_OUTPUT_CHANNELS == 0;
match (type_, dir, index) { match (type_, dir, index) {
(t, _, _) if t == vst3_sys::vst::MediaTypes::kAudio as i32 => { (t, d, _)
if t == vst3_sys::vst::MediaTypes::kAudio as i32
&& d == vst3_sys::vst::BusDirections::kInput as i32 =>
{
*info = mem::zeroed(); *info = mem::zeroed();
let info = &mut *info; let info = &mut *info;
@ -156,87 +155,71 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
info.direction = dir; info.direction = dir;
info.flags = vst3_sys::vst::BusFlags::kDefaultActive as u32; info.flags = vst3_sys::vst::BusFlags::kDefaultActive as u32;
// This is fun since main IO is optional let has_main_input = current_audio_io_layout.main_input_channels.is_some();
let bus_config = self.inner.current_bus_config.load(); let aux_input_start_idx = if has_main_input { 1 } else { 0 };
if dir == vst3_sys::vst::BusDirections::kInput as i32 { let aux_input_idx = (index - aux_input_start_idx).max(0) as usize;
let aux_inputs_only = if index == 0 && has_main_input {
P::DEFAULT_INPUT_CHANNELS == 0 && P::DEFAULT_AUX_INPUTS.is_some(); info.bus_type = vst3_sys::vst::BusTypes::kMain as i32;
let aux_input_start_idx = if aux_inputs_only { 0 } else { 1 }; info.channel_count =
if !aux_inputs_only && index == 0 { current_audio_io_layout.main_input_channels.unwrap().get() as i32;
info.bus_type = vst3_sys::vst::BusTypes::kMain as i32; u16strlcpy(&mut info.name, &current_audio_io_layout.main_input_name());
info.channel_count = bus_config.num_input_channels as i32;
u16strlcpy(&mut info.name, P::PORT_NAMES.main_input.unwrap_or("Input"));
kResultOk kResultOk
} else if (aux_input_start_idx } else if aux_input_idx < current_audio_io_layout.aux_input_ports.len() {
..(aux_input_start_idx + bus_config.aux_input_busses.num_busses as i32)) info.bus_type = vst3_sys::vst::BusTypes::kAux as i32;
.contains(&index) info.channel_count =
{ current_audio_io_layout.aux_input_ports[aux_input_idx].get() as i32;
info.bus_type = vst3_sys::vst::BusTypes::kAux as i32; u16strlcpy(
info.channel_count = bus_config.aux_input_busses.num_channels as i32; &mut info.name,
&current_audio_io_layout
.aux_input_name(aux_input_idx)
.expect("Out of bounds auxiliary input port"),
);
let aux_input_idx = index - aux_input_start_idx; kResultOk
let custom_port_name = P::PORT_NAMES } else {
.aux_inputs kInvalidArgument
.map(|names| names[aux_input_idx as usize]); }
if bus_config.aux_input_busses.num_busses <= 1 { }
u16strlcpy( (t, d, _)
&mut info.name, if t == vst3_sys::vst::MediaTypes::kAudio as i32
custom_port_name.unwrap_or("Sidechain Input"), && d == vst3_sys::vst::BusDirections::kOutput as i32 =>
); {
} else { *info = mem::zeroed();
u16strlcpy(
&mut info.name,
custom_port_name
.unwrap_or(&format!("Sidechain Input {}", aux_input_idx + 1)),
);
}
kResultOk let info = &mut *info;
} else { info.media_type = vst3_sys::vst::MediaTypes::kAudio as i32;
kInvalidArgument info.direction = dir;
} info.flags = vst3_sys::vst::BusFlags::kDefaultActive as u32;
} else if dir == vst3_sys::vst::BusDirections::kOutput as i32 {
let aux_outputs_only =
P::DEFAULT_OUTPUT_CHANNELS == 0 && P::DEFAULT_AUX_OUTPUTS.is_some();
let aux_output_start_idx = if aux_outputs_only { 0 } else { 1 };
if (!aux_outputs_only || no_main_audio_io) && 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,
P::PORT_NAMES.main_output.unwrap_or("Output"),
);
kResultOk // HACK: Bitwig will not call the process function at all if the plugin does not have any
} else if (aux_output_start_idx // audio IO, so we'll add a zero channel output to work around this if that is the
..(aux_output_start_idx + bus_config.aux_output_busses.num_busses as i32)) // case
.contains(&index) let aux_output_start_idx = 1;
{ let aux_output_idx = (index - aux_output_start_idx).max(0) as usize;
info.bus_type = vst3_sys::vst::BusTypes::kAux as i32; if index == 0 {
info.channel_count = bus_config.aux_output_busses.num_channels as i32; info.bus_type = vst3_sys::vst::BusTypes::kMain as i32;
// NOTE: See above, this becomes a 0 channel output if the plugin doesn't have a
// main output
info.channel_count = current_audio_io_layout
.main_output_channels
.map(NonZeroU32::get)
.unwrap_or_default() as i32;
u16strlcpy(&mut info.name, &current_audio_io_layout.main_output_name());
let aux_output_idx = index - aux_output_start_idx; kResultOk
let custom_port_name = P::PORT_NAMES } else if aux_output_idx < current_audio_io_layout.aux_output_ports.len() {
.aux_outputs info.bus_type = vst3_sys::vst::BusTypes::kAux as i32;
.map(|names| names[aux_output_idx as usize]); info.channel_count =
if bus_config.aux_output_busses.num_busses <= 1 { current_audio_io_layout.aux_output_ports[aux_output_idx].get() as i32;
u16strlcpy( u16strlcpy(
&mut info.name, &mut info.name,
custom_port_name.unwrap_or("Auxiliary Output"), &current_audio_io_layout
); .aux_output_name(aux_output_idx)
} else { .expect("Out of bounds auxiliary output port"),
u16strlcpy( );
&mut info.name,
custom_port_name
.unwrap_or(&format!("Auxiliary Output {}", aux_output_idx + 1)),
);
}
kResultOk kResultOk
} else {
kInvalidArgument
}
} else { } else {
kInvalidArgument kInvalidArgument
} }
@ -284,6 +267,8 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
) -> tresult { ) -> tresult {
check_null_ptr!(in_info, out_info); check_null_ptr!(in_info, out_info);
let current_audio_io_layout = self.inner.current_audio_io_layout.load();
*out_info = mem::zeroed(); *out_info = mem::zeroed();
let in_info = &*in_info; let in_info = &*in_info;
@ -292,8 +277,8 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
(t, 0) (t, 0)
if t == vst3_sys::vst::MediaTypes::kAudio as i32 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 // We only have an IO pair when the plugin has both a main input and a main output
&& P::DEFAULT_INPUT_CHANNELS > 0 && current_audio_io_layout.main_input_channels.is_some()
&& P::DEFAULT_OUTPUT_CHANNELS > 0 => && current_audio_io_layout.main_output_channels.is_some() =>
{ {
out_info.media_type = vst3_sys::vst::MediaTypes::kAudio as i32; out_info.media_type = vst3_sys::vst::MediaTypes::kAudio as i32;
out_info.bus_index = in_info.bus_index; out_info.bus_index = in_info.bus_index;
@ -321,21 +306,27 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
type_: vst3_sys::vst::MediaType, type_: vst3_sys::vst::MediaType,
dir: vst3_sys::vst::BusDirection, dir: vst3_sys::vst::BusDirection,
index: i32, index: i32,
_state: vst3_sys::base::TBool, state: vst3_sys::base::TBool,
) -> tresult { ) -> tresult {
// HACK: Bitwig will not call the process function at all if the plugin does not have any let current_audio_io_layout = self.inner.current_audio_io_layout.load();
// audio IO, so we'll add a zero channel output to work around this if that is the
// case // We don't support this, and we'll just pretend to support enabling busses (even though
let no_main_audio_io = P::DEFAULT_INPUT_CHANNELS == 0 && P::DEFAULT_OUTPUT_CHANNELS == 0; // they're enabled by default) in case a host requires this
if state != 1 {
return kResultFalse;
}
// We don't need any special handling here
match (type_, dir, index) { match (type_, dir, index) {
(t, d, _) (t, d, _)
if t == vst3_sys::vst::MediaTypes::kAudio as i32 if t == vst3_sys::vst::MediaTypes::kAudio as i32
&& d == vst3_sys::vst::BusDirections::kInput as i32 => && d == vst3_sys::vst::BusDirections::kInput as i32 =>
{ {
let main_busses = if P::DEFAULT_INPUT_CHANNELS > 0 { 1 } else { 0 }; let main_busses = if current_audio_io_layout.main_input_channels.is_some() {
let aux_busses = P::DEFAULT_AUX_INPUTS.unwrap_or_default().num_busses as i32; 1
} else {
0
};
let aux_busses = current_audio_io_layout.aux_input_ports.len() as i32;
if (0..main_busses + aux_busses).contains(&index) { if (0..main_busses + aux_busses).contains(&index) {
kResultOk kResultOk
@ -347,12 +338,11 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
if t == vst3_sys::vst::MediaTypes::kAudio as i32 if t == vst3_sys::vst::MediaTypes::kAudio as i32
&& d == vst3_sys::vst::BusDirections::kOutput as i32 => && d == vst3_sys::vst::BusDirections::kOutput as i32 =>
{ {
let main_busses = if P::DEFAULT_OUTPUT_CHANNELS > 0 || no_main_audio_io { // HACK: Bitwig will not call the process function at all if the plugin does not have any
1 // audio IO, so we'll add a zero channel output to work around this if that is the
} else { // case
0 let main_busses = 1;
}; let aux_busses = current_audio_io_layout.aux_output_ports.len() as i32;
let aux_busses = P::DEFAULT_AUX_OUTPUTS.unwrap_or_default().num_busses as i32;
if (0..main_busses + aux_busses).contains(&index) { if (0..main_busses + aux_busses).contains(&index) {
kResultOk kResultOk
@ -391,9 +381,9 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
// NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks // NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks
let mut init_context = self.inner.make_init_context(); let mut init_context = self.inner.make_init_context();
let bus_config = self.inner.current_bus_config.load(); let audio_io_layout = self.inner.current_audio_io_layout.load();
let mut plugin = self.inner.plugin.lock(); let mut plugin = self.inner.plugin.lock();
if plugin.initialize(&bus_config, &buffer_config, &mut init_context) { if plugin.initialize(&audio_io_layout, &buffer_config, &mut init_context) {
// NOTE: We don't call `Plugin::reset()` here. The call is done in `set_process()` // NOTE: We don't call `Plugin::reset()` here. The call is done in `set_process()`
// instead. Otherwise we would call the function twice, and `set_process()` needs // instead. Otherwise we would call the function twice, and `set_process()` needs
// to be called after this function before the plugin may process audio again. // to be called after this function before the plugin may process audio again.
@ -404,8 +394,13 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
.output_buffer .output_buffer
.borrow_mut() .borrow_mut()
.set_slices(0, |output_slices| { .set_slices(0, |output_slices| {
output_slices output_slices.resize_with(
.resize_with(bus_config.num_output_channels as usize, || &mut []); audio_io_layout
.main_output_channels
.map(NonZeroU32::get)
.unwrap_or_default() as usize,
|| &mut [],
);
// All slices must have the same length, so if the number of output // All slices must have the same length, so if the number of output
// channels has changed since the last call then we should make sure to // channels has changed since the last call then we should make sure to
// clear any old (dangling) slices to be consistent // clear any old (dangling) slices to be consistent
@ -416,45 +411,36 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
// sidechain inputs. The slices will be assigned in the process function as this // sidechain inputs. The slices will be assigned in the process function as this
// object may have been moved before then. // object may have been moved before then.
let mut aux_input_storage = self.inner.aux_input_storage.borrow_mut(); let mut aux_input_storage = self.inner.aux_input_storage.borrow_mut();
aux_input_storage let mut aux_input_buffers = self.inner.aux_input_buffers.borrow_mut();
.resize_with(bus_config.aux_input_busses.num_busses as usize, Vec::new); aux_input_storage.resize_with(audio_io_layout.aux_input_ports.len(), Vec::new);
for bus_storage in aux_input_storage.iter_mut() { aux_input_buffers
bus_storage.resize_with( .resize_with(audio_io_layout.aux_input_ports.len(), Buffer::default);
bus_config.aux_input_busses.num_channels as usize, for ((buffer_storage, buffer), num_channels) in aux_input_storage
Vec::new, .iter_mut()
); .zip(aux_input_buffers.iter_mut())
for channel_storage in bus_storage { .zip(audio_io_layout.aux_input_ports.iter())
{
buffer_storage.resize_with(num_channels.get() as usize, Vec::new);
for channel_storage in buffer_storage {
channel_storage.resize(buffer_config.max_buffer_size as usize, 0.0); channel_storage.resize(buffer_config.max_buffer_size as usize, 0.0);
} }
}
let mut aux_input_buffers = self.inner.aux_input_buffers.borrow_mut();
aux_input_buffers.resize_with(
bus_config.aux_input_busses.num_busses as usize,
Buffer::default,
);
for buffer in aux_input_buffers.iter_mut() {
buffer.set_slices(0, |channel_slices| { buffer.set_slices(0, |channel_slices| {
channel_slices.resize_with( channel_slices.resize_with(num_channels.get() as usize, || &mut []);
bus_config.aux_input_busses.num_channels as usize,
|| &mut [],
);
channel_slices.fill_with(|| &mut []); channel_slices.fill_with(|| &mut []);
}); });
} }
// And the same thing for the output buffers // And the same thing for the output buffers
let mut aux_output_buffers = self.inner.aux_output_buffers.borrow_mut(); let mut aux_output_buffers = self.inner.aux_output_buffers.borrow_mut();
aux_output_buffers.resize_with( aux_output_buffers
bus_config.aux_output_busses.num_busses as usize, .resize_with(audio_io_layout.aux_output_ports.len(), Buffer::default);
Buffer::default, for (buffer, num_channels) in aux_output_buffers
); .iter_mut()
for buffer in aux_output_buffers.iter_mut() { .zip(audio_io_layout.aux_output_ports.iter())
{
buffer.set_slices(0, |channel_slices| { buffer.set_slices(0, |channel_slices| {
channel_slices.resize_with( channel_slices.resize_with(num_channels.get() as usize, || &mut []);
bus_config.aux_output_busses.num_channels as usize,
|| &mut [],
);
channel_slices.fill_with(|| &mut []); channel_slices.fill_with(|| &mut []);
}); });
} }
@ -522,9 +508,9 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
if let Some(buffer_config) = self.inner.current_buffer_config.load() { if let Some(buffer_config) = self.inner.current_buffer_config.load() {
// NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks // NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks
let mut init_context = self.inner.make_init_context(); let mut init_context = self.inner.make_init_context();
let bus_config = self.inner.current_bus_config.load(); let audio_io_layout = self.inner.current_audio_io_layout.load();
let mut plugin = self.inner.plugin.lock(); let mut plugin = self.inner.plugin.lock();
plugin.initialize(&bus_config, &buffer_config, &mut init_context); plugin.initialize(&audio_io_layout, &buffer_config, &mut init_context);
// TODO: This also goes for the CLAP version, but should we call reset here? Won't the // TODO: This also goes for the CLAP version, but should we call reset here? Won't the
// host always restart playback? Check this with a couple of hosts and remove the // host always restart playback? Check this with a couple of hosts and remove the
// duplicate reset if it's not needed. // duplicate reset if it's not needed.
@ -789,89 +775,80 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
) -> tresult { ) -> tresult {
check_null_ptr!(inputs, outputs); check_null_ptr!(inputs, outputs);
// HACK: Bitwig will not call the process function at all if the plugin does not have any
// audio IO, so we'll add a zero channel output to work around this if that is the
// case
let no_main_audio_io = P::DEFAULT_INPUT_CHANNELS == 0 && P::DEFAULT_OUTPUT_CHANNELS == 0;
// Why are these signed integers again? // Why are these signed integers again?
if num_ins < 0 || num_outs < 0 { if num_ins < 0 || num_outs < 0 {
return kInvalidArgument; return kInvalidArgument;
} }
// Every auxiliary input or output needs to have the same number of channels. In order to // NIH-plug no longer supports flexible IO layouts. Instead we'll try to find an audio IO
// support plugins with no main IO but with auxiliary IO, we'll need to take that into // layout that matches the host's requested layout.
// account when asserting this. If that's the case, then the first bus for that direction let matching_layout = P::AUDIO_IO_LAYOUTS
// will have been marked auxiliary. .iter()
let aux_inputs_only = P::DEFAULT_INPUT_CHANNELS == 0 && P::DEFAULT_AUX_INPUTS.is_some(); .find(|layout| {
let num_input_channels = if aux_inputs_only || num_ins < 1 { // If the number of ports/busses doesn't match then we can immediately discard the
0 // layout. VST3 doesn't allow for optional switchable ports like CLAP does. Only the
} else { // channel counts can change.
(*inputs).count_ones() let num_layout_ins = if layout.main_input_channels.is_some() {
}; 1
} else {
0
} + layout.aux_input_ports.len();
// HACK: Bitwig will not call the process function at all if the plugin does not
// have any audio IO, so we'll add a zero channel output to work around this
// if that is the case
let num_layout_outs = 1 + layout.aux_input_ports.len();
if num_ins as usize != num_layout_ins || num_outs as usize != num_layout_outs {
return false;
}
let aux_input_start_idx = if aux_inputs_only { 0 } else { 1 }; // NOTE: We completely ignore the speaker arrangements and only look at the channel
let num_aux_input_busses = (num_ins as u32).saturating_sub(aux_input_start_idx); // counts here. This may cause issues at some point, but it works for now.
let num_aux_input_channels = if num_aux_input_busses == 0 { let has_main_input = layout.main_input_channels.is_some();
0 let aux_input_start_idx = if has_main_input { 0 } else { 1 };
} else { if has_main_input
(*inputs.offset(aux_input_start_idx as isize)).count_ones() && (*inputs).count_ones() != layout.main_input_channels.unwrap().get()
}; {
for i in 1..num_aux_input_busses { return false;
if (*inputs.offset((aux_input_start_idx + i) as isize)).count_ones() }
!= num_aux_input_channels for (aux_input_idx, channel_count) in layout.aux_input_ports.iter().enumerate() {
{ if (*inputs.add(aux_input_idx + aux_input_start_idx)).count_ones()
nih_debug_assert_failure!("Mismatching auxiliary input bus channels set by host"); != channel_count.get()
return kResultFalse; {
return false;
}
}
// See above, there is always a main output
let aux_output_start_idx = 1;
if (*outputs).count_ones()
!= layout
.main_output_channels
.map(NonZeroU32::get)
.unwrap_or_default()
{
return false;
}
for (aux_output_idx, channel_count) in layout.aux_output_ports.iter().enumerate() {
if (*outputs.add(aux_output_idx + aux_output_start_idx)).count_ones()
!= channel_count.get()
{
return false;
}
}
true
})
.copied();
match matching_layout {
Some(layout) => {
// This layout is used from hereon onwards, at least until this function is called
// again
self.inner.current_audio_io_layout.store(layout);
kResultOk
} }
} None => kResultFalse,
let aux_outputs_only = P::DEFAULT_OUTPUT_CHANNELS == 0 && P::DEFAULT_AUX_OUTPUTS.is_some();
let num_output_channels = if (aux_outputs_only && !no_main_audio_io) || num_outs < 1 {
0
} else {
(*outputs).count_ones()
};
let aux_output_start_idx = if aux_outputs_only { 0 } else { 1 };
let num_aux_output_busses = (num_outs 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,
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
.plugin
.lock()
.accepts_bus_config(&proposed_config)
{
self.inner.current_bus_config.store(proposed_config);
kResultOk
} else {
kResultFalse
} }
} }
@ -883,11 +860,6 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
) -> tresult { ) -> tresult {
check_null_ptr!(arr); check_null_ptr!(arr);
// HACK: Bitwig will not call the process function at all if the plugin does not have any
// audio IO, so we'll add a zero channel output to work around this if that is the
// case
let no_main_audio_io = P::DEFAULT_INPUT_CHANNELS == 0 && P::DEFAULT_OUTPUT_CHANNELS == 0;
let channel_count_to_map = |count| match count { let channel_count_to_map = |count| match count {
0 => vst3_sys::vst::kEmpty, 0 => vst3_sys::vst::kEmpty,
1 => vst3_sys::vst::kMono, 1 => vst3_sys::vst::kMono,
@ -905,31 +877,28 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
} }
}; };
let bus_config = self.inner.current_bus_config.load(); let current_audio_io_layout = self.inner.current_audio_io_layout.load();
let num_channels = if dir == vst3_sys::vst::BusDirections::kInput as i32 { let num_channels = if dir == vst3_sys::vst::BusDirections::kInput as i32 {
let aux_inputs_only = P::DEFAULT_INPUT_CHANNELS == 0 && P::DEFAULT_AUX_INPUTS.is_some(); let has_main_input = current_audio_io_layout.main_input_channels.is_some();
let aux_input_start_idx = if aux_inputs_only { 0 } else { 1 }; let aux_input_start_idx = if has_main_input { 1 } else { 0 };
if !aux_inputs_only && index == 0 { let aux_input_idx = (index - aux_input_start_idx).max(0) as usize;
bus_config.num_input_channels if index == 0 && has_main_input {
} else if (aux_input_start_idx current_audio_io_layout.main_input_channels.unwrap().get()
..(aux_input_start_idx + bus_config.aux_input_busses.num_busses as i32)) } else if aux_input_idx < current_audio_io_layout.aux_input_ports.len() {
.contains(&index) current_audio_io_layout.aux_input_ports[aux_input_idx].get()
{
bus_config.aux_input_busses.num_channels
} else { } else {
return kInvalidArgument; return kInvalidArgument;
} }
} else if dir == vst3_sys::vst::BusDirections::kOutput as i32 { } else if dir == vst3_sys::vst::BusDirections::kOutput as i32 {
let aux_outputs_only = // HACK: Bitwig will not call the process function at all if the plugin does not have any
P::DEFAULT_OUTPUT_CHANNELS == 0 && P::DEFAULT_AUX_OUTPUTS.is_some(); // audio IO, so we'll add a zero channel output to work around this if that is the
let aux_output_start_idx = if aux_outputs_only { 0 } else { 1 }; // case
if (!aux_outputs_only || no_main_audio_io) && index == 0 { let aux_output_start_idx = 1;
bus_config.num_output_channels let aux_output_idx = (index - aux_output_start_idx).max(0) as usize;
} else if (aux_output_start_idx if index == 0 {
..(aux_output_start_idx + bus_config.aux_output_busses.num_busses as i32)) current_audio_io_layout.main_output_channels.unwrap().get()
.contains(&index) } else if aux_output_idx < current_audio_io_layout.aux_output_ports.len() {
{ current_audio_io_layout.aux_output_ports[aux_output_idx].get()
bus_config.aux_output_busses.num_channels
} else { } else {
return kInvalidArgument; return kInvalidArgument;
} }
@ -1052,13 +1021,12 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
// Before doing anything, clear out any auxiliary outputs since they may contain // Before doing anything, clear out any auxiliary outputs since they may contain
// uninitialized data when the host assumes that we'll always write something there // uninitialized data when the host assumes that we'll always write something there
let current_bus_config = self.inner.current_bus_config.load(); let current_audio_io_layout = self.inner.current_audio_io_layout.load();
let has_main_input = current_bus_config.num_input_channels > 0; let has_main_input = current_audio_io_layout.main_input_channels.is_some();
// HACK: Bitwig requires VST3 plugins to always have a main output. We'll however still
// use this variable here to maintain consistency between the backends.
let has_main_output = true;
if !data.outputs.is_null() { if !data.outputs.is_null() {
for output_idx in if has_main_output { 1 } else { 0 }..data.num_outputs as isize { // HACK: Bitwig requires VST3 plugins to always have a main output. We'll however
// still use this variable here to maintain consistency between the backends.
for output_idx in 1..data.num_outputs as isize {
let host_output = data.outputs.offset(output_idx); let host_output = data.outputs.offset(output_idx);
if !(*host_output).buffers.is_null() { if !(*host_output).buffers.is_null() {
for channel_idx in 0..(*host_output).num_channels as isize { for channel_idx in 0..(*host_output).num_channels as isize {
@ -1454,11 +1422,9 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
// And the same thing for auxiliary output buffers // And the same thing for auxiliary output buffers
let mut aux_output_buffers = self.inner.aux_output_buffers.borrow_mut(); let mut aux_output_buffers = self.inner.aux_output_buffers.borrow_mut();
for (auxiliary_output_idx, buffer) in aux_output_buffers.iter_mut().enumerate() { for (auxiliary_output_idx, buffer) in aux_output_buffers.iter_mut().enumerate() {
let host_output_idx = if has_main_output { // HACK: Bitwig requires VST3 plugins to always have a main output. We'll however still
auxiliary_output_idx as isize + 1 // use this variable here to maintain consistency between the backends.
} else { let host_output_idx = auxiliary_output_idx as isize + 1;
auxiliary_output_idx as isize
};
let host_output = data.outputs.offset(host_output_idx); let host_output = data.outputs.offset(host_output_idx);
if host_output_idx >= data.num_outputs as isize if host_output_idx >= data.num_outputs as isize
|| data.outputs.is_null() || data.outputs.is_null()
@ -1824,14 +1790,16 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
// NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks // NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks
let mut init_context = self.inner.make_init_context(); let mut init_context = self.inner.make_init_context();
let bus_config = self.inner.current_bus_config.load(); let audio_io_layout = self.inner.current_audio_io_layout.load();
let buffer_config = self.inner.current_buffer_config.load().unwrap(); let buffer_config = self.inner.current_buffer_config.load().unwrap();
let mut plugin = self.inner.plugin.lock(); let mut plugin = self.inner.plugin.lock();
// FIXME: This is obviously not realtime-safe, but loading presets without doing // FIXME: This is obviously not realtime-safe, but loading presets without doing
// this could lead to inconsistencies. It's the plugin's responsibility to // this could lead to inconsistencies. It's the plugin's responsibility to
// not perform any realtime-unsafe work when the initialize function is // not perform any realtime-unsafe work when the initialize function is
// called a second time if it supports runtime preset loading. // called a second time if it supports runtime preset loading.
permit_alloc(|| plugin.initialize(&bus_config, &buffer_config, &mut init_context)); permit_alloc(|| {
plugin.initialize(&audio_io_layout, &buffer_config, &mut init_context)
});
plugin.reset(); plugin.reset();
let task_posted = self.inner.schedule_gui(Task::ParameterValuesChanged); let task_posted = self.inner.schedule_gui(Task::ParameterValuesChanged);