1
0
Fork 0

Add somewhat shady enum parameters

This commit is contained in:
Robbert van der Helm 2022-02-14 02:04:17 +01:00
parent d878fd692a
commit 39e8dfc83c
7 changed files with 270 additions and 3 deletions

45
Cargo.lock generated
View file

@ -276,6 +276,7 @@ name = "diopser"
version = "0.1.0"
dependencies = [
"nih_plug",
"strum",
]
[[package]]
@ -399,6 +400,15 @@ dependencies = [
"xml-rs",
]
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "itoa"
version = "1.0.1"
@ -519,6 +529,7 @@ dependencies = [
"raw-window-handle",
"serde",
"serde_json",
"strum",
"vst3-sys",
"widestring",
"windows",
@ -693,6 +704,12 @@ dependencies = [
"bitflags",
]
[[package]]
name = "rustversion"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f"
[[package]]
name = "ryu"
version = "1.0.9"
@ -783,6 +800,28 @@ dependencies = [
"wayland-client",
]
[[package]]
name = "strum"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cae14b91c7d11c9a851d3fbc80a963198998c2a64eec840477fa92d8ce9b70bb"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bb0dc7ee9c15cea6199cde9a127fa16a4c5819af85395457ad72d68edc85a38"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]]
name = "syn"
version = "1.0.86"
@ -800,6 +839,12 @@ version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ccbe8381883510b6a2d8f1e32905bddd178c11caef8083086d0c0c9ab0ac281"
[[package]]
name = "unicode-segmentation"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
[[package]]
name = "unicode-xid"
version = "0.2.2"

View file

@ -33,6 +33,7 @@ parking_lot = "0.12"
raw-window-handle = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
strum = { version = "0.23", features = ["derive"] }
# This contains a number of fixes for the reference counting, cross compilation
# support, and an incorrect return type
vst3-sys = { git = "https://github.com/robbert-vdh/vst3-sys.git", branch = "fix/atomic-reference-count" }

View file

