From fb71d0fcce759e7677e13e9cac0a8d10cef1e561 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 20 Oct 2022 12:10:35 +0200 Subject: [PATCH] Move the Params trait out of params::internals This makes much more sense, since this trait is a cornerstone of NIH-plug. --- BREAKING_CHANGES.md | 7 ++ nih_plug_iced/src/lib.rs | 2 +- nih_plug_vizia/src/lib.rs | 2 +- src/context.rs | 2 +- src/param.rs | 113 +++++++++++++++++++++++++++++- src/param/internals.rs | 106 ---------------------------- src/plugin.rs | 2 +- src/prelude.rs | 3 +- src/wrapper/clap/wrapper.rs | 4 +- src/wrapper/standalone/wrapper.rs | 4 +- src/wrapper/state.rs | 6 +- src/wrapper/vst3/inner.rs | 4 +- 12 files changed, 133 insertions(+), 122 deletions(-) diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md index 43c9b3f6..4e30b9a2 100644 --- a/BREAKING_CHANGES.md +++ b/BREAKING_CHANGES.md @@ -6,6 +6,13 @@ new and what's changed, this document lists all breaking changes in reverse chronological order. If a new feature did not require any changes to existing code then it will not be listed here. +## [2022-10-20] + +- Some items have been moved out of `nih_plug::param::internals`. The main + `Params` trait is now located under `nih_plug::param`, and the + `PersistentTrait` trait, implementations, and helper functions are now part of + a new `nih_plug::param::persist` module. + ## [2022-10-13] - The `#[nested]` parameter attribute has gained super powers and has its syntax diff --git a/nih_plug_iced/src/lib.rs b/nih_plug_iced/src/lib.rs index d5af6195..f1fbead4 100644 --- a/nih_plug_iced/src/lib.rs +++ b/nih_plug_iced/src/lib.rs @@ -235,7 +235,7 @@ pub trait IcedEditor: 'static + Send + Sync + Sized { #[derive(Serialize, Deserialize)] pub struct IcedState { /// The window's size in logical pixels before applying `scale_factor`. - #[serde(with = "nih_plug::param::internals::serialize_atomic_cell")] + #[serde(with = "nih_plug::param::persist::serialize_atomic_cell")] size: AtomicCell<(u32, u32)>, /// Whether the editor's window is currently open. #[serde(skip)] diff --git a/nih_plug_vizia/src/lib.rs b/nih_plug_vizia/src/lib.rs index e2e38f5c..da7fb925 100644 --- a/nih_plug_vizia/src/lib.rs +++ b/nih_plug_vizia/src/lib.rs @@ -5,7 +5,7 @@ use baseview::{WindowHandle, WindowScalePolicy}; use crossbeam::atomic::AtomicCell; -use nih_plug::param::internals::PersistentField; +use nih_plug::param::persist::PersistentField; use nih_plug::prelude::{Editor, GuiContext, ParentWindowHandle}; use serde::{Deserialize, Serialize}; use std::sync::atomic::{AtomicBool, Ordering}; diff --git a/src/context.rs b/src/context.rs index 825f7d48..da9f90ba 100644 --- a/src/context.rs +++ b/src/context.rs @@ -220,7 +220,7 @@ pub struct Transport { } /// A convenience helper for setting parameter values. Any changes made here will be broadcasted to -/// the host and reflected in the plugin's [`Params`][crate::param::internals::Params] object. These +/// the host and reflected in the plugin's [`Params`][crate::param::Params] object. These /// functions should only be called from the main thread. pub struct ParamSetter<'a> { pub raw_context: &'a dyn GuiContext, diff --git a/src/param.rs b/src/param.rs index 9a96a70d..84fa3e59 100644 --- a/src/param.rs +++ b/src/param.rs @@ -1,10 +1,17 @@ //! NIH-plug can handle floating point, integer, boolean, and enum parameters. Parameters are -//! managed by creating a struct deriving the [`Params`][internals::Params] trait containing fields +//! managed by creating a struct deriving the [`Params`][Params] trait containing fields //! for those parameter types, and then returning a reference to that object from your //! [`Plugin::params()`][crate::prelude::Plugin::params()] method. See the `Params` trait for more //! information. +use std::collections::BTreeMap; use std::fmt::Display; +use std::sync::Arc; + +use self::internals::ParamPtr; + +// The proc-macro for deriving `Params` +pub use nih_plug_derive::Params; // Parameter types mod boolean; @@ -158,7 +165,7 @@ pub trait Param: Display { /// Flags to control the parameter's behavior. See [`ParamFlags`]. fn flags(&self) -> ParamFlags; - /// Internal implementation detail for implementing [`Params`][internals::Params]. This should + /// Internal implementation detail for implementing [`Params`][Params]. This should /// not be used directly. fn as_ptr(&self) -> internals::ParamPtr; } @@ -194,3 +201,105 @@ pub(crate) trait ParamMut: Param { /// reset to the current value. fn update_smoother(&self, sample_rate: f32, reset: bool); } + +/// Describes a struct containing parameters and other persistent fields. +/// +/// # Deriving `Params` and `#[id = "stable"]` +/// +/// This trait can be derived on a struct containing [`FloatParam`][super::FloatParam] and other +/// parameter fields by adding `#[derive(Params)]`. When deriving this trait, any of those parameter +/// fields should have the `#[id = "stable"]` attribute, where `stable` is an up to 6 character long +/// string (to avoid collisions) that will be used to identify the parameter internally so you can +/// safely move it around and rename the field without breaking compatibility with old presets. +/// +/// ## `#[persist = "key"]` +/// +/// The struct can also contain other fields that should be persisted along with the rest of the +/// preset data. These fields should be [`PersistentField`]s annotated with the `#[persist = "key"]` +/// attribute containing types that can be serialized and deserialized with +/// [Serde](https://serde.rs/). +/// +/// ## `#[nested]`, `#[nested(group_name = "group name")]` +/// +/// Finally, the `Params` object may include parameters from other objects. Setting a group name is +/// optional, but some hosts can use this information to display the parameters in a tree structure. +/// Parameter IDs and persisting keys still need to be **unique** when using nested parameter +/// structs. This currently has the following caveats: +/// +/// - Enforcing that parameter IDs and persist keys are unique does not work across nested structs. +/// - Deserializing persisted fields will give false positives about fields not existing. +/// +/// Take a look at the example gain example plugin to see how this is used. +/// +/// ## `#[nested(id_prefix = "foo", group_name = "Foo")]` +/// +/// Adding this attribute to a `Params` sub-object works similarly to the regular `#[nested]` +/// attribute, but it also adds an ID to all parameters from the nested object. If a parameter in +/// the nested nested object normally has parameter ID `bar`, the parameter's ID will now be renamed +/// to `foo_bar`. _This makes it possible to reuse same parameter struct with different names and +/// parameter indices._ +/// +/// This does **not** support persistent fields. +/// +/// ## `#[nested(array, group_name = "Foo")]` +/// +/// This can be applied to an array-like data structure and it works similar to a `nested` attribute +/// with an `id_name`, except that it will iterate over the array and create unique indices for all +/// nested parameters. If the nested parameters object has a parameter called `bar`, then that +/// parameter will belong to the group `Foo {array_index + 1}`, and it will have the renamed +/// parameter ID `bar_{array_index + 1}`. +/// +/// This does **not** support persistent fields. +/// +/// # Safety +/// +/// This implementation is safe when using from the wrapper because the plugin's returned `Params` +/// object lives in an `Arc`, and the wrapper also holds a reference to this `Arc`. +pub unsafe trait Params: 'static + Send + Sync { + /// Create a mapping from unique parameter IDs to parameter pointers along with the name of the + /// group/unit/module they are in, as a `(param_id, param_ptr, group)` triple. The order of the + /// `Vec` determines the display order in the (host's) generic UI. The group name is either an + /// empty string for top level parameters, or a slash/delimited `"group name 1/Group Name 2"` if + /// this `Params` object contains nested child objects. All components of a group path must + /// exist or you may encounter panics. The derive macro does this for every parameter field + /// marked with `#[id = "stable"]`, and it also inlines all fields from nested child `Params` + /// structs marked with `#[nested(...)]` while prefixing that group name before the parameter's + /// original group name. Dereferencing the pointers stored in the values is only valid as long + /// as this object is valid. + /// + /// # Note + /// + /// This uses `String` even though for the `Params` derive macro `&'static str` would have been + /// fine to be able to support custom reusable Params implementations. + fn param_map(&self) -> Vec<(String, ParamPtr, String)>; + + /// Serialize all fields marked with `#[persist = "stable_name"]` into a hash map containing + /// JSON-representations of those fields so they can be written to the plugin's state and + /// recalled later. This uses [`serialize_field()`] under the hood. + fn serialize_fields(&self) -> BTreeMap { + BTreeMap::new() + } + + /// Restore all fields marked with `#[persist = "stable_name"]` from a hashmap created by + /// [`serialize_fields()`][Self::serialize_fields()]. All of these fields should be wrapped in a + /// [`PersistentField`] with thread safe interior mutability, like an `RwLock` or a `Mutex`. + /// This gets called when the plugin's state is being restored. This uses [deserialize_field()] + /// under the hood. + #[allow(unused_variables)] + fn deserialize_fields(&self, serialized: &BTreeMap) {} +} + +/// This may be useful when building generic UIs using nested `Params` objects. +unsafe impl Params for Arc

{ + fn param_map(&self) -> Vec<(String, ParamPtr, String)> { + self.as_ref().param_map() + } + + fn serialize_fields(&self) -> BTreeMap { + self.as_ref().serialize_fields() + } + + fn deserialize_fields(&self, serialized: &BTreeMap) { + self.as_ref().deserialize_fields(serialized) + } +} diff --git a/src/param/internals.rs b/src/param/internals.rs index 69a1bde1..afe09482 100644 --- a/src/param/internals.rs +++ b/src/param/internals.rs @@ -1,11 +1,7 @@ //! Implementation details for the parameter management. -use std::collections::BTreeMap; -use std::sync::Arc; - use super::{Param, ParamFlags, ParamMut}; -pub use nih_plug_derive::Params; /// Re-export for use in the [`Params`] proc-macro. pub use serde_json::from_str as deserialize_field; /// Re-export for use in the [`Params`] proc-macro. @@ -34,108 +30,6 @@ pub mod serialize_atomic_cell { } } -/// Describes a struct containing parameters and other persistent fields. -/// -/// # Deriving `Params` and `#[id = "stable"]` -/// -/// This trait can be derived on a struct containing [`FloatParam`][super::FloatParam] and other -/// parameter fields by adding `#[derive(Params)]`. When deriving this trait, any of those parameter -/// fields should have the `#[id = "stable"]` attribute, where `stable` is an up to 6 character long -/// string (to avoid collisions) that will be used to identify the parameter internally so you can -/// safely move it around and rename the field without breaking compatibility with old presets. -/// -/// ## `#[persist = "key"]` -/// -/// The struct can also contain other fields that should be persisted along with the rest of the -/// preset data. These fields should be [`PersistentField`]s annotated with the `#[persist = "key"]` -/// attribute containing types that can be serialized and deserialized with -/// [Serde](https://serde.rs/). -/// -/// ## `#[nested]`, `#[nested(group_name = "group name")]` -/// -/// Finally, the `Params` object may include parameters from other objects. Setting a group name is -/// optional, but some hosts can use this information to display the parameters in a tree structure. -/// Parameter IDs and persisting keys still need to be **unique** when using nested parameter -/// structs. This currently has the following caveats: -/// -/// - Enforcing that parameter IDs and persist keys are unique does not work across nested structs. -/// - Deserializing persisted fields will give false positives about fields not existing. -/// -/// Take a look at the example gain example plugin to see how this is used. -/// -/// ## `#[nested(id_prefix = "foo", group_name = "Foo")]` -/// -/// Adding this attribute to a `Params` sub-object works similarly to the regular `#[nested]` -/// attribute, but it also adds an ID to all parameters from the nested object. If a parameter in -/// the nested nested object normally has parameter ID `bar`, the parameter's ID will now be renamed -/// to `foo_bar`. _This makes it possible to reuse same parameter struct with different names and -/// parameter indices._ -/// -/// This does **not** support persistent fields. -/// -/// ## `#[nested(array, group_name = "Foo")]` -/// -/// This can be applied to an array-like data structure and it works similar to a `nested` attribute -/// with an `id_name`, except that it will iterate over the array and create unique indices for all -/// nested parameters. If the nested parameters object has a parameter called `bar`, then that -/// parameter will belong to the group `Foo {array_index + 1}`, and it will have the renamed -/// parameter ID `bar_{array_index + 1}`. -/// -/// This does **not** support persistent fields. -/// -/// # Safety -/// -/// This implementation is safe when using from the wrapper because the plugin's returned `Params` -/// object lives in an `Arc`, and the wrapper also holds a reference to this `Arc`. -pub unsafe trait Params: 'static + Send + Sync { - /// Create a mapping from unique parameter IDs to parameter pointers along with the name of the - /// group/unit/module they are in, as a `(param_id, param_ptr, group)` triple. The order of the - /// `Vec` determines the display order in the (host's) generic UI. The group name is either an - /// empty string for top level parameters, or a slash/delimited `"group name 1/Group Name 2"` if - /// this `Params` object contains nested child objects. All components of a group path must - /// exist or you may encounter panics. The derive macro does this for every parameter field - /// marked with `#[id = "stable"]`, and it also inlines all fields from nested child `Params` - /// structs marked with `#[nested(...)]` while prefixing that group name before the parameter's - /// original group name. Dereferencing the pointers stored in the values is only valid as long - /// as this object is valid. - /// - /// # Note - /// - /// This uses `String` even though for the `Params` derive macro `&'static str` would have been - /// fine to be able to support custom reusable Params implementations. - fn param_map(&self) -> Vec<(String, ParamPtr, String)>; - - /// Serialize all fields marked with `#[persist = "stable_name"]` into a hash map containing - /// JSON-representations of those fields so they can be written to the plugin's state and - /// recalled later. This uses [`serialize_field()`] under the hood. - fn serialize_fields(&self) -> BTreeMap { - BTreeMap::new() - } - - /// Restore all fields marked with `#[persist = "stable_name"]` from a hashmap created by - /// [`serialize_fields()`][Self::serialize_fields()]. All of these fields should be wrapped in a - /// [`PersistentField`] with thread safe interior mutability, like an `RwLock` or a `Mutex`. - /// This gets called when the plugin's state is being restored. This uses [deserialize_field()] - /// under the hood. - #[allow(unused_variables)] - fn deserialize_fields(&self, serialized: &BTreeMap) {} -} - -/// This may be useful when building generic UIs using nested `Params` objects. -unsafe impl Params for Arc

{ - fn param_map(&self) -> Vec<(String, ParamPtr, String)> { - self.as_ref().param_map() - } - - fn serialize_fields(&self) -> BTreeMap { - self.as_ref().serialize_fields() - } - - fn deserialize_fields(&self, serialized: &BTreeMap) { - self.as_ref().deserialize_fields(serialized) - } -} - /// Internal pointers to parameters. This is an implementation detail used by the wrappers for type /// erasure. #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] diff --git a/src/plugin.rs b/src/plugin.rs index 132ae650..bf9805ed 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use crate::buffer::Buffer; use crate::context::{GuiContext, InitContext, ProcessContext}; use crate::midi::MidiConfig; -use crate::param::internals::Params; +use crate::param::Params; use crate::wrapper::clap::features::ClapFeature; /// Basic functionality that needs to be implemented by a plugin. The wrappers will use this to diff --git a/src/prelude.rs b/src/prelude.rs index 286cd89c..a471e74f 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -15,9 +15,10 @@ pub use crate::context::{GuiContext, InitContext, ParamSetter, PluginApi, Proces // This also includes the derive macro pub use crate::midi::{control_change, MidiConfig, NoteEvent}; pub use crate::param::enums::{Enum, EnumParam}; -pub use crate::param::internals::{ParamPtr, Params}; +pub use crate::param::internals::ParamPtr; pub use crate::param::range::{FloatRange, IntRange}; pub use crate::param::smoothing::{Smoothable, Smoother, SmoothingStyle}; +pub use crate::param::Params; pub use crate::param::{BoolParam, FloatParam, IntParam, Param, ParamFlags}; pub use crate::plugin::{ AuxiliaryBuffers, AuxiliaryIOConfig, BufferConfig, BusConfig, ClapPlugin, Editor, diff --git a/src/wrapper/clap/wrapper.rs b/src/wrapper/clap/wrapper.rs index 3825beec..25d2d353 100644 --- a/src/wrapper/clap/wrapper.rs +++ b/src/wrapper/clap/wrapper.rs @@ -80,8 +80,8 @@ use crate::buffer::Buffer; use crate::context::Transport; use crate::event_loop::{EventLoop, MainThreadExecutor, TASK_QUEUE_CAPACITY}; use crate::midi::{MidiConfig, NoteEvent}; -use crate::param::internals::{ParamPtr, Params}; -use crate::param::ParamFlags; +use crate::param::internals::ParamPtr; +use crate::param::{ParamFlags, Params}; use crate::plugin::{ AuxiliaryBuffers, BufferConfig, BusConfig, ClapPlugin, Editor, ParentWindowHandle, ProcessMode, ProcessStatus, diff --git a/src/wrapper/standalone/wrapper.rs b/src/wrapper/standalone/wrapper.rs index 9484504e..04283488 100644 --- a/src/wrapper/standalone/wrapper.rs +++ b/src/wrapper/standalone/wrapper.rs @@ -15,8 +15,8 @@ use super::config::WrapperConfig; use super::context::{WrapperGuiContext, WrapperInitContext, WrapperProcessContext}; use crate::context::Transport; use crate::midi::NoteEvent; -use crate::param::internals::{ParamPtr, Params}; -use crate::param::ParamFlags; +use crate::param::internals::ParamPtr; +use crate::param::{ParamFlags, Params}; use crate::plugin::{ AuxiliaryBuffers, AuxiliaryIOConfig, BufferConfig, BusConfig, Editor, ParentWindowHandle, Plugin, ProcessMode, ProcessStatus, diff --git a/src/wrapper/state.rs b/src/wrapper/state.rs index 4d7c7a17..7a797117 100644 --- a/src/wrapper/state.rs +++ b/src/wrapper/state.rs @@ -6,8 +6,8 @@ use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, HashMap}; use std::sync::Arc; -use crate::param::internals::{ParamPtr, Params}; -use crate::param::{Param, ParamMut}; +use crate::param::internals::ParamPtr; +use crate::param::{Param, ParamMut, Params}; use crate::plugin::{BufferConfig, Plugin}; // These state objects are also exposed directly to the plugin so it can do its own internal preset @@ -45,7 +45,7 @@ pub struct PluginState { /// parameter automation though, depending on how the host implements that. pub params: BTreeMap, /// Arbitrary fields that should be persisted together with the plugin's parameters. Any field - /// on the [`Params`][crate::param::internals::Params] struct that's annotated with `#[persist = + /// on the [`Params`][crate::param::Params] struct that's annotated with `#[persist = /// "stable_name"]` will be persisted this way. /// /// The individual fields are also serialized as JSON so they can safely be restored diff --git a/src/wrapper/vst3/inner.rs b/src/wrapper/vst3/inner.rs index 15161232..dc5f36f9 100644 --- a/src/wrapper/vst3/inner.rs +++ b/src/wrapper/vst3/inner.rs @@ -18,8 +18,8 @@ use crate::buffer::Buffer; use crate::context::Transport; use crate::event_loop::{EventLoop, MainThreadExecutor, OsEventLoop}; use crate::midi::{MidiConfig, NoteEvent}; -use crate::param::internals::{ParamPtr, Params}; -use crate::param::ParamFlags; +use crate::param::internals::ParamPtr; +use crate::param::{ParamFlags, Params}; use crate::plugin::{BufferConfig, BusConfig, Editor, ProcessMode, ProcessStatus, Vst3Plugin}; use crate::wrapper::state::{self, PluginState}; use crate::wrapper::util::{hash_param_id, process_wrapper};