1
0
Fork 0

Send individual parameter changes for VST3 plugins

This commit is contained in:
Robbert van der Helm 2023-01-11 16:59:21 +01:00
parent adb49cb6d1
commit e5a26ac199
7 changed files with 55 additions and 49 deletions

View file

@ -475,7 +475,7 @@ enum NestedParams {
field: syn::Ident, field: syn::Ident,
group: Option<syn::LitStr>, group: Option<syn::LitStr>,
}, },
/// The nested struct's parameters will get an ID prefix. The original parmaeter with ID `foo` /// The nested struct's parameters will get an ID prefix. The original parameter with ID `foo`
/// will become `{id_prefix}_foo`. /// will become `{id_prefix}_foo`.
Prefixed { Prefixed {
field: syn::Ident, field: syn::Ident,
@ -492,7 +492,7 @@ enum NestedParams {
} }
impl NestedParams { impl NestedParams {
/// Constrruct an iterator that iterates over all parmaeters of a nested parameter object. This /// Constrruct an iterator that iterates over all parameters of a nested parameter object. This
/// takes ID prefixes and suffixes into account, and prefixes the group to the parameter's /// takes ID prefixes and suffixes into account, and prefixes the group to the parameter's
/// existing groups if the `group` attribute on the `#[nested]` macro was specified. /// existing groups if the `group` attribute on the `#[nested]` macro was specified.
fn param_map_tokens(&self) -> proc_macro2::TokenStream { fn param_map_tokens(&self) -> proc_macro2::TokenStream {

View file

@ -26,7 +26,7 @@ use crate::params;
use crate::spectrum::SpectrumOutput; use crate::spectrum::SpectrumOutput;
/// A very abstract spectrum analyzer. This draws the magnitude spectrum's bins as vertical lines /// A very abstract spectrum analyzer. This draws the magnitude spectrum's bins as vertical lines
/// with the same distirubtion as the filter frequency parmaeter.. /// with the same distirubtion as the filter frequency parameter..
pub struct SpectrumAnalyzer { pub struct SpectrumAnalyzer {
spectrum: Arc<Mutex<SpectrumOutput>>, spectrum: Arc<Mutex<SpectrumOutput>>,
sample_rate: Arc<AtomicF32>, sample_rate: Arc<AtomicF32>,

View file

@ -30,7 +30,7 @@ const GRANULAR_DRAG_MULTIPLIER: f32 = 0.1;
const HANDLE_WIDTH_PX: f32 = 20.0; const HANDLE_WIDTH_PX: f32 = 20.0;
/// An X-Y pad that controlers two parameters at the same time by binding them to one of the two /// An X-Y pad that controlers two parameters at the same time by binding them to one of the two
/// axes. This specific implementation has a tooltip for the X-axis parmaeter and allows /// axes. This specific implementation has a tooltip for the X-axis parameter and allows
/// Alt+clicking to enter a specific value. /// Alt+clicking to enter a specific value.
/// ///
/// The x-parameter's range is restricted when safe mode is enabled. See `RestrictedParamSlider` for /// The x-parameter's range is restricted when safe mode is enabled. See `RestrictedParamSlider` for
@ -309,7 +309,7 @@ impl XyPad {
/// Should be called at the start of a drag operation. /// Should be called at the start of a drag operation.
fn begin_set_parameters(&self, cx: &mut EventContext) { fn begin_set_parameters(&self, cx: &mut EventContext) {
// NOTE: Since the X-parameter is the main parmaeter, we'll always modify this parameter // NOTE: Since the X-parameter is the main parameter, we'll always modify this parameter
// last so the host will keep this parameter highlighted // last so the host will keep this parameter highlighted
self.y_param_base.begin_set_parameter(cx); self.y_param_base.begin_set_parameter(cx);
self.x_param_base.begin_set_parameter(cx); self.x_param_base.begin_set_parameter(cx);

View file

@ -215,7 +215,7 @@ pub struct Wrapper<P: ClapPlugin> {
/// parameters belonging to the plugin. These addresses will remain stable as long as the /// parameters belonging to the plugin. These addresses will remain stable as long as the
/// `params` object does not get deallocated. /// `params` object does not get deallocated.
param_by_hash: HashMap<u32, ParamPtr>, param_by_hash: HashMap<u32, ParamPtr>,
/// Mappings from parmaeter hashes to string parameter IDs. Used for notifying the plugin's /// Mappings from parameter hashes to string parameter IDs. Used for notifying the plugin's
/// editor about parameter changes. /// editor about parameter changes.
param_id_by_hash: HashMap<u32, String>, param_id_by_hash: HashMap<u32, String>,
/// The group name of a parameter, indexed by the parameter's hash. Nested groups are delimited /// The group name of a parameter, indexed by the parameter's hash. Nested groups are delimited

View file

@ -166,7 +166,6 @@ impl<P: Vst3Plugin> GuiContext for WrapperGuiContext<P> {
.load() .load()
.map(|c| c.sample_rate), .map(|c| c.sample_rate),
); );
self.inner.notify_param_values_changed();
} }
handler.perform_edit(*hash, normalized as f64); handler.perform_edit(*hash, normalized as f64);

View file

@ -139,6 +139,9 @@ pub(crate) struct WrapperInner<P: Vst3Plugin> {
/// parameters belonging to the plugin. These addresses will remain stable as long as the /// parameters belonging to the plugin. These addresses will remain stable as long as the
/// `params` object does not get deallocated. /// `params` object does not get deallocated.
pub param_by_hash: HashMap<u32, ParamPtr>, pub param_by_hash: HashMap<u32, ParamPtr>,
/// Mappings from parameter hashes to string parameter IDs. Used for notifying the plugin's
/// editor about parameter changes.
pub param_id_by_hash: HashMap<u32, String>,
pub param_units: ParamUnits, pub param_units: ParamUnits,
/// Mappings from string parameter identifiers to parameter hashes. Useful for debug logging /// Mappings from string parameter identifiers to parameter hashes. Useful for debug logging
/// and when storing and restoring plugin state. /// and when storing and restoring plugin state.
@ -157,6 +160,11 @@ pub(crate) struct WrapperInner<P: Vst3Plugin> {
pub enum Task<P: Plugin> { pub enum Task<P: Plugin> {
/// Execute one of the plugin's background tasks. /// Execute one of the plugin's background tasks.
PluginTask(P::BackgroundTask), PluginTask(P::BackgroundTask),
/// Inform the plugin that one or more parameter values have changed.
ParameterValuesChanged,
/// Inform the plugin that one parameter's value has changed. This uses the parameter hashes
/// since the task will be created from the audio thread.
ParameterValueChanged(u32, f32),
/// Trigger a restart with the given restart flags. This is a bit set of the flags from /// Trigger a restart with the given restart flags. This is a bit set of the flags from
/// [`vst3_sys::vst::RestartFlags`]. /// [`vst3_sys::vst::RestartFlags`].
TriggerRestart(i32), TriggerRestart(i32),
@ -264,6 +272,10 @@ impl<P: Vst3Plugin> WrapperInner<P> {
.iter() .iter()
.map(|(_, hash, ptr, _)| (*hash, *ptr)) .map(|(_, hash, ptr, _)| (*hash, *ptr))
.collect(); .collect();
let param_id_by_hash = param_id_hashes_ptrs_groups
.iter()
.map(|(id, hash, _, _)| (*hash, id.clone()))
.collect();
let param_units = ParamUnits::from_param_groups( let param_units = ParamUnits::from_param_groups(
param_id_hashes_ptrs_groups param_id_hashes_ptrs_groups
.iter() .iter()
@ -320,6 +332,7 @@ impl<P: Vst3Plugin> WrapperInner<P> {
param_hashes, param_hashes,
param_by_hash, param_by_hash,
param_id_by_hash,
param_units, param_units,
param_id_to_hash, param_id_to_hash,
param_ptr_to_hash, param_ptr_to_hash,
@ -420,23 +433,6 @@ impl<P: Vst3Plugin> WrapperInner<P> {
} }
} }
/// If there's an editor open, let it know that parameter values have changed. This should be
/// called whenever there's been a call or multiple calls to
/// [`set_normalized_value_by_hash()[Self::set_normalized_value_by_hash()`]. In the off-chance
/// that the editor instance is currently locked then nothing will happen, and the request can
/// safely be ignored.
pub fn notify_param_values_changed(&self) {
if let Some(editor) = self.editor.borrow().as_ref() {
match editor.try_lock() {
Some(editor) => editor.param_values_changed(),
None => nih_debug_assert_failure!(
"The editor was locked when sending a parameter value change notification, \
ignoring"
),
}
}
}
/// Convenience function for setting a value for a parameter as triggered by a VST3 parameter /// Convenience function for setting a value for a parameter as triggered by a VST3 parameter
/// update. The same rate is for updating parameter smoothing. /// update. The same rate is for updating parameter smoothing.
/// ///
@ -461,6 +457,10 @@ impl<P: Vst3Plugin> WrapperInner<P> {
_ => unsafe { param_ptr.set_normalized_value(normalized_value) }, _ => unsafe { param_ptr.set_normalized_value(normalized_value) },
} }
let task_posted =
self.schedule_gui(Task::ParameterValueChanged(hash, normalized_value));
nih_debug_assert!(task_posted, "The task queue is full, dropping task...");
kResultOk kResultOk
} }
_ => kInvalidArgument, _ => kInvalidArgument,
@ -524,7 +524,6 @@ impl<P: Vst3Plugin> WrapperInner<P> {
); );
} }
self.notify_param_values_changed();
let bus_config = self.current_bus_config.load(); let bus_config = self.current_bus_config.load();
if let Some(buffer_config) = self.current_buffer_config.load() { if let Some(buffer_config) = self.current_buffer_config.load() {
// NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks // NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks
@ -534,6 +533,9 @@ impl<P: Vst3Plugin> WrapperInner<P> {
process_wrapper(|| plugin.reset()); process_wrapper(|| plugin.reset());
} }
let task_posted = self.schedule_gui(Task::ParameterValuesChanged);
nih_debug_assert!(task_posted, "The task queue is full, dropping task...");
break; break;
} }
} }
@ -566,6 +568,23 @@ impl<P: Vst3Plugin> MainThreadExecutor<Task<P>> for WrapperInner<P> {
// This function is always called from the main thread // This function is always called from the main thread
match task { match task {
Task::PluginTask(task) => (self.task_executor.lock())(task), Task::PluginTask(task) => (self.task_executor.lock())(task),
Task::ParameterValuesChanged => {
if self.plug_view.read().is_some() {
if let Some(editor) = self.editor.borrow().as_ref() {
editor.lock().param_values_changed();
}
}
}
Task::ParameterValueChanged(param_hash, normalized_value) => {
if self.plug_view.read().is_some() {
if let Some(editor) = self.editor.borrow().as_ref() {
let param_id = &self.param_id_by_hash[&param_hash];
editor
.lock()
.param_value_changed(param_id, normalized_value);
}
}
}
Task::TriggerRestart(flags) => match &*self.component_handler.borrow() { Task::TriggerRestart(flags) => match &*self.component_handler.borrow() {
Some(handler) => unsafe { Some(handler) => unsafe {
nih_debug_assert!(is_gui_thread); nih_debug_assert!(is_gui_thread);

View file

@ -18,10 +18,12 @@ use vst3_sys::vst::{
use vst3_sys::VST3; use vst3_sys::VST3;
use widestring::U16CStr; use widestring::U16CStr;
use super::inner::WrapperInner; use super::inner::{ProcessEvent, Task, WrapperInner};
use super::note_expressions::{self, NoteExpressionController};
use super::util::{ use super::util::{
u16strlcpy, VstPtr, VST3_MIDI_CCS, VST3_MIDI_NUM_PARAMS, VST3_MIDI_PARAMS_START, u16strlcpy, VstPtr, VST3_MIDI_CCS, VST3_MIDI_NUM_PARAMS, VST3_MIDI_PARAMS_START,
}; };
use super::util::{VST3_MIDI_CHANNELS, VST3_MIDI_PARAMS_END};
use super::view::WrapperView; use super::view::WrapperView;
use crate::buffer::Buffer; use crate::buffer::Buffer;
use crate::context::process::Transport; use crate::context::process::Transport;
@ -34,9 +36,6 @@ use crate::plugin::{
use crate::util::permit_alloc; use crate::util::permit_alloc;
use crate::wrapper::state; use crate::wrapper::state;
use crate::wrapper::util::process_wrapper; use crate::wrapper::util::process_wrapper;
use crate::wrapper::vst3::inner::ProcessEvent;
use crate::wrapper::vst3::note_expressions::{self, NoteExpressionController};
use crate::wrapper::vst3::util::{VST3_MIDI_CHANNELS, VST3_MIDI_PARAMS_END};
// Alias needed for the VST3 attribute macro // Alias needed for the VST3 attribute macro
use vst3_sys as vst3_com; use vst3_sys as vst3_com;
@ -512,9 +511,6 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
return kResultFalse; return kResultFalse;
} }
// Reinitialize the plugin after loading state so it can respond to the new parameter values
self.inner.notify_param_values_changed();
if let Some(buffer_config) = self.inner.current_buffer_config.load() { if let Some(buffer_config) = self.inner.current_buffer_config.load() {
// NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks // NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks
let mut init_context = self.inner.make_init_context(); let mut init_context = self.inner.make_init_context();
@ -527,6 +523,10 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
process_wrapper(|| plugin.reset()); process_wrapper(|| plugin.reset());
} }
// Reinitialize the plugin after loading state so it can respond to the new parameter values
let task_posted = self.inner.schedule_gui(Task::ParameterValuesChanged);
nih_debug_assert!(task_posted, "The task queue is full, dropping task...");
nih_trace!("Loaded state ({} bytes)", read_buffer.len()); nih_trace!("Loaded state ({} bytes)", read_buffer.len());
kResultOk kResultOk
@ -747,12 +747,8 @@ impl<P: Vst3Plugin> IEditController for Wrapper<P> {
.current_buffer_config .current_buffer_config
.load() .load()
.map(|c| c.sample_rate); .map(|c| c.sample_rate);
let result = self self.inner
.inner .set_normalized_value_by_hash(id, value as f32, sample_rate)
.set_normalized_value_by_hash(id, value as f32, sample_rate);
self.inner.notify_param_values_changed();
result
} }
unsafe fn set_component_handler( unsafe fn set_component_handler(
@ -1073,7 +1069,6 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
// messages are sent through parameter changes. This vector gets sorted at the end so we // messages are sent through parameter changes. This vector gets sorted at the end so we
// can treat it as a sort of queue. // can treat it as a sort of queue.
let mut process_events = self.inner.process_events.borrow_mut(); let mut process_events = self.inner.process_events.borrow_mut();
let mut parameter_values_changed = false;
process_events.clear(); process_events.clear();
// First we'll go through the parameter changes. This may also include MIDI CC messages // First we'll go through the parameter changes. This may also include MIDI CC messages
@ -1152,7 +1147,6 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
value, value,
Some(sample_rate), Some(sample_rate),
); );
parameter_values_changed = true;
} }
} }
} }
@ -1297,7 +1291,6 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
normalized_value, normalized_value,
Some(sample_rate), Some(sample_rate),
); );
parameter_values_changed = true;
} }
ProcessEvent::NoteEvent { ProcessEvent::NoteEvent {
timing: _, timing: _,
@ -1312,12 +1305,6 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
} }
} }
// This allows the GUI to react to incoming parameter changes
if parameter_values_changed {
self.inner.notify_param_values_changed();
parameter_values_changed = false;
}
// This vector has been preallocated to contain enough slices as there are output // This vector has been preallocated to contain enough slices as there are output
// channels. In case the does does not provide an output or if they don't provide // channels. In case the does does not provide an output or if they don't provide
// all of the channels (this should not happen, but Ableton Live might do it) then // all of the channels (this should not happen, but Ableton Live might do it) then
@ -1797,8 +1784,6 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
self.inner.current_buffer_config.load().as_ref(), self.inner.current_buffer_config.load().as_ref(),
); );
self.inner.notify_param_values_changed();
// NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks // NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks
let mut init_context = self.inner.make_init_context(); let mut init_context = self.inner.make_init_context();
let bus_config = self.inner.current_bus_config.load(); let bus_config = self.inner.current_bus_config.load();
@ -1811,6 +1796,9 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
permit_alloc(|| plugin.initialize(&bus_config, &buffer_config, &mut init_context)); permit_alloc(|| plugin.initialize(&bus_config, &buffer_config, &mut init_context));
plugin.reset(); plugin.reset();
let task_posted = self.inner.schedule_gui(Task::ParameterValuesChanged);
nih_debug_assert!(task_posted, "The task queue is full, dropping task...");
// We'll pass the state object back to the GUI thread so deallocation can happen // We'll pass the state object back to the GUI thread so deallocation can happen
// there without potentially blocking the audio thread // there without potentially blocking the audio thread
if let Err(err) = self.inner.updated_state_sender.send(state) { if let Err(err) = self.inner.updated_state_sender.send(state) {