From cdee721e9c21e8173087953efddf3b6369cbfda3 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 5 Jul 2022 23:24:05 +0200 Subject: [PATCH] Allow the plugin to change current voice capacity --- src/context.rs | 16 ++++++++ src/plugin.rs | 6 +-- src/wrapper/clap/context.rs | 8 ++++ src/wrapper/clap/wrapper.rs | 61 +++++++++++++++++++++++++++++-- src/wrapper/standalone/context.rs | 8 ++++ src/wrapper/vst3/context.rs | 8 ++++ 6 files changed, 100 insertions(+), 7 deletions(-) diff --git a/src/context.rs b/src/context.rs index 4b6c37d9..730ddfdf 100644 --- a/src/context.rs +++ b/src/context.rs @@ -21,6 +21,14 @@ pub trait InitContext { /// Update the current latency of the plugin. If the plugin is currently processing audio, then /// this may cause audio playback to be restarted. fn set_latency_samples(&self, samples: u32); + + /// Set the current voice **capacity** for this plugin (so not the number of currently active + /// voices). This may only be called if + /// [`ClapPlugin::CLAP_POLY_MODULATION_CONFIG`][crate::prelude::ClapPlugin::CLAP_POLY_MODULATION_CONFIG] + /// is set. `capacity` must be between 1 and the configured maximum capacity. Changing this at + /// runtime allows the host to better optimize polyphonic modulation, or to switch to strictly + /// monophonic modulation when dropping the capacity down to 1. + fn set_current_voice_capacity(&self, capacity: u32); } /// Contains both context data and callbacks the plugin can use during processing. Most notably this @@ -82,6 +90,14 @@ pub trait ProcessContext { /// this may cause audio playback to be restarted. fn set_latency_samples(&self, samples: u32); + /// Set the current voice **capacity** for this plugin (so not the number of currently active + /// voices). This may only be called if + /// [`ClapPlugin::CLAP_POLY_MODULATION_CONFIG`][crate::prelude::ClapPlugin::CLAP_POLY_MODULATION_CONFIG] + /// is set. `capacity` must be between 1 and the configured maximum capacity. Changing this at + /// runtime allows the host to better optimize polyphonic modulation, or to switch to strictly + /// monophonic modulation when dropping the capacity down to 1. + fn set_current_voice_capacity(&self, capacity: u32); + // TODO: Add this, this works similar to [GuiContext::set_parameter] but it adds the parameter // change to a queue (or directly to the VST3 plugin's parameter output queues) instead of // using main thread host automation (and all the locks involved there). diff --git a/src/plugin.rs b/src/plugin.rs index 05207bad..bf3b2c82 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -426,9 +426,9 @@ pub enum ProcessMode { /// Configuration for the plugin's polyphonic modulation options, if it supports . pub struct PolyModulationConfig { /// The maximum number of voices this plugin will ever use. Call the context's - /// `set_current_voices()` method during initialization or audio processing to set the number of - /// currently active voices. - pub max_voices: u32, + /// `set_current_voice_capacity()` method during initialization or audio processing to set the + /// polyphony limit. + pub max_voice_capacity: u32, /// If set to `true`, then the host may send note events for the same channel and key, but using /// different voice IDs. Bitwig Studio, for instance, can use this to do voice stacking. After /// enabling this, you should always prioritize using voice IDs to map note events to voices. diff --git a/src/wrapper/clap/context.rs b/src/wrapper/clap/context.rs index 42c6ad79..0e54d415 100644 --- a/src/wrapper/clap/context.rs +++ b/src/wrapper/clap/context.rs @@ -120,6 +120,10 @@ impl InitContext for WrapperInitContext<'_, P> { fn set_latency_samples(&self, samples: u32) { self.wrapper.set_latency_samples(samples) } + + fn set_current_voice_capacity(&self, capacity: u32) { + self.wrapper.set_current_voice_capacity(capacity) + } } impl ProcessContext for WrapperProcessContext<'_, P> { @@ -142,4 +146,8 @@ impl ProcessContext for WrapperProcessContext<'_, P> { fn set_latency_samples(&self, samples: u32) { self.wrapper.set_latency_samples(samples) } + + fn set_current_voice_capacity(&self, capacity: u32) { + self.wrapper.set_current_voice_capacity(capacity) + } } diff --git a/src/wrapper/clap/wrapper.rs b/src/wrapper/clap/wrapper.rs index 59f0c352..c7c53ea4 100644 --- a/src/wrapper/clap/wrapper.rs +++ b/src/wrapper/clap/wrapper.rs @@ -22,7 +22,7 @@ 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::draft::voice_info::{ - clap_plugin_voice_info, clap_voice_info, CLAP_EXT_VOICE_INFO, + clap_host_voice_info, clap_plugin_voice_info, clap_voice_info, CLAP_EXT_VOICE_INFO, CLAP_VOICE_INFO_SUPPORTS_OVERLAPPING_NOTES, }; use clap_sys::ext::gui::{ @@ -240,6 +240,11 @@ pub struct Wrapper { clap_plugin_tail: clap_plugin_tail, clap_plugin_voice_info: clap_plugin_voice_info, + host_voice_info: AtomicRefCell>>, + /// If `P::CLAP_POLY_MODULATION_CONFIG` is set, then the plugin can configure the current number + /// of active voices using a context method called from the initialization or processing + /// context. This defaults to the maximum number of voices. + current_voice_capacity: AtomicU32, /// A queue of tasks that still need to be performed. Because CLAP lets the plugin request a /// host callback directly, we don't need to use the OsEventLoop we use in our other plugin @@ -261,6 +266,8 @@ pub struct Wrapper { pub enum Task { /// Inform the host that the latency has changed. LatencyChanged, + /// Inform the host that the voice info has changed. + VoiceInfoChanged, /// Tell the host that it should rescan the current parameter values. RescanParamValues, } @@ -351,6 +358,12 @@ impl MainThreadExecutor for Wrapper

{ } None => nih_debug_assert_failure!("Host does not support the latency extension"), }, + Task::VoiceInfoChanged => match &*self.host_voice_info.borrow() { + Some(host_voice_info) => { + clap_call! { host_voice_info=>changed(&*self.host_callback) }; + } + None => nih_debug_assert_failure!("Host does not support the voice-info extension"), + }, Task::RescanParamValues => match &*self.host_params.borrow() { Some(host_params) => { clap_call! { host_params=>rescan(&*self.host_callback, CLAP_PARAM_RESCAN_VALUES) }; @@ -616,6 +629,18 @@ impl Wrapper

{ clap_plugin_voice_info: clap_plugin_voice_info { get: Some(Self::ext_voice_info_get), }, + host_voice_info: AtomicRefCell::new(None), + current_voice_capacity: AtomicU32::new( + P::CLAP_POLY_MODULATION_CONFIG + .map(|c| { + nih_debug_assert!( + c.max_voice_capacity >= 1, + "The maximum voice capacity cannot be zero" + ); + c.max_voice_capacity + }) + .unwrap_or(1), + ), tasks: ArrayQueue::new(TASK_QUEUE_CAPACITY), main_thread_id: thread::current().id(), @@ -1577,6 +1602,30 @@ impl Wrapper

{ } } + pub fn set_current_voice_capacity(&self, capacity: u32) { + match P::CLAP_POLY_MODULATION_CONFIG { + Some(config) => { + let clamped_capacity = capacity.clamp(1, config.max_voice_capacity); + nih_debug_assert_eq!( + capacity, + clamped_capacity, + "The current voice capacity must be between 1 and the maximum capacity" + ); + + if clamped_capacity != self.current_voice_capacity.load(Ordering::Relaxed) { + self.current_voice_capacity + .store(clamped_capacity, Ordering::Relaxed); + let task_posted = self.do_maybe_async(Task::VoiceInfoChanged); + nih_debug_assert!(task_posted, "The task queue is full, dropping task..."); + } + } + None => nih_debug_assert_failure!( + "Configuring the current voice capacity is only possible when \ + 'ClapPlugin::CLAP_POLY_MODULATION_CONFIG' is set" + ), + } + } + unsafe extern "C" fn init(plugin: *const clap_plugin) -> bool { check_null_ptr!(false, plugin); let wrapper = &*(plugin as *const Self); @@ -1588,6 +1637,10 @@ impl Wrapper

{ query_host_extension::(&wrapper.host_callback, CLAP_EXT_LATENCY); *wrapper.host_params.borrow_mut() = query_host_extension::(&wrapper.host_callback, CLAP_EXT_PARAMS); + *wrapper.host_voice_info.borrow_mut() = query_host_extension::( + &wrapper.host_callback, + CLAP_EXT_VOICE_INFO, + ); *wrapper.host_thread_check.borrow_mut() = query_host_extension::( &wrapper.host_callback, CLAP_EXT_THREAD_CHECK, @@ -3081,13 +3134,13 @@ impl Wrapper

{ info: *mut clap_voice_info, ) -> bool { check_null_ptr!(false, plugin, info); + let wrapper = &*(plugin as *const Self); - // TODO: Allow the plugin to set the active voice count match P::CLAP_POLY_MODULATION_CONFIG { Some(config) => { *info = clap_voice_info { - voice_count: config.max_voices, - voice_capacity: config.max_voices, + voice_count: wrapper.current_voice_capacity.load(Ordering::Relaxed), + voice_capacity: config.max_voice_capacity, flags: if config.supports_overlapping_voices { CLAP_VOICE_INFO_SUPPORTS_OVERLAPPING_NOTES } else { diff --git a/src/wrapper/standalone/context.rs b/src/wrapper/standalone/context.rs index 565a6334..1086993c 100644 --- a/src/wrapper/standalone/context.rs +++ b/src/wrapper/standalone/context.rs @@ -90,6 +90,10 @@ impl InitContext for WrapperInitContext<'_, P, B> { fn set_latency_samples(&self, _samples: u32) { nih_debug_assert_failure!("TODO: WrapperInitContext::set_latency_samples()"); } + + fn set_current_voice_capacity(&self, _capacity: u32) { + // This is only supported by CLAP + } } impl ProcessContext for WrapperProcessContext<'_, P, B> { @@ -120,4 +124,8 @@ impl ProcessContext for WrapperProcessContext<'_, P, B> { fn set_latency_samples(&self, _samples: u32) { nih_debug_assert_failure!("TODO: WrapperProcessContext::set_latency_samples()"); } + + fn set_current_voice_capacity(&self, _capacity: u32) { + // This is only supported by CLAP + } } diff --git a/src/wrapper/vst3/context.rs b/src/wrapper/vst3/context.rs index ecf3d6f6..166a80fb 100644 --- a/src/wrapper/vst3/context.rs +++ b/src/wrapper/vst3/context.rs @@ -124,6 +124,10 @@ impl InitContext for WrapperInitContext<'_, P> { fn set_latency_samples(&self, samples: u32) { self.inner.set_latency_samples(samples) } + + fn set_current_voice_capacity(&self, _capacity: u32) { + // This is only supported by CLAP + } } impl ProcessContext for WrapperProcessContext<'_, P> { @@ -146,4 +150,8 @@ impl ProcessContext for WrapperProcessContext<'_, P> { fn set_latency_samples(&self, samples: u32) { self.inner.set_latency_samples(samples) } + + fn set_current_voice_capacity(&self, _capacity: u32) { + // This is only supported by CLAP + } }