From 70c81642a563da1362006fd117798606d3a5819c Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 2 Mar 2022 14:57:30 +0100 Subject: [PATCH] Implement CLAP audio ports config extension --- src/wrapper/clap/plugin.rs | 155 ++++++++++++++++++++++++++++++++++++- 1 file changed, 153 insertions(+), 2 deletions(-) diff --git a/src/wrapper/clap/plugin.rs b/src/wrapper/clap/plugin.rs index 7aa41b96..27b3bfc2 100644 --- a/src/wrapper/clap/plugin.rs +++ b/src/wrapper/clap/plugin.rs @@ -8,6 +8,10 @@ use clap_sys::events::{ CLAP_EVENT_NOTE_EXPRESSION, CLAP_EVENT_NOTE_OFF, CLAP_EVENT_NOTE_ON, CLAP_EVENT_PARAM_MOD, CLAP_EVENT_PARAM_VALUE, }; +use clap_sys::ext::audio_ports::{CLAP_PORT_MONO, CLAP_PORT_STEREO}; +use clap_sys::ext::audio_ports_config::{ + clap_audio_ports_config, clap_plugin_audio_ports_config, CLAP_EXT_AUDIO_PORTS_CONFIG, +}; use clap_sys::ext::params::{ clap_param_info, clap_plugin_params, CLAP_EXT_PARAMS, CLAP_PARAM_IS_BYPASS, CLAP_PARAM_IS_STEPPED, @@ -40,6 +44,7 @@ use crate::event_loop::{EventLoop, MainThreadExecutor, TASK_QUEUE_CAPACITY}; use crate::param::internals::ParamPtr; use crate::plugin::{BufferConfig, BusConfig, ClapPlugin, NoteEvent, ProcessStatus}; use crate::wrapper::util::{hash_param_id, process_wrapper, strlcpy}; +use crate::Plugin; /// Right now the wrapper adds its own bypass parameter. /// @@ -90,6 +95,16 @@ pub struct Wrapper { /// this. plugin_descriptor: Box>, + 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 taht + supported_bus_configs: Vec, + clap_plugin_params: clap_plugin_params, // These fiels are exactly the same as their VST3 wrapper counterparts. // @@ -227,6 +242,13 @@ impl Wrapper

{ plugin_descriptor, + clap_plugin_audio_ports_config: clap_plugin_audio_ports_config { + count: Self::ext_audio_ports_config_count, + get: Self::ext_audio_ports_config_get, + select: Self::ext_audio_ports_config_select, + }, + supported_bus_configs: Vec::new(), + clap_plugin_params: clap_plugin_params { count: Self::ext_params_count, get_info: Self::ext_params_get_info, @@ -245,6 +267,35 @@ impl Wrapper

{ main_thread_id: thread::current().id(), }; + // Query all sensible bus configurations supported by the plugin. We don't do surround or + // anything beyond stereo right now. + for num_output_channels in [1, 2] { + for num_input_channels in [0, num_output_channels] { + let bus_config = BusConfig { + num_input_channels, + num_output_channels, + }; + if wrapper.plugin.read().accepts_bus_config(&bus_config) { + wrapper.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_NUM_INPUTS, + num_output_channels: P::DEFAULT_NUM_OUTPUTS, + }; + if !wrapper.supported_bus_configs.contains(&default_bus_config) + && wrapper + .plugin + .read() + .accepts_bus_config(&default_bus_config) + { + wrapper.supported_bus_configs.push(default_bus_config); + } + // This is a mapping from the parameter IDs specified by the plugin to pointers to thsoe // parameters. Since the object returned by `params()` is pinned, these pointers are safe to // dereference as long as `wrapper.plugin` is alive @@ -437,6 +488,7 @@ impl Wrapper

{ _min_frames_count: u32, max_frames_count: u32, ) -> bool { + check_null_ptr!(false, plugin); let wrapper = &*(plugin as *const Self); let bus_config = wrapper.current_bus_config.load(); @@ -605,16 +657,25 @@ impl Wrapper

{ check_null_ptr!(ptr::null(), plugin, id); let wrapper = &*(plugin as *const Self); - // TODO: Implement the other useful extensions. Like uh audio inputs. + // TODO: Implement the following extensions: + // - audio ports (for naming audio ports) + // - gui + // - the non-freestanding GUI extensions depending on the platform + // - latency + // - state let id = CStr::from_ptr(id); - if id == CStr::from_ptr(CLAP_EXT_PARAMS) { + if id == CStr::from_ptr(CLAP_EXT_AUDIO_PORTS_CONFIG) { + &wrapper.clap_plugin_audio_ports_config as *const _ as *const c_void + } else if id == CStr::from_ptr(CLAP_EXT_PARAMS) { &wrapper.clap_plugin_params as *const _ as *const c_void } else { + nih_log!("Host tried to query unknown extension '{:?}'", id); ptr::null() } } unsafe extern "C" fn on_main_thread(plugin: *const clap_plugin) { + check_null_ptr!((), plugin); let wrapper = &*(plugin as *const Self); // [Self::do_maybe_async] posts a task to the queue and asks the host to call this function @@ -624,6 +685,96 @@ impl Wrapper

{ } } + unsafe extern "C" fn ext_audio_ports_config_count(plugin: *const clap_plugin) -> u32 { + // TODO: Implement sidechain nputs and auxiliary outputs + check_null_ptr!(0, plugin); + let wrapper = &*(plugin as *const Self); + + wrapper.supported_bus_configs.len() as u32 + } + + unsafe extern "C" fn ext_audio_ports_config_get( + plugin: *const clap_plugin, + index: u32, + config: *mut clap_audio_ports_config, + ) -> bool { + check_null_ptr!(false, plugin, config); + let wrapper = &*(plugin as *const Self); + + match wrapper.supported_bus_configs.get(index as usize) { + Some(bus_config) => { + 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, + 2 => CLAP_PORT_STEREO, + _ => ptr::null(), + }; + let output_port_type = match bus_config.num_output_channels { + 1 => CLAP_PORT_MONO, + 2 => CLAP_PORT_STEREO, + _ => ptr::null(), + }; + + *config = std::mem::zeroed(); + let config = &mut *config; + config.id = index; + strlcpy(&mut config.name, &name); + config.input_channel_count = bus_config.num_input_channels; + config.input_port_type = input_port_type; + config.output_channel_count = bus_config.num_output_channels; + config.output_port_type = output_port_type; + + true + } + None => { + nih_debug_assert_failure!( + "Host tried to query out of bounds audio port config {}", + index + ); + + false + } + } + } + + unsafe extern "C" fn ext_audio_ports_config_select( + plugin: *const clap_plugin, + config_id: clap_id, + ) -> bool { + check_null_ptr!(false, plugin); + let wrapper = &*(plugin 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); + + true + } + None => { + nih_debug_assert_failure!( + "Host tried to select out of bounds audio port config {}", + config_id + ); + + false + } + } + } + unsafe extern "C" fn ext_params_count(plugin: *const clap_plugin) -> u32 { check_null_ptr!(0, plugin); let wrapper = &*(plugin as *const Self);