Add a CLAP GuiContext for sending param changes
This commit is contained in:
parent
a4930dc887
commit
b5993c1bb8
|
@ -1,28 +1,84 @@
|
|||
use parking_lot::RwLockWriteGuard;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::wrapper::{Task, Wrapper};
|
||||
use super::wrapper::{OutputParamChange, Task, Wrapper};
|
||||
use crate::context::ProcessContext;
|
||||
use crate::event_loop::EventLoop;
|
||||
use crate::param::internals::ParamPtr;
|
||||
use crate::plugin::{ClapPlugin, NoteEvent};
|
||||
use crate::GuiContext;
|
||||
|
||||
/// A [GuiContext] implementation for the wrapper. This is passed to the plugin in
|
||||
/// [crate::Editor::spawn()] so it can interact with the rest of the plugin and with the host for
|
||||
/// things like setting parameters.
|
||||
pub(crate) struct WrapperGuiContext<P: ClapPlugin> {
|
||||
pub(super) wrapper: Arc<Wrapper<P>>,
|
||||
}
|
||||
|
||||
/// A [ProcessContext] implementation for the wrapper. This is a separate object so it can hold on
|
||||
/// to lock guards for event queues. Otherwise reading these events would require constant
|
||||
/// unnecessary atomic operations to lock the uncontested RwLocks.
|
||||
pub(crate) struct WrapperProcessContext<'a, P: ClapPlugin> {
|
||||
pub(super) plugin: &'a Wrapper<P>,
|
||||
pub(super) wrapper: &'a Wrapper<P>,
|
||||
pub(super) input_events_guard: RwLockWriteGuard<'a, VecDeque<NoteEvent>>,
|
||||
}
|
||||
|
||||
impl<P: ClapPlugin> GuiContext for WrapperGuiContext<P> {
|
||||
// All of these functions are supposed to be called from the main thread, so we'll put some
|
||||
// trust in the caller and assume that this is indeed the case
|
||||
unsafe fn raw_begin_set_parameter(&self, _param: ParamPtr) {
|
||||
// TODO: Parameter event gestures are a bit weird in CLAP right now because they're
|
||||
// implemented as flags on events, and you don't know when a gesture ends before it
|
||||
// has ended. Implement this once that's a bit clearer.
|
||||
}
|
||||
|
||||
unsafe fn raw_set_parameter_normalized(&self, param: ParamPtr, normalized: f32) {
|
||||
match self.wrapper.param_ptr_to_hash.get(¶m) {
|
||||
Some(hash) => {
|
||||
// We queue the parameter change event here, and it will be sent to the host either
|
||||
// at the end of the current processing cycle or after requesting an explicit flush
|
||||
// (when the plugin isn't processing audio). The parameter's actual value will only
|
||||
// be changed when the output event is written to prevent changing parameter values
|
||||
// in the middle of processing audio.
|
||||
let clap_plain_value = normalized as f64 * param.step_count().unwrap_or(1) as f64;
|
||||
let success = self.wrapper.queue_parameter_change(OutputParamChange {
|
||||
param_hash: *hash,
|
||||
clap_plain_value,
|
||||
});
|
||||
|
||||
nih_debug_assert!(success, "Parameter output queue was full, parameter change will not be sent to the host");
|
||||
}
|
||||
None => nih_debug_assert_failure!("Unknown parameter: {:?}", param),
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn raw_end_set_parameter(&self, _param: ParamPtr) {
|
||||
// TODO: Parameter event gestures are a bit weird in CLAP right now because they're
|
||||
// implemented as flags on events, and you don't know when a gesture ends before it
|
||||
// has ended. Implement this once that's a bit clearer.
|
||||
}
|
||||
|
||||
unsafe fn raw_default_normalized_param_value(&self, param: ParamPtr) -> f32 {
|
||||
match self.wrapper.param_ptr_to_hash.get(¶m) {
|
||||
Some(hash) => self.wrapper.param_defaults_normalized[hash],
|
||||
None => {
|
||||
nih_debug_assert_failure!("Unknown parameter: {:?}", param);
|
||||
0.5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: ClapPlugin> ProcessContext for WrapperProcessContext<'_, P> {
|
||||
fn set_latency_samples(&self, samples: u32) {
|
||||
// Only make a callback if it's actually needed
|
||||
// XXX: For CLAP we could move this handling to the Plugin struct, but it may be worthwhile
|
||||
// to keep doing it this way to stay consistent with VST3.
|
||||
let old_latency = self.plugin.current_latency.swap(samples, Ordering::SeqCst);
|
||||
let old_latency = self.wrapper.current_latency.swap(samples, Ordering::SeqCst);
|
||||
if old_latency != samples {
|
||||
let task_posted = self.plugin.do_maybe_async(Task::LatencyChanged);
|
||||
let task_posted = self.wrapper.do_maybe_async(Task::LatencyChanged);
|
||||
nih_debug_assert!(task_posted, "The task queue is full, dropping task...");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ use clap_sys::ext::audio_ports_config::{
|
|||
use clap_sys::ext::gui::{clap_plugin_gui, CLAP_EXT_GUI};
|
||||
use clap_sys::ext::latency::{clap_host_latency, clap_plugin_latency, CLAP_EXT_LATENCY};
|
||||
use clap_sys::ext::params::{
|
||||
clap_param_info, clap_plugin_params, CLAP_EXT_PARAMS, CLAP_PARAM_IS_BYPASS,
|
||||
clap_host_params, clap_param_info, clap_plugin_params, CLAP_EXT_PARAMS, CLAP_PARAM_IS_BYPASS,
|
||||
CLAP_PARAM_IS_STEPPED,
|
||||
};
|
||||
use clap_sys::ext::state::{clap_plugin_state, CLAP_EXT_STATE};
|
||||
|
@ -135,6 +135,7 @@ pub struct Wrapper<P: ClapPlugin> {
|
|||
host_latency: Option<ClapPtr<clap_host_latency>>,
|
||||
|
||||
clap_plugin_params: clap_plugin_params,
|
||||
host_params: Option<ClapPtr<clap_host_params>>,
|
||||
// These fiels are exactly the same as their VST3 wrapper counterparts.
|
||||
//
|
||||
/// The keys from `param_map` in a stable order.
|
||||
|
@ -146,14 +147,14 @@ pub struct Wrapper<P: ClapPlugin> {
|
|||
/// The default normalized parameter value for every parameter in `param_ids`. We need to store
|
||||
/// this in case the host requeries the parmaeter later. This is also indexed by the hash so we
|
||||
/// can retrieve them later for the UI if needed.
|
||||
param_defaults_normalized: HashMap<u32, f32>,
|
||||
pub param_defaults_normalized: HashMap<u32, f32>,
|
||||
/// Mappings from string parameter indentifiers to parameter hashes. Useful for debug logging
|
||||
/// and when storing and restoring plugin state.
|
||||
param_id_to_hash: HashMap<&'static str, u32>,
|
||||
/// The inverse mapping from [Self::param_by_hash]. This is needed to be able to have an
|
||||
/// ergonomic parameter setting API that uses references to the parameters instead of having to
|
||||
/// add a setter function to the parameter (or even worse, have it be completely untyped).
|
||||
param_ptr_to_hash: HashMap<ParamPtr, u32>,
|
||||
pub param_ptr_to_hash: HashMap<ParamPtr, u32>,
|
||||
/// A queue of parameter changes that should be output in either the next process call or in the
|
||||
/// next parameter flush.
|
||||
///
|
||||
|
@ -198,12 +199,12 @@ pub enum ClapParamUpdate {
|
|||
|
||||
/// A parameter change that should be output by the plugin, stored in a queue on the wrapper and
|
||||
/// written to the host either at the end of the process function or during a flush.
|
||||
struct OutputParamChange {
|
||||
pub struct OutputParamChange {
|
||||
/// The internal hash for the parameter.
|
||||
param_hash: u32,
|
||||
pub param_hash: u32,
|
||||
/// The 'plain' value as reported to CLAP. This is the normalized value multiplied by
|
||||
/// [crate::Param::step_size].
|
||||
clap_plain_value: f64,
|
||||
pub clap_plain_value: f64,
|
||||
}
|
||||
|
||||
/// Because CLAP has this [clap_host::request_host_callback()] function, we don't need to use
|
||||
|
@ -262,6 +263,8 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
let host_callback = unsafe { ClapPtr::new(host_callback) };
|
||||
let host_latency =
|
||||
unsafe { query_host_extension::<clap_host_latency>(&host_callback, CLAP_EXT_LATENCY) };
|
||||
let host_params =
|
||||
unsafe { query_host_extension::<clap_host_params>(&host_callback, CLAP_EXT_PARAMS) };
|
||||
let host_thread_check = unsafe {
|
||||
query_host_extension::<clap_host_thread_check>(&host_callback, CLAP_EXT_THREAD_CHECK)
|
||||
};
|
||||
|
@ -345,6 +348,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
text_to_value: Self::ext_params_text_to_value,
|
||||
flush: Self::ext_params_flush,
|
||||
},
|
||||
host_params,
|
||||
param_hashes: Vec::new(),
|
||||
param_by_hash: HashMap::new(),
|
||||
param_defaults_normalized: HashMap::new(),
|
||||
|
@ -441,11 +445,30 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
|
||||
fn make_process_context(&self) -> WrapperProcessContext<'_, P> {
|
||||
WrapperProcessContext {
|
||||
plugin: self,
|
||||
wrapper: self,
|
||||
input_events_guard: self.input_events.write(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Queue a parmeter change to be sent to the host at the end of the audio processing cycle, and
|
||||
/// request a parameter flush from the host if the plugin is not currently processing audio. The
|
||||
/// parameter's actual value will only be updated at that point so the value won't change in the
|
||||
/// middle of a processing call.
|
||||
///
|
||||
/// Returns `false` if the parameter value queue was full and the update will not be sent to the
|
||||
/// host (it will still be set on the plugin either way).
|
||||
pub fn queue_parameter_change(&self, change: OutputParamChange) -> bool {
|
||||
let result = self.output_parameter_changes.push(change).is_ok();
|
||||
match &self.host_params {
|
||||
Some(host_params) if !self.is_processing.load(Ordering::SeqCst) => {
|
||||
unsafe { (host_params.request_flush)(&*self.host_callback) };
|
||||
}
|
||||
_ => nih_debug_assert_failure!("The host does not support parameters? What?"),
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Convenience function for setting a value for a parameter as triggered by a VST3 parameter
|
||||
/// update. The same rate is for updating parameter smoothing.
|
||||
///
|
||||
|
@ -514,11 +537,20 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Write the unflushed parameter changes to the host's output event queue.
|
||||
/// Write the unflushed parameter changes to the host's output event queue. This will also
|
||||
/// modify the actual parameter values, since we should only do that while the wrapped plugin is
|
||||
/// not actually processing audio.
|
||||
pub unsafe fn handle_out_events(&self, out: &clap_output_events) {
|
||||
// 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);
|
||||
while let Some(change) = self.output_parameter_changes.pop() {
|
||||
self.update_plain_value_by_hash(
|
||||
change.param_hash,
|
||||
ClapParamUpdate::PlainValueSet(change.clap_plain_value),
|
||||
sample_rate,
|
||||
);
|
||||
|
||||
let event = clap_event_param_value {
|
||||
header: clap_event_header {
|
||||
size: mem::size_of::<clap_event_param_value>() as u32,
|
||||
|
|
Loading…
Reference in a new issue