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 /// Update the current latency of the plugin. If the plugin is currently processing audio, then
/// this may cause audio playback to be restarted. /// this may cause audio playback to be restarted.
fn set_latency_samples(&self, samples: u32); 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 /// 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. /// this may cause audio playback to be restarted.
fn set_latency_samples(&self, samples: u32); 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 // 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 // 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). // 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 . /// Configuration for the plugin's polyphonic modulation options, if it supports .
pub struct PolyModulationConfig { pub struct PolyModulationConfig {
/// The maximum number of voices this plugin will ever use. Call the context's /// 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 /// `set_current_voice_capacity()` method during initialization or audio processing to set the
/// currently active voices. /// polyphony limit.
pub max_voices: u32, pub max_voice_capacity: u32,
/// If set to `true`, then the host may send note events for the same channel and key, but using /// 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 /// 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. /// 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) { fn set_latency_samples(&self, samples: u32) {
self.wrapper.set_latency_samples(samples) 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> { 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) { fn set_latency_samples(&self, samples: u32) {
self.wrapper.set_latency_samples(samples) 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, clap_audio_ports_config, clap_plugin_audio_ports_config, CLAP_EXT_AUDIO_PORTS_CONFIG,
}; };
use clap_sys::ext::draft::voice_info::{ 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, CLAP_VOICE_INFO_SUPPORTS_OVERLAPPING_NOTES,
}; };
use clap_sys::ext::gui::{ use clap_sys::ext::gui::{
@ -240,6 +240,11 @@ pub struct Wrapper<P: ClapPlugin> {
clap_plugin_tail: clap_plugin_tail, clap_plugin_tail: clap_plugin_tail,
clap_plugin_voice_info: clap_plugin_voice_info, 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 /// 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 /// 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 { pub enum Task {
/// Inform the host that the latency has changed. /// Inform the host that the latency has changed.
LatencyChanged, LatencyChanged,
/// Inform the host that the voice info has changed.
VoiceInfoChanged,
/// Tell the host that it should rescan the current parameter values. /// Tell the host that it should rescan the current parameter values.
RescanParamValues, 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"), 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() { Task::RescanParamValues => match &*self.host_params.borrow() {
Some(host_params) => { Some(host_params) => {
clap_call! { host_params=>rescan(&*self.host_callback, CLAP_PARAM_RESCAN_VALUES) }; 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 { clap_plugin_voice_info: clap_plugin_voice_info {
get: Some(Self::ext_voice_info_get), 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), tasks: ArrayQueue::new(TASK_QUEUE_CAPACITY),
main_thread_id: thread::current().id(), 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 { unsafe extern "C" fn init(plugin: *const clap_plugin) -> bool {
check_null_ptr!(false, plugin); check_null_ptr!(false, plugin);
let wrapper = &*(plugin as *const Self); 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); query_host_extension::<clap_host_latency>(&wrapper.host_callback, CLAP_EXT_LATENCY);
*wrapper.host_params.borrow_mut() = *wrapper.host_params.borrow_mut() =
query_host_extension::<clap_host_params>(&wrapper.host_callback, CLAP_EXT_PARAMS); 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_thread_check.borrow_mut() = query_host_extension::<clap_host_thread_check>(
&wrapper.host_callback, &wrapper.host_callback,
CLAP_EXT_THREAD_CHECK, CLAP_EXT_THREAD_CHECK,
@ -3081,13 +3134,13 @@ impl<P: ClapPlugin> Wrapper<P> {
info: *mut clap_voice_info, info: *mut clap_voice_info,
) -> bool { ) -> bool {
check_null_ptr!(false, plugin, info); 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 { match P::CLAP_POLY_MODULATION_CONFIG {
Some(config) => { Some(config) => {
*info = clap_voice_info { *info = clap_voice_info {
voice_count: config.max_voices, voice_count: wrapper.current_voice_capacity.load(Ordering::Relaxed),
voice_capacity: config.max_voices, voice_capacity: config.max_voice_capacity,
flags: if config.supports_overlapping_voices { flags: if config.supports_overlapping_voices {
CLAP_VOICE_INFO_SUPPORTS_OVERLAPPING_NOTES CLAP_VOICE_INFO_SUPPORTS_OVERLAPPING_NOTES
} else { } else {

View file

@ -90,6 +90,10 @@ impl<P: Plugin, B: Backend> InitContext for WrapperInitContext<'_, P, B> {
fn set_latency_samples(&self, _samples: u32) { fn set_latency_samples(&self, _samples: u32) {
nih_debug_assert_failure!("TODO: WrapperInitContext::set_latency_samples()"); 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> { 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) { fn set_latency_samples(&self, _samples: u32) {
nih_debug_assert_failure!("TODO: WrapperProcessContext::set_latency_samples()"); 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) { fn set_latency_samples(&self, samples: u32) {
self.inner.set_latency_samples(samples) 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> { 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) { fn set_latency_samples(&self, samples: u32) {
self.inner.set_latency_samples(samples) self.inner.set_latency_samples(samples)
} }
fn set_current_voice_capacity(&self, _capacity: u32) {
// This is only supported by CLAP
}
} }