1
0
Fork 0

Allow the plugin to change current voice capacity

This commit is contained in:
Robbert van der Helm 2022-07-05 23:24:05 +02:00
parent f828761677
commit cdee721e9c
6 changed files with 100 additions and 7 deletions

View file

@ -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).

View file

@ -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.

View file

@ -120,6 +120,10 @@ impl<P: ClapPlugin> 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<P: ClapPlugin> ProcessContext for WrapperProcessContext<'_, P> {
@ -142,4 +146,8 @@ impl<P: ClapPlugin> 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)
}
}

View file

@ -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<P: ClapPlugin> {
clap_plugin_tail: clap_plugin_tail,
clap_plugin_voice_info: clap_plugin_voice_info,
host_voice_info: AtomicRefCell<Option<ClapPtr<clap_host_voice_info>>>,
/// 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<P: ClapPlugin> {
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<P: ClapPlugin> MainThreadExecutor<Task> for Wrapper<P> {
}
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<P: ClapPlugin> Wrapper<P> {
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<P: ClapPlugin> Wrapper<P> {
}
}
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<P: ClapPlugin> Wrapper<P> {
query_host_extension::<clap_host_latency>(&wrapper.host_callback, CLAP_EXT_LATENCY);
*wrapper.host_params.borrow_mut() =
query_host_extension::<clap_host_params>(&wrapper.host_callback, CLAP_EXT_PARAMS);
*wrapper.host_voice_info.borrow_mut() = query_host_extension::<clap_host_voice_info>(
&wrapper.host_callback,
CLAP_EXT_VOICE_INFO,
);
*wrapper.host_thread_check.borrow_mut() = query_host_extension::<clap_host_thread_check>(
&wrapper.host_callback,
CLAP_EXT_THREAD_CHECK,
@ -3081,13 +3134,13 @@ impl<P: ClapPlugin> Wrapper<P> {
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 {

View file

@ -90,6 +90,10 @@ impl<P: Plugin, B: Backend> 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<P: Plugin, B: Backend> ProcessContext for WrapperProcessContext<'_, P, B> {
@ -120,4 +124,8 @@ impl<P: Plugin, B: Backend> 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
}
}

View file

@ -124,6 +124,10 @@ impl<P: Vst3Plugin> 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<P: Vst3Plugin> ProcessContext for WrapperProcessContext<'_, P> {
@ -146,4 +150,8 @@ impl<P: Vst3Plugin> 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
}
}