1
0
Fork 0

Send individual parameter changes for CLAP plugins

This commit is contained in:
Robbert van der Helm 2023-01-11 14:54:06 +01:00
parent 4e53d3a799
commit 951a0739d7

View file

@ -214,6 +214,9 @@ 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
/// 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 /// The group name of a parameter, indexed by the parameter's hash. Nested groups are delimited
/// by slashes, and they're only used to allow the DAW to display parameters in a tree /// by slashes, and they're only used to allow the DAW to display parameters in a tree
/// structure. /// structure.
@ -276,6 +279,14 @@ pub struct Wrapper<P: ClapPlugin> {
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),
/// Inform the plugin that one parameter's modulation offset has changed. This uses the
/// parameter hashes since the task will be created from the audio thread.
ParameterModulationChanged(u32, f32),
/// 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. /// Inform the host that the voice info has changed.
@ -365,6 +376,33 @@ impl<P: ClapPlugin> MainThreadExecutor<Task<P>> for Wrapper<P> {
// This function is always called from the main thread, from [Self::on_main_thread]. // This function is always called from the main thread, from [Self::on_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.editor_handle.lock().is_some() {
if let Some(editor) = self.editor.borrow().as_ref() {
editor.lock().param_values_changed();
}
}
}
Task::ParameterValueChanged(param_hash, normalized_value) => {
if self.editor_handle.lock().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::ParameterModulationChanged(param_hash, modulation_offset) => {
if self.editor_handle.lock().is_some() {
if let Some(editor) = self.editor.borrow().as_ref() {
let param_id = &self.param_id_by_hash[&param_hash];
editor
.lock()
.param_modulation_changed(param_id, modulation_offset);
}
}
}
Task::LatencyChanged => match &*self.host_latency.borrow() { Task::LatencyChanged => match &*self.host_latency.borrow() {
Some(host_latency) => { Some(host_latency) => {
nih_debug_assert!(is_gui_thread); nih_debug_assert!(is_gui_thread);
@ -437,6 +475,10 @@ impl<P: ClapPlugin> Wrapper<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_group_by_hash = param_id_hashes_ptrs_groups let param_group_by_hash = param_id_hashes_ptrs_groups
.iter() .iter()
.map(|(_, hash, _, group)| (*hash, group.clone())) .map(|(_, hash, _, group)| (*hash, group.clone()))
@ -644,6 +686,7 @@ impl<P: ClapPlugin> Wrapper<P> {
host_params: AtomicRefCell::new(None), host_params: AtomicRefCell::new(None),
param_hashes, param_hashes,
param_by_hash, param_by_hash,
param_id_by_hash,
param_group_by_hash, param_group_by_hash,
param_id_to_hash, param_id_to_hash,
param_ptr_to_hash, param_ptr_to_hash,
@ -773,23 +816,6 @@ impl<P: ClapPlugin> Wrapper<P> {
result result
} }
/// 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
/// [`update_plain_value_by_hash()[Self::update_plain_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"
),
}
}
}
/// Request a resize based on the editor's current reported size. As of CLAP 0.24 this can /// Request a resize based on the editor's current reported size. As of CLAP 0.24 this can
/// safely be called from any thread. If this returns `false`, then the plugin should reset its /// safely be called from any thread. If this returns `false`, then the plugin should reset its
/// size back to the previous value. /// size back to the previous value.
@ -845,6 +871,12 @@ impl<P: ClapPlugin> Wrapper<P> {
unsafe { param_ptr.update_smoother(sample_rate, false) }; unsafe { param_ptr.update_smoother(sample_rate, false) };
} }
// The GUI needs to be informed about the changed parameter value. This
// triggers an `Editor::param_value_changed()` call on the GUI thread.
let task_posted =
self.schedule_gui(Task::ParameterValueChanged(hash, normalized_value));
nih_debug_assert!(task_posted, "The task queue is full, dropping task...");
true true
} }
ClapParamUpdate::PlainValueMod(clap_plain_delta) => { ClapParamUpdate::PlainValueMod(clap_plain_delta) => {
@ -857,6 +889,10 @@ impl<P: ClapPlugin> Wrapper<P> {
unsafe { param_ptr.update_smoother(sample_rate, false) }; unsafe { param_ptr.update_smoother(sample_rate, false) };
} }
let task_posted = self
.schedule_gui(Task::ParameterModulationChanged(hash, normalized_delta));
nih_debug_assert!(task_posted, "The task queue is full, dropping task...");
true true
} }
} }
@ -871,17 +907,10 @@ impl<P: ClapPlugin> Wrapper<P> {
input_events.clear(); input_events.clear();
let num_events = clap_call! { in_=>size(in_) }; let num_events = clap_call! { in_=>size(in_) };
let mut parameter_values_changed = false;
for event_idx in 0..num_events { for event_idx in 0..num_events {
let event = clap_call! { in_=>get(in_, event_idx) }; let event = clap_call! { in_=>get(in_, event_idx) };
parameter_values_changed |=
self.handle_in_event(event, &mut input_events, None, current_sample_idx); self.handle_in_event(event, &mut input_events, None, current_sample_idx);
} }
// Allow the GUI to react to any parameter values that might have been changed
if parameter_values_changed {
self.notify_param_values_changed();
}
} }
/// Similar to [`handle_in_events()`][Self::handle_in_events()], but will stop just before an /// Similar to [`handle_in_events()`][Self::handle_in_events()], but will stop just before an
@ -912,9 +941,8 @@ impl<P: ClapPlugin> Wrapper<P> {
let start_idx = resume_from_event_idx as u32; let start_idx = resume_from_event_idx as u32;
let mut event: *const clap_event_header = clap_call! { in_=>get(in_, start_idx) }; let mut event: *const clap_event_header = clap_call! { in_=>get(in_, start_idx) };
let mut parameter_values_changed = false;
for next_event_idx in (start_idx + 1)..num_events { for next_event_idx in (start_idx + 1)..num_events {
parameter_values_changed |= self.handle_in_event( self.handle_in_event(
event, event,
&mut input_events, &mut input_events,
Some(transport_info), Some(transport_info),
@ -932,20 +960,13 @@ impl<P: ClapPlugin> Wrapper<P> {
} }
// Don't forget about the last event // Don't forget about the last event
parameter_values_changed |= self.handle_in_event( self.handle_in_event(
event, event,
&mut input_events, &mut input_events,
Some(transport_info), Some(transport_info),
current_sample_idx, current_sample_idx,
); );
// NOTE: We explicitly did not do this on a block split because that seems a bit excessive.
// When we're performing a block split we're guaranteed that there's still at least one more
// parameter event after the split so this function will still be called.
if parameter_values_changed {
self.notify_param_values_changed();
}
None None
} }
@ -957,7 +978,6 @@ impl<P: ClapPlugin> Wrapper<P> {
// We'll always write these events to the first sample, so even when we add note output we // We'll always write these events to the first sample, so even when we add note output we
// shouldn't have to think about interleaving events here // shouldn't have to think about interleaving events here
let sample_rate = self.current_buffer_config.load().map(|c| c.sample_rate); let sample_rate = self.current_buffer_config.load().map(|c| c.sample_rate);
let mut parameter_values_changed = false;
while let Some(change) = self.output_parameter_events.pop() { while let Some(change) = self.output_parameter_events.pop() {
let push_successful = match change { let push_successful = match change {
OutputParamEvent::BeginGesture { param_hash } => { OutputParamEvent::BeginGesture { param_hash } => {
@ -983,7 +1003,6 @@ impl<P: ClapPlugin> Wrapper<P> {
ClapParamUpdate::PlainValueSet(clap_plain_value), ClapParamUpdate::PlainValueSet(clap_plain_value),
sample_rate, sample_rate,
); );
parameter_values_changed = true;
let event = clap_event_param_value { let event = clap_event_param_value {
header: clap_event_header { header: clap_event_header {
@ -1023,12 +1042,6 @@ impl<P: ClapPlugin> Wrapper<P> {
nih_debug_assert!(push_successful); nih_debug_assert!(push_successful);
} }
// Allow the editor to react to the new parameter values if the editor uses a reactive data
// binding model
if parameter_values_changed {
self.notify_param_values_changed();
}
// Also send all note events generated by the plugin // Also send all note events generated by the plugin
let mut output_events = self.output_events.borrow_mut(); let mut output_events = self.output_events.borrow_mut();
while let Some(event) = output_events.pop_front() { while let Some(event) = output_events.pop_front() {
@ -1331,18 +1344,13 @@ impl<P: ClapPlugin> Wrapper<P> {
/// ///
/// If the event was a transport event and the `transport_info` argument is not `None`, then the /// If the event was a transport event and the `transport_info` argument is not `None`, then the
/// pointer will be changed to point to the transport information from this event. /// pointer will be changed to point to the transport information from this event.
///
/// The return value indicates whether this was a parameter event. If it is a parameter event,
/// then [`notify_param_values_changed()`][Self::notify_param_values_changed()] should be called
/// once all of these events have been processed.
#[must_use]
pub unsafe fn handle_in_event( pub unsafe fn handle_in_event(
&self, &self,
event: *const clap_event_header, event: *const clap_event_header,
input_events: &mut AtomicRefMut<VecDeque<NoteEvent>>, input_events: &mut AtomicRefMut<VecDeque<NoteEvent>>,
transport_info: Option<&mut *const clap_event_transport>, transport_info: Option<&mut *const clap_event_transport>,
current_sample_idx: usize, current_sample_idx: usize,
) -> bool { ) {
let raw_event = &*event; let raw_event = &*event;
let timing = raw_event.time - current_sample_idx as u32; let timing = raw_event.time - current_sample_idx as u32;
@ -1372,8 +1380,6 @@ impl<P: ClapPlugin> Wrapper<P> {
normalized_value, normalized_value,
}); });
} }
true
} }
(CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_PARAM_MOD) => { (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_PARAM_MOD) => {
let event = &*(event as *const clap_event_param_mod); let event = &*(event as *const clap_event_param_mod);
@ -1397,7 +1403,7 @@ impl<P: ClapPlugin> Wrapper<P> {
normalized_offset, normalized_offset,
}); });
return true; return;
} }
None => nih_debug_assert_failure!( None => nih_debug_assert_failure!(
"Polyphonic modulation sent for a parameter without a poly modulation \ "Polyphonic modulation sent for a parameter without a poly modulation \
@ -1411,16 +1417,12 @@ impl<P: ClapPlugin> Wrapper<P> {
ClapParamUpdate::PlainValueMod(event.amount), ClapParamUpdate::PlainValueMod(event.amount),
self.current_buffer_config.load().map(|c| c.sample_rate), self.current_buffer_config.load().map(|c| c.sample_rate),
); );
true
} }
(CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_TRANSPORT) => { (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_TRANSPORT) => {
let event = &*(event as *const clap_event_transport); let event = &*(event as *const clap_event_transport);
if let Some(transport_info) = transport_info { if let Some(transport_info) = transport_info {
*transport_info = event; *transport_info = event;
} }
false
} }
(CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_ON) => { (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_ON) => {
if P::MIDI_INPUT >= MidiConfig::Basic { if P::MIDI_INPUT >= MidiConfig::Basic {
@ -1439,8 +1441,6 @@ impl<P: ClapPlugin> Wrapper<P> {
velocity: event.velocity as f32, velocity: event.velocity as f32,
}); });
} }
false
} }
(CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_OFF) => { (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_OFF) => {
if P::MIDI_INPUT >= MidiConfig::Basic { if P::MIDI_INPUT >= MidiConfig::Basic {
@ -1457,8 +1457,6 @@ impl<P: ClapPlugin> Wrapper<P> {
velocity: event.velocity as f32, velocity: event.velocity as f32,
}); });
} }
false
} }
(CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_CHOKE) => { (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_CHOKE) => {
if P::MIDI_INPUT >= MidiConfig::Basic { if P::MIDI_INPUT >= MidiConfig::Basic {
@ -1474,8 +1472,6 @@ impl<P: ClapPlugin> Wrapper<P> {
note: event.key as u8, note: event.key as u8,
}); });
} }
false
} }
(CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_EXPRESSION) => { (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_EXPRESSION) => {
if P::MIDI_INPUT >= MidiConfig::Basic { if P::MIDI_INPUT >= MidiConfig::Basic {
@ -1577,8 +1573,6 @@ impl<P: ClapPlugin> Wrapper<P> {
n => nih_debug_assert_failure!("Unhandled note expression ID {}", n), n => nih_debug_assert_failure!("Unhandled note expression ID {}", n),
} }
} }
false
} }
(CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_MIDI) => { (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_MIDI) => {
// In the Basic note port type, we'll still handle note on, note off, and polyphonic // In the Basic note port type, we'll still handle note on, note off, and polyphonic
@ -1600,8 +1594,6 @@ impl<P: ClapPlugin> Wrapper<P> {
Ok(_) => (), Ok(_) => (),
Err(n) => nih_debug_assert_failure!("Unhandled MIDI message type {}", n), Err(n) => nih_debug_assert_failure!("Unhandled MIDI message type {}", n),
}; };
false
} }
_ => { _ => {
nih_trace!( nih_trace!(
@ -1609,8 +1601,6 @@ impl<P: ClapPlugin> Wrapper<P> {
raw_event.type_, raw_event.type_,
raw_event.space_id raw_event.space_id
); );
false
} }
} }
} }
@ -1672,7 +1662,6 @@ impl<P: ClapPlugin> Wrapper<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
@ -1682,6 +1671,9 @@ impl<P: ClapPlugin> Wrapper<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;
} }
} }
@ -2314,8 +2306,6 @@ impl<P: ClapPlugin> Wrapper<P> {
wrapper.current_buffer_config.load().as_ref(), wrapper.current_buffer_config.load().as_ref(),
); );
wrapper.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 = wrapper.make_init_context(); let mut init_context = wrapper.make_init_context();
let bus_config = wrapper.current_bus_config.load(); let bus_config = wrapper.current_bus_config.load();
@ -2328,6 +2318,10 @@ impl<P: ClapPlugin> 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();
// Reinitialize the plugin after loading state so it can respond to the new parameter values
let task_posted = wrapper.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) = wrapper.updated_state_sender.send(state) { if let Err(err) = wrapper.updated_state_sender.send(state) {
@ -3215,9 +3209,6 @@ impl<P: ClapPlugin> Wrapper<P> {
return false; return false;
} }
// Reinitialize the plugin after loading state so it can respond to the new parameter values
wrapper.notify_param_values_changed();
let bus_config = wrapper.current_bus_config.load(); let bus_config = wrapper.current_bus_config.load();
if let Some(buffer_config) = wrapper.current_buffer_config.load() { if let Some(buffer_config) = wrapper.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
@ -3230,6 +3221,10 @@ impl<P: ClapPlugin> 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 = wrapper.schedule_gui(Task::ParameterValuesChanged);
nih_debug_assert!(task_posted, "The task queue is full, dropping task...");
nih_trace!("Loaded state ({length} bytes)"); nih_trace!("Loaded state ({length} bytes)");
true true