@ -17,6 +17,8 @@ pub use param::internals::Params;
pub use param::range::Range;
pub use param::smoothing::{Smoother, SmoothingStyle};
pub use param::{BoolParam, FloatParam, IntParam, Param};
// TODO: Consider re-exporting these from another module so you can import them all at once
pub use param::{Display, EnumIter, EnumParam};
pub use plugin::{
BufferConfig, BusConfig, Editor, NoteEvent, ParentWindowHandle, Plugin, ProcessStatus,
Vst3Plugin,

View file

@ -5,13 +5,17 @@
use std::fmt::Display;
use std::sync::Arc;
use self::range::{NormalizebleRange, Range};
use self::smoothing::{Smoother, SmoothingStyle};
// Re-export for the [EnumParam]
// TODO: Consider re-exporting this from a non-root module to make it a bit less spammy:w
pub use strum::{Display, EnumIter, IntoEnumIterator as EnumIter};
pub mod internals;
pub mod range;
pub mod smoothing;
use self::range::{NormalizebleRange, Range};
use self::smoothing::{Smoother, SmoothingStyle};
pub type FloatParam = PlainParam<f32>;
pub type IntParam = PlainParam<i32>;
@ -144,6 +148,21 @@ pub struct BoolParam {
pub string_to_value: Option<Arc<dyn Fn(&str) -> Option<bool> + Send + Sync>>,
}
/// An [IntParam]-backed categorical parameter that allows convenient conversion to and from a
/// simple enum. This enum must derive the re-exported [EnumIter], [EnumString] and [Display]
/// traits.
//
// TODO: Figure out a more sound way to get the same interface
pub struct EnumParam<T: EnumIter + Eq + Copy + Display> {
/// The integer parameter backing this enum parameter.
pub inner: IntParam,
/// An associative list of the variants converted to an i32 and their names. We need this
/// because we're doing some nasty type erasure things with [ParamPtr::Enum], so we can't
/// directly query the associated functions on `T` after the parameter when handling function
/// calls from the wrapper.
variants: Vec<(T, String)>,
}
impl<T> Default for PlainParam<T>
where
T: Default,
@ -177,6 +196,27 @@ impl Default for BoolParam {
}
}
impl<T: EnumIter + Eq + Copy + Display + Default> Default for EnumParam<T> {
fn default() -> Self {
let variants: Vec<_> = T::iter().map(|v| (v, v.to_string())).collect();
let default = T::default();
Self {
inner: IntParam {
value: T::iter()
.position(|v| v == default)
.expect("Invalid variant in init") as i32,
range: Range::Linear {
min: 0,
max: variants.len() as i32 - 1,
},
..Default::default()
},
variants,
}
}
}
macro_rules! impl_plainparam {
($ty:ident, $plain:ty) => {
impl Param for $ty {
@ -353,6 +393,67 @@ impl Param for BoolParam {
}
}
impl<T: EnumIter + Eq + Copy + Display> Param for EnumParam<T> {
type Plain = T;
fn update_smoother(&mut self, sample_rate: f32, reset: bool) {
self.inner.update_smoother(sample_rate, reset)
}
fn set_from_string(&mut self, string: &str) -> bool {
match self.variants.iter().find(|(_, repr)| repr == string) {
Some((variant, _)) => {
self.inner.set_plain_value(self.to_index(*variant));
true
}
None => false,
}
}
fn plain_value(&self) -> Self::Plain {
self.from_index(self.inner.plain_value())
}
fn set_plain_value(&mut self, plain: Self::Plain) {
self.inner.set_plain_value(self.to_index(plain))
}
fn normalized_value(&self) -> f32 {
self.inner.normalized_value()
}
fn set_normalized_value(&mut self, normalized: f32) {
self.inner.set_normalized_value(normalized)
}
fn normalized_value_to_string(&self, normalized: f32, _include_unit: bool) -> String {
// XXX: As mentioned below, our type punning would cause `.to_string()` to print the
// incorect value. Because of that, we already stored the string representations for
// variants values in this struct.
let plain = self.preview_plain(normalized);
let index = self.to_index(plain);
self.variants[index as usize].1.clone()
}
fn string_to_normalized_value(&self, string: &str) -> Option<f32> {
self.inner.string_to_normalized_value(string)
}
fn preview_normalized(&self, plain: Self::Plain) -> f32 {
self.inner.preview_normalized(self.to_index(plain))
}
fn preview_plain(&self, normalized: f32) -> Self::Plain {
self.from_index(self.inner.preview_plain(normalized))
}
fn as_ptr(&self) -> internals::ParamPtr {
internals::ParamPtr::EnumParam(
self as *const EnumParam<T> as *mut EnumParam<T> as *mut EnumParam<internals::AnyEnum>,
)
}
}
impl<T: Display + Copy> Display for PlainParam<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match (&self.value_to_string, &self.step_size) {
@ -376,6 +477,12 @@ impl Display for BoolParam {
}
}
impl<T: EnumIter + Eq + Copy + Display> Display for EnumParam<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.variants[self.inner.plain_value() as usize].1)
}
}
impl<T: Default> PlainParam<T>
where
Range<T>: Default,
@ -492,6 +599,77 @@ impl BoolParam {
}
}
impl<T: EnumIter + Eq + Copy + Display> EnumParam<T> {
/// Build a new [Self]. Use the other associated functions to modify the behavior of the
/// parameter.
pub fn new(name: &'static str, default: T) -> Self {
let variants: Vec<_> = T::iter().map(|v| (v, v.to_string())).collect();
Self {
inner: IntParam {
value: variants
.iter()
.position(|(v, _)| v == &default)
.expect("Invalid variant in init") as i32,
range: Range::Linear {
min: 0,
max: variants.len() as i32 - 1,
},
name,
..Default::default()
},
variants,
}
}
// We currently don't implement callbacks here. If we want to do that, then we'll need to add
// the IntParam fields to the parameter itself.
// TODO: Do exactly that
}
impl<T: EnumIter + Eq + Copy + Display> EnumParam<T> {
// TODO: There doesn't seem to be a single enum crate that gives you a dense [0, n_variatns)
// mapping between integers and enum variants. So far linear search over this variants has
// been the best approach. We should probably replace this with our own macro at some
// point.
/// The number of variants for this parameter
//
// This is part of the magic sauce that lets [ParamPtr::Enum] work. The type parmaeter there is
// a dummy type, acting as a somewhat unsound way to do type erasure. Because all data is stored
// in the struct after initialization (i.e. we no longer rely on T's specifics) and AnyParam is
// represented by an i32 this EnumParam behaves correctly even when casted between Ts.
//
// TODO: Come up with a sounder way to do this.
#[allow(clippy::len_without_is_empty)]
#[inline(never)]
pub fn len(&self) -> usize {
self.variants.len()
}
/// Get the index associated to an enum variant.
#[inline(never)]
fn to_index(&self, variant: T) -> i32 {
self.variants
.iter()
// This is somewhat shady, as `T` is going to be `AnyEnum` when this is indirectly
// called from the wrapper.
.position(|(v, _)| v == &variant)
.expect("Invalid enum variant") as i32
}
/// Get a variant from a index.
///
/// # Panics
///
/// indices `>= Self::len()` will trigger a panic.
#[allow(clippy::wrong_self_convention)]
#[inline(never)]
fn from_index(&self, index: i32) -> T {
self.variants[index as usize].0
}
}
/// Caldculate how many decimals to round to when displaying a floating point value with a specific
/// step size. We'll perform some rounding to ignore spurious extra precision caused by the floating
/// point quantization.

