1
0
Fork 0

Redo the parameters without atomics

These atomics make things more difficult and they don't solve the main
problem: storing the parameter objects in an easy to use struct while
still allowing hash based access to them from the plugin wrapper. Going
through this new Params trait makes a lot more sense, and with pinning
this should be safe.
This commit is contained in:
Robbert van der Helm 2022-01-25 02:17:30 +01:00
parent b1415a36da
commit e8697d9a74
5 changed files with 109 additions and 126 deletions

9
Cargo.lock generated
View file

@ -2,15 +2,6 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "atomic_float"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62af46d040ba9df09edc6528dae9d8e49f5f3e82f55b7d2ec31a733c38dbc49d"
[[package]] [[package]]
name = "nih-plug" name = "nih-plug"
version = "0.1.0" version = "0.1.0"
dependencies = [
"atomic_float",
]

View file

@ -6,4 +6,3 @@ authors = ["Robbert van der Helm <mail@robbertvanderhelm.nl>"]
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
[dependencies] [dependencies]
atomic_float = "0.1.0"

View file

@ -1,42 +0,0 @@
// nih-plugs: plugins, but rewritten in Rust
// Copyright (C) 2022 Robbert van der Helm
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use atomic_float::AtomicF32;
use std::sync::atomic::AtomicI32;
// Type families galore!
pub trait AtomicType {
/// An atomic version of this type with interior mutability.
type AtomicType;
fn new_atomic(self) -> Self::AtomicType;
}
impl AtomicType for f32 {
type AtomicType = AtomicF32;
fn new_atomic(self) -> AtomicF32 {
AtomicF32::new(self)
}
}
impl AtomicType for i32 {
type AtomicType = AtomicI32;
fn new_atomic(self) -> AtomicI32 {
AtomicI32::new(self)
}
}

View file

@ -14,5 +14,4 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
pub mod atomic;
pub mod params; pub mod params;

View file

