Add explicit bypass parameter handling
Plugins can mark a `BoolParam` with `.is_bypass()`. Hosts can then link use that parameter directly in their own UI.
This commit is contained in:
parent
3d7a23c812
commit
8090d0ae41
|
@ -66,6 +66,9 @@ for download links.
|
||||||
`Params` object and annotating them with `#[persist = "key"]`.
|
`Params` object and annotating them with `#[persist = "key"]`.
|
||||||
- Group your parameters into logical groups by nesting `Params` objects using
|
- Group your parameters into logical groups by nesting `Params` objects using
|
||||||
the `#[nested = "Group Name"]`attribute.
|
the `#[nested = "Group Name"]`attribute.
|
||||||
|
- When needed, you can also provide your own implementation for the `Params`
|
||||||
|
trait to enable dynamically generated parameters and arrays of if mostly
|
||||||
|
identical parameter objects.
|
||||||
- Stateful. Behaves mostly like JUCE, just without all of the boilerplate.
|
- Stateful. Behaves mostly like JUCE, just without all of the boilerplate.
|
||||||
- Does not make any assumptions on how you want to process audio, but does come
|
- Does not make any assumptions on how you want to process audio, but does come
|
||||||
with utilities and adapters to help with common access patterns.
|
with utilities and adapters to help with common access patterns.
|
||||||
|
|
|
@ -22,13 +22,18 @@ bitflags::bitflags! {
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ParamFlags: u32 {
|
pub struct ParamFlags: u32 {
|
||||||
|
/// When applied to a [`BoolParam`], this will cause the parameter to be linked to the
|
||||||
|
/// host's bypass control. Only a single parameter can be marked as a bypass parameter. If
|
||||||
|
/// you don't have a bypass parameter, then NIH-plug will add one for you. You will need to
|
||||||
|
/// implement this yourself if your plugin introduces latency.
|
||||||
|
const BYPASS = 1 << 0;
|
||||||
/// The parameter cannot be automated from the host. Setting this flag also prevents it from
|
/// The parameter cannot be automated from the host. Setting this flag also prevents it from
|
||||||
/// showing up in the host's own generic UI for this plugin. The parameter can still be
|
/// showing up in the host's own generic UI for this plugin. The parameter can still be
|
||||||
/// changed from the plugin's editor GUI.
|
/// changed from the plugin's editor GUI.
|
||||||
const NON_AUTOMATABLE = 1 << 0;
|
const NON_AUTOMATABLE = 1 << 1;
|
||||||
/// Don't show this parameter when generating a generic UI for the plugin using one of
|
/// Don't show this parameter when generating a generic UI for the plugin using one of
|
||||||
/// NIH-plug's generic UI widgets.
|
/// NIH-plug's generic UI widgets.
|
||||||
const HIDE_IN_GENERIC_UI = 1 << 1;
|
const HIDE_IN_GENERIC_UI = 1 << 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -182,6 +182,15 @@ impl BoolParam {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mark this parameter as a bypass parameter. Plugin hosts can integrate this parameter into
|
||||||
|
/// their UI. Only a single [`BoolParam`] can be a bypass parmaeter, and NIH-plug will add one
|
||||||
|
/// if you don't create one yourself. You will need to implement this yourself if your plugin
|
||||||
|
/// introduces latency.
|
||||||
|
pub fn is_bypass(mut self) -> Self {
|
||||||
|
self.flags.insert(ParamFlags::BYPASS);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Mark the paramter as non-automatable. This means that the parameter cannot be automated from
|
/// Mark the paramter as non-automatable. This means that the parameter cannot be automated from
|
||||||
/// the host. Setting this flag also prevents it from showing up in the host's own generic UI
|
/// the host. Setting this flag also prevents it from showing up in the host's own generic UI
|
||||||
/// for this plugin. The parameter can still be changed from the plugin's editor GUI.
|
/// for this plugin. The parameter can still be changed from the plugin's editor GUI.
|
||||||
|
|
|
@ -21,8 +21,6 @@ use crate::param::internals::Params;
|
||||||
/// - Sidechain inputs
|
/// - Sidechain inputs
|
||||||
/// - Multiple output busses
|
/// - Multiple output busses
|
||||||
/// - Special handling for offline processing
|
/// - Special handling for offline processing
|
||||||
/// - Bypass parameters, right now the plugin wrappers generates one for you but there's no way to
|
|
||||||
/// interact with it yet
|
|
||||||
/// - Outputting parameter changes from the plugin
|
/// - Outputting parameter changes from the plugin
|
||||||
/// - MIDI CC handling
|
/// - MIDI CC handling
|
||||||
/// - Outputting MIDI events from the process function (you can output parmaeter changes from an
|
/// - Outputting MIDI events from the process function (you can output parmaeter changes from an
|
||||||
|
|
|
@ -48,7 +48,6 @@ use clap_sys::process::{
|
||||||
use clap_sys::stream::{clap_istream, clap_ostream};
|
use clap_sys::stream::{clap_istream, clap_ostream};
|
||||||
use crossbeam::atomic::AtomicCell;
|
use crossbeam::atomic::AtomicCell;
|
||||||
use crossbeam::queue::ArrayQueue;
|
use crossbeam::queue::ArrayQueue;
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use raw_window_handle::RawWindowHandle;
|
use raw_window_handle::RawWindowHandle;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
@ -75,15 +74,9 @@ use crate::plugin::{
|
||||||
};
|
};
|
||||||
use crate::util::permit_alloc;
|
use crate::util::permit_alloc;
|
||||||
use crate::wrapper::state;
|
use crate::wrapper::state;
|
||||||
use crate::wrapper::util::{hash_param_id, process_wrapper, strlcpy};
|
use crate::wrapper::util::{
|
||||||
|
hash_param_id, process_wrapper, strlcpy, Bypass, BYPASS_PARAM_HASH, BYPASS_PARAM_ID,
|
||||||
/// Right now the wrapper adds its own bypass parameter.
|
};
|
||||||
///
|
|
||||||
/// TODO: Actually use this parameter.
|
|
||||||
pub const BYPASS_PARAM_ID: &str = "bypass";
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref BYPASS_PARAM_HASH: u32 = hash_param_id(BYPASS_PARAM_ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// How many output parameter changes we can store in our output parameter change queue. Storing
|
/// How many output parameter changes we can store in our output parameter change queue. Storing
|
||||||
/// more than this many parmaeters at a time will cause changes to get lost.
|
/// more than this many parmaeters at a time will cause changes to get lost.
|
||||||
|
@ -119,9 +112,9 @@ pub struct Wrapper<P: ClapPlugin> {
|
||||||
/// The current buffer configuration, containing the sample rate and the maximum block size.
|
/// The current buffer configuration, containing the sample rate and the maximum block size.
|
||||||
/// Will be set in `clap_plugin::activate()`.
|
/// Will be set in `clap_plugin::activate()`.
|
||||||
current_buffer_config: AtomicCell<Option<BufferConfig>>,
|
current_buffer_config: AtomicCell<Option<BufferConfig>>,
|
||||||
/// Whether the plugin is currently bypassed. This is not yet integrated with the `Plugin`
|
/// Contains either a boolean indicating whether the plugin is currently bypassed, or a bypass
|
||||||
/// trait.
|
/// parameter if the plugin has one.
|
||||||
bypass_state: AtomicBool,
|
bypass: Bypass,
|
||||||
/// The incoming events for the plugin, if `P::ACCEPTS_MIDI` is set.
|
/// The incoming events for the plugin, if `P::ACCEPTS_MIDI` is set.
|
||||||
///
|
///
|
||||||
/// TODO: Maybe load these lazily at some point instead of needing to spool them all to this
|
/// TODO: Maybe load these lazily at some point instead of needing to spool them all to this
|
||||||
|
@ -345,10 +338,6 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(id, _, _, _)| id.clone())
|
.map(|(id, _, _, _)| id.clone())
|
||||||
.collect();
|
.collect();
|
||||||
nih_debug_assert!(
|
|
||||||
!param_ids.contains(BYPASS_PARAM_ID),
|
|
||||||
"The wrapper already adds its own bypass parameter"
|
|
||||||
);
|
|
||||||
nih_debug_assert_eq!(
|
nih_debug_assert_eq!(
|
||||||
param_map.len(),
|
param_map.len(),
|
||||||
param_ids.len(),
|
param_ids.len(),
|
||||||
|
@ -356,6 +345,31 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The plugin either supplies its own bypass parameter, or we'll create one for it
|
||||||
|
let mut bypass = Bypass::Dummy(AtomicBool::new(false));
|
||||||
|
for (id, _, ptr, _) in ¶m_id_hashes_ptrs_groups {
|
||||||
|
let flags = unsafe { ptr.flags() };
|
||||||
|
let is_bypass = flags.contains(ParamFlags::BYPASS);
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
if id == BYPASS_PARAM_ID && !is_bypass {
|
||||||
|
nih_debug_assert_failure!("Bypass parameters need to be marked with `.is_bypass()`, weird things will happen");
|
||||||
|
}
|
||||||
|
if is_bypass && matches!(bypass, Bypass::Parameter(_)) {
|
||||||
|
nih_debug_assert_failure!(
|
||||||
|
"Duplicate bypass parameters found, using the first one"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_bypass {
|
||||||
|
bypass = Bypass::Parameter(*ptr);
|
||||||
|
if !cfg!(debug_assertions) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let param_hashes = param_id_hashes_ptrs_groups
|
let param_hashes = param_id_hashes_ptrs_groups
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(_, hash, _, _)| *hash)
|
.map(|(_, hash, _, _)| *hash)
|
||||||
|
@ -439,7 +453,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
num_output_channels: P::DEFAULT_NUM_OUTPUTS,
|
num_output_channels: P::DEFAULT_NUM_OUTPUTS,
|
||||||
}),
|
}),
|
||||||
current_buffer_config: AtomicCell::new(None),
|
current_buffer_config: AtomicCell::new(None),
|
||||||
bypass_state: AtomicBool::new(false),
|
bypass,
|
||||||
input_events: AtomicRefCell::new(VecDeque::with_capacity(512)),
|
input_events: AtomicRefCell::new(VecDeque::with_capacity(512)),
|
||||||
last_process_status: AtomicCell::new(ProcessStatus::Normal),
|
last_process_status: AtomicCell::new(ProcessStatus::Normal),
|
||||||
current_latency: AtomicU32::new(0),
|
current_latency: AtomicU32::new(0),
|
||||||
|
@ -589,33 +603,36 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
update: ClapParamUpdate,
|
update: ClapParamUpdate,
|
||||||
sample_rate: Option<f32>,
|
sample_rate: Option<f32>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if hash == *BYPASS_PARAM_HASH {
|
match (&self.bypass, self.param_by_hash.get(&hash)) {
|
||||||
match update {
|
(Bypass::Dummy(bypass_state), _) if hash == *BYPASS_PARAM_HASH => {
|
||||||
ClapParamUpdate::PlainValueSet(clap_plain_value) => self
|
match update {
|
||||||
.bypass_state
|
ClapParamUpdate::PlainValueSet(clap_plain_value) => {
|
||||||
.store(clap_plain_value >= 0.5, Ordering::SeqCst),
|
bypass_state.store(clap_plain_value >= 0.5, 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
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Also update the parameter's smoothing if applicable
|
true
|
||||||
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) },
|
|
||||||
}
|
}
|
||||||
|
(_, Some(param_ptr)) => {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
true
|
// Also update the parameter's smoothing if applicable
|
||||||
} else {
|
match (param_ptr, sample_rate) {
|
||||||
false
|
(_, 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
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1170,16 +1187,20 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only process audio if the plugin is not currently bypassed
|
// Only process audio if the plugin isn't bypassed. If the plugin provides its
|
||||||
let result = if !wrapper.bypass_state.load(Ordering::Relaxed) {
|
// own bypass parameter then it should decide what to do by itself.
|
||||||
let mut plugin = wrapper.plugin.write();
|
let result = match &wrapper.bypass {
|
||||||
let mut context = wrapper.make_process_context(transport);
|
Bypass::Dummy(bypass_state) if bypass_state.load(Ordering::Relaxed) => {
|
||||||
let result = plugin.process(&mut output_buffer, &mut context);
|
wrapper.last_process_status.store(ProcessStatus::Normal);
|
||||||
wrapper.last_process_status.store(result);
|
ProcessStatus::Normal
|
||||||
result
|
}
|
||||||
} else {
|
_ => {
|
||||||
wrapper.last_process_status.store(ProcessStatus::Normal);
|
let mut plugin = wrapper.plugin.write();
|
||||||
ProcessStatus::Normal
|
let mut context = wrapper.make_process_context(transport);
|
||||||
|
let result = plugin.process(&mut output_buffer, &mut context);
|
||||||
|
wrapper.last_process_status.store(result);
|
||||||
|
result
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let clap_result = match result {
|
let clap_result = match result {
|
||||||
|
@ -1710,9 +1731,13 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
check_null_ptr!(0, plugin);
|
check_null_ptr!(0, plugin);
|
||||||
let wrapper = &*(plugin as *const Self);
|
let wrapper = &*(plugin as *const Self);
|
||||||
|
|
||||||
// NOTE: We add a bypass parameter ourselves on index `plugin.param_hashes.len()`, so
|
// NOTE: We add a bypass parameter ourselves on index `self.inner.param_hashes.len()` if the
|
||||||
// these indices are all off by one
|
// plugin does not provide its own bypass parmaeter, in which case these indices will
|
||||||
wrapper.param_hashes.len() as u32 + 1
|
// all be off by one
|
||||||
|
match wrapper.bypass {
|
||||||
|
Bypass::Parameter(_) => wrapper.param_hashes.len() as u32,
|
||||||
|
Bypass::Dummy(_) => wrapper.param_hashes.len() as u32 + 1,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe extern "C" fn ext_params_get_info(
|
unsafe extern "C" fn ext_params_get_info(
|
||||||
|
@ -1723,8 +1748,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
check_null_ptr!(false, plugin, param_info);
|
check_null_ptr!(false, plugin, param_info);
|
||||||
let wrapper = &*(plugin as *const Self);
|
let wrapper = &*(plugin as *const Self);
|
||||||
|
|
||||||
// Parameter index `self.param_ids.len()` is our own bypass parameter
|
if param_index < 0 || param_index > Self::ext_params_count(plugin) as i32 {
|
||||||
if param_index < 0 || param_index > wrapper.param_hashes.len() as i32 {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1733,7 +1757,9 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
// TODO: We don't use the cookies at this point. In theory this would be faster than the ID
|
// TODO: We don't use the cookies at this point. In theory this would be faster than the ID
|
||||||
// hashmap lookup, but for now we'll stay consistent with the VST3 implementation.
|
// hashmap lookup, but for now we'll stay consistent with the VST3 implementation.
|
||||||
let param_info = &mut *param_info;
|
let param_info = &mut *param_info;
|
||||||
if param_index == wrapper.param_hashes.len() as i32 {
|
if matches!(wrapper.bypass, Bypass::Dummy(_))
|
||||||
|
&& param_index == wrapper.param_hashes.len() as i32
|
||||||
|
{
|
||||||
param_info.id = *BYPASS_PARAM_HASH;
|
param_info.id = *BYPASS_PARAM_HASH;
|
||||||
param_info.flags =
|
param_info.flags =
|
||||||
CLAP_PARAM_IS_STEPPED | CLAP_PARAM_IS_BYPASS | CLAP_PARAM_IS_AUTOMATABLE;
|
CLAP_PARAM_IS_STEPPED | CLAP_PARAM_IS_BYPASS | CLAP_PARAM_IS_AUTOMATABLE;
|
||||||
|
@ -1749,7 +1775,9 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
let param_ptr = &wrapper.param_by_hash[param_hash];
|
let param_ptr = &wrapper.param_by_hash[param_hash];
|
||||||
let default_value = param_ptr.default_normalized_value();
|
let default_value = param_ptr.default_normalized_value();
|
||||||
let step_count = param_ptr.step_count();
|
let step_count = param_ptr.step_count();
|
||||||
let automatable = !param_ptr.flags().contains(ParamFlags::NON_AUTOMATABLE);
|
let flags = param_ptr.flags();
|
||||||
|
let automatable = !flags.contains(ParamFlags::NON_AUTOMATABLE);
|
||||||
|
let is_bypass = flags.contains(ParamFlags::BYPASS);
|
||||||
|
|
||||||
param_info.id = *param_hash;
|
param_info.id = *param_hash;
|
||||||
param_info.flags = if automatable {
|
param_info.flags = if automatable {
|
||||||
|
@ -1757,6 +1785,9 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
} else {
|
} else {
|
||||||
CLAP_PARAM_IS_HIDDEN | CLAP_PARAM_IS_READONLY
|
CLAP_PARAM_IS_HIDDEN | CLAP_PARAM_IS_READONLY
|
||||||
};
|
};
|
||||||
|
if is_bypass {
|
||||||
|
param_info.flags |= CLAP_PARAM_IS_BYPASS
|
||||||
|
}
|
||||||
if step_count.is_some() {
|
if step_count.is_some() {
|
||||||
param_info.flags |= CLAP_PARAM_IS_STEPPED
|
param_info.flags |= CLAP_PARAM_IS_STEPPED
|
||||||
}
|
}
|
||||||
|
@ -1785,19 +1816,23 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
check_null_ptr!(false, plugin, value);
|
check_null_ptr!(false, plugin, value);
|
||||||
let wrapper = &*(plugin as *const Self);
|
let wrapper = &*(plugin as *const Self);
|
||||||
|
|
||||||
if param_id == *BYPASS_PARAM_HASH {
|
match (&wrapper.bypass, wrapper.param_by_hash.get(¶m_id)) {
|
||||||
*value = if wrapper.bypass_state.load(Ordering::SeqCst) {
|
(Bypass::Dummy(bypass_state), _) if param_id == *BYPASS_PARAM_HASH => {
|
||||||
1.0
|
*value = if bypass_state.load(Ordering::Relaxed) {
|
||||||
} else {
|
1.0
|
||||||
0.0
|
} else {
|
||||||
};
|
0.0
|
||||||
true
|
};
|
||||||
} else if let Some(param_ptr) = wrapper.param_by_hash.get(¶m_id) {
|
|
||||||
*value =
|
true
|
||||||
param_ptr.normalized_value() as f64 * param_ptr.step_count().unwrap_or(1) as f64;
|
}
|
||||||
true
|
(_, Some(param_ptr)) => {
|
||||||
} else {
|
*value = param_ptr.normalized_value() as f64
|
||||||
false
|
* param_ptr.step_count().unwrap_or(1) as f64;
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1813,27 +1848,29 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
|
|
||||||
let dest = std::slice::from_raw_parts_mut(display, size as usize);
|
let dest = std::slice::from_raw_parts_mut(display, size as usize);
|
||||||
|
|
||||||
if param_id == *BYPASS_PARAM_HASH {
|
match (&wrapper.bypass, wrapper.param_by_hash.get(¶m_id)) {
|
||||||
if value > 0.5 {
|
(Bypass::Dummy(_), _) if param_id == *BYPASS_PARAM_HASH => {
|
||||||
strlcpy(dest, "Bypassed")
|
if value > 0.5 {
|
||||||
} else {
|
strlcpy(dest, "Bypassed")
|
||||||
strlcpy(dest, "Not Bypassed")
|
} else {
|
||||||
|
strlcpy(dest, "Not Bypassed")
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
(_, Some(param_ptr)) => {
|
||||||
|
strlcpy(
|
||||||
|
dest,
|
||||||
|
// CLAP does not have a separate unit, so we'll include the unit here
|
||||||
|
¶m_ptr.normalized_value_to_string(
|
||||||
|
value as f32 / param_ptr.step_count().unwrap_or(1) as f32,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
true
|
true
|
||||||
} else if let Some(param_ptr) = wrapper.param_by_hash.get(¶m_id) {
|
}
|
||||||
strlcpy(
|
_ => false,
|
||||||
dest,
|
|
||||||
// CLAP does not have a separate unit, so we'll include the unit here
|
|
||||||
¶m_ptr.normalized_value_to_string(
|
|
||||||
value as f32 / param_ptr.step_count().unwrap_or(1) as f32,
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1851,25 +1888,30 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
Err(_) => return false,
|
Err(_) => return false,
|
||||||
};
|
};
|
||||||
|
|
||||||
if param_id == *BYPASS_PARAM_HASH {
|
match (&wrapper.bypass, wrapper.param_by_hash.get(¶m_id)) {
|
||||||
let normalized_valeu = match display {
|
(Bypass::Dummy(_), _) if param_id == *BYPASS_PARAM_HASH => {
|
||||||
"Bypassed" => 1.0,
|
let display = display.trim();
|
||||||
"Not Bypassed" => 0.0,
|
let normalized_valeu = if display.eq_ignore_ascii_case("bypassed") {
|
||||||
_ => return false,
|
1.0
|
||||||
};
|
} else if display.eq_ignore_ascii_case("not bypassed") {
|
||||||
*value = normalized_valeu;
|
0.0
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
*value = normalized_valeu;
|
||||||
|
|
||||||
true
|
true
|
||||||
} else if let Some(param_ptr) = wrapper.param_by_hash.get(¶m_id) {
|
}
|
||||||
let normalized_value = match param_ptr.string_to_normalized_value(display) {
|
(_, Some(param_ptr)) => {
|
||||||
Some(v) => v as f64,
|
let normalized_value = match param_ptr.string_to_normalized_value(display) {
|
||||||
None => return false,
|
Some(v) => v as f64,
|
||||||
};
|
None => return false,
|
||||||
*value = normalized_value * param_ptr.step_count().unwrap_or(1) as f64;
|
};
|
||||||
|
*value = normalized_value * param_ptr.step_count().unwrap_or(1) as f64;
|
||||||
|
|
||||||
true
|
true
|
||||||
} else {
|
}
|
||||||
false
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1901,8 +1943,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
wrapper.plugin.read().params(),
|
wrapper.plugin.read().params(),
|
||||||
&wrapper.param_by_hash,
|
&wrapper.param_by_hash,
|
||||||
&wrapper.param_id_to_hash,
|
&wrapper.param_id_to_hash,
|
||||||
BYPASS_PARAM_ID,
|
&wrapper.bypass,
|
||||||
&wrapper.bypass_state,
|
|
||||||
);
|
);
|
||||||
match serialized {
|
match serialized {
|
||||||
Ok(serialized) => {
|
Ok(serialized) => {
|
||||||
|
@ -1964,8 +2005,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
&wrapper.param_by_hash,
|
&wrapper.param_by_hash,
|
||||||
&wrapper.param_id_to_hash,
|
&wrapper.param_id_to_hash,
|
||||||
wrapper.current_buffer_config.load().as_ref(),
|
wrapper.current_buffer_config.load().as_ref(),
|
||||||
BYPASS_PARAM_ID,
|
&wrapper.bypass,
|
||||||
&wrapper.bypass_state,
|
|
||||||
);
|
);
|
||||||
if !success {
|
if !success {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
|
use super::util::{Bypass, BYPASS_PARAM_ID};
|
||||||
use crate::param::internals::{ParamPtr, Params};
|
use crate::param::internals::{ParamPtr, Params};
|
||||||
use crate::param::Param;
|
use crate::param::Param;
|
||||||
use crate::plugin::BufferConfig;
|
use crate::plugin::BufferConfig;
|
||||||
|
@ -40,8 +41,7 @@ pub(crate) unsafe fn serialize(
|
||||||
plugin_params: Pin<&dyn Params>,
|
plugin_params: Pin<&dyn Params>,
|
||||||
param_by_hash: &HashMap<u32, ParamPtr>,
|
param_by_hash: &HashMap<u32, ParamPtr>,
|
||||||
param_id_to_hash: &HashMap<String, u32>,
|
param_id_to_hash: &HashMap<String, u32>,
|
||||||
bypass_param_id: &str,
|
bypass: &Bypass,
|
||||||
bypass_state: &AtomicBool,
|
|
||||||
) -> serde_json::Result<Vec<u8>> {
|
) -> serde_json::Result<Vec<u8>> {
|
||||||
// We'll serialize parmaeter values as a simple `string_param_id: display_value` map.
|
// We'll serialize parmaeter values as a simple `string_param_id: display_value` map.
|
||||||
let mut params: HashMap<_, _> = param_id_to_hash
|
let mut params: HashMap<_, _> = param_id_to_hash
|
||||||
|
@ -72,11 +72,13 @@ pub(crate) unsafe fn serialize(
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Don't forget about the bypass parameter
|
// Don't forget about the bypass parameter if we added one for the plugin
|
||||||
params.insert(
|
if let Bypass::Dummy(bypass_state) = bypass {
|
||||||
bypass_param_id.to_string(),
|
params.insert(
|
||||||
ParamValue::Bool(bypass_state.load(Ordering::SeqCst)),
|
String::from(BYPASS_PARAM_ID),
|
||||||
);
|
ParamValue::Bool(bypass_state.load(Ordering::SeqCst)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// The plugin can also persist arbitrary fields alongside its parameters. This is useful for
|
// The plugin can also persist arbitrary fields alongside its parameters. This is useful for
|
||||||
// storing things like sample data.
|
// storing things like sample data.
|
||||||
|
@ -97,8 +99,7 @@ pub(crate) unsafe fn deserialize(
|
||||||
param_by_hash: &HashMap<u32, ParamPtr>,
|
param_by_hash: &HashMap<u32, ParamPtr>,
|
||||||
param_id_to_hash: &HashMap<String, u32>,
|
param_id_to_hash: &HashMap<String, u32>,
|
||||||
current_buffer_config: Option<&BufferConfig>,
|
current_buffer_config: Option<&BufferConfig>,
|
||||||
bypass_param_id: &str,
|
bypass: &Bypass,
|
||||||
bypass_state: &AtomicBool,
|
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let state: State = match serde_json::from_slice(state) {
|
let state: State = match serde_json::from_slice(state) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
|
@ -110,52 +111,55 @@ pub(crate) unsafe fn deserialize(
|
||||||
|
|
||||||
let sample_rate = current_buffer_config.map(|c| c.sample_rate);
|
let sample_rate = current_buffer_config.map(|c| c.sample_rate);
|
||||||
for (param_id_str, param_value) in state.params {
|
for (param_id_str, param_value) in state.params {
|
||||||
// Handle the bypass parameter separately
|
// Handle the automatically generated bypass parameter separately
|
||||||
if param_id_str == bypass_param_id {
|
match bypass {
|
||||||
match param_value {
|
Bypass::Dummy(bypass_state) if param_id_str == BYPASS_PARAM_ID => {
|
||||||
ParamValue::Bool(b) => bypass_state.store(b, Ordering::SeqCst),
|
match param_value {
|
||||||
_ => nih_debug_assert_failure!(
|
ParamValue::Bool(b) => bypass_state.store(b, Ordering::SeqCst),
|
||||||
"Invalid serialized value {:?} for parameter \"{}\"",
|
_ => nih_debug_assert_failure!(
|
||||||
param_value,
|
"Invalid serialized value {:?} for parameter \"{}\"",
|
||||||
param_id_str,
|
param_value,
|
||||||
),
|
param_id_str,
|
||||||
};
|
),
|
||||||
continue;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
let param_ptr = match param_id_to_hash
|
|
||||||
.get(param_id_str.as_str())
|
|
||||||
.and_then(|hash| param_by_hash.get(hash))
|
|
||||||
{
|
|
||||||
Some(ptr) => ptr,
|
|
||||||
None => {
|
|
||||||
nih_debug_assert_failure!("Unknown parameter: {}", param_id_str);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
_ => {
|
||||||
|
let param_ptr = match param_id_to_hash
|
||||||
|
.get(param_id_str.as_str())
|
||||||
|
.and_then(|hash| param_by_hash.get(hash))
|
||||||
|
{
|
||||||
|
Some(ptr) => ptr,
|
||||||
|
None => {
|
||||||
|
nih_debug_assert_failure!("Unknown parameter: {}", param_id_str);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
match (param_ptr, param_value) {
|
match (param_ptr, param_value) {
|
||||||
(ParamPtr::FloatParam(p), ParamValue::F32(v)) => (**p).set_plain_value(v),
|
(ParamPtr::FloatParam(p), ParamValue::F32(v)) => (**p).set_plain_value(v),
|
||||||
(ParamPtr::IntParam(p), ParamValue::I32(v)) => (**p).set_plain_value(v),
|
(ParamPtr::IntParam(p), ParamValue::I32(v)) => (**p).set_plain_value(v),
|
||||||
(ParamPtr::BoolParam(p), ParamValue::Bool(v)) => (**p).set_plain_value(v),
|
(ParamPtr::BoolParam(p), ParamValue::Bool(v)) => (**p).set_plain_value(v),
|
||||||
// Enums are serialized based on the active variant's index (which may not be the
|
// Enums are serialized based on the active variant's index (which may not be the
|
||||||
// same as the discriminator)
|
// same as the discriminator)
|
||||||
(ParamPtr::EnumParam(p), ParamValue::I32(variant_idx)) => {
|
(ParamPtr::EnumParam(p), ParamValue::I32(variant_idx)) => {
|
||||||
(**p).set_plain_value(variant_idx)
|
(**p).set_plain_value(variant_idx)
|
||||||
}
|
}
|
||||||
(param_ptr, param_value) => {
|
(param_ptr, param_value) => {
|
||||||
nih_debug_assert_failure!(
|
nih_debug_assert_failure!(
|
||||||
"Invalid serialized value {:?} for parameter \"{}\" ({:?})",
|
"Invalid serialized value {:?} for parameter \"{}\" ({:?})",
|
||||||
param_value,
|
param_value,
|
||||||
param_id_str,
|
param_id_str,
|
||||||
param_ptr,
|
param_ptr,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure everything starts out in sync
|
// Make sure everything starts out in sync
|
||||||
if let Some(sample_rate) = sample_rate {
|
if let Some(sample_rate) = sample_rate {
|
||||||
param_ptr.update_smoother(sample_rate, true);
|
param_ptr.update_smoother(sample_rate, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,36 @@
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::os::raw::c_char;
|
use std::os::raw::c_char;
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
use vst3_sys::vst::TChar;
|
use vst3_sys::vst::TChar;
|
||||||
use widestring::U16CString;
|
use widestring::U16CString;
|
||||||
|
|
||||||
|
use crate::param::internals::ParamPtr;
|
||||||
|
|
||||||
#[cfg(all(debug_assertions, feature = "assert_process_allocs"))]
|
#[cfg(all(debug_assertions, feature = "assert_process_allocs"))]
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
static A: assert_no_alloc::AllocDisabler = assert_no_alloc::AllocDisabler;
|
static A: assert_no_alloc::AllocDisabler = assert_no_alloc::AllocDisabler;
|
||||||
|
|
||||||
|
/// The ID of the automatically generated bypass parameter. Added if the plugin does not define its
|
||||||
|
/// own bypass parameter.
|
||||||
|
pub const BYPASS_PARAM_ID: &str = "bypass";
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref BYPASS_PARAM_HASH: u32 = hash_param_id(BYPASS_PARAM_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The plugin's bypass parameter. If [`Plugin::params()`] contains a [`ParamFlags::BYPASS`]
|
||||||
|
/// parameter then that will be used as the plugin's bypass parameter. Otherwise NIH-plug will add a
|
||||||
|
/// dummy boolean parameter.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Bypass {
|
||||||
|
/// A parameter from `P` that's marked as a bypass parameter.
|
||||||
|
Parameter(ParamPtr),
|
||||||
|
/// A boolean that keeps track of the plugin's bypass state. Added automatically if the plugin
|
||||||
|
/// doesn't have a bypass parameter.
|
||||||
|
Dummy(AtomicBool),
|
||||||
|
}
|
||||||
|
|
||||||
/// A Rabin fingerprint based string hash for parameter ID strings.
|
/// A Rabin fingerprint based string hash for parameter ID strings.
|
||||||
pub fn hash_param_id(id: &str) -> u32 {
|
pub fn hash_param_id(id: &str) -> u32 {
|
||||||
let mut overflow;
|
let mut overflow;
|
||||||
|
|
|
@ -11,14 +11,15 @@ use vst3_sys::vst::IComponentHandler;
|
||||||
|
|
||||||
use super::context::{WrapperGuiContext, WrapperProcessContext};
|
use super::context::{WrapperGuiContext, WrapperProcessContext};
|
||||||
use super::param_units::ParamUnits;
|
use super::param_units::ParamUnits;
|
||||||
use super::util::{ObjectPtr, VstPtr, BYPASS_PARAM_HASH, BYPASS_PARAM_ID};
|
use super::util::{ObjectPtr, VstPtr};
|
||||||
use super::view::WrapperView;
|
use super::view::WrapperView;
|
||||||
use crate::buffer::Buffer;
|
use crate::buffer::Buffer;
|
||||||
use crate::context::Transport;
|
use crate::context::Transport;
|
||||||
use crate::event_loop::{EventLoop, MainThreadExecutor, OsEventLoop};
|
use crate::event_loop::{EventLoop, MainThreadExecutor, OsEventLoop};
|
||||||
use crate::param::internals::ParamPtr;
|
use crate::param::internals::ParamPtr;
|
||||||
|
use crate::param::ParamFlags;
|
||||||
use crate::plugin::{BufferConfig, BusConfig, Editor, NoteEvent, ProcessStatus, Vst3Plugin};
|
use crate::plugin::{BufferConfig, BusConfig, Editor, NoteEvent, ProcessStatus, Vst3Plugin};
|
||||||
use crate::wrapper::util::hash_param_id;
|
use crate::wrapper::util::{hash_param_id, Bypass, BYPASS_PARAM_HASH, BYPASS_PARAM_ID};
|
||||||
|
|
||||||
/// The actual wrapper bits. We need this as an `Arc<T>` so we can safely use our event loop API.
|
/// The actual wrapper bits. We need this as an `Arc<T>` so we can safely use our event loop API.
|
||||||
/// Since we can't combine that with VST3's interior reference counting this just has to be moved to
|
/// Since we can't combine that with VST3's interior reference counting this just has to be moved to
|
||||||
|
@ -56,9 +57,9 @@ pub(crate) struct WrapperInner<P: Vst3Plugin> {
|
||||||
/// The current buffer configuration, containing the sample rate and the maximum block size.
|
/// The current buffer configuration, containing the sample rate and the maximum block size.
|
||||||
/// Will be set in `IAudioProcessor::setupProcessing()`.
|
/// Will be set in `IAudioProcessor::setupProcessing()`.
|
||||||
pub current_buffer_config: AtomicCell<Option<BufferConfig>>,
|
pub current_buffer_config: AtomicCell<Option<BufferConfig>>,
|
||||||
/// Whether the plugin is currently bypassed. This is not yet integrated with the `Plugin`
|
/// Contains either a boolean indicating whether the plugin is currently bypassed, or a bypass
|
||||||
/// trait.
|
/// parameter if the plugin has one.
|
||||||
pub bypass_state: AtomicBool,
|
pub bypass: Bypass,
|
||||||
/// The last process status returned by the plugin. This is used for tail handling.
|
/// The last process status returned by the plugin. This is used for tail handling.
|
||||||
pub last_process_status: AtomicCell<ProcessStatus>,
|
pub last_process_status: AtomicCell<ProcessStatus>,
|
||||||
/// The current latency in samples, as set by the plugin through the [`ProcessContext`].
|
/// The current latency in samples, as set by the plugin through the [`ProcessContext`].
|
||||||
|
@ -153,10 +154,6 @@ impl<P: Vst3Plugin> WrapperInner<P> {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(id, _, _, _)| id.clone())
|
.map(|(id, _, _, _)| id.clone())
|
||||||
.collect();
|
.collect();
|
||||||
nih_debug_assert!(
|
|
||||||
!param_ids.contains(BYPASS_PARAM_ID),
|
|
||||||
"The wrapper already adds its own bypass parameter"
|
|
||||||
);
|
|
||||||
nih_debug_assert_eq!(
|
nih_debug_assert_eq!(
|
||||||
param_map.len(),
|
param_map.len(),
|
||||||
param_ids.len(),
|
param_ids.len(),
|
||||||
|
@ -164,13 +161,38 @@ impl<P: Vst3Plugin> WrapperInner<P> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The plugin either supplies its own bypass parameter, or we'll create one for it
|
||||||
|
let mut bypass = Bypass::Dummy(AtomicBool::new(false));
|
||||||
|
for (id, _, ptr, _) in ¶m_id_hashes_ptrs_groups {
|
||||||
|
let flags = unsafe { ptr.flags() };
|
||||||
|
let is_bypass = flags.contains(ParamFlags::BYPASS);
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
if id == BYPASS_PARAM_ID && !is_bypass {
|
||||||
|
nih_debug_assert_failure!("Bypass parameters need to be marked with `.is_bypass()`, weird things will happen");
|
||||||
|
}
|
||||||
|
if is_bypass && matches!(bypass, Bypass::Parameter(_)) {
|
||||||
|
nih_debug_assert_failure!(
|
||||||
|
"Duplicate bypass parameters found, using the first one"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_bypass {
|
||||||
|
bypass = Bypass::Parameter(*ptr);
|
||||||
|
if !cfg!(debug_assertions) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let param_hashes = param_id_hashes_ptrs_groups
|
let param_hashes = param_id_hashes_ptrs_groups
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&(_, hash, _, _)| hash)
|
.map(|(_, hash, _, _)| *hash)
|
||||||
.collect();
|
.collect();
|
||||||
let param_by_hash = param_id_hashes_ptrs_groups
|
let param_by_hash = param_id_hashes_ptrs_groups
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&(_, hash, ptr, _)| (hash, ptr))
|
.map(|(_, hash, ptr, _)| (*hash, *ptr))
|
||||||
.collect();
|
.collect();
|
||||||
let param_units = ParamUnits::from_param_groups(
|
let param_units = ParamUnits::from_param_groups(
|
||||||
param_id_hashes_ptrs_groups
|
param_id_hashes_ptrs_groups
|
||||||
|
@ -207,7 +229,7 @@ impl<P: Vst3Plugin> WrapperInner<P> {
|
||||||
num_output_channels: P::DEFAULT_NUM_OUTPUTS,
|
num_output_channels: P::DEFAULT_NUM_OUTPUTS,
|
||||||
}),
|
}),
|
||||||
current_buffer_config: AtomicCell::new(None),
|
current_buffer_config: AtomicCell::new(None),
|
||||||
bypass_state: AtomicBool::new(false),
|
bypass,
|
||||||
last_process_status: AtomicCell::new(ProcessStatus::Normal),
|
last_process_status: AtomicCell::new(ProcessStatus::Normal),
|
||||||
current_latency: AtomicU32::new(0),
|
current_latency: AtomicU32::new(0),
|
||||||
output_buffer: AtomicRefCell::new(Buffer::default()),
|
output_buffer: AtomicRefCell::new(Buffer::default()),
|
||||||
|
@ -271,24 +293,25 @@ impl<P: Vst3Plugin> WrapperInner<P> {
|
||||||
normalized_value: f32,
|
normalized_value: f32,
|
||||||
sample_rate: Option<f32>,
|
sample_rate: Option<f32>,
|
||||||
) -> tresult {
|
) -> tresult {
|
||||||
if hash == *BYPASS_PARAM_HASH {
|
match (&self.bypass, self.param_by_hash.get(&hash)) {
|
||||||
self.bypass_state
|
(Bypass::Dummy(bypass_state), _) if hash == *BYPASS_PARAM_HASH => {
|
||||||
.store(normalized_value >= 0.5, Ordering::SeqCst);
|
bypass_state.store(normalized_value >= 0.5, Ordering::Relaxed);
|
||||||
|
|
||||||
kResultOk
|
kResultOk
|
||||||
} else if let Some(param_ptr) = self.param_by_hash.get(&hash) {
|
|
||||||
// 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) },
|
|
||||||
}
|
}
|
||||||
|
(_, Some(param_ptr)) => {
|
||||||
|
// 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) },
|
||||||
|
}
|
||||||
|
|
||||||
kResultOk
|
kResultOk
|
||||||
} else {
|
}
|
||||||
kInvalidArgument
|
_ => kInvalidArgument,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,6 @@
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use vst3_sys::{interfaces::IUnknown, ComInterface};
|
use vst3_sys::{interfaces::IUnknown, ComInterface};
|
||||||
|
|
||||||
use crate::wrapper::util::hash_param_id;
|
|
||||||
|
|
||||||
/// Right now the wrapper adds its own bypass parameter.
|
|
||||||
///
|
|
||||||
/// TODO: Actually use this parameter.
|
|
||||||
pub const BYPASS_PARAM_ID: &str = "bypass";
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref BYPASS_PARAM_HASH: u32 = hash_param_id(BYPASS_PARAM_ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Early exit out of a VST3 function when one of the passed pointers is null
|
/// Early exit out of a VST3 function when one of the passed pointers is null
|
||||||
macro_rules! check_null_ptr {
|
macro_rules! check_null_ptr {
|
||||||
($ptr:expr $(, $ptrs:expr)* $(, )?) => {
|
($ptr:expr $(, $ptrs:expr)* $(, )?) => {
|
||||||
|
|
|
@ -15,13 +15,13 @@ use vst3_sys::VST3;
|
||||||
use widestring::U16CStr;
|
use widestring::U16CStr;
|
||||||
|
|
||||||
use super::inner::WrapperInner;
|
use super::inner::WrapperInner;
|
||||||
use super::util::{VstPtr, BYPASS_PARAM_HASH, BYPASS_PARAM_ID};
|
use super::util::VstPtr;
|
||||||
use super::view::WrapperView;
|
use super::view::WrapperView;
|
||||||
use crate::context::Transport;
|
use crate::context::Transport;
|
||||||
use crate::param::ParamFlags;
|
use crate::param::ParamFlags;
|
||||||
use crate::plugin::{BufferConfig, BusConfig, NoteEvent, ProcessStatus, Vst3Plugin};
|
use crate::plugin::{BufferConfig, BusConfig, NoteEvent, ProcessStatus, Vst3Plugin};
|
||||||
use crate::wrapper::state;
|
use crate::wrapper::state;
|
||||||
use crate::wrapper::util::{process_wrapper, u16strlcpy};
|
use crate::wrapper::util::{process_wrapper, u16strlcpy, Bypass, BYPASS_PARAM_HASH};
|
||||||
use crate::wrapper::vst3::inner::ParameterChange;
|
use crate::wrapper::vst3::inner::ParameterChange;
|
||||||
|
|
||||||
// Alias needed for the VST3 attribute macro
|
// Alias needed for the VST3 attribute macro
|
||||||
|
@ -226,8 +226,7 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
|
||||||
&self.inner.param_by_hash,
|
&self.inner.param_by_hash,
|
||||||
&self.inner.param_id_to_hash,
|
&self.inner.param_id_to_hash,
|
||||||
self.inner.current_buffer_config.load().as_ref(),
|
self.inner.current_buffer_config.load().as_ref(),
|
||||||
BYPASS_PARAM_ID,
|
&self.inner.bypass,
|
||||||
&self.inner.bypass_state,
|
|
||||||
);
|
);
|
||||||
if !success {
|
if !success {
|
||||||
return kResultFalse;
|
return kResultFalse;
|
||||||
|
@ -261,8 +260,7 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
|
||||||
self.inner.plugin.read().params(),
|
self.inner.plugin.read().params(),
|
||||||
&self.inner.param_by_hash,
|
&self.inner.param_by_hash,
|
||||||
&self.inner.param_id_to_hash,
|
&self.inner.param_id_to_hash,
|
||||||
BYPASS_PARAM_ID,
|
&self.inner.bypass,
|
||||||
&self.inner.bypass_state,
|
|
||||||
);
|
);
|
||||||
match serialized {
|
match serialized {
|
||||||
Ok(serialized) => {
|
Ok(serialized) => {
|
||||||
|
@ -304,9 +302,13 @@ impl<P: Vst3Plugin> IEditController for Wrapper<P> {
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn get_parameter_count(&self) -> i32 {
|
unsafe fn get_parameter_count(&self) -> i32 {
|
||||||
// NOTE: We add a bypass parameter ourselves on index `self.inner.param_hashes.len()`, so
|
// NOTE: We add a bypass parameter ourselves on index `self.inner.param_hashes.len()` if the
|
||||||
// these indices are all off by one
|
// plugin does not provide its own bypass parmaeter, in which case these indices will
|
||||||
self.inner.param_hashes.len() as i32 + 1
|
// all be off by one
|
||||||
|
match self.inner.bypass {
|
||||||
|
Bypass::Parameter(_) => self.inner.param_hashes.len() as i32,
|
||||||
|
Bypass::Dummy(_) => self.inner.param_hashes.len() as i32 + 1,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn get_parameter_info(
|
unsafe fn get_parameter_info(
|
||||||
|
@ -316,15 +318,16 @@ impl<P: Vst3Plugin> IEditController for Wrapper<P> {
|
||||||
) -> tresult {
|
) -> tresult {
|
||||||
check_null_ptr!(info);
|
check_null_ptr!(info);
|
||||||
|
|
||||||
// Parameter index `self.param_ids.len()` is our own bypass parameter
|
if param_index < 0 || param_index > self.get_parameter_count() {
|
||||||
if param_index < 0 || param_index > self.inner.param_hashes.len() as i32 {
|
|
||||||
return kInvalidArgument;
|
return kInvalidArgument;
|
||||||
}
|
}
|
||||||
|
|
||||||
*info = std::mem::zeroed();
|
*info = std::mem::zeroed();
|
||||||
|
|
||||||
let info = &mut *info;
|
let info = &mut *info;
|
||||||
if param_index == self.inner.param_hashes.len() as i32 {
|
if matches!(self.inner.bypass, Bypass::Dummy(_))
|
||||||
|
&& param_index == self.inner.param_hashes.len() as i32
|
||||||
|
{
|
||||||
info.id = *BYPASS_PARAM_HASH;
|
info.id = *BYPASS_PARAM_HASH;
|
||||||
u16strlcpy(&mut info.title, "Bypass");
|
u16strlcpy(&mut info.title, "Bypass");
|
||||||
u16strlcpy(&mut info.short_title, "Bypass");
|
u16strlcpy(&mut info.short_title, "Bypass");
|
||||||
|
@ -343,7 +346,9 @@ impl<P: Vst3Plugin> IEditController for Wrapper<P> {
|
||||||
.expect("Inconsistent parameter data");
|
.expect("Inconsistent parameter data");
|
||||||
let param_ptr = &self.inner.param_by_hash[param_hash];
|
let param_ptr = &self.inner.param_by_hash[param_hash];
|
||||||
let default_value = param_ptr.default_normalized_value();
|
let default_value = param_ptr.default_normalized_value();
|
||||||
let automatable = !param_ptr.flags().contains(ParamFlags::NON_AUTOMATABLE);
|
let flags = param_ptr.flags();
|
||||||
|
let automatable = !flags.contains(ParamFlags::NON_AUTOMATABLE);
|
||||||
|
let is_bypass = flags.contains(ParamFlags::BYPASS);
|
||||||
|
|
||||||
info.id = *param_hash;
|
info.id = *param_hash;
|
||||||
u16strlcpy(&mut info.title, param_ptr.name());
|
u16strlcpy(&mut info.title, param_ptr.name());
|
||||||
|
@ -357,6 +362,9 @@ impl<P: Vst3Plugin> IEditController for Wrapper<P> {
|
||||||
} else {
|
} else {
|
||||||
vst3_sys::vst::ParameterFlags::kIsReadOnly as i32 | (1 << 4) // kIsHidden
|
vst3_sys::vst::ParameterFlags::kIsReadOnly as i32 | (1 << 4) // kIsHidden
|
||||||
};
|
};
|
||||||
|
if is_bypass {
|
||||||
|
info.flags |= vst3_sys::vst::ParameterFlags::kIsBypass as i32;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kResultOk
|
kResultOk
|
||||||
|
@ -370,26 +378,27 @@ impl<P: Vst3Plugin> IEditController for Wrapper<P> {
|
||||||
) -> tresult {
|
) -> tresult {
|
||||||
check_null_ptr!(string);
|
check_null_ptr!(string);
|
||||||
|
|
||||||
// Somehow there's no length there, so we'll assume our own maximum
|
|
||||||
let dest = &mut *(string as *mut [TChar; 128]);
|
let dest = &mut *(string as *mut [TChar; 128]);
|
||||||
|
|
||||||
if id == *BYPASS_PARAM_HASH {
|
match (&self.inner.bypass, self.inner.param_by_hash.get(&id)) {
|
||||||
if value_normalized > 0.5 {
|
(Bypass::Dummy(_), _) if id == *BYPASS_PARAM_HASH => {
|
||||||
u16strlcpy(dest, "Bypassed")
|
if value_normalized > 0.5 {
|
||||||
} else {
|
u16strlcpy(dest, "Bypassed")
|
||||||
u16strlcpy(dest, "Not Bypassed")
|
} else {
|
||||||
|
u16strlcpy(dest, "Not Bypassed")
|
||||||
|
}
|
||||||
|
|
||||||
|
kResultOk
|
||||||
}
|
}
|
||||||
|
(_, Some(param_ptr)) => {
|
||||||
|
u16strlcpy(
|
||||||
|
dest,
|
||||||
|
¶m_ptr.normalized_value_to_string(value_normalized as f32, false),
|
||||||
|
);
|
||||||
|
|
||||||
kResultOk
|
kResultOk
|
||||||
} else if let Some(param_ptr) = self.inner.param_by_hash.get(&id) {
|
}
|
||||||
u16strlcpy(
|
_ => kInvalidArgument,
|
||||||
dest,
|
|
||||||
¶m_ptr.normalized_value_to_string(value_normalized as f32, false),
|
|
||||||
);
|
|
||||||
|
|
||||||
kResultOk
|
|
||||||
} else {
|
|
||||||
kInvalidArgument
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -406,59 +415,60 @@ impl<P: Vst3Plugin> IEditController for Wrapper<P> {
|
||||||
Err(_) => return kInvalidArgument,
|
Err(_) => return kInvalidArgument,
|
||||||
};
|
};
|
||||||
|
|
||||||
if id == *BYPASS_PARAM_HASH {
|
match (&self.inner.bypass, self.inner.param_by_hash.get(&id)) {
|
||||||
let value = match string.as_str() {
|
(Bypass::Dummy(_), _) if id == *BYPASS_PARAM_HASH => {
|
||||||
"Bypassed" => 1.0,
|
let string = string.trim();
|
||||||
"Not Bypassed" => 0.0,
|
let value = if string.eq_ignore_ascii_case("bypassed") {
|
||||||
_ => return kResultFalse,
|
1.0
|
||||||
};
|
} else if string.eq_ignore_ascii_case("not bypassed") {
|
||||||
*value_normalized = value;
|
0.0
|
||||||
|
} else {
|
||||||
|
return kInvalidArgument;
|
||||||
|
};
|
||||||
|
*value_normalized = value;
|
||||||
|
|
||||||
kResultOk
|
kResultOk
|
||||||
} else if let Some(param_ptr) = self.inner.param_by_hash.get(&id) {
|
}
|
||||||
let value = match param_ptr.string_to_normalized_value(&string) {
|
(_, Some(param_ptr)) => {
|
||||||
Some(v) => v as f64,
|
let value = match param_ptr.string_to_normalized_value(&string) {
|
||||||
None => return kResultFalse,
|
Some(v) => v as f64,
|
||||||
};
|
None => return kResultFalse,
|
||||||
*value_normalized = value;
|
};
|
||||||
|
*value_normalized = value;
|
||||||
|
|
||||||
kResultOk
|
kResultOk
|
||||||
} else {
|
}
|
||||||
kInvalidArgument
|
_ => kInvalidArgument,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn normalized_param_to_plain(&self, id: u32, value_normalized: f64) -> f64 {
|
unsafe fn normalized_param_to_plain(&self, id: u32, value_normalized: f64) -> f64 {
|
||||||
if id == *BYPASS_PARAM_HASH {
|
match (&self.inner.bypass, self.inner.param_by_hash.get(&id)) {
|
||||||
value_normalized
|
(Bypass::Dummy(_), _) if id == *BYPASS_PARAM_HASH => value_normalized.clamp(0.0, 1.0),
|
||||||
} else if let Some(param_ptr) = self.inner.param_by_hash.get(&id) {
|
(_, Some(param_ptr)) => param_ptr.preview_plain(value_normalized as f32) as f64,
|
||||||
param_ptr.preview_plain(value_normalized as f32) as f64
|
_ => 0.5,
|
||||||
} else {
|
|
||||||
0.5
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn plain_param_to_normalized(&self, id: u32, plain_value: f64) -> f64 {
|
unsafe fn plain_param_to_normalized(&self, id: u32, plain_value: f64) -> f64 {
|
||||||
if id == *BYPASS_PARAM_HASH {
|
match (&self.inner.bypass, self.inner.param_by_hash.get(&id)) {
|
||||||
plain_value.clamp(0.0, 1.0)
|
(Bypass::Dummy(_), _) if id == *BYPASS_PARAM_HASH => plain_value.clamp(0.0, 1.0),
|
||||||
} else if let Some(param_ptr) = self.inner.param_by_hash.get(&id) {
|
(_, Some(param_ptr)) => param_ptr.preview_normalized(plain_value as f32) as f64,
|
||||||
param_ptr.preview_normalized(plain_value as f32) as f64
|
_ => 0.5,
|
||||||
} else {
|
|
||||||
0.5
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn get_param_normalized(&self, id: u32) -> f64 {
|
unsafe fn get_param_normalized(&self, id: u32) -> f64 {
|
||||||
if id == *BYPASS_PARAM_HASH {
|
match (&self.inner.bypass, self.inner.param_by_hash.get(&id)) {
|
||||||
if self.inner.bypass_state.load(Ordering::SeqCst) {
|
(Bypass::Dummy(bypass_state), _) if id == *BYPASS_PARAM_HASH => {
|
||||||
1.0
|
if bypass_state.load(Ordering::SeqCst) {
|
||||||
} else {
|
1.0
|
||||||
0.0
|
} else {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if let Some(param_ptr) = self.inner.param_by_hash.get(&id) {
|
(_, Some(param_ptr)) => param_ptr.normalized_value() as f64,
|
||||||
param_ptr.normalized_value() as f64
|
_ => 0.5,
|
||||||
} else {
|
|
||||||
0.5
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -921,21 +931,25 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
|
||||||
let mut plugin = self.inner.plugin.write();
|
let mut plugin = self.inner.plugin.write();
|
||||||
let mut context = self.inner.make_process_context(transport);
|
let mut context = self.inner.make_process_context(transport);
|
||||||
|
|
||||||
// Only process audio if the plugin isn't bypassed
|
// Only process audio if the plugin isn't bypassed. If the plugin provides its
|
||||||
if !self.inner.bypass_state.load(Ordering::Relaxed) {
|
// own bypass parameter then it should decide what to do by itself.
|
||||||
let result = plugin.process(&mut output_buffer, &mut context);
|
match &self.inner.bypass {
|
||||||
self.inner.last_process_status.store(result);
|
Bypass::Dummy(bypass_state) if bypass_state.load(Ordering::Relaxed) => {
|
||||||
match result {
|
self.inner.last_process_status.store(ProcessStatus::Normal);
|
||||||
ProcessStatus::Error(err) => {
|
kResultOk
|
||||||
nih_debug_assert_failure!("Process error: {}", err);
|
}
|
||||||
|
_ => {
|
||||||
return kResultFalse;
|
let result = plugin.process(&mut output_buffer, &mut context);
|
||||||
}
|
self.inner.last_process_status.store(result);
|
||||||
_ => kResultOk,
|
match result {
|
||||||
|
ProcessStatus::Error(err) => {
|
||||||
|
nih_debug_assert_failure!("Process error: {}", err);
|
||||||
|
|
||||||
|
return kResultFalse;
|
||||||
|
}
|
||||||
|
_ => kResultOk,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
self.inner.last_process_status.store(ProcessStatus::Normal);
|
|
||||||
kResultOk
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue