Send individual parameter changes for VST3 plugins
This commit is contained in:
parent
adb49cb6d1
commit
e5a26ac199
|
@ -475,7 +475,7 @@ enum NestedParams {
|
|||
field: syn::Ident,
|
||||
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`.
|
||||
Prefixed {
|
||||
field: syn::Ident,
|
||||
|
@ -492,7 +492,7 @@ enum 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
|
||||
/// existing groups if the `group` attribute on the `#[nested]` macro was specified.
|
||||
fn param_map_tokens(&self) -> proc_macro2::TokenStream {
|
||||
|
|
|
@ -26,7 +26,7 @@ use crate::params;
|
|||
use crate::spectrum::SpectrumOutput;
|
||||
|
||||
/// 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 {
|
||||
spectrum: Arc<Mutex<SpectrumOutput>>,
|
||||
sample_rate: Arc<AtomicF32>,
|
||||
|
|
|
@ -30,7 +30,7 @@ const GRANULAR_DRAG_MULTIPLIER: f32 = 0.1;
|
|||
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
|
||||
/// 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.
|
||||
///
|
||||
/// 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.
|
||||
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
|
||||
self.y_param_base.begin_set_parameter(cx);
|
||||
self.x_param_base.begin_set_parameter(cx);
|
||||
|
|
|
@ -215,7 +215,7 @@ pub struct Wrapper<P: ClapPlugin> {
|
|||
/// parameters belonging to the plugin. These addresses will remain stable as long as the
|
||||
/// `params` object does not get deallocated.
|
||||
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.
|
||||
param_id_by_hash: HashMap<u32, String>,
|
||||
/// The group name of a parameter, indexed by the parameter's hash. Nested groups are delimited
|
||||
|
|
|
@ -166,7 +166,6 @@ impl<P: Vst3Plugin> GuiContext for WrapperGuiContext<P> {
|
|||
.load()
|
||||
.map(|c| c.sample_rate),
|
||||
);
|
||||
self.inner.notify_param_values_changed();
|
||||
}
|
||||
|
||||
handler.perform_edit(*hash, normalized as f64);
|
||||
|
|
|
@ -139,6 +139,9 @@ pub(crate) struct WrapperInner<P: Vst3Plugin> {
|
|||
/// parameters belonging to the plugin. These addresses will remain stable as long as the
|
||||
/// `params` object does not get deallocated.
|
||||
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,
|
||||
/// Mappings from string parameter identifiers to parameter hashes. Useful for debug logging
|
||||
/// and when storing and restoring plugin state.
|
||||
|
@ -157,6 +160,11 @@ pub(crate) struct WrapperInner<P: Vst3Plugin> {
|
|||
pub enum Task<P: Plugin> {
|
||||
/// Execute one of the plugin's background tasks.
|
||||
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
|
||||
/// [`vst3_sys::vst::RestartFlags`].
|
||||
TriggerRestart(i32),
|
||||
|
@ -264,6 +272,10 @@ impl<P: Vst3Plugin> WrapperInner<P> {
|
|||
.iter()
|
||||
.map(|(_, hash, ptr, _)| (*hash, *ptr))
|
||||
.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(
|
||||
param_id_hashes_ptrs_groups
|
||||
.iter()
|
||||
|
@ -320,6 +332,7 @@ impl<P: Vst3Plugin> WrapperInner<P> {
|
|||
|
||||
param_hashes,
|
||||
param_by_hash,
|
||||
param_id_by_hash,
|
||||
param_units,
|
||||
param_id_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
|
||||
/// 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) },
|
||||
}
|
||||
|
||||
let task_posted =
|
||||
self.schedule_gui(Task::ParameterValueChanged(hash, normalized_value));
|
||||
nih_debug_assert!(task_posted, "The task queue is full, dropping task...");
|
||||
|
||||
kResultOk
|
||||
}
|
||||
_ => kInvalidArgument,
|
||||
|
@ -524,7 +524,6 @@ impl<P: Vst3Plugin> WrapperInner<P> {
|
|||
);
|
||||
}
|
||||
|
||||
self.notify_param_values_changed();
|
||||
let bus_config = self.current_bus_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
|
||||
|
@ -534,6 +533,9 @@ impl<P: Vst3Plugin> WrapperInner<P> {
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
@ -566,6 +568,23 @@ impl<P: Vst3Plugin> MainThreadExecutor<Task<P>> for WrapperInner<P> {
|
|||
// This function is always called from the main thread
|
||||
match 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[¶m_hash];
|
||||
editor
|
||||
.lock()
|
||||
.param_value_changed(param_id, normalized_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Task::TriggerRestart(flags) => match &*self.component_handler.borrow() {
|
||||
Some(handler) => unsafe {
|
||||
nih_debug_assert!(is_gui_thread);
|
||||
|
|
|
@ -18,10 +18,12 @@ use vst3_sys::vst::{
|
|||
use vst3_sys::VST3;
|
||||
use widestring::U16CStr;
|
||||
|
||||
use super::inner::WrapperInner;
|
||||
use super::inner::{ProcessEvent, Task, WrapperInner};
|
||||
use super::note_expressions::{self, NoteExpressionController};
|
||||
use super::util::{
|
||||
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 crate::buffer::Buffer;
|
||||
use crate::context::process::Transport;
|
||||
|
@ -34,9 +36,6 @@ use crate::plugin::{
|
|||
use crate::util::permit_alloc;
|
||||
use crate::wrapper::state;
|
||||
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
|
||||
use vst3_sys as vst3_com;
|
||||
|
@ -512,9 +511,6 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
|
|||
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() {
|
||||
// NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks
|
||||
let mut init_context = self.inner.make_init_context();
|
||||
|
@ -527,6 +523,10 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
|
|||
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());
|
||||
|
||||
kResultOk
|
||||
|
@ -747,12 +747,8 @@ impl<P: Vst3Plugin> IEditController for Wrapper<P> {
|
|||
.current_buffer_config
|
||||
.load()
|
||||
.map(|c| c.sample_rate);
|
||||
let result = self
|
||||
.inner
|
||||
.set_normalized_value_by_hash(id, value as f32, sample_rate);
|
||||
self.inner.notify_param_values_changed();
|
||||
|
||||
result
|
||||
self.inner
|
||||
.set_normalized_value_by_hash(id, value as f32, sample_rate)
|
||||
}
|
||||
|
||||
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
|
||||
// can treat it as a sort of queue.
|
||||
let mut process_events = self.inner.process_events.borrow_mut();
|
||||
let mut parameter_values_changed = false;
|
||||
process_events.clear();
|
||||
|
||||
// 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,
|
||||
Some(sample_rate),
|
||||
);
|
||||
parameter_values_changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1297,7 +1291,6 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
|
|||
normalized_value,
|
||||
Some(sample_rate),
|
||||
);
|
||||
parameter_values_changed = true;
|
||||
}
|
||||
ProcessEvent::NoteEvent {
|
||||
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
|
||||
// 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
|
||||
|
@ -1797,8 +1784,6 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
|
|||
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
|
||||
let mut init_context = self.inner.make_init_context();
|
||||
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));
|
||||
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
|
||||
// there without potentially blocking the audio thread
|
||||
if let Err(err) = self.inner.updated_state_sender.send(state) {
|
||||
|
|
Loading…
Reference in a new issue