@ -14,15 +14,12 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
use std::{fmt::Display, sync::atomic::Ordering}; use std::collections::HashMap;
use std::fmt::Display;
use std::pin::Pin;
use crate::atomic::AtomicType; pub type FloatParam = PlainParam<f32>;
pub type IntParam = PlainParam<i32>;
/// Describes a single normalized parameter and also stores its value.
pub enum Param {
FloatParam(PlainParam<f32>),
IntParam(PlainParam<i32>),
}
/// A distribution for a parameter's range. Probably need to add some forms of skewed ranges and /// A distribution for a parameter's range. Probably need to add some forms of skewed ranges and
/// maybe a callback based implementation at some point. /// maybe a callback based implementation at some point.
@ -45,12 +42,11 @@ trait NormalizebleRange<T> {
/// A numerical parameter that's stored unnormalized. The range is used for the normalization /// A numerical parameter that's stored unnormalized. The range is used for the normalization
/// process. /// process.
pub struct PlainParam<T: AtomicType> { pub struct PlainParam<T> {
/// The field's current, normalized value. Should be initialized with the default value using /// The field's current, normalized value. Should be initialized with the default value. Storing
/// `T::new_atomic(...)` ([AtomicType::new_atomic]). Storing parameter values like this instead /// parameter values like this instead of in a single contiguous array is bad for cache
/// of in a single contiguous array is bad for cache locality, but it does allow for a much /// locality, but it does allow for a much nicer declarative API.
/// nicer declarative API. pub value: T,
pub value: <T as AtomicType>::AtomicType,
/// The distribution of the parameter's values. /// The distribution of the parameter's values.
pub range: Range<T>, pub range: Range<T>,
@ -66,24 +62,97 @@ pub struct PlainParam<T: AtomicType> {
pub string_to_value: Option<Box<dyn Fn(&str) -> Option<T>>>, pub string_to_value: Option<Box<dyn Fn(&str) -> Option<T>>>,
} }
impl Param { /// Describes a single normalized parameter and also stores its value.
///
/// TODO: This is an implementation detail, maybe hide this somewhere else
pub enum ParamPtr {
FloatParam(*mut FloatParam),
IntParam(*mut IntParam),
}
/// Describes a struct containing parameters. The idea is that we can have a normal struct
/// containing [FloatParam] and other parameter types with attributes describing a unique identifier
/// for each parameter. We can then build a mapping from those parameter IDs to the parameters using
/// the [param_map] function. That way we can have easy to work with JUCE-style parameter objects in
/// the plugin without needing to manually register each parameter, like you would in JUCE.
///
/// # Safety
///
/// This implementation is safe when using from the wrapper because the plugin object needs to be
/// pinned, and it can never outlive the wrapper.
///
/// TODO: Create a derive macro for this
pub trait Params {
/// Create a mapping from unique parameter IDs to parameters. Dereferencing the pointers stored
/// in the values is only valid as long as this pinned object is valid.
fn param_map(self: Pin<&Self>) -> HashMap<&'static str, ParamPtr>;
}
impl ParamPtr {
/// Get the human readable name for this parameter. /// Get the human readable name for this parameter.
pub fn name(&self) -> &'static str { ///
/// # Safety
///
/// Calling this function is only safe as long as the object this `ParamPtr` was created for is
/// still alive.
pub unsafe fn name(&self) -> &'static str {
match &self { match &self {
Param::FloatParam(p) => p.name, ParamPtr::FloatParam(p) => (**p).name,
Param::IntParam(p) => p.name, ParamPtr::IntParam(p) => (**p).name,
} }
} }
/// Set this parameter based on a string. Returns whether the updating succeeded. That can fail /// Set this parameter based on a string. Returns whether the updating succeeded. That can fail
/// if the string cannot be parsed. /// if the string cannot be parsed.
/// ///
/// TODO: After implementing VST3, check if we handle parsing failures correctly /// # Safety
pub fn from_string(&self, string: &str) -> bool { ///
// TODO: Debug asserts on failures /// Calling this function is only safe as long as the object this `ParamPtr` was created for is
/// still alive.
pub unsafe fn from_string(&mut self, string: &str) -> bool {
match &self { match &self {
Param::FloatParam(p) => { ParamPtr::FloatParam(p) => (**p).from_string(string),
let value = match &p.string_to_value { ParamPtr::IntParam(p) => (**p).from_string(string),
}
}
/// Get the normalized `[0, 1]` value for this parameter.
///
/// # Safety
///
/// Calling this function is only safe as long as the object this `ParamPtr` was created for is
/// still alive.
pub unsafe fn normalized_value(&self) -> f32 {
match &self {
ParamPtr::FloatParam(p) => (**p).normalized_value(),
ParamPtr::IntParam(p) => (**p).normalized_value(),
}
}
/// Set this parameter based on a normalized value.
///
/// # Safety
///
/// Calling this function is only safe as long as the object this `ParamPtr` was created for is
/// still alive.
pub unsafe fn set_normalized_value(&self, normalized: f32) {
match &self {
ParamPtr::FloatParam(p) => (**p).set_normalized_value(normalized),
ParamPtr::IntParam(p) => (**p).set_normalized_value(normalized),
}
}
}
macro_rules! impl_plainparam {
($ty:ty) => {
impl $ty {
/// Set this parameter based on a string. Returns whether the updating succeeded. That
/// can fail if the string cannot be parsed.
///
/// TODO: After implementing VST3, check if we handle parsing failures correctly
pub fn from_string(&mut self, string: &str) -> bool {
// TODO: Debug asserts on failures
let value = match &self.string_to_value {
Some(f) => f(string), Some(f) => f(string),
// TODO: Check how Rust's parse function handles trailing garbage // TODO: Check how Rust's parse function handles trailing garbage
None => string.parse().ok(), None => string.parse().ok(),
@ -91,67 +160,34 @@ impl Param {
match value { match value {
Some(unnormalized) => { Some(unnormalized) => {
p.value.store(unnormalized, Ordering::Relaxed); self.value = unnormalized;
true true
} }
None => false, None => false,
} }
} }
Param::IntParam(p) => {
let value = match &p.string_to_value {
Some(f) => f(string),
None => string.parse().ok(),
};
match value {
Some(unnormalized) => {
p.value.store(unnormalized, Ordering::Relaxed);
true
}
None => false,
}
}
}
}
/// Get the normalized `[0, 1]` value for this parameter. /// Get the normalized `[0, 1]` value for this parameter.
pub fn normalized_value(&self) -> f32 { pub fn normalized_value(&self) -> f32 {
match &self { self.range.normalize(self.value)
Param::FloatParam(p) => p.range.normalize(p.value.load(Ordering::Relaxed)),
Param::IntParam(p) => p.range.normalize(p.value.load(Ordering::Relaxed)),
}
} }
/// Set this parameter based on a normalized value. /// Set this parameter based on a normalized value.
pub fn set_normalized_value(&self, normalized: f32) { pub fn set_normalized_value(&mut self, normalized: f32) {
match &self { self.value = self.range.unnormalize(normalized);
Param::FloatParam(p) => p
.value
.store(p.range.unnormalize(normalized), Ordering::Relaxed),
Param::IntParam(p) => p
.value
.store(p.range.unnormalize(normalized), Ordering::Relaxed),
} }
} }
};
} }
impl Display for Param { impl_plainparam!(FloatParam);
impl_plainparam!(IntParam);
impl<T: Display + Copy> Display for PlainParam<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self { match &self.value_to_string {
Param::FloatParam(p) => { Some(func) => write!(f, "{}{}", func(self.value), self.unit),
let unnormalized = p.value.load(Ordering::Relaxed); None => write!(f, "{}{}", self.value, self.unit),
match &p.value_to_string {
Some(func) => write!(f, "{}{}", func(unnormalized), p.unit),
None => write!(f, "{}{}", unnormalized, p.unit),
}
}
Param::IntParam(p) => {
let unnormalized = p.value.load(Ordering::Relaxed);
match &p.value_to_string {
Some(func) => write!(f, "{}{}", func(unnormalized), p.unit),
None => write!(f, "{}{}", unnormalized, p.unit),
}
}
} }
} }
} }