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.
version = 3
[[package]]
name = "atomic_float"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62af46d040ba9df09edc6528dae9d8e49f5f3e82f55b7d2ec31a733c38dbc49d"
[[package]]
name = "nih-plug"
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"
[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
// along with this program. If not, see <https://www.gnu.org/licenses/>.
pub mod atomic;
pub mod params;

View file

@ -14,15 +14,12 @@
// 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 std::{fmt::Display, sync::atomic::Ordering};
use std::collections::HashMap;
use std::fmt::Display;
use std::pin::Pin;
use crate::atomic::AtomicType;
/// Describes a single normalized parameter and also stores its value.
pub enum Param {
FloatParam(PlainParam<f32>),
IntParam(PlainParam<i32>),
}
pub type FloatParam = PlainParam<f32>;
pub type IntParam = PlainParam<i32>;
/// 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.
@ -45,12 +42,11 @@ trait NormalizebleRange<T> {
/// A numerical parameter that's stored unnormalized. The range is used for the normalization
/// process.
pub struct PlainParam<T: AtomicType> {
/// The field's current, normalized value. Should be initialized with the default value using
/// `T::new_atomic(...)` ([AtomicType::new_atomic]). Storing parameter values like this instead
/// of in a single contiguous array is bad for cache locality, but it does allow for a much
/// nicer declarative API.
pub value: <T as AtomicType>::AtomicType,
pub struct PlainParam<T> {
/// The field's current, normalized value. Should be initialized with the default value. Storing
/// parameter values like this instead of in a single contiguous array is bad for cache
/// locality, but it does allow for a much nicer declarative API.
pub value: T,
/// The distribution of the parameter's values.
pub range: Range<T>,
@ -66,24 +62,97 @@ pub struct PlainParam<T: AtomicType> {
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.
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 {
Param::FloatParam(p) => p.name,
Param::IntParam(p) => p.name,
ParamPtr::FloatParam(p) => (**p).name,
ParamPtr::IntParam(p) => (**p).name,
}
}
/// 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(&self, string: &str) -> bool {
// TODO: Debug asserts on failures
/// # Safety
///
/// 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 {
Param::FloatParam(p) => {
let value = match &p.string_to_value {
ParamPtr::FloatParam(p) => (**p).from_string(string),
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),
// TODO: Check how Rust's parse function handles trailing garbage
None => string.parse().ok(),
@ -91,67 +160,34 @@ impl Param {
match value {
Some(unnormalized) => {
p.value.store(unnormalized, Ordering::Relaxed);
self.value = unnormalized;
true
}
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.
pub fn normalized_value(&self) -> f32 {
match &self {
Param::FloatParam(p) => p.range.normalize(p.value.load(Ordering::Relaxed)),
Param::IntParam(p) => p.range.normalize(p.value.load(Ordering::Relaxed)),
}
self.range.normalize(self.value)
}
/// Set this parameter based on a normalized value.
pub fn set_normalized_value(&self, normalized: f32) {
match &self {
Param::FloatParam(p) => p
.value
.store(p.range.unnormalize(normalized), Ordering::Relaxed),
Param::IntParam(p) => p
.value
.store(p.range.unnormalize(normalized), Ordering::Relaxed),
pub fn set_normalized_value(&mut self, normalized: f32) {
self.value = self.range.unnormalize(normalized);
}
}
};
}
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 {
match &self {
Param::FloatParam(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),
}
}
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),
}
}
match &self.value_to_string {
Some(func) => write!(f, "{}{}", func(self.value), self.unit),
None => write!(f, "{}{}", self.value, self.unit),
}
}
}