View file

@ -3,7 +3,7 @@
use std::collections::HashMap;
use std::pin::Pin;
use super::Param;
use super::{Display, EnumIter, Param};
/// Re-export for use in the [Params] proc-macro.
pub use serde_json::from_str as deserialize_field;
@ -48,12 +48,24 @@ pub trait Params {
fn deserialize_fields(&self, serialized: &HashMap<String, String>);
}
/// Dummy enum for in [ParamPtr]. This type needs an explicit representation size so we can compare
/// the discriminants.
#[derive(Display, Clone, Copy, PartialEq, Eq, EnumIter)]
#[repr(i32)]
pub enum AnyEnum {
Foo,
Bar,
}
/// Internal pointers to parameters. This is an implementation detail used by the wrappers.
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub enum ParamPtr {
FloatParam(*mut super::FloatParam),
IntParam(*mut super::IntParam),
BoolParam(*mut super::BoolParam),
/// The enum type parameter is used only as a phantom type, so we can safely cast between these
/// pointers.
EnumParam(*mut super::EnumParam<AnyEnum>),
}
// These pointers only point to fields on pinned structs, and the caller always needs to make sure
@ -88,6 +100,7 @@ impl ParamPtr {
ParamPtr::FloatParam(p) => (**p).name,
ParamPtr::IntParam(p) => (**p).name,
ParamPtr::BoolParam(p) => (**p).name,
ParamPtr::EnumParam(p) => (**p).inner.name,
}
}
@ -102,6 +115,7 @@ impl ParamPtr {
ParamPtr::FloatParam(p) => (**p).unit,
ParamPtr::IntParam(p) => (**p).unit,
ParamPtr::BoolParam(_) => "",
ParamPtr::EnumParam(_) => "",
}
}
@ -118,6 +132,7 @@ impl ParamPtr {
ParamPtr::FloatParam(p) => (**p).update_smoother(sample_rate, reset),
ParamPtr::IntParam(p) => (**p).update_smoother(sample_rate, reset),
ParamPtr::BoolParam(p) => (**p).update_smoother(sample_rate, reset),
ParamPtr::EnumParam(p) => (**p).update_smoother(sample_rate, reset),
}
}
@ -133,6 +148,7 @@ impl ParamPtr {
ParamPtr::FloatParam(p) => (**p).set_from_string(string),
ParamPtr::IntParam(p) => (**p).set_from_string(string),
ParamPtr::BoolParam(p) => (**p).set_from_string(string),
ParamPtr::EnumParam(p) => (**p).set_from_string(string),
}
}
@ -147,6 +163,7 @@ impl ParamPtr {
ParamPtr::FloatParam(p) => (**p).normalized_value(),
ParamPtr::IntParam(p) => (**p).normalized_value(),
ParamPtr::BoolParam(p) => (**p).normalized_value(),
ParamPtr::EnumParam(p) => (**p).normalized_value(),
}
}
@ -163,6 +180,7 @@ impl ParamPtr {
ParamPtr::FloatParam(p) => (**p).set_normalized_value(normalized),
ParamPtr::IntParam(p) => (**p).set_normalized_value(normalized),
ParamPtr::BoolParam(p) => (**p).set_normalized_value(normalized),
ParamPtr::EnumParam(p) => (**p).set_normalized_value(normalized),
}
}
@ -178,6 +196,7 @@ impl ParamPtr {
ParamPtr::FloatParam(p) => (**p).preview_normalized(plain),
ParamPtr::IntParam(p) => (**p).preview_normalized(plain as i32),
ParamPtr::BoolParam(_) => plain,
ParamPtr::EnumParam(p) => (**p).inner.preview_normalized(plain as i32),
}
}
@ -193,6 +212,7 @@ impl ParamPtr {
ParamPtr::FloatParam(p) => (**p).preview_plain(normalized),
ParamPtr::IntParam(p) => (**p).preview_plain(normalized) as f32,
ParamPtr::BoolParam(_) => normalized,
ParamPtr::EnumParam(p) => (**p).inner.preview_plain(normalized) as f32,
}
}
@ -209,6 +229,7 @@ impl ParamPtr {
ParamPtr::FloatParam(p) => (**p).normalized_value_to_string(normalized, include_unit),
ParamPtr::IntParam(p) => (**p).normalized_value_to_string(normalized, include_unit),
ParamPtr::BoolParam(p) => (**p).normalized_value_to_string(normalized, include_unit),
ParamPtr::EnumParam(p) => (**p).normalized_value_to_string(normalized, include_unit),
}
}
@ -223,6 +244,7 @@ impl ParamPtr {
ParamPtr::FloatParam(p) => (**p).string_to_normalized_value(string),
ParamPtr::IntParam(p) => (**p).string_to_normalized_value(string),
ParamPtr::BoolParam(p) => (**p).string_to_normalized_value(string),
ParamPtr::EnumParam(p) => (**p).string_to_normalized_value(string),
}
}
}

