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,
|
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 {
|
||||||
|
|
|
@ -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>,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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[¶m_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);
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue