1
0
Fork 0

Implement CLAP input parameter handling

This commit is contained in:
Robbert van der Helm 2022-03-01 19:31:16 +01:00
parent 09534a2657
commit 3bd83ca55a
2 changed files with 176 additions and 9 deletions

View file

@ -1,4 +1,7 @@
use clap_sys::events::{clap_input_events, clap_output_events}; use clap_sys::events::{
clap_event_header, clap_event_param_mod, clap_event_param_value, clap_input_events,
clap_output_events, CLAP_EVENT_PARAM_MOD, CLAP_EVENT_PARAM_VALUE,
};
use clap_sys::ext::params::{ use clap_sys::ext::params::{
clap_param_info, clap_plugin_params, CLAP_EXT_PARAMS, CLAP_PARAM_IS_BYPASS, clap_param_info, clap_plugin_params, CLAP_EXT_PARAMS, CLAP_PARAM_IS_BYPASS,
CLAP_PARAM_IS_STEPPED, CLAP_PARAM_IS_STEPPED,
@ -115,6 +118,15 @@ pub enum Task {
LatencyChanged, LatencyChanged,
} }
/// The types of CLAP parameter updates for events.
pub enum ClapParamUpdate {
/// Set the parameter to this plain value. In our wrapper the plain values are the normalized
/// values multiplied by the step count for discrete parameters.
PlainValueSet(f64),
/// Add a delta to the parameter's current plain value (so again, multiplied by the step size).
PlainValueMod(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
/// `OsEventLoop` and can instead just request a main thread callback directly. /// `OsEventLoop` and can instead just request a main thread callback directly.
impl<P: ClapPlugin> EventLoop<Task, Wrapper<P>> for Wrapper<P> { impl<P: ClapPlugin> EventLoop<Task, Wrapper<P>> for Wrapper<P> {
@ -267,6 +279,93 @@ impl<P: ClapPlugin> Wrapper<P> {
} }
} }
/// Convenience function for setting a value for a parameter as triggered by a VST3 parameter
/// update. The same rate is for updating parameter smoothing.
///
/// # Note
///
/// These values are CLAP plain values, which include a step count multiplier for discrete
/// parameter values.
pub fn update_plain_value_by_hash(
&self,
hash: u32,
update: ClapParamUpdate,
sample_rate: Option<f32>,
) -> bool {
if hash == *BYPASS_PARAM_HASH {
match update {
ClapParamUpdate::PlainValueSet(clap_plain_value) => self
.bypass_state
.store(clap_plain_value >= 0.5, Ordering::SeqCst),
ClapParamUpdate::PlainValueMod(clap_plain_mod) => {
if clap_plain_mod > 0.0 {
self.bypass_state.store(true, Ordering::SeqCst)
} else if clap_plain_mod < 0.0 {
self.bypass_state.store(false, Ordering::SeqCst)
}
}
}
true
} else if let Some(param_ptr) = self.param_by_hash.get(&hash) {
let normalized_value = match update {
ClapParamUpdate::PlainValueSet(clap_plain_value) => {
clap_plain_value as f32 / unsafe { param_ptr.step_count() }.unwrap_or(1) as f32
}
ClapParamUpdate::PlainValueMod(clap_plain_mod) => {
let current_normalized_value = unsafe { param_ptr.normalized_value() };
current_normalized_value
+ (clap_plain_mod as f32
/ unsafe { param_ptr.step_count() }.unwrap_or(1) as f32)
}
};
// Also update the parameter's smoothing if applicable
match (param_ptr, sample_rate) {
(_, Some(sample_rate)) => unsafe {
param_ptr.set_normalized_value(normalized_value);
param_ptr.update_smoother(sample_rate, false);
},
_ => unsafe { param_ptr.set_normalized_value(normalized_value) },
}
true
} else {
false
}
}
/// Handle an incoming CLAP event. You must clear [Self::input_events] first before calling this
/// from the process function.
pub unsafe fn handle_event(&self, event: *const clap_event_header) {
let raw_event = &*event;
match raw_event.type_ {
CLAP_EVENT_PARAM_VALUE => {
let event = &*(event as *const clap_event_param_value);
self.update_plain_value_by_hash(
event.param_id,
ClapParamUpdate::PlainValueSet(event.value),
self.current_buffer_config.load().map(|c| c.sample_rate),
);
}
CLAP_EVENT_PARAM_MOD => {
let event = &*(event as *const clap_event_param_mod);
self.update_plain_value_by_hash(
event.param_id,
ClapParamUpdate::PlainValueMod(event.amount),
self.current_buffer_config.load().map(|c| c.sample_rate),
);
}
// TODO: Handle MIDI
// TODO: Make sure this only gets logged in debug mode
_ => nih_log!(
"Unhandled CLAP event type {} for namespace {}",
raw_event.type_,
raw_event.space_id
),
}
}
unsafe extern "C" fn init(_plugin: *const clap_plugin) -> bool { unsafe extern "C" fn init(_plugin: *const clap_plugin) -> bool {
// We don't need any special initialization // We don't need any special initialization
true true
@ -459,8 +558,36 @@ impl<P: ClapPlugin> Wrapper<P> {
display: *mut c_char, display: *mut c_char,
size: u32, size: u32,
) -> bool { ) -> bool {
// TODO: Don't forget to add the unit let wrapper = &*(plugin as *const Self);
todo!();
if display.is_null() {
return false;
}
let dest = std::slice::from_raw_parts_mut(display, size as usize);
if param_id == *BYPASS_PARAM_HASH {
if value > 0.5 {
strlcpy(dest, "Bypassed")
} else {
strlcpy(dest, "Enabled")
}
true
} else if let Some(param_ptr) = wrapper.param_by_hash.get(&param_id) {
strlcpy(
dest,
// CLAP does not have a separate unit, so we'll include the unit here
&param_ptr.normalized_value_to_string(
value as f32 * param_ptr.step_count().unwrap_or(1) as f32,
true,
),
);
true
} else {
false
}
} }
unsafe extern "C" fn ext_params_text_to_value( unsafe extern "C" fn ext_params_text_to_value(
@ -469,7 +596,37 @@ impl<P: ClapPlugin> Wrapper<P> {
display: *const c_char, display: *const c_char,
value: *mut f64, value: *mut f64,
) -> bool { ) -> bool {
todo!(); let wrapper = &*(plugin as *const Self);
if display.is_null() || value.is_null() {
return false;
}
let display = match CStr::from_ptr(display).to_str() {
Ok(s) => s,
Err(_) => return false,
};
if param_id == *BYPASS_PARAM_HASH {
let normalized_valeu = match display {
"Bypassed" => 1.0,
"Enabled" => 0.0,
_ => return false,
};
*value = normalized_valeu;
true
} else if let Some(param_ptr) = wrapper.param_by_hash.get(&param_id) {
let normalized_value = match param_ptr.string_to_normalized_value(display) {
Some(v) => v as f64,
None => return false,
};
*value = normalized_value;
true
} else {
false
}
} }
unsafe extern "C" fn ext_params_flush( unsafe extern "C" fn ext_params_flush(
@ -477,7 +634,17 @@ impl<P: ClapPlugin> Wrapper<P> {
in_: *const clap_input_events, in_: *const clap_input_events,
out: *const clap_output_events, out: *const clap_output_events,
) { ) {
todo!(); let wrapper = &*(plugin as *const Self);
if !in_.is_null() {
let num_events = ((*in_).size)(&*in_);
for event_idx in 0..num_events {
let event = ((*in_).get)(&*in_, event_idx);
wrapper.handle_event(event);
}
}
// TODO: Handle automation/outputs
} }
} }

View file

@ -197,7 +197,7 @@ impl<P: Vst3Plugin> WrapperInner<P> {
/// 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.
pub unsafe fn set_normalized_value_by_hash( pub fn set_normalized_value_by_hash(
&self, &self,
hash: u32, hash: u32,
normalized_value: f32, normalized_value: f32,
@ -211,11 +211,11 @@ impl<P: Vst3Plugin> WrapperInner<P> {
} else if let Some(param_ptr) = self.param_by_hash.get(&hash) { } else if let Some(param_ptr) = self.param_by_hash.get(&hash) {
// Also update the parameter's smoothing if applicable // Also update the parameter's smoothing if applicable
match (param_ptr, sample_rate) { match (param_ptr, sample_rate) {
(_, Some(sample_rate)) => { (_, Some(sample_rate)) => unsafe {
param_ptr.set_normalized_value(normalized_value); param_ptr.set_normalized_value(normalized_value);
param_ptr.update_smoother(sample_rate, false); param_ptr.update_smoother(sample_rate, false);
} },
_ => param_ptr.set_normalized_value(normalized_value), _ => unsafe { param_ptr.set_normalized_value(normalized_value) },
} }
kResultOk kResultOk