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:
parent
36c48157db
commit
e8fd18ab80
|
@ -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
|
||||
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]
|
||||
|
||||
- The `Vst3Plugin::VST3_CATEGORIES` string constant has been replaced by a
|
||||
|
|
|
@ -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
|
||||
types to wrap around those messages so they don't need to interact with raw
|
||||
byte buffers in the process function.
|
||||
- Support for flexible dynamic buffer configurations, including multiple input
|
||||
and output busses.
|
||||
- Support for flexible dynamic buffer configurations, including variable numbers
|
||||
of input and output ports.
|
||||
- A plugin bundler accessible through the
|
||||
`cargo xtask bundle <package> <build_arguments>` command that automatically
|
||||
detects which plugin targets your plugin exposes and creates the correct
|
||||
|
|
|
@ -24,6 +24,8 @@ mod envelope;
|
|||
/// recording buffer's size.
|
||||
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
|
||||
/// values to buffers since these values may need to be reused for multiple voices.
|
||||
const MAX_BLOCK_SIZE: usize = 64;
|
||||
|
@ -181,8 +183,12 @@ impl Plugin for BuffrGlitch {
|
|||
|
||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
const DEFAULT_INPUT_CHANNELS: u32 = 2;
|
||||
const DEFAULT_OUTPUT_CHANNELS: u32 = 2;
|
||||
// We'll only do stereo for now
|
||||
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;
|
||||
|
||||
|
@ -193,23 +199,21 @@ impl Plugin for BuffrGlitch {
|
|||
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(
|
||||
&mut self,
|
||||
bus_config: &BusConfig,
|
||||
audio_io_layout: &AudioIOLayout,
|
||||
buffer_config: &BufferConfig,
|
||||
_context: &mut impl InitContext<Self>,
|
||||
) -> 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;
|
||||
for voice in &mut self.voices {
|
||||
voice.buffer.resize(
|
||||
bus_config.num_input_channels as usize,
|
||||
buffer_config.sample_rate,
|
||||
);
|
||||
voice
|
||||
.buffer
|
||||
.resize(num_output_channels, buffer_config.sample_rate);
|
||||
}
|
||||
|
||||
true
|
||||
|
@ -443,6 +447,7 @@ impl Vst3Plugin for BuffrGlitch {
|
|||
const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = &[
|
||||
Vst3SubCategory::Fx,
|
||||
Vst3SubCategory::Synth,
|
||||
Vst3SubCategory::Stereo,
|
||||
Vst3SubCategory::Custom("Glitch"),
|
||||
];
|
||||
}
|
||||
|
|
|
@ -301,8 +301,12 @@ impl Plugin for Crisp {
|
|||
|
||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
const DEFAULT_INPUT_CHANNELS: u32 = NUM_CHANNELS;
|
||||
const DEFAULT_OUTPUT_CHANNELS: u32 = NUM_CHANNELS;
|
||||
// We'll add a SIMD version in a bit which only supports stereo
|
||||
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;
|
||||
|
||||
|
@ -317,20 +321,12 @@ impl Plugin for Crisp {
|
|||
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(
|
||||
&mut self,
|
||||
bus_config: &BusConfig,
|
||||
_audio_io_layout: &AudioIOLayout,
|
||||
buffer_config: &BufferConfig,
|
||||
_context: &mut impl InitContext<Self>,
|
||||
) -> 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;
|
||||
|
||||
// The filter coefficients need to be reinitialized when loading a patch
|
||||
|
@ -500,7 +496,6 @@ impl ClapPlugin for Crisp {
|
|||
const CLAP_FEATURES: &'static [ClapFeature] = &[
|
||||
ClapFeature::AudioEffect,
|
||||
ClapFeature::Stereo,
|
||||
ClapFeature::Mono,
|
||||
ClapFeature::Distortion,
|
||||
];
|
||||
}
|
||||
|
@ -511,6 +506,7 @@ impl Vst3Plugin for Crisp {
|
|||
Vst3SubCategory::Fx,
|
||||
Vst3SubCategory::Filter,
|
||||
Vst3SubCategory::Distortion,
|
||||
Vst3SubCategory::Stereo,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -166,22 +166,24 @@ impl Plugin for Crossover {
|
|||
|
||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
const DEFAULT_INPUT_CHANNELS: u32 = NUM_CHANNELS;
|
||||
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),
|
||||
|
||||
const DEFAULT_AUX_OUTPUTS: Option<AuxiliaryIOConfig> = Some(AuxiliaryIOConfig {
|
||||
// Two to five of these busses will be used at a time
|
||||
num_busses: 5,
|
||||
num_channels: NUM_CHANNELS,
|
||||
});
|
||||
aux_input_ports: &[],
|
||||
// Two to five of these ports will be used at a time
|
||||
aux_output_ports: &[new_nonzero_u32(NUM_CHANNELS); 5],
|
||||
|
||||
const PORT_NAMES: PortNames = PortNames {
|
||||
main_input: None,
|
||||
// We won't output any sound here
|
||||
main_output: Some("The Void"),
|
||||
aux_inputs: None,
|
||||
aux_outputs: Some(&["Band 1", "Band 2", "Band 3", "Band 4", "Band 5"]),
|
||||
};
|
||||
names: PortNames {
|
||||
layout: Some("Up to five bands"),
|
||||
|
||||
main_input: None,
|
||||
// We won't output any sound here
|
||||
main_output: Some("The Void"),
|
||||
aux_inputs: &[],
|
||||
aux_outputs: &["Band 1", "Band 2", "Band 3", "Band 4", "Band 5"],
|
||||
},
|
||||
}];
|
||||
|
||||
type SysExMessage = ();
|
||||
type BackgroundTask = ();
|
||||
|
@ -190,16 +192,9 @@ impl Plugin for Crossover {
|
|||
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(
|
||||
&mut self,
|
||||
_bus_config: &BusConfig,
|
||||
_audio_io_layout: &AudioIOLayout,
|
||||
buffer_config: &BufferConfig,
|
||||
context: &mut impl InitContext<Self>,
|
||||
) -> bool {
|
||||
|
|
|
@ -34,6 +34,8 @@ mod filter;
|
|||
mod params;
|
||||
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.
|
||||
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)));
|
||||
|
||||
// We only do stereo right now so this is simple
|
||||
let (spectrum_input, spectrum_output) =
|
||||
SpectrumInput::new(Self::DEFAULT_OUTPUT_CHANNELS as usize);
|
||||
let (spectrum_input, spectrum_output) = SpectrumInput::new(NUM_CHANNELS as usize);
|
||||
|
||||
Self {
|
||||
params: Arc::new(DiopserParams::new(
|
||||
|
@ -113,8 +114,12 @@ impl Plugin for Diopser {
|
|||
|
||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
const DEFAULT_INPUT_CHANNELS: u32 = 2;
|
||||
const DEFAULT_OUTPUT_CHANNELS: u32 = 2;
|
||||
// The SIMD version only supports stereo
|
||||
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;
|
||||
|
||||
|
@ -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(
|
||||
&mut self,
|
||||
_bus_config: &BusConfig,
|
||||
_audio_io_layout: &AudioIOLayout,
|
||||
buffer_config: &BufferConfig,
|
||||
_context: &mut impl InitContext<Self>,
|
||||
) -> bool {
|
||||
|
@ -355,8 +355,11 @@ impl ClapPlugin for Diopser {
|
|||
|
||||
impl Vst3Plugin for Diopser {
|
||||
const VST3_CLASS_ID: [u8; 16] = *b"DiopserPlugRvdH.";
|
||||
const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] =
|
||||
&[Vst3SubCategory::Fx, Vst3SubCategory::Filter];
|
||||
const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = &[
|
||||
Vst3SubCategory::Fx,
|
||||
Vst3SubCategory::Filter,
|
||||
Vst3SubCategory::Stereo,
|
||||
];
|
||||
}
|
||||
|
||||
nih_export_clap!(Diopser);
|
||||
|
|
|
@ -120,11 +120,27 @@ impl Plugin for Gain {
|
|||
|
||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
const DEFAULT_INPUT_CHANNELS: u32 = 2;
|
||||
const DEFAULT_OUTPUT_CHANNELS: u32 = 2;
|
||||
// The first audio IO layout is used as the default. The other layouts may be selected either
|
||||
// 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;
|
||||
const DEFAULT_AUX_OUTPUTS: Option<AuxiliaryIOConfig> = None;
|
||||
aux_input_ports: &[],
|
||||
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;
|
||||
// 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()
|
||||
}
|
||||
|
||||
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
|
||||
// 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
|
||||
|
|
|
@ -78,8 +78,18 @@ impl Plugin for Gain {
|
|||
|
||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
const DEFAULT_INPUT_CHANNELS: u32 = 2;
|
||||
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()
|
||||
},
|
||||
AudioIOLayout {
|
||||
main_input_channels: NonZeroU32::new(1),
|
||||
main_output_channels: NonZeroU32::new(1),
|
||||
..AudioIOLayout::const_default()
|
||||
},
|
||||
];
|
||||
|
||||
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(
|
||||
&mut self,
|
||||
_bus_config: &BusConfig,
|
||||
_audio_io_layout: &AudioIOLayout,
|
||||
buffer_config: &BufferConfig,
|
||||
_context: &mut impl InitContext<Self>,
|
||||
) -> bool {
|
||||
|
|
|
@ -75,8 +75,18 @@ impl Plugin for Gain {
|
|||
|
||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
const DEFAULT_INPUT_CHANNELS: u32 = 2;
|
||||
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()
|
||||
},
|
||||
AudioIOLayout {
|
||||
main_input_channels: NonZeroU32::new(1),
|
||||
main_output_channels: NonZeroU32::new(1),
|
||||
..AudioIOLayout::const_default()
|
||||
},
|
||||
];
|
||||
|
||||
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(
|
||||
&mut self,
|
||||
_bus_config: &BusConfig,
|
||||
_audio_io_layout: &AudioIOLayout,
|
||||
buffer_config: &BufferConfig,
|
||||
_context: &mut impl InitContext<Self>,
|
||||
) -> bool {
|
||||
|
|
|
@ -74,8 +74,18 @@ impl Plugin for Gain {
|
|||
|
||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
const DEFAULT_INPUT_CHANNELS: u32 = 2;
|
||||
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()
|
||||
},
|
||||
AudioIOLayout {
|
||||
main_input_channels: NonZeroU32::new(1),
|
||||
main_output_channels: NonZeroU32::new(1),
|
||||
..AudioIOLayout::const_default()
|
||||
},
|
||||
];
|
||||
|
||||
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(
|
||||
&mut self,
|
||||
_bus_config: &BusConfig,
|
||||
_audio_io_layout: &AudioIOLayout,
|
||||
buffer_config: &BufferConfig,
|
||||
_context: &mut impl InitContext<Self>,
|
||||
) -> bool {
|
||||
|
|
|
@ -26,8 +26,8 @@ impl Plugin for MidiInverter {
|
|||
|
||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
const DEFAULT_INPUT_CHANNELS: u32 = 0;
|
||||
const DEFAULT_OUTPUT_CHANNELS: u32 = 0;
|
||||
// This plugin doesn't have any audio IO
|
||||
const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[];
|
||||
|
||||
const MIDI_INPUT: MidiConfig = MidiConfig::MidiCCs;
|
||||
const MIDI_OUTPUT: MidiConfig = MidiConfig::MidiCCs;
|
||||
|
|
|
@ -148,8 +148,11 @@ impl Plugin for PolyModSynth {
|
|||
|
||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
const DEFAULT_INPUT_CHANNELS: u32 = 2;
|
||||
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()
|
||||
}];
|
||||
|
||||
// We won't need any MIDI CCs here, we just want notes and polyphonic modulation
|
||||
const MIDI_INPUT: MidiConfig = MidiConfig::Basic;
|
||||
|
@ -612,8 +615,11 @@ impl ClapPlugin for PolyModSynth {
|
|||
// modulation
|
||||
impl Vst3Plugin for PolyModSynth {
|
||||
const VST3_CLASS_ID: [u8; 16] = *b"PolyM0dSynth1337";
|
||||
const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] =
|
||||
&[Vst3SubCategory::Instrument, Vst3SubCategory::Synth];
|
||||
const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = &[
|
||||
Vst3SubCategory::Instrument,
|
||||
Vst3SubCategory::Synth,
|
||||
Vst3SubCategory::Stereo,
|
||||
];
|
||||
}
|
||||
|
||||
nih_export_clap!(PolyModSynth);
|
||||
|
|
|
@ -105,8 +105,19 @@ impl Plugin for Sine {
|
|||
|
||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
const DEFAULT_INPUT_CHANNELS: u32 = 0;
|
||||
const DEFAULT_OUTPUT_CHANNELS: u32 = 2;
|
||||
const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[
|
||||
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 SAMPLE_ACCURATE_AUTOMATION: bool = true;
|
||||
|
@ -118,14 +129,9 @@ impl Plugin for Sine {
|
|||
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(
|
||||
&mut self,
|
||||
_bus_config: &BusConfig,
|
||||
_audio_io_layout: &AudioIOLayout,
|
||||
buffer_config: &BufferConfig,
|
||||
_context: &mut impl InitContext<Self>,
|
||||
) -> bool {
|
||||
|
|
|
@ -90,8 +90,12 @@ impl Plugin for Stft {
|
|||
|
||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
const DEFAULT_INPUT_CHANNELS: u32 = 2;
|
||||
const DEFAULT_OUTPUT_CHANNELS: u32 = 2;
|
||||
// We'll only do stereo for simplicity's sake
|
||||
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;
|
||||
|
||||
|
@ -102,14 +106,9 @@ impl Plugin for Stft {
|
|||
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(
|
||||
&mut self,
|
||||
_bus_config: &BusConfig,
|
||||
_audio_io_layout: &AudioIOLayout,
|
||||
_buffer_config: &BufferConfig,
|
||||
context: &mut impl InitContext<Self>,
|
||||
) -> bool {
|
||||
|
@ -172,15 +171,17 @@ impl ClapPlugin for Stft {
|
|||
const CLAP_FEATURES: &'static [ClapFeature] = &[
|
||||
ClapFeature::AudioEffect,
|
||||
ClapFeature::Stereo,
|
||||
ClapFeature::Mono,
|
||||
ClapFeature::Utility,
|
||||
];
|
||||
}
|
||||
|
||||
impl Vst3Plugin for Stft {
|
||||
const VST3_CLASS_ID: [u8; 16] = *b"StftMoistestPlug";
|
||||
const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] =
|
||||
&[Vst3SubCategory::Fx, Vst3SubCategory::Tools];
|
||||
const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = &[
|
||||
Vst3SubCategory::Fx,
|
||||
Vst3SubCategory::Tools,
|
||||
Vst3SubCategory::Stereo,
|
||||
];
|
||||
}
|
||||
|
||||
nih_export_clap!(Stft);
|
||||
|
|
|
@ -51,8 +51,8 @@ impl Plugin for SysEx {
|
|||
|
||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
const DEFAULT_INPUT_CHANNELS: u32 = 0;
|
||||
const DEFAULT_OUTPUT_CHANNELS: u32 = 0;
|
||||
// This plugin doesn't have any audio IO
|
||||
const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[];
|
||||
|
||||
const SAMPLE_ACCURATE_AUTOMATION: bool = true;
|
||||
|
||||
|
|
|
@ -120,8 +120,18 @@ impl Plugin for LoudnessWarWinner {
|
|||
|
||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
const DEFAULT_INPUT_CHANNELS: u32 = 2;
|
||||
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()
|
||||
},
|
||||
AudioIOLayout {
|
||||
main_input_channels: NonZeroU32::new(1),
|
||||
main_output_channels: NonZeroU32::new(1),
|
||||
..AudioIOLayout::const_default()
|
||||
},
|
||||
];
|
||||
|
||||
type SysExMessage = ();
|
||||
type BackgroundTask = ();
|
||||
|
@ -130,22 +140,20 @@ impl Plugin for LoudnessWarWinner {
|
|||
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(
|
||||
&mut self,
|
||||
bus_config: &BusConfig,
|
||||
audio_io_layout: &AudioIOLayout,
|
||||
buffer_config: &BufferConfig,
|
||||
_context: &mut impl InitContext<Self>,
|
||||
) -> bool {
|
||||
self.sample_rate = buffer_config.sample_rate;
|
||||
|
||||
self.bp_filters.resize(
|
||||
bus_config.num_output_channels as usize,
|
||||
[filter::Biquad::default(); 4],
|
||||
);
|
||||
let num_output_channels = audio_io_layout
|
||||
.main_output_channels
|
||||
.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.silence_fadeout_start_samples =
|
||||
|
|
|
@ -165,8 +165,12 @@ impl Plugin for PubertySimulator {
|
|||
|
||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
const DEFAULT_INPUT_CHANNELS: u32 = 2;
|
||||
const DEFAULT_OUTPUT_CHANNELS: u32 = 2;
|
||||
// We'll only do stereo for simplicity's sake
|
||||
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 BackgroundTask = ();
|
||||
|
@ -175,14 +179,9 @@ impl Plugin for PubertySimulator {
|
|||
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(
|
||||
&mut self,
|
||||
_bus_config: &BusConfig,
|
||||
_audio_io_layout: &AudioIOLayout,
|
||||
_buffer_config: &BufferConfig,
|
||||
context: &mut impl InitContext<Self>,
|
||||
) -> bool {
|
||||
|
@ -431,8 +430,11 @@ impl ClapPlugin for PubertySimulator {
|
|||
|
||||
impl Vst3Plugin for PubertySimulator {
|
||||
const VST3_CLASS_ID: [u8; 16] = *b"PubertySim..RvdH";
|
||||
const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] =
|
||||
&[Vst3SubCategory::Fx, Vst3SubCategory::PitchShift];
|
||||
const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = &[
|
||||
Vst3SubCategory::Fx,
|
||||
Vst3SubCategory::PitchShift,
|
||||
Vst3SubCategory::Stereo,
|
||||
];
|
||||
}
|
||||
|
||||
nih_export_clap!(PubertySimulator);
|
||||
|
|
|
@ -155,8 +155,18 @@ impl Plugin for SafetyLimiter {
|
|||
|
||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
const DEFAULT_INPUT_CHANNELS: u32 = 2;
|
||||
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()
|
||||
},
|
||||
AudioIOLayout {
|
||||
main_input_channels: NonZeroU32::new(1),
|
||||
main_output_channels: NonZeroU32::new(1),
|
||||
..AudioIOLayout::const_default()
|
||||
},
|
||||
];
|
||||
|
||||
type SysExMessage = ();
|
||||
type BackgroundTask = ();
|
||||
|
@ -165,13 +175,9 @@ impl Plugin for SafetyLimiter {
|
|||
self.params.clone()
|
||||
}
|
||||
|
||||
fn accepts_bus_config(&self, config: &BusConfig) -> bool {
|
||||
config.num_input_channels == config.num_output_channels
|
||||
}
|
||||
|
||||
fn initialize(
|
||||
&mut self,
|
||||
_bus_config: &BusConfig,
|
||||
_audio_io_layout: &AudioIOLayout,
|
||||
buffer_config: &BufferConfig,
|
||||
_context: &mut impl InitContext<Self>,
|
||||
) -> bool {
|
||||
|
|
|
@ -132,10 +132,7 @@ impl Default for SpectralCompressor {
|
|||
fn default() -> Self {
|
||||
// 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
|
||||
let compressor_bank = compressor_bank::CompressorBank::new(
|
||||
Self::DEFAULT_OUTPUT_CHANNELS as usize,
|
||||
MAX_WINDOW_SIZE,
|
||||
);
|
||||
let compressor_bank = compressor_bank::CompressorBank::new(2, MAX_WINDOW_SIZE);
|
||||
|
||||
SpectralCompressor {
|
||||
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
|
||||
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),
|
||||
dry_wet_mixer: dry_wet_mixer::DryWetMixer::new(0, 0, 0),
|
||||
compressor_bank,
|
||||
|
@ -256,12 +253,24 @@ impl Plugin for SpectralCompressor {
|
|||
|
||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
const DEFAULT_INPUT_CHANNELS: u32 = 2;
|
||||
const DEFAULT_OUTPUT_CHANNELS: u32 = 2;
|
||||
const DEFAULT_AUX_INPUTS: Option<AuxiliaryIOConfig> = Some(AuxiliaryIOConfig {
|
||||
num_busses: 1,
|
||||
num_channels: 2,
|
||||
});
|
||||
const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[
|
||||
AudioIOLayout {
|
||||
main_input_channels: NonZeroU32::new(2),
|
||||
main_output_channels: NonZeroU32::new(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;
|
||||
|
||||
|
@ -276,35 +285,31 @@ impl Plugin for SpectralCompressor {
|
|||
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(
|
||||
&mut self,
|
||||
bus_config: &BusConfig,
|
||||
audio_io_layout: &AudioIOLayout,
|
||||
buffer_config: &BufferConfig,
|
||||
context: &mut impl InitContext<Self>,
|
||||
) -> bool {
|
||||
// Needed to update the compressors later
|
||||
self.buffer_config = *buffer_config;
|
||||
|
||||
// This plugin can accept any number of channels, so we need to resize channel-dependent
|
||||
// data structures accordinly
|
||||
if self.stft.num_channels() != bus_config.num_output_channels as usize {
|
||||
// This plugin can accept a variable number of audio channels, so we need to resize
|
||||
// channel-dependent data structures accordingly
|
||||
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.dry_wet_mixer.resize(
|
||||
bus_config.num_output_channels as usize,
|
||||
num_output_channels,
|
||||
buffer_config.max_buffer_size as usize,
|
||||
MAX_WINDOW_SIZE,
|
||||
);
|
||||
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
|
||||
// FFTs we might need in advance
|
||||
|
@ -572,6 +577,7 @@ impl ClapPlugin for SpectralCompressor {
|
|||
const CLAP_FEATURES: &'static [ClapFeature] = &[
|
||||
ClapFeature::AudioEffect,
|
||||
ClapFeature::Stereo,
|
||||
ClapFeature::Mono,
|
||||
ClapFeature::PhaseVocoder,
|
||||
ClapFeature::Compressor,
|
||||
ClapFeature::Custom("nih:spectral"),
|
||||
|
|
|
@ -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;
|
||||
|
||||
/// The plugin's IO configuration.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct BusConfig {
|
||||
/// The number of input channels for the plugin.
|
||||
pub num_input_channels: u32,
|
||||
/// The number of output channels for the plugin.
|
||||
pub num_output_channels: u32,
|
||||
/// Any additional sidechain inputs.
|
||||
pub aux_input_busses: AuxiliaryIOConfig,
|
||||
/// Any additional outputs.
|
||||
pub aux_output_busses: AuxiliaryIOConfig,
|
||||
/// A description of a plugin's audio IO configuration. The [`Plugin`][crate::prelude::Plugin]
|
||||
/// defines a list of supported audio IO configs, with the first one acting as the default layout.
|
||||
/// Depending on the plugin API, the host may pick a different configuration from the list and use
|
||||
/// that instead. The final chosen configuration is passed as an argument to the
|
||||
/// [`Plugin::initialize()`][crate::prelude::Plugin::initialize] function so the plugin can allocate
|
||||
/// its data structures based on the number of audio channels it needs to process.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct AudioIOLayout {
|
||||
/// The number of main input channels for the plugin, if it has a main input port. This can be
|
||||
/// set to `None` if the plugin does not have one.
|
||||
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`].
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct AuxiliaryIOConfig {
|
||||
/// The number of auxiliary input or output busses.
|
||||
pub num_busses: u32,
|
||||
/// The number of channels in each bus.
|
||||
pub num_channels: u32,
|
||||
/// Construct a `NonZeroU32` value at compile time. Equivalent to `NonZeroU32::new(n).unwrap()`.
|
||||
pub const fn new_nonzero_u32(n: u32) -> NonZeroU32 {
|
||||
match NonZeroU32::new(n) {
|
||||
Some(n) => n,
|
||||
None => panic!("'new_nonzero_u32()' called with a zero value"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains auxiliary (sidechain) input and output buffers for a process call.
|
||||
pub struct AuxiliaryBuffers<'a> {
|
||||
/// All auxiliary (sidechain) inputs defined for this plugin. The data in these buffers can
|
||||
/// safely be overwritten. Auxiliary inputs can be defined by setting
|
||||
/// [`Plugin::DEFAULT_AUX_INPUTS`][`crate::prelude::Plugin::DEFAULT_AUX_INPUTS`].
|
||||
/// Buffers for all auxiliary (sidechain) inputs defined for this plugin. The data in these
|
||||
/// buffers can safely be overwritten. Auxiliary inputs can be defined using the
|
||||
/// [`AudioIOLayout::aux_input_ports`] field.
|
||||
pub inputs: &'a mut [Buffer<'a>],
|
||||
/// Get all auxiliary outputs defined for this plugin. Auxiliary outputs can be defined by
|
||||
/// setting [`Plugin::DEFAULT_AUX_OUTPUTS`][`crate::prelude::Plugin::DEFAULT_AUX_OUTPUTS`].
|
||||
/// Buffers for all auxiliary outputs defined for this plugin. Auxiliary outputs can be defined using the
|
||||
/// [`AudioIOLayout::aux_output_ports`] field.
|
||||
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
|
||||
/// output ports. Setting these is optional, but it makes working with multi-output plugins much
|
||||
/// more convenient.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
/// Contains names for the ports defined in an `AudioIOLayout`. Setting these is optional, but it
|
||||
/// makes working with multi-output plugins much more convenient.
|
||||
///
|
||||
/// 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 {
|
||||
/// 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.
|
||||
pub main_input: Option<&'static str>,
|
||||
/// The name for the main output port. Will be generated if not set.
|
||||
pub main_output: Option<&'static str>,
|
||||
/// Names for auxiliary (sidechain) input ports. Will be generated if not set or if this slice
|
||||
/// 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
|
||||
/// contain enough names.
|
||||
pub aux_outputs: Option<&'static [&'static str]>,
|
||||
pub aux_outputs: &'static [&'static str],
|
||||
}
|
||||
|
||||
/// Configuration for (the host's) audio buffers.
|
||||
|
@ -82,3 +104,101 @@ pub enum ProcessMode {
|
|||
/// processed.
|
||||
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: &[],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,11 +36,10 @@
|
|||
//! 1. When the host loads the plugin, your plugin object will be instantiated using its
|
||||
//! [`Default`] implementation. The plugin should refrain from performing expensive
|
||||
//! calculations or IO at this point.
|
||||
//! 2. The host or the plugin wrapper will call
|
||||
//! [`Plugin::accepts_bus_config()`][prelude::Plugin::accepts_bus_config()] several times with
|
||||
//! different IO configurations to poll whether your plugin supports certain IO configurations.
|
||||
//! The plugin should not do any work at this point and just reply with boolean whether it
|
||||
//! supports the configuration or not.
|
||||
//! 2. The host will select an audio IO layout from
|
||||
//! [`Plugin::AUDIO_IO_LAYOUTS`][prelude::Plugin::AUDIO_IO_LAYOUTS]. The first layout is always
|
||||
//! used as the default one, and should reflect the plugin's most commonly used configuration.
|
||||
//! Usually this is a stereo layout.
|
||||
//! 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
|
||||
//! data structures you need or precompute data that depends on the sample rate or maximum
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
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::context::gui::AsyncExecutor;
|
||||
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.
|
||||
const VERSION: &'static str;
|
||||
|
||||
/// The default number of input channels. This merely serves as a default. The host will probe
|
||||
/// the plugin's supported configuration using
|
||||
/// [`accepts_bus_config()`][Self::accepts_bus_config()], and the selected configuration is
|
||||
/// passed to [`initialize()`][Self::initialize()]. Some hosts like, like Bitwig and Ardour, use
|
||||
/// the defaults instead of setting up the busses properly.
|
||||
/// The plugin's supported audio IO layouts. The first config will be used as the default config
|
||||
/// if the host doesn't or can't select an alternative configuration. Because of that it's
|
||||
/// recommended to begin this slice with a stereo layout. For maximum compatibility with the
|
||||
/// different plugin formats this default layout should also include all of the plugin's
|
||||
/// 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.
|
||||
const DEFAULT_INPUT_CHANNELS: u32 = 2;
|
||||
/// The default number of output channels. All of the same caveats mentioned for
|
||||
/// `DEFAULT_INPUT_CHANNELS` apply here.
|
||||
/// Both [`AudioIOLayout`] and [`PortNames`][crate::prelude::PortNames] have `.const_default()`
|
||||
/// functions for compile-time equivalents to `Default::default()`:
|
||||
///
|
||||
/// Setting this to zero causes the plugin to have no main output bus.
|
||||
const DEFAULT_OUTPUT_CHANNELS: u32 = 2;
|
||||
|
||||
/// If set, then the plugin will have this many sidechain input busses with a default number of
|
||||
/// channels. Not all hosts support more than one sidechain input bus. Negotiating the actual
|
||||
/// configuration works the same was as with `DEFAULT_INPUT_CHANNELS`.
|
||||
const DEFAULT_AUX_INPUTS: Option<AuxiliaryIOConfig> = None;
|
||||
/// If set, then the plugin will have this many auxiliary output busses with a default number of
|
||||
/// channels. Negotiating the actual configuration works the same was as with
|
||||
/// `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
|
||||
/// set. This is mostly useful to give descriptive names to the outputs for multi-output
|
||||
/// plugins.
|
||||
const PORT_NAMES: PortNames = PortNames {
|
||||
main_input: None,
|
||||
main_output: None,
|
||||
aux_inputs: None,
|
||||
aux_outputs: None,
|
||||
};
|
||||
/// ```
|
||||
/// # use nih_plug::prelude::*;
|
||||
/// const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[AudioIOLayout {
|
||||
/// main_input_channels: NonZeroU32::new(2),
|
||||
/// main_output_channels: NonZeroU32::new(2),
|
||||
///
|
||||
/// aux_input_ports: &[new_nonzero_u32(2)],
|
||||
///
|
||||
/// ..AudioIOLayout::const_default()
|
||||
/// }];
|
||||
/// ```
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Some plugin hosts, like Ableton Live, don't support MIDI-only plugins and may refuse to load
|
||||
/// plugins with no main output or with zero main output channels.
|
||||
const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout];
|
||||
|
||||
/// 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.
|
||||
|
@ -152,30 +147,28 @@ pub trait Plugin: Default + Send + 'static {
|
|||
// 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
|
||||
/// shouldn't do anything beyond returning true or false.
|
||||
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`.
|
||||
/// Initialize the plugin for the given audio IO configuration. From this point onwards the
|
||||
/// audio IO layouts and the buffer sizes are fixed until this function is called again.
|
||||
///
|
||||
/// Before this point, the plugin should not have done any expensive initialization. Please
|
||||
/// 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
|
||||
/// 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(
|
||||
&mut self,
|
||||
bus_config: &BusConfig,
|
||||
audio_io_layout: &AudioIOLayout,
|
||||
buffer_config: &BufferConfig,
|
||||
context: &mut impl InitContext<Self>,
|
||||
) -> bool {
|
||||
|
|
|
@ -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
|
||||
pub use crate::debug::*;
|
||||
|
||||
|
@ -11,7 +14,7 @@ pub use crate::formatters;
|
|||
pub use crate::util;
|
||||
|
||||
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::context::gui::{AsyncExecutor, GuiContext, ParamSetter};
|
||||
|
|
|
@ -68,6 +68,7 @@ use std::cmp;
|
|||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::ffi::{c_void, CStr};
|
||||
use std::mem;
|
||||
use std::num::NonZeroU32;
|
||||
use std::os::raw::c_char;
|
||||
use std::ptr;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
||||
|
@ -78,7 +79,7 @@ use std::time::Duration;
|
|||
use super::context::{WrapperGuiContext, WrapperInitContext, WrapperProcessContext};
|
||||
use super::descriptor::PluginDescriptor;
|
||||
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::context::gui::AsyncExecutor;
|
||||
use crate::context::process::Transport;
|
||||
|
@ -127,8 +128,8 @@ pub struct Wrapper<P: ClapPlugin> {
|
|||
|
||||
is_processing: AtomicBool,
|
||||
/// The current IO configuration, modified through the `clap_plugin_audio_ports_config`
|
||||
/// extension.
|
||||
current_bus_config: AtomicCell<BusConfig>,
|
||||
/// extension. Initialized to the plugin's first audio IO configuration.
|
||||
current_audio_io_layout: AtomicCell<AudioIOLayout>,
|
||||
/// The current buffer configuration, containing the sample rate and the maximum block size.
|
||||
/// Will be set in `clap_plugin::activate()`.
|
||||
current_buffer_config: AtomicCell<Option<BufferConfig>>,
|
||||
|
@ -181,14 +182,6 @@ pub struct Wrapper<P: ClapPlugin> {
|
|||
host_callback: ClapPtr<clap_host>,
|
||||
|
||||
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
|
||||
// `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 {
|
||||
this: AtomicRefCell::new(Weak::new()),
|
||||
|
||||
|
@ -596,12 +543,9 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
editor_scaling_factor: AtomicF32::new(1.0),
|
||||
|
||||
is_processing: AtomicBool::new(false),
|
||||
current_bus_config: AtomicCell::new(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(),
|
||||
}),
|
||||
current_audio_io_layout: AtomicCell::new(
|
||||
P::AUDIO_IO_LAYOUTS.first().copied().unwrap_or_default(),
|
||||
),
|
||||
current_buffer_config: AtomicCell::new(None),
|
||||
current_process_mode: AtomicCell::new(ProcessMode::Realtime),
|
||||
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),
|
||||
select: Some(Self::ext_audio_ports_config_select),
|
||||
},
|
||||
supported_bus_configs,
|
||||
|
||||
clap_plugin_audio_ports: clap_plugin_audio_ports {
|
||||
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() {
|
||||
// NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks
|
||||
let mut init_context = self.make_init_context();
|
||||
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());
|
||||
}
|
||||
|
||||
|
@ -1841,7 +1784,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
check_null_ptr!(false, plugin, (*plugin).plugin_data);
|
||||
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 {
|
||||
sample_rate: sample_rate as f32,
|
||||
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
|
||||
let mut init_context = wrapper.make_init_context();
|
||||
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
|
||||
// this function
|
||||
|
||||
|
@ -1867,7 +1810,13 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
.output_buffer
|
||||
.borrow_mut()
|
||||
.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
|
||||
// changed since the last call then we should make sure to clear any old
|
||||
// (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
|
||||
// been moved before then.
|
||||
let mut aux_input_storage = wrapper.aux_input_storage.borrow_mut();
|
||||
aux_input_storage
|
||||
.resize_with(bus_config.aux_input_busses.num_busses as usize, Vec::new);
|
||||
for bus_storage in aux_input_storage.iter_mut() {
|
||||
bus_storage
|
||||
.resize_with(bus_config.aux_input_busses.num_channels as usize, Vec::new);
|
||||
for channel_storage in bus_storage {
|
||||
let mut aux_input_buffers = wrapper.aux_input_buffers.borrow_mut();
|
||||
aux_input_storage.resize_with(audio_io_layout.aux_input_ports.len(), Vec::new);
|
||||
aux_input_buffers.resize_with(audio_io_layout.aux_input_ports.len(), Buffer::default);
|
||||
for ((buffer_storage, buffer), num_channels) in aux_input_storage
|
||||
.iter_mut()
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
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| {
|
||||
channel_slices
|
||||
.resize_with(bus_config.aux_input_busses.num_channels as usize, || {
|
||||
&mut []
|
||||
});
|
||||
channel_slices.resize_with(num_channels.get() as usize, || &mut []);
|
||||
channel_slices.fill_with(|| &mut []);
|
||||
});
|
||||
}
|
||||
|
||||
// And the same thing for the output buffers
|
||||
let mut aux_output_buffers = wrapper.aux_output_buffers.borrow_mut();
|
||||
aux_output_buffers.resize_with(
|
||||
bus_config.aux_output_busses.num_busses as usize,
|
||||
Buffer::default,
|
||||
);
|
||||
for buffer in aux_output_buffers.iter_mut() {
|
||||
aux_output_buffers.resize_with(audio_io_layout.aux_output_ports.len(), Buffer::default);
|
||||
for (buffer, num_channels) in aux_output_buffers
|
||||
.iter_mut()
|
||||
.zip(audio_io_layout.aux_output_ports.iter())
|
||||
{
|
||||
buffer.set_slices(0, |channel_slices| {
|
||||
channel_slices
|
||||
.resize_with(bus_config.aux_output_busses.num_channels as usize, || {
|
||||
&mut []
|
||||
});
|
||||
channel_slices.resize_with(num_channels.get() as usize, || &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
|
||||
// uninitialized data when the host assumes that we'll always write something there
|
||||
let current_bus_config = wrapper.current_bus_config.load();
|
||||
let has_main_input = current_bus_config.num_input_channels > 0;
|
||||
let has_main_output = current_bus_config.num_output_channels > 0;
|
||||
let current_audio_io_layout = wrapper.current_audio_io_layout.load();
|
||||
let (aux_input_start_idx, aux_output_start_idx) = {
|
||||
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() {
|
||||
for output_idx in
|
||||
if has_main_output { 1 } else { 0 }..process.audio_outputs_count as isize
|
||||
{
|
||||
let host_output = process.audio_outputs.offset(output_idx);
|
||||
for output_idx in aux_output_start_idx..process.audio_outputs_count as usize {
|
||||
let host_output = process.audio_outputs.add(output_idx);
|
||||
if !(*host_output).data32.is_null() {
|
||||
for channel_idx in 0..(*host_output).channel_count as isize {
|
||||
ptr::write_bytes(
|
||||
|
@ -2160,13 +2104,9 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
.zip(aux_input_buffers.iter_mut())
|
||||
.enumerate()
|
||||
{
|
||||
let host_input_idx = if has_main_input {
|
||||
auxiliary_input_idx as isize + 1
|
||||
} else {
|
||||
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
|
||||
let host_input_idx = auxiliary_input_idx + aux_input_start_idx;
|
||||
let host_input = process.audio_inputs.add(host_input_idx);
|
||||
if host_input_idx >= process.audio_inputs_count as usize
|
||||
|| process.audio_inputs.is_null()
|
||||
|| (*host_input).data32.is_null()
|
||||
// Would only happen if the user configured zero channels for the
|
||||
|
@ -2174,7 +2114,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
|| storage.is_empty()
|
||||
|| (*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!(!(*host_input).data32.is_null());
|
||||
nih_debug_assert!(!storage.is_empty());
|
||||
|
@ -2217,18 +2157,14 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
// And the same thing for auxiliary output buffers
|
||||
let mut aux_output_buffers = wrapper.aux_output_buffers.borrow_mut();
|
||||
for (auxiliary_output_idx, buffer) in aux_output_buffers.iter_mut().enumerate() {
|
||||
let host_output_idx = if has_main_output {
|
||||
auxiliary_output_idx as isize + 1
|
||||
} else {
|
||||
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
|
||||
let host_output_idx = auxiliary_output_idx + aux_output_start_idx;
|
||||
let host_output = process.audio_outputs.add(host_output_idx);
|
||||
if host_output_idx >= process.audio_outputs_count as usize
|
||||
|| process.audio_outputs.is_null()
|
||||
|| (*host_output).data32.is_null()
|
||||
|| 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!(!(*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
|
||||
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 mut plugin = wrapper.plugin.lock();
|
||||
// FIXME: This is obviously not realtime-safe, but loading presets without doing
|
||||
// this could lead to inconsistencies. It's the plugin's responsibility to
|
||||
// not perform any realtime-unsafe work when the initialize function is
|
||||
// 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();
|
||||
|
||||
// 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 {
|
||||
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(
|
||||
|
@ -2492,37 +2429,24 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
config: *mut clap_audio_ports_config,
|
||||
) -> bool {
|
||||
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) {
|
||||
Some(bus_config) => {
|
||||
// We don't support variable auxiliary IO configs right now, so we don't need to
|
||||
// specify sidechain inputs and aux outputs in these descriptions
|
||||
let name = match bus_config {
|
||||
BusConfig {
|
||||
num_input_channels: _,
|
||||
num_output_channels: 1,
|
||||
..
|
||||
} => String::from("Mono"),
|
||||
BusConfig {
|
||||
num_input_channels: _,
|
||||
num_output_channels: 2,
|
||||
..
|
||||
} => String::from("Stereo"),
|
||||
BusConfig {
|
||||
num_input_channels,
|
||||
num_output_channels,
|
||||
..
|
||||
} => format!("{num_input_channels} inputs, {num_output_channels} outputs"),
|
||||
};
|
||||
let input_port_type = match bus_config.num_input_channels {
|
||||
1 => CLAP_PORT_MONO.as_ptr(),
|
||||
2 => CLAP_PORT_STEREO.as_ptr(),
|
||||
// This function directly maps to `P::AUDIO_IO_LAYOUTS`, and we thus also don't need to
|
||||
// access the `wrapper` instance
|
||||
match P::AUDIO_IO_LAYOUTS.get(index as usize) {
|
||||
Some(audio_io_layout) => {
|
||||
let name = audio_io_layout.name();
|
||||
|
||||
let main_input_channels = audio_io_layout.main_input_channels.map(NonZeroU32::get);
|
||||
let main_output_channels =
|
||||
audio_io_layout.main_output_channels.map(NonZeroU32::get);
|
||||
let input_port_type = match main_input_channels {
|
||||
Some(1) => CLAP_PORT_MONO.as_ptr(),
|
||||
Some(2) => CLAP_PORT_STEREO.as_ptr(),
|
||||
_ => ptr::null(),
|
||||
};
|
||||
let output_port_type = match bus_config.num_output_channels {
|
||||
1 => CLAP_PORT_MONO.as_ptr(),
|
||||
2 => CLAP_PORT_STEREO.as_ptr(),
|
||||
let output_port_type = match main_output_channels {
|
||||
Some(1) => CLAP_PORT_MONO.as_ptr(),
|
||||
Some(2) => CLAP_PORT_STEREO.as_ptr(),
|
||||
_ => ptr::null(),
|
||||
};
|
||||
|
||||
|
@ -2531,21 +2455,17 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
let config = &mut *config;
|
||||
config.id = index;
|
||||
strlcpy(&mut config.name, &name);
|
||||
config.input_port_count = if bus_config.num_input_channels > 0 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
} + bus_config.aux_input_busses.num_busses;
|
||||
config.output_port_count = if bus_config.num_output_channels > 0 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
} + bus_config.aux_output_busses.num_busses;
|
||||
config.has_main_input = bus_config.num_output_channels > 0;
|
||||
config.main_input_channel_count = bus_config.num_output_channels;
|
||||
config.input_port_count = (if main_input_channels.is_some() { 1 } else { 0 }
|
||||
+ audio_io_layout.aux_input_ports.len())
|
||||
as u32;
|
||||
config.output_port_count = (if main_output_channels.is_some() { 1 } else { 0 }
|
||||
+ audio_io_layout.aux_output_ports.len())
|
||||
as u32;
|
||||
config.has_main_input = main_input_channels.is_some();
|
||||
config.main_input_channel_count = main_input_channels.unwrap_or_default();
|
||||
config.main_input_port_type = input_port_type;
|
||||
config.has_main_output = bus_config.num_output_channels > 0;
|
||||
config.main_output_channel_count = bus_config.num_output_channels;
|
||||
config.has_main_output = main_output_channels.is_some();
|
||||
config.main_output_channel_count = main_output_channels.unwrap_or_default();
|
||||
config.main_output_port_type = output_port_type;
|
||||
|
||||
true
|
||||
|
@ -2569,9 +2489,9 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
let wrapper = &*((*plugin).plugin_data as *const Self);
|
||||
|
||||
// We use the vector indices for the config ID
|
||||
match wrapper.supported_bus_configs.get(config_id as usize) {
|
||||
Some(bus_config) => {
|
||||
wrapper.current_bus_config.store(*bus_config);
|
||||
match P::AUDIO_IO_LAYOUTS.get(config_id as usize) {
|
||||
Some(audio_io_layout) => {
|
||||
wrapper.current_audio_io_layout.store(*audio_io_layout);
|
||||
|
||||
true
|
||||
}
|
||||
|
@ -2590,25 +2510,25 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
check_null_ptr!(0, plugin, (*plugin).plugin_data);
|
||||
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 {
|
||||
let main_busses = if bus_config.num_input_channels > 0 {
|
||||
let main_ports = if audio_io_layout.main_input_channels.is_some() {
|
||||
1
|
||||
} else {
|
||||
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 {
|
||||
let main_busses = if bus_config.num_output_channels > 0 {
|
||||
let main_ports = if audio_io_layout.main_output_channels.is_some() {
|
||||
1
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
let current_bus_config = wrapper.current_bus_config.load();
|
||||
let has_main_input = current_bus_config.num_input_channels > 0;
|
||||
let has_main_output = current_bus_config.num_output_channels > 0;
|
||||
let current_audio_io_layout = wrapper.current_audio_io_layout.load();
|
||||
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();
|
||||
|
||||
// Whether this port is a main port or an auxiliary (sidechain) port
|
||||
let is_main_port =
|
||||
|
@ -2656,11 +2576,22 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
_ => CLAP_INVALID_ID,
|
||||
};
|
||||
|
||||
let channel_count = match (is_input, is_main_port) {
|
||||
(true, true) => current_bus_config.num_input_channels,
|
||||
(false, true) => current_bus_config.num_output_channels,
|
||||
(true, false) => current_bus_config.aux_input_busses.num_channels,
|
||||
(false, false) => current_bus_config.aux_output_busses.num_channels,
|
||||
let channel_count = match (index, is_input) {
|
||||
(0, true) if has_main_input => {
|
||||
current_audio_io_layout.main_input_channels.unwrap().get()
|
||||
}
|
||||
(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 {
|
||||
|
@ -2674,46 +2605,25 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
let info = &mut *info;
|
||||
info.id = stable_id;
|
||||
match (is_input, is_main_port) {
|
||||
(true, true) => strlcpy(&mut info.name, P::PORT_NAMES.main_input.unwrap_or("Input")),
|
||||
(false, true) => strlcpy(
|
||||
&mut info.name,
|
||||
P::PORT_NAMES.main_output.unwrap_or("Output"),
|
||||
),
|
||||
(true, true) => strlcpy(&mut info.name, ¤t_audio_io_layout.main_input_name()),
|
||||
(false, true) => strlcpy(&mut info.name, ¤t_audio_io_layout.main_output_name()),
|
||||
(true, false) => {
|
||||
let aux_input_idx = if has_main_input { index - 1 } else { index };
|
||||
let custom_port_name = P::PORT_NAMES
|
||||
.aux_inputs
|
||||
.map(|names| names[aux_input_idx as usize]);
|
||||
if current_bus_config.aux_input_busses.num_busses <= 1 {
|
||||
strlcpy(
|
||||
&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)),
|
||||
);
|
||||
}
|
||||
let aux_input_idx = if has_main_input { index - 1 } else { index } as usize;
|
||||
strlcpy(
|
||||
&mut info.name,
|
||||
¤t_audio_io_layout
|
||||
.aux_input_name(aux_input_idx)
|
||||
.expect("Out of bounds auxiliary input port"),
|
||||
);
|
||||
}
|
||||
(false, false) => {
|
||||
let aux_output_idx = if has_main_output { index - 1 } else { index };
|
||||
let custom_port_name = P::PORT_NAMES
|
||||
.aux_outputs
|
||||
.map(|names| names[aux_output_idx as usize]);
|
||||
if current_bus_config.aux_output_busses.num_busses <= 1 {
|
||||
strlcpy(
|
||||
&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)),
|
||||
);
|
||||
}
|
||||
let aux_output_idx = if has_main_output { index - 1 } else { index } as usize;
|
||||
strlcpy(
|
||||
&mut info.name,
|
||||
¤t_audio_io_layout
|
||||
.aux_output_name(aux_output_idx)
|
||||
.expect("Out of bounds auxiliary output port"),
|
||||
);
|
||||
}
|
||||
};
|
||||
info.flags = if is_main_port {
|
||||
|
@ -3301,12 +3211,12 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
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() {
|
||||
// NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks
|
||||
let mut init_context = wrapper.make_init_context();
|
||||
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
|
||||
// host always restart playback? Check this with a couple of hosts and remove the
|
||||
// duplicate reset if it's not needed.
|
||||
|
|
|
@ -196,15 +196,6 @@ fn run_wrapper<P: Plugin, B: Backend<P>>(backend: B, config: WrapperConfig) -> b
|
|||
|
||||
fn print_error(error: WrapperError) {
|
||||
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 => {
|
||||
nih_error!("The plugin failed to initialize");
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::num::NonZeroU32;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use cpal::{
|
||||
traits::*, Device, InputCallbackInfo, OutputCallbackInfo, Sample, SampleFormat, Stream,
|
||||
|
@ -8,7 +10,7 @@ use rtrb::RingBuffer;
|
|||
|
||||
use super::super::config::WrapperConfig;
|
||||
use super::Backend;
|
||||
use crate::audio_setup::{AuxiliaryIOConfig, BusConfig};
|
||||
use crate::audio_setup::AudioIOLayout;
|
||||
use crate::buffer::Buffer;
|
||||
use crate::context::process::Transport;
|
||||
use crate::midi::{MidiConfig, PluginNoteEvent};
|
||||
|
@ -17,7 +19,7 @@ use crate::plugin::Plugin;
|
|||
/// Uses CPAL for audio and midir for MIDI.
|
||||
pub struct Cpal {
|
||||
config: WrapperConfig,
|
||||
bus_config: BusConfig,
|
||||
audio_io_layout: AudioIOLayout,
|
||||
|
||||
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
|
||||
/// reason.
|
||||
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")?;
|
||||
|
||||
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!(
|
||||
"Audio inputs are not connected automatically to prevent feedback. Use the \
|
||||
'--input-device' option to choose an input device."
|
||||
|
@ -192,16 +195,12 @@ impl Cpal {
|
|||
.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_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
|
||||
.map(|device| -> Result<(Device, StreamConfig, SampleFormat)> {
|
||||
let input_configs: Vec<_> = device
|
||||
|
@ -209,7 +208,7 @@ impl Cpal {
|
|||
.context("Could not get supported audio input configurations")?
|
||||
.filter(|c| match c.buffer_size() {
|
||||
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())
|
||||
.contains(&requested_sample_rate)
|
||||
&& (min..=max).contains(&&config.period_size)
|
||||
|
@ -227,7 +226,7 @@ impl Cpal {
|
|||
format!(
|
||||
"The audio input device does not support {} audio channels at a \
|
||||
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()?;
|
||||
|
||||
let num_output_channels = audio_io_layout
|
||||
.main_output_channels
|
||||
.map(NonZeroU32::get)
|
||||
.unwrap_or_default() as usize;
|
||||
let output_configs: Vec<_> = output_device
|
||||
.supported_output_configs()
|
||||
.context("Could not get supported audio output configurations")?
|
||||
.filter(|c| match c.buffer_size() {
|
||||
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())
|
||||
.contains(&requested_sample_rate)
|
||||
&& (min..=max).contains(&&config.period_size)
|
||||
|
@ -265,7 +268,7 @@ impl Cpal {
|
|||
format!(
|
||||
"The audio output device does not support {} audio channels at a sample rate \
|
||||
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 {
|
||||
|
@ -282,7 +285,7 @@ impl Cpal {
|
|||
|
||||
Ok(Cpal {
|
||||
config,
|
||||
bus_config,
|
||||
audio_io_layout,
|
||||
|
||||
input,
|
||||
|
||||
|
@ -328,12 +331,15 @@ impl Cpal {
|
|||
// 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.
|
||||
// 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
|
||||
// is pretty nasty, come up with a cleaner alternative
|
||||
let mut channels = vec![
|
||||
vec![0.0f32; self.config.period_size as usize];
|
||||
self.bus_config.num_output_channels as usize
|
||||
];
|
||||
// need to live just as long as `buffer` when they get moved into the closure.
|
||||
// FIXME: This is pretty nasty, come up with a cleaner alternative
|
||||
let num_output_channels = self
|
||||
.audio_io_layout
|
||||
.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();
|
||||
unsafe {
|
||||
buffer.set_slices(0, |output_slices| {
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use std::num::NonZeroU32;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use super::super::config::WrapperConfig;
|
||||
use super::Backend;
|
||||
use crate::audio_setup::{AuxiliaryIOConfig, BusConfig};
|
||||
use crate::audio_setup::AudioIOLayout;
|
||||
use crate::buffer::Buffer;
|
||||
use crate::context::process::Transport;
|
||||
use crate::midi::PluginNoteEvent;
|
||||
|
@ -13,7 +14,7 @@ use crate::plugin::Plugin;
|
|||
/// useful for testing plugin GUIs.
|
||||
pub struct Dummy {
|
||||
config: WrapperConfig,
|
||||
bus_config: BusConfig,
|
||||
audio_io_layout: AudioIOLayout,
|
||||
}
|
||||
|
||||
impl<P: Plugin> Backend<P> for Dummy {
|
||||
|
@ -33,10 +34,13 @@ impl<P: Plugin> Backend<P> for Dummy {
|
|||
let interval =
|
||||
Duration::from_secs_f32(self.config.period_size as f32 / self.config.sample_rate);
|
||||
|
||||
let mut channels = vec![
|
||||
vec![0.0f32; self.config.period_size as usize];
|
||||
self.bus_config.num_output_channels as usize
|
||||
];
|
||||
let num_output_channels = self
|
||||
.audio_io_layout
|
||||
.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();
|
||||
unsafe {
|
||||
buffer.set_slices(self.config.period_size as usize, |output_slices| {
|
||||
|
@ -81,13 +85,7 @@ impl<P: Plugin> Backend<P> for Dummy {
|
|||
impl Dummy {
|
||||
pub fn new<P: Plugin>(config: WrapperConfig) -> Self {
|
||||
Self {
|
||||
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(),
|
||||
},
|
||||
audio_io_layout: config.audio_io_layout_or_exit::<P>(),
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use std::borrow::Borrow;
|
||||
use std::num::NonZeroU32;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
@ -18,6 +19,9 @@ use crate::plugin::Plugin;
|
|||
use crate::wrapper::util::{clamp_input_event_timing, clamp_output_event_timing};
|
||||
|
||||
/// Uses JACK audio and MIDI.
|
||||
//
|
||||
// TODO: Use the bus names
|
||||
// TODO: Support auxiliary inputs and outputs
|
||||
pub struct Jack {
|
||||
config: WrapperConfig,
|
||||
/// 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
|
||||
/// standalone should expose JACK MIDI ports.
|
||||
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)
|
||||
.context("Error while initializing the JACK client")?;
|
||||
if !status.is_empty() {
|
||||
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!(
|
||||
"Audio inputs are not connected automatically to prevent feedback. Use the \
|
||||
'--connect-jack-inputs' option to connect the input ports."
|
||||
|
@ -207,7 +212,10 @@ impl Jack {
|
|||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
// above.
|
||||
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 {
|
||||
outputs.push(client.register_port(&format!("output_{port_no}"), AudioOut)?);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
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.
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
|
@ -22,15 +26,14 @@ pub struct WrapperConfig {
|
|||
#[clap(value_parser, long)]
|
||||
pub output_device: Option<String>,
|
||||
|
||||
// These will default to the plugin's default input and output channel count. We could set the
|
||||
// default value here to match those, but that would require a custom Args+FromArgMatches
|
||||
// implementation and access to the `Plugin` type.
|
||||
/// The number of input channels.
|
||||
#[clap(value_parser, short = 'i', long)]
|
||||
pub input_channels: Option<u32>,
|
||||
/// The number of output channels.
|
||||
#[clap(value_parser, short = 'o', long)]
|
||||
pub output_channels: Option<u32>,
|
||||
/// The audio layout to use. Defaults to the first layout.
|
||||
///
|
||||
/// Specifying an empty argument or other invalid value will list all available audio layouts.
|
||||
//
|
||||
// NOTE: This takes a `String` instead of a `usize` so we can list the layouts when the argument
|
||||
// is invalid
|
||||
#[clap(value_parser, short = 'l', long)]
|
||||
pub audio_layout: Option<String>,
|
||||
/// The audio backend's sample rate.
|
||||
///
|
||||
/// 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.
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ use super::backend::Backend;
|
|||
use super::config::WrapperConfig;
|
||||
use super::context::{WrapperGuiContext, WrapperInitContext, WrapperProcessContext};
|
||||
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::process::Transport;
|
||||
use crate::editor::{Editor, ParentWindowHandle};
|
||||
|
@ -67,7 +67,7 @@ pub struct Wrapper<P: Plugin, B: Backend<P>> {
|
|||
param_id_to_ptr: HashMap<String, ParamPtr>,
|
||||
|
||||
/// The bus and buffer configurations are static for the standalone target.
|
||||
bus_config: BusConfig,
|
||||
audio_io_layout: AudioIOLayout,
|
||||
buffer_config: BufferConfig,
|
||||
|
||||
/// 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.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
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.
|
||||
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
|
||||
/// not accept the IO configuration from the wrapper config.
|
||||
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 task_executor = Mutex::new(plugin.task_executor());
|
||||
let params = plugin.params();
|
||||
|
@ -214,11 +214,11 @@ impl<P: Plugin, B: Backend<P>> Wrapper<P, B> {
|
|||
}
|
||||
|
||||
// TODO: Sidechain inputs and auxiliary outputs
|
||||
if P::DEFAULT_AUX_INPUTS.is_some() {
|
||||
nih_log!("Sidechain inputs are not yet supported in this standalone version");
|
||||
if !audio_io_layout.aux_input_ports.is_empty() {
|
||||
nih_warn!("Sidechain inputs are not yet supported in this standalone version");
|
||||
}
|
||||
if P::DEFAULT_AUX_OUTPUTS.is_some() {
|
||||
nih_log!("Auxiliary outputs are not yet supported in this standalone version");
|
||||
if !audio_io_layout.aux_output_ports.is_empty() {
|
||||
nih_warn!("Auxiliary outputs are not yet supported in this standalone version");
|
||||
}
|
||||
|
||||
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))
|
||||
.collect(),
|
||||
|
||||
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: Expose additional sidechain IO in the JACK backend
|
||||
aux_input_busses: AuxiliaryIOConfig::default(),
|
||||
aux_output_busses: AuxiliaryIOConfig::default(),
|
||||
},
|
||||
audio_io_layout,
|
||||
buffer_config: BufferConfig {
|
||||
sample_rate: config.sample_rate,
|
||||
min_buffer_size: None,
|
||||
|
@ -289,24 +283,15 @@ impl<P: Plugin, B: Backend<P>> Wrapper<P, B> {
|
|||
})
|
||||
.map(|editor| Arc::new(Mutex::new(editor)));
|
||||
|
||||
// Right now the IO configuration is fixed in the standalone target, so if the plugin cannot
|
||||
// work with this then we cannot initialize the plugin at all.
|
||||
// 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) };
|
||||
}
|
||||
|
||||
{
|
||||
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(
|
||||
&wrapper.bus_config,
|
||||
&wrapper.audio_io_layout,
|
||||
&wrapper.buffer_config,
|
||||
&mut wrapper.make_init_context(),
|
||||
) {
|
||||
|
@ -558,7 +543,7 @@ impl<P: Plugin, B: Backend<P>> Wrapper<P, B> {
|
|||
// runtime preset loading.
|
||||
permit_alloc(|| {
|
||||
plugin.initialize(
|
||||
&self.bus_config,
|
||||
&self.audio_io_layout,
|
||||
&self.buffer_config,
|
||||
&mut self.make_init_context(),
|
||||
)
|
||||
|
|
|
@ -14,7 +14,7 @@ use super::note_expressions::NoteExpressionController;
|
|||
use super::param_units::ParamUnits;
|
||||
use super::util::{ObjectPtr, VstPtr, VST3_MIDI_PARAMS_END, VST3_MIDI_PARAMS_START};
|
||||
use super::view::WrapperView;
|
||||
use crate::audio_setup::{BufferConfig, BusConfig, ProcessMode};
|
||||
use crate::audio_setup::{AudioIOLayout, BufferConfig, ProcessMode};
|
||||
use crate::buffer::Buffer;
|
||||
use crate::context::gui::AsyncExecutor;
|
||||
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
|
||||
/// `IAudioProcessor::setActive()` has been called with.
|
||||
pub is_processing: AtomicBool,
|
||||
/// The current bus configuration, modified through `IAudioProcessor::setBusArrangements()`.
|
||||
pub current_bus_config: AtomicCell<BusConfig>,
|
||||
/// The current audio IO layout. Modified through `IAudioProcessor::setBusArrangements()` after
|
||||
/// 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.
|
||||
/// Will be set in `IAudioProcessor::setupProcessing()`.
|
||||
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
|
||||
// that, we'll always initialize this configuration even before the host requests a
|
||||
// channel layout.
|
||||
current_bus_config: AtomicCell::new(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(),
|
||||
}),
|
||||
current_audio_io_layout: AtomicCell::new(
|
||||
P::AUDIO_IO_LAYOUTS.first().copied().unwrap_or_default(),
|
||||
),
|
||||
current_buffer_config: AtomicCell::new(None),
|
||||
current_process_mode: AtomicCell::new(ProcessMode::Realtime),
|
||||
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() {
|
||||
// NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks
|
||||
let mut init_context = self.make_init_context();
|
||||
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());
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ use std::borrow::Borrow;
|
|||
use std::cmp;
|
||||
use std::ffi::c_void;
|
||||
use std::mem::{self, MaybeUninit};
|
||||
use std::num::NonZeroU32;
|
||||
use std::ptr;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
|
@ -26,9 +27,7 @@ use super::util::{
|
|||
};
|
||||
use super::util::{VST3_MIDI_CHANNELS, VST3_MIDI_PARAMS_END};
|
||||
use super::view::WrapperView;
|
||||
use crate::audio_setup::{
|
||||
AuxiliaryBuffers, AuxiliaryIOConfig, BufferConfig, BusConfig, ProcessMode,
|
||||
};
|
||||
use crate::audio_setup::{AuxiliaryBuffers, BufferConfig, ProcessMode};
|
||||
use crate::buffer::Buffer;
|
||||
use crate::context::process::Transport;
|
||||
use crate::midi::sysex::SysExMessage;
|
||||
|
@ -89,10 +88,7 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
|
|||
type_: vst3_sys::vst::MediaType,
|
||||
dir: vst3_sys::vst::BusDirection,
|
||||
) -> i32 {
|
||||
// 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 current_audio_io_layout = self.inner.current_audio_io_layout.load();
|
||||
|
||||
// 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
|
||||
|
@ -100,22 +96,25 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
|
|||
x if x == vst3_sys::vst::MediaTypes::kAudio as i32
|
||||
&& dir == vst3_sys::vst::BusDirections::kInput as i32 =>
|
||||
{
|
||||
let main_busses = if P::DEFAULT_INPUT_CHANNELS > 0 { 1 } else { 0 };
|
||||
let aux_busses = P::DEFAULT_AUX_INPUTS.unwrap_or_default().num_busses as i32;
|
||||
let main_busses = if current_audio_io_layout.main_input_channels.is_some() {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let aux_busses = current_audio_io_layout.aux_input_ports.len() as i32;
|
||||
|
||||
main_busses + aux_busses
|
||||
}
|
||||
x if x == vst3_sys::vst::MediaTypes::kAudio as i32
|
||||
&& dir == vst3_sys::vst::BusDirections::kOutput as i32 =>
|
||||
{
|
||||
let main_busses = if P::DEFAULT_OUTPUT_CHANNELS > 0 { 1 } else { 0 };
|
||||
let aux_busses = P::DEFAULT_AUX_OUTPUTS.unwrap_or_default().num_busses as i32;
|
||||
// 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 main_busses = 1;
|
||||
let aux_busses = current_audio_io_layout.aux_output_ports.len() as i32;
|
||||
|
||||
if no_main_audio_io {
|
||||
1
|
||||
} else {
|
||||
main_busses + aux_busses
|
||||
}
|
||||
main_busses + aux_busses
|
||||
}
|
||||
x if x == vst3_sys::vst::MediaTypes::kEvent as i32
|
||||
&& dir == vst3_sys::vst::BusDirections::kInput as i32
|
||||
|
@ -142,13 +141,13 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
|
|||
) -> tresult {
|
||||
check_null_ptr!(info);
|
||||
|
||||
// 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 current_audio_io_layout = self.inner.current_audio_io_layout.load();
|
||||
|
||||
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();
|
||||
|
||||
let info = &mut *info;
|
||||
|
@ -156,87 +155,71 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
|
|||
info.direction = dir;
|
||||
info.flags = vst3_sys::vst::BusFlags::kDefaultActive as u32;
|
||||
|
||||
// This is fun since main IO is optional
|
||||
let bus_config = self.inner.current_bus_config.load();
|
||||
if dir == vst3_sys::vst::BusDirections::kInput as i32 {
|
||||
let aux_inputs_only =
|
||||
P::DEFAULT_INPUT_CHANNELS == 0 && P::DEFAULT_AUX_INPUTS.is_some();
|
||||
let aux_input_start_idx = if aux_inputs_only { 0 } else { 1 };
|
||||
if !aux_inputs_only && index == 0 {
|
||||
info.bus_type = vst3_sys::vst::BusTypes::kMain as i32;
|
||||
info.channel_count = bus_config.num_input_channels as i32;
|
||||
u16strlcpy(&mut info.name, P::PORT_NAMES.main_input.unwrap_or("Input"));
|
||||
let has_main_input = current_audio_io_layout.main_input_channels.is_some();
|
||||
let aux_input_start_idx = if has_main_input { 1 } else { 0 };
|
||||
let aux_input_idx = (index - aux_input_start_idx).max(0) as usize;
|
||||
if index == 0 && has_main_input {
|
||||
info.bus_type = vst3_sys::vst::BusTypes::kMain as i32;
|
||||
info.channel_count =
|
||||
current_audio_io_layout.main_input_channels.unwrap().get() as i32;
|
||||
u16strlcpy(&mut info.name, ¤t_audio_io_layout.main_input_name());
|
||||
|
||||
kResultOk
|
||||
} else if (aux_input_start_idx
|
||||
..(aux_input_start_idx + bus_config.aux_input_busses.num_busses as i32))
|
||||
.contains(&index)
|
||||
{
|
||||
info.bus_type = vst3_sys::vst::BusTypes::kAux as i32;
|
||||
info.channel_count = bus_config.aux_input_busses.num_channels as i32;
|
||||
kResultOk
|
||||
} else if aux_input_idx < current_audio_io_layout.aux_input_ports.len() {
|
||||
info.bus_type = vst3_sys::vst::BusTypes::kAux as i32;
|
||||
info.channel_count =
|
||||
current_audio_io_layout.aux_input_ports[aux_input_idx].get() as i32;
|
||||
u16strlcpy(
|
||||
&mut info.name,
|
||||
¤t_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;
|
||||
let custom_port_name = P::PORT_NAMES
|
||||
.aux_inputs
|
||||
.map(|names| names[aux_input_idx as usize]);
|
||||
if bus_config.aux_input_busses.num_busses <= 1 {
|
||||
u16strlcpy(
|
||||
&mut info.name,
|
||||
custom_port_name.unwrap_or("Sidechain Input"),
|
||||
);
|
||||
} else {
|
||||
u16strlcpy(
|
||||
&mut info.name,
|
||||
custom_port_name
|
||||
.unwrap_or(&format!("Sidechain Input {}", aux_input_idx + 1)),
|
||||
);
|
||||
}
|
||||
kResultOk
|
||||
} else {
|
||||
kInvalidArgument
|
||||
}
|
||||
}
|
||||
(t, d, _)
|
||||
if t == vst3_sys::vst::MediaTypes::kAudio as i32
|
||||
&& d == vst3_sys::vst::BusDirections::kOutput as i32 =>
|
||||
{
|
||||
*info = mem::zeroed();
|
||||
|
||||
kResultOk
|
||||
} else {
|
||||
kInvalidArgument
|
||||
}
|
||||
} 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"),
|
||||
);
|
||||
let info = &mut *info;
|
||||
info.media_type = vst3_sys::vst::MediaTypes::kAudio as i32;
|
||||
info.direction = dir;
|
||||
info.flags = vst3_sys::vst::BusFlags::kDefaultActive as u32;
|
||||
|
||||
kResultOk
|
||||
} else if (aux_output_start_idx
|
||||
..(aux_output_start_idx + bus_config.aux_output_busses.num_busses as i32))
|
||||
.contains(&index)
|
||||
{
|
||||
info.bus_type = vst3_sys::vst::BusTypes::kAux as i32;
|
||||
info.channel_count = bus_config.aux_output_busses.num_channels as i32;
|
||||
// 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 aux_output_start_idx = 1;
|
||||
let aux_output_idx = (index - aux_output_start_idx).max(0) as usize;
|
||||
if index == 0 {
|
||||
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, ¤t_audio_io_layout.main_output_name());
|
||||
|
||||
let aux_output_idx = index - aux_output_start_idx;
|
||||
let custom_port_name = P::PORT_NAMES
|
||||
.aux_outputs
|
||||
.map(|names| names[aux_output_idx as usize]);
|
||||
if bus_config.aux_output_busses.num_busses <= 1 {
|
||||
u16strlcpy(
|
||||
&mut info.name,
|
||||
custom_port_name.unwrap_or("Auxiliary Output"),
|
||||
);
|
||||
} else {
|
||||
u16strlcpy(
|
||||
&mut info.name,
|
||||
custom_port_name
|
||||
.unwrap_or(&format!("Auxiliary Output {}", aux_output_idx + 1)),
|
||||
);
|
||||
}
|
||||
kResultOk
|
||||
} else if aux_output_idx < current_audio_io_layout.aux_output_ports.len() {
|
||||
info.bus_type = vst3_sys::vst::BusTypes::kAux as i32;
|
||||
info.channel_count =
|
||||
current_audio_io_layout.aux_output_ports[aux_output_idx].get() as i32;
|
||||
u16strlcpy(
|
||||
&mut info.name,
|
||||
¤t_audio_io_layout
|
||||
.aux_output_name(aux_output_idx)
|
||||
.expect("Out of bounds auxiliary output port"),
|
||||
);
|
||||
|
||||
kResultOk
|
||||
} else {
|
||||
kInvalidArgument
|
||||
}
|
||||
kResultOk
|
||||
} else {
|
||||
kInvalidArgument
|
||||
}
|
||||
|
@ -284,6 +267,8 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
|
|||
) -> tresult {
|
||||
check_null_ptr!(in_info, out_info);
|
||||
|
||||
let current_audio_io_layout = self.inner.current_audio_io_layout.load();
|
||||
|
||||
*out_info = mem::zeroed();
|
||||
|
||||
let in_info = &*in_info;
|
||||
|
@ -292,8 +277,8 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
|
|||
(t, 0)
|
||||
if t == vst3_sys::vst::MediaTypes::kAudio as i32
|
||||
// We only have an IO pair when the plugin has both a main input and a main output
|
||||
&& P::DEFAULT_INPUT_CHANNELS > 0
|
||||
&& P::DEFAULT_OUTPUT_CHANNELS > 0 =>
|
||||
&& current_audio_io_layout.main_input_channels.is_some()
|
||||
&& current_audio_io_layout.main_output_channels.is_some() =>
|
||||
{
|
||||
out_info.media_type = vst3_sys::vst::MediaTypes::kAudio as i32;
|
||||
out_info.bus_index = in_info.bus_index;
|
||||
|
@ -321,21 +306,27 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
|
|||
type_: vst3_sys::vst::MediaType,
|
||||
dir: vst3_sys::vst::BusDirection,
|
||||
index: i32,
|
||||
_state: vst3_sys::base::TBool,
|
||||
state: vst3_sys::base::TBool,
|
||||
) -> tresult {
|
||||
// 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 current_audio_io_layout = self.inner.current_audio_io_layout.load();
|
||||
|
||||
// We don't support this, and we'll just pretend to support enabling busses (even though
|
||||
// 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) {
|
||||
(t, d, _)
|
||||
if t == vst3_sys::vst::MediaTypes::kAudio as i32
|
||||
&& d == vst3_sys::vst::BusDirections::kInput as i32 =>
|
||||
{
|
||||
let main_busses = if P::DEFAULT_INPUT_CHANNELS > 0 { 1 } else { 0 };
|
||||
let aux_busses = P::DEFAULT_AUX_INPUTS.unwrap_or_default().num_busses as i32;
|
||||
let main_busses = if current_audio_io_layout.main_input_channels.is_some() {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let aux_busses = current_audio_io_layout.aux_input_ports.len() as i32;
|
||||
|
||||
if (0..main_busses + aux_busses).contains(&index) {
|
||||
kResultOk
|
||||
|
@ -347,12 +338,11 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
|
|||
if t == vst3_sys::vst::MediaTypes::kAudio as i32
|
||||
&& d == vst3_sys::vst::BusDirections::kOutput as i32 =>
|
||||
{
|
||||
let main_busses = if P::DEFAULT_OUTPUT_CHANNELS > 0 || no_main_audio_io {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let aux_busses = P::DEFAULT_AUX_OUTPUTS.unwrap_or_default().num_busses as i32;
|
||||
// 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 main_busses = 1;
|
||||
let aux_busses = current_audio_io_layout.aux_output_ports.len() as i32;
|
||||
|
||||
if (0..main_busses + aux_busses).contains(&index) {
|
||||
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
|
||||
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();
|
||||
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()`
|
||||
// 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.
|
||||
|
@ -404,8 +394,13 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
|
|||
.output_buffer
|
||||
.borrow_mut()
|
||||
.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 changed since the last call then we should make sure to
|
||||
// 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
|
||||
// object may have been moved before then.
|
||||
let mut aux_input_storage = self.inner.aux_input_storage.borrow_mut();
|
||||
aux_input_storage
|
||||
.resize_with(bus_config.aux_input_busses.num_busses as usize, Vec::new);
|
||||
for bus_storage in aux_input_storage.iter_mut() {
|
||||
bus_storage.resize_with(
|
||||
bus_config.aux_input_busses.num_channels as usize,
|
||||
Vec::new,
|
||||
);
|
||||
for channel_storage in bus_storage {
|
||||
let mut aux_input_buffers = self.inner.aux_input_buffers.borrow_mut();
|
||||
aux_input_storage.resize_with(audio_io_layout.aux_input_ports.len(), Vec::new);
|
||||
aux_input_buffers
|
||||
.resize_with(audio_io_layout.aux_input_ports.len(), Buffer::default);
|
||||
for ((buffer_storage, buffer), num_channels) in aux_input_storage
|
||||
.iter_mut()
|
||||
.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(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| {
|
||||
channel_slices.resize_with(
|
||||
bus_config.aux_input_busses.num_channels as usize,
|
||||
|| &mut [],
|
||||
);
|
||||
channel_slices.resize_with(num_channels.get() as usize, || &mut []);
|
||||
channel_slices.fill_with(|| &mut []);
|
||||
});
|
||||
}
|
||||
|
||||
// And the same thing for the output buffers
|
||||
let mut aux_output_buffers = self.inner.aux_output_buffers.borrow_mut();
|
||||
aux_output_buffers.resize_with(
|
||||
bus_config.aux_output_busses.num_busses as usize,
|
||||
Buffer::default,
|
||||
);
|
||||
for buffer in aux_output_buffers.iter_mut() {
|
||||
aux_output_buffers
|
||||
.resize_with(audio_io_layout.aux_output_ports.len(), Buffer::default);
|
||||
for (buffer, num_channels) in aux_output_buffers
|
||||
.iter_mut()
|
||||
.zip(audio_io_layout.aux_output_ports.iter())
|
||||
{
|
||||
buffer.set_slices(0, |channel_slices| {
|
||||
channel_slices.resize_with(
|
||||
bus_config.aux_output_busses.num_channels as usize,
|
||||
|| &mut [],
|
||||
);
|
||||
channel_slices.resize_with(num_channels.get() as usize, || &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() {
|
||||
// NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks
|
||||
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();
|
||||
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
|
||||
// host always restart playback? Check this with a couple of hosts and remove the
|
||||
// duplicate reset if it's not needed.
|
||||
|
@ -789,89 +775,80 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
|
|||
) -> tresult {
|
||||
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?
|
||||
if num_ins < 0 || num_outs < 0 {
|
||||
return kInvalidArgument;
|
||||
}
|
||||
|
||||
// Every auxiliary input or output needs to have the same number of channels. In order to
|
||||
// support plugins with no main IO but with auxiliary IO, we'll need to take that into
|
||||
// account when asserting this. If that's the case, then the first bus for that direction
|
||||
// will have been marked auxiliary.
|
||||
let aux_inputs_only = P::DEFAULT_INPUT_CHANNELS == 0 && P::DEFAULT_AUX_INPUTS.is_some();
|
||||
let num_input_channels = if aux_inputs_only || num_ins < 1 {
|
||||
0
|
||||
} else {
|
||||
(*inputs).count_ones()
|
||||
};
|
||||
// NIH-plug no longer supports flexible IO layouts. Instead we'll try to find an audio IO
|
||||
// layout that matches the host's requested layout.
|
||||
let matching_layout = P::AUDIO_IO_LAYOUTS
|
||||
.iter()
|
||||
.find(|layout| {
|
||||
// If the number of ports/busses doesn't match then we can immediately discard the
|
||||
// layout. VST3 doesn't allow for optional switchable ports like CLAP does. Only the
|
||||
// channel counts can change.
|
||||
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 };
|
||||
let num_aux_input_busses = (num_ins as u32).saturating_sub(aux_input_start_idx);
|
||||
let num_aux_input_channels = if num_aux_input_busses == 0 {
|
||||
0
|
||||
} else {
|
||||
(*inputs.offset(aux_input_start_idx as isize)).count_ones()
|
||||
};
|
||||
for i in 1..num_aux_input_busses {
|
||||
if (*inputs.offset((aux_input_start_idx + i) as isize)).count_ones()
|
||||
!= num_aux_input_channels
|
||||
{
|
||||
nih_debug_assert_failure!("Mismatching auxiliary input bus channels set by host");
|
||||
return kResultFalse;
|
||||
// NOTE: We completely ignore the speaker arrangements and only look at the channel
|
||||
// counts here. This may cause issues at some point, but it works for now.
|
||||
let has_main_input = layout.main_input_channels.is_some();
|
||||
let aux_input_start_idx = if has_main_input { 0 } else { 1 };
|
||||
if has_main_input
|
||||
&& (*inputs).count_ones() != layout.main_input_channels.unwrap().get()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
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()
|
||||
!= channel_count.get()
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
None => kResultFalse,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -883,11 +860,6 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
|
|||
) -> tresult {
|
||||
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 {
|
||||
0 => vst3_sys::vst::kEmpty,
|
||||
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 aux_inputs_only = P::DEFAULT_INPUT_CHANNELS == 0 && P::DEFAULT_AUX_INPUTS.is_some();
|
||||
let aux_input_start_idx = if aux_inputs_only { 0 } else { 1 };
|
||||
if !aux_inputs_only && index == 0 {
|
||||
bus_config.num_input_channels
|
||||
} else if (aux_input_start_idx
|
||||
..(aux_input_start_idx + bus_config.aux_input_busses.num_busses as i32))
|
||||
.contains(&index)
|
||||
{
|
||||
bus_config.aux_input_busses.num_channels
|
||||
let has_main_input = current_audio_io_layout.main_input_channels.is_some();
|
||||
let aux_input_start_idx = if has_main_input { 1 } else { 0 };
|
||||
let aux_input_idx = (index - aux_input_start_idx).max(0) as usize;
|
||||
if index == 0 && has_main_input {
|
||||
current_audio_io_layout.main_input_channels.unwrap().get()
|
||||
} else if aux_input_idx < current_audio_io_layout.aux_input_ports.len() {
|
||||
current_audio_io_layout.aux_input_ports[aux_input_idx].get()
|
||||
} else {
|
||||
return kInvalidArgument;
|
||||
}
|
||||
} 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 {
|
||||
bus_config.num_output_channels
|
||||
} else if (aux_output_start_idx
|
||||
..(aux_output_start_idx + bus_config.aux_output_busses.num_busses as i32))
|
||||
.contains(&index)
|
||||
{
|
||||
bus_config.aux_output_busses.num_channels
|
||||
// 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 aux_output_start_idx = 1;
|
||||
let aux_output_idx = (index - aux_output_start_idx).max(0) as usize;
|
||||
if index == 0 {
|
||||
current_audio_io_layout.main_output_channels.unwrap().get()
|
||||
} else if aux_output_idx < current_audio_io_layout.aux_output_ports.len() {
|
||||
current_audio_io_layout.aux_output_ports[aux_output_idx].get()
|
||||
} else {
|
||||
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
|
||||
// uninitialized data when the host assumes that we'll always write something there
|
||||
let current_bus_config = self.inner.current_bus_config.load();
|
||||
let has_main_input = current_bus_config.num_input_channels > 0;
|
||||
// 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;
|
||||
let current_audio_io_layout = self.inner.current_audio_io_layout.load();
|
||||
let has_main_input = current_audio_io_layout.main_input_channels.is_some();
|
||||
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);
|
||||
if !(*host_output).buffers.is_null() {
|
||||
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
|
||||
let mut aux_output_buffers = self.inner.aux_output_buffers.borrow_mut();
|
||||
for (auxiliary_output_idx, buffer) in aux_output_buffers.iter_mut().enumerate() {
|
||||
let host_output_idx = if has_main_output {
|
||||
auxiliary_output_idx as isize + 1
|
||||
} else {
|
||||
auxiliary_output_idx 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.
|
||||
let host_output_idx = auxiliary_output_idx as isize + 1;
|
||||
let host_output = data.outputs.offset(host_output_idx);
|
||||
if host_output_idx >= data.num_outputs as isize
|
||||
|| 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
|
||||
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 mut plugin = self.inner.plugin.lock();
|
||||
// FIXME: This is obviously not realtime-safe, but loading presets without doing
|
||||
// this could lead to inconsistencies. It's the plugin's responsibility to
|
||||
// not perform any realtime-unsafe work when the initialize function is
|
||||
// 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();
|
||||
|
||||
let task_posted = self.inner.schedule_gui(Task::ParameterValuesChanged);
|
||||
|
|
Loading…
Reference in a new issue