View file

@ -10,6 +10,7 @@ pub(crate) enum ParamValue {
F32(f32),
I32(i32),
Bool(bool),
EnumVariant(String),
}
/// A plugin's state so it can be restored at a later point.

View file

@ -265,6 +265,16 @@ impl<P: Plugin> IComponent for Wrapper<P> {
(ParamPtr::FloatParam(p), ParamValue::F32(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::EnumParam(p), ParamValue::EnumVariant(s)) => {
if !(**p).set_from_string(&s) {
nih_debug_assert_failure!(
"Invalid stored value '{}' for parameter \"{}\" ({:?})",
s,
param_id_str,
param_ptr,
);
}
}
(param_ptr, param_value) => {
nih_debug_assert_failure!(
"Invalid serialized value {:?} for parameter \"{}\" ({:?})",
@ -329,6 +339,13 @@ impl<P: Plugin> IComponent for Wrapper<P> {
param_id_str.to_string(),
ParamValue::Bool((*p).plain_value()),
),
ParamPtr::EnumParam(p) => (
param_id_str.to_string(),
// XXX: This works, but it's a bit of a roundabout conversion
ParamValue::EnumVariant(
(*p).normalized_value_to_string((*p).normalized_value(), false),
),
),
})
.collect();
@ -430,6 +447,7 @@ impl<P: Plugin> IEditController for Wrapper<P> {
Range::SymmetricalSkewed { min, max, .. } => max - min,
},
ParamPtr::BoolParam(_) => 1,
ParamPtr::EnumParam(p) => (**p).len() as i32 - 1,
};
info.default_normalized_value = *default_value as f64;
info.unit_id = vst3_sys::vst::kRootUnitId;