diff --git a/src/wrapper/clap/wrapper.rs b/src/wrapper/clap/wrapper.rs index 518c387c..7301a40f 100644 --- a/src/wrapper/clap/wrapper.rs +++ b/src/wrapper/clap/wrapper.rs @@ -214,6 +214,9 @@ pub struct Wrapper { /// parameters belonging to the plugin. These addresses will remain stable as long as the /// `params` object does not get deallocated. param_by_hash: HashMap, + /// Mappings from parmaeter hashes to string parameter IDs. Used for notifying the plugin's + /// editor about parameter changes. + param_id_by_hash: HashMap, /// 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 /// structure. @@ -276,6 +279,14 @@ pub struct Wrapper { pub enum Task { /// 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), + /// 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. LatencyChanged, /// Inform the host that the voice info has changed. @@ -365,6 +376,33 @@ impl MainThreadExecutor> for Wrapper

{ // This function is always called from the main thread, from [Self::on_main_thread]. match 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[¶m_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[¶m_hash]; + editor + .lock() + .param_modulation_changed(param_id, modulation_offset); + } + } + } Task::LatencyChanged => match &*self.host_latency.borrow() { Some(host_latency) => { nih_debug_assert!(is_gui_thread); @@ -437,6 +475,10 @@ impl Wrapper

{ .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_group_by_hash = param_id_hashes_ptrs_groups .iter() .map(|(_, hash, _, group)| (*hash, group.clone())) @@ -644,6 +686,7 @@ impl Wrapper

{ host_params: AtomicRefCell::new(None), param_hashes, param_by_hash, + param_id_by_hash, param_group_by_hash, param_id_to_hash, param_ptr_to_hash, @@ -773,23 +816,6 @@ impl Wrapper

{ 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 /// safely be called from any thread. If this returns `false`, then the plugin should reset its /// size back to the previous value. @@ -845,6 +871,12 @@ impl Wrapper

{ 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 } ClapParamUpdate::PlainValueMod(clap_plain_delta) => { @@ -857,6 +889,10 @@ impl Wrapper

{ 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 } } @@ -871,16 +907,9 @@ impl Wrapper

{ input_events.clear(); let num_events = clap_call! { in_=>size(in_) }; - let mut parameter_values_changed = false; for event_idx in 0..num_events { let event = clap_call! { in_=>get(in_, event_idx) }; - parameter_values_changed |= - 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(); + self.handle_in_event(event, &mut input_events, None, current_sample_idx); } } @@ -912,9 +941,8 @@ impl Wrapper

{ 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 parameter_values_changed = false; for next_event_idx in (start_idx + 1)..num_events { - parameter_values_changed |= self.handle_in_event( + self.handle_in_event( event, &mut input_events, Some(transport_info), @@ -932,20 +960,13 @@ impl Wrapper

{ } // Don't forget about the last event - parameter_values_changed |= self.handle_in_event( + self.handle_in_event( event, &mut input_events, Some(transport_info), 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 } @@ -957,7 +978,6 @@ impl Wrapper

{ // 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 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() { let push_successful = match change { OutputParamEvent::BeginGesture { param_hash } => { @@ -983,7 +1003,6 @@ impl Wrapper

{ ClapParamUpdate::PlainValueSet(clap_plain_value), sample_rate, ); - parameter_values_changed = true; let event = clap_event_param_value { header: clap_event_header { @@ -1023,12 +1042,6 @@ impl Wrapper

{ 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 let mut output_events = self.output_events.borrow_mut(); while let Some(event) = output_events.pop_front() { @@ -1331,18 +1344,13 @@ impl Wrapper

{ /// /// 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. - /// - /// 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( &self, event: *const clap_event_header, input_events: &mut AtomicRefMut>, transport_info: Option<&mut *const clap_event_transport>, current_sample_idx: usize, - ) -> bool { + ) { let raw_event = &*event; let timing = raw_event.time - current_sample_idx as u32; @@ -1372,8 +1380,6 @@ impl Wrapper

{ normalized_value, }); } - - true } (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_PARAM_MOD) => { let event = &*(event as *const clap_event_param_mod); @@ -1397,7 +1403,7 @@ impl Wrapper

{ normalized_offset, }); - return true; + return; } None => nih_debug_assert_failure!( "Polyphonic modulation sent for a parameter without a poly modulation \ @@ -1411,16 +1417,12 @@ impl Wrapper

{ ClapParamUpdate::PlainValueMod(event.amount), self.current_buffer_config.load().map(|c| c.sample_rate), ); - - true } (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_TRANSPORT) => { let event = &*(event as *const clap_event_transport); if let Some(transport_info) = transport_info { *transport_info = event; } - - false } (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_ON) => { if P::MIDI_INPUT >= MidiConfig::Basic { @@ -1439,8 +1441,6 @@ impl Wrapper

{ velocity: event.velocity as f32, }); } - - false } (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_OFF) => { if P::MIDI_INPUT >= MidiConfig::Basic { @@ -1457,8 +1457,6 @@ impl Wrapper

{ velocity: event.velocity as f32, }); } - - false } (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_CHOKE) => { if P::MIDI_INPUT >= MidiConfig::Basic { @@ -1474,8 +1472,6 @@ impl Wrapper

{ note: event.key as u8, }); } - - false } (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_EXPRESSION) => { if P::MIDI_INPUT >= MidiConfig::Basic { @@ -1577,8 +1573,6 @@ impl Wrapper

{ n => nih_debug_assert_failure!("Unhandled note expression ID {}", n), } } - - false } (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_MIDI) => { // In the Basic note port type, we'll still handle note on, note off, and polyphonic @@ -1600,8 +1594,6 @@ impl Wrapper

{ Ok(_) => (), Err(n) => nih_debug_assert_failure!("Unhandled MIDI message type {}", n), }; - - false } _ => { nih_trace!( @@ -1609,8 +1601,6 @@ impl Wrapper

{ raw_event.type_, raw_event.space_id ); - - false } } } @@ -1672,7 +1662,6 @@ impl Wrapper

{ ); } - 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 @@ -1682,6 +1671,9 @@ impl Wrapper

{ 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; } } @@ -2314,8 +2306,6 @@ impl Wrapper

{ 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 let mut init_context = wrapper.make_init_context(); let bus_config = wrapper.current_bus_config.load(); @@ -2328,6 +2318,10 @@ impl Wrapper

{ permit_alloc(|| plugin.initialize(&bus_config, &buffer_config, &mut init_context)); 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 // there without potentially blocking the audio thread if let Err(err) = wrapper.updated_state_sender.send(state) { @@ -3215,9 +3209,6 @@ impl Wrapper

{ 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(); if let Some(buffer_config) = wrapper.current_buffer_config.load() { // NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks @@ -3230,6 +3221,10 @@ impl Wrapper

{ 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)"); true