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 parking_lot::RwLockWriteGuard;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::sync::atomic::Ordering;
|
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::context::ProcessContext;
|
||||||
use crate::event_loop::EventLoop;
|
use crate::event_loop::EventLoop;
|
||||||
|
use crate::param::internals::ParamPtr;
|
||||||
use crate::plugin::{ClapPlugin, NoteEvent};
|
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
|
/// 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
|
/// to lock guards for event queues. Otherwise reading these events would require constant
|
||||||
/// unnecessary atomic operations to lock the uncontested RwLocks.
|
/// unnecessary atomic operations to lock the uncontested RwLocks.
|
||||||
pub(crate) struct WrapperProcessContext<'a, P: ClapPlugin> {
|
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>>,
|
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> {
|
impl<P: ClapPlugin> ProcessContext for WrapperProcessContext<'_, P> {
|
||||||
fn set_latency_samples(&self, samples: u32) {
|
fn set_latency_samples(&self, samples: u32) {
|
||||||
// Only make a callback if it's actually needed
|
// 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
|
// 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.
|
// 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 {
|
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...");
|
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::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::latency::{clap_host_latency, clap_plugin_latency, CLAP_EXT_LATENCY};
|
||||||
use clap_sys::ext::params::{
|
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,
|
CLAP_PARAM_IS_STEPPED,
|
||||||
};
|
};
|
||||||
use clap_sys::ext::state::{clap_plugin_state, CLAP_EXT_STATE};
|
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>>,
|
host_latency: Option<ClapPtr<clap_host_latency>>,
|
||||||
|
|
||||||
clap_plugin_params: clap_plugin_params,
|
clap_plugin_params: clap_plugin_params,
|
||||||
|
host_params: Option<ClapPtr<clap_host_params>>,
|
||||||
// These fiels are exactly the same as their VST3 wrapper counterparts.
|
// These fiels are exactly the same as their VST3 wrapper counterparts.
|
||||||
//
|
//
|
||||||
/// The keys from `param_map` in a stable order.
|
/// 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
|
/// 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
|
/// 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.
|
/// 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
|
/// Mappings from string parameter indentifiers to parameter hashes. Useful for debug logging
|
||||||
/// and when storing and restoring plugin state.
|
/// and when storing and restoring plugin state.
|
||||||
param_id_to_hash: HashMap<&'static str, u32>,
|
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
|
/// 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
|
/// 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).
|
/// 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
|
/// A queue of parameter changes that should be output in either the next process call or in the
|
||||||
/// next parameter flush.
|
/// 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
|
/// 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.
|
/// 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.
|
/// 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
|
/// The 'plain' value as reported to CLAP. This is the normalized value multiplied by
|
||||||
/// [crate::Param::step_size].
|
/// [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
|
/// 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_callback = unsafe { ClapPtr::new(host_callback) };
|
||||||
let host_latency =
|
let host_latency =
|
||||||
unsafe { query_host_extension::<clap_host_latency>(&host_callback, CLAP_EXT_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 {
|
let host_thread_check = unsafe {
|
||||||
query_host_extension::<clap_host_thread_check>(&host_callback, CLAP_EXT_THREAD_CHECK)
|
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,
|
text_to_value: Self::ext_params_text_to_value,
|
||||||
flush: Self::ext_params_flush,
|
flush: Self::ext_params_flush,
|
||||||
},
|
},
|
||||||
|
host_params,
|
||||||
param_hashes: Vec::new(),
|
param_hashes: Vec::new(),
|
||||||
param_by_hash: HashMap::new(),
|
param_by_hash: HashMap::new(),
|
||||||
param_defaults_normalized: HashMap::new(),
|
param_defaults_normalized: HashMap::new(),
|
||||||
|
@ -441,11 +445,30 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
|
|
||||||
fn make_process_context(&self) -> WrapperProcessContext<'_, P> {
|
fn make_process_context(&self) -> WrapperProcessContext<'_, P> {
|
||||||
WrapperProcessContext {
|
WrapperProcessContext {
|
||||||
plugin: self,
|
wrapper: self,
|
||||||
input_events_guard: self.input_events.write(),
|
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
|
/// 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.
|
||||||
///
|
///
|
||||||
|
@ -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) {
|
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
|
// 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);
|
||||||
while let Some(change) = self.output_parameter_changes.pop() {
|
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 {
|
let event = clap_event_param_value {
|
||||||
header: clap_event_header {
|
header: clap_event_header {
|
||||||
size: mem::size_of::<clap_event_param_value>() as u32,
|
size: mem::size_of::<clap_event_param_value>() as u32,
|
||||||
|
|
Loading…
Reference in a new issue