From e8697d9a74a37ac6f3db6415fb3dab59b301c2c1 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 25 Jan 2022 02:17:30 +0100 Subject: [PATCH] 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. --- Cargo.lock | 9 --- Cargo.toml | 1 - src/atomic.rs | 42 ------------ src/lib.rs | 1 - src/params.rs | 182 ++++++++++++++++++++++++++++++-------------------- 5 files changed, 109 insertions(+), 126 deletions(-) delete mode 100644 src/atomic.rs diff --git a/Cargo.lock b/Cargo.lock index faa4cd5c..563bbda9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", -] diff --git a/Cargo.toml b/Cargo.toml index df3143c5..4309dd66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,3 @@ authors = ["Robbert van der Helm "] license = "GPL-3.0-or-later" [dependencies] -atomic_float = "0.1.0" diff --git a/src/atomic.rs b/src/atomic.rs deleted file mode 100644 index d8678ea6..00000000 --- a/src/atomic.rs +++ /dev/null @@ -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 . - -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) - } -} diff --git a/src/lib.rs b/src/lib.rs index 34ae8e4b..7a015229 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,5 +14,4 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -pub mod atomic; pub mod params; diff --git a/src/params.rs b/src/params.rs index a29cddda..2ccbc3e9 100644 --- a/src/params.rs +++ b/src/params.rs @@ -14,15 +14,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -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), - IntParam(PlainParam), -} +pub type FloatParam = PlainParam; +pub type IntParam = PlainParam; /// 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 { /// A numerical parameter that's stored unnormalized. The range is used for the normalization /// process. -pub struct PlainParam { - /// 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: ::AtomicType, +pub struct PlainParam { + /// 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, @@ -66,24 +62,97 @@ pub struct PlainParam { pub string_to_value: Option Option>>, } -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 { + self.range.normalize(self.value) + } + + /// Set this parameter based on a normalized value. + pub fn set_normalized_value(&mut self, normalized: f32) { + self.value = self.range.unnormalize(normalized); } } - } - - /// 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)), - } - } - - /// 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), - } - } + }; } -impl Display for Param { +impl_plainparam!(FloatParam); +impl_plainparam!(IntParam); + +impl Display for PlainParam { 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), } } }