1
0
Fork 0

Implement saving parameter state

Restoring is next.
This commit is contained in:
Robbert van der Helm 2022-01-29 14:20:14 +01:00
parent ef021915e5
commit 97a88e0db2
5 changed files with 143 additions and 21 deletions

45
Cargo.lock generated
View file

@ -9,6 +9,12 @@ dependencies = [
"nih_plug", "nih_plug",
] ]
[[package]]
name = "itoa"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.4.0"
@ -21,6 +27,8 @@ version = "0.1.0"
dependencies = [ dependencies = [
"lazy_static", "lazy_static",
"nih_plug_derive", "nih_plug_derive",
"serde",
"serde_json",
"vst3-sys", "vst3-sys",
"widestring", "widestring",
] ]
@ -51,6 +59,43 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "ryu"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
[[package]]
name = "serde"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.86" version = "1.0.86"

View file

@ -11,6 +11,8 @@ members = ["nih_plug_derive", "plugins/gain"]
[dependencies] [dependencies]
nih_plug_derive = { path = "nih_plug_derive" } nih_plug_derive = { path = "nih_plug_derive" }
lazy_static = "1.4" lazy_static = "1.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# Upstream currently does not support structs with generics and comments # Upstream currently does not support structs with generics and comments
vst3-sys = { git = "https://github.com/robbert-vdh/vst3-sys.git", branch = "fix/vst3-macro-generics" } vst3-sys = { git = "https://github.com/robbert-vdh/vst3-sys.git", branch = "fix/vst3-macro-generics" }
widestring = "1.0.0-beta.1" widestring = "1.0.0-beta.1"

View file

@ -17,5 +17,6 @@
//! Wrappers for different plugin types. Each wrapper has an entry point macro that you can pass the //! Wrappers for different plugin types. Each wrapper has an entry point macro that you can pass the
//! name of a type that implements `Plugin` to. The macro will handle the rest. //! name of a type that implements `Plugin` to. The macro will handle the rest.
pub mod util; pub(crate) mod state;
pub(crate) mod util;
pub mod vst3; pub mod vst3;

38
src/wrapper/state.rs Normal file
View file

@ -0,0 +1,38 @@
// nih-plug: 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/>.
//! Utilities for saving a [Plugin]'s state.
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// An unnormalized value for a parameter.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum ParamValue {
F32(f32),
I32(i32),
}
/// A plugin's state so it can be restored at a later point.
#[derive(Debug, Serialize, Deserialize)]
#[serde(bound(deserialize = "'de: 'static"))]
pub(crate) struct State {
/// The plugin's parameter values. These are stored unnormalized. This mean sthe old values will
/// be recalled when when the parameter's range gets increased. Doing so may still mess with
/// parmaeter automation though, depending on how the host impelments that.
pub params: HashMap<&'static str, ParamValue>,
}

View file

@ -28,16 +28,16 @@ use std::mem;
use std::ptr; use std::ptr;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use vst3_sys::base::{kInvalidArgument, kNoInterface, kResultFalse, kResultOk, tresult, TBool}; use vst3_sys::base::{kInvalidArgument, kNoInterface, kResultFalse, kResultOk, tresult, TBool};
use vst3_sys::base::{IPluginBase, IPluginFactory, IPluginFactory2, IPluginFactory3}; use vst3_sys::base::{IBStream, IPluginBase, IPluginFactory, IPluginFactory2, IPluginFactory3};
use vst3_sys::vst::TChar;
use vst3_sys::vst::{ use vst3_sys::vst::{
IAudioProcessor, IComponent, IEditController, IParamValueQueue, IParameterChanges, IAudioProcessor, IComponent, IEditController, IParamValueQueue, IParameterChanges, TChar,
}; };
use vst3_sys::VST3; use vst3_sys::{ComPtr, VST3};
use widestring::U16CStr; use widestring::U16CStr;
use crate::params::ParamPtr; use crate::params::ParamPtr;
use crate::plugin::{BufferConfig, BusConfig, Plugin, ProcessStatus, Vst3Plugin}; use crate::plugin::{BufferConfig, BusConfig, Plugin, ProcessStatus, Vst3Plugin};
use crate::wrapper::state::{ParamValue, State};
use crate::wrapper::util::{hash_param_id, strlcpy, u16strlcpy}; use crate::wrapper::util::{hash_param_id, strlcpy, u16strlcpy};
// Alias needed for the VST3 attribute macro // Alias needed for the VST3 attribute macro
@ -74,9 +74,12 @@ macro_rules! check_null_ptr_msg {
} }
#[VST3(implements(IComponent, IEditController, IAudioProcessor))] #[VST3(implements(IComponent, IEditController, IAudioProcessor))]
pub struct Wrapper<'a, P: Plugin> { pub(crate) struct Wrapper<'a, P: Plugin> {
/// The wrapped plugin instance. /// The wrapped plugin instance.
plugin: RefCell<P>, plugin: RefCell<P>,
/// The current bus configuration, modified through `IAudioProcessor::setBusArrangements()`.
current_bus_config: RefCell<BusConfig>,
/// Whether the plugin is currently bypassed. This is not yet integrated with the `Plugin` /// Whether the plugin is currently bypassed. This is not yet integrated with the `Plugin`
/// trait. /// trait.
bypass_state: Cell<bool>, bypass_state: Cell<bool>,
@ -101,23 +104,12 @@ pub struct Wrapper<'a, P: Plugin> {
/// Mappings from parameter hashes back to string parameter indentifiers. Useful for debug /// Mappings from parameter hashes back to string parameter indentifiers. Useful for debug
/// logging and when handling plugin state. /// logging and when handling plugin state.
param_id_hashes: HashMap<u32, &'static str>, param_id_hashes: HashMap<u32, &'static str>,
/// The current bus configuration, modified through `IAudioProcessor::setBusArrangements()`.
current_bus_config: RefCell<BusConfig>,
} }
impl<P: Plugin> Wrapper<'_, P> { impl<P: Plugin> Wrapper<'_, P> {
pub fn new() -> Box<Self> { pub fn new() -> Box<Self> {
let mut wrapper = Self::allocate( let mut wrapper = Self::allocate(
RefCell::new(P::default()), // plugin RefCell::new(P::default()),
Cell::new(false), // bypass_state
Cell::new(ProcessStatus::Normal), // last_process_status
AtomicBool::new(false), // is_processing
RefCell::new(Vec::new()), // output_slices
HashMap::new(), // param_by_hash
Vec::new(), // param_hashes
Vec::new(), // param_defaults_normalized
HashMap::new(), // param_id_hashes
// Some hosts, like the current version of Bitwig and Ardour at the time of writing, // Some hosts, like the current version of Bitwig and Ardour at the time of writing,
// will try using the plugin's default not yet initialized bus arrangement. Because of // will try using the plugin's default not yet initialized bus arrangement. Because of
// that, we'll always initialize this configuration even before the host requests a // that, we'll always initialize this configuration even before the host requests a
@ -126,6 +118,14 @@ impl<P: Plugin> Wrapper<'_, P> {
num_input_channels: P::DEFAULT_NUM_INPUTS, num_input_channels: P::DEFAULT_NUM_INPUTS,
num_output_channels: P::DEFAULT_NUM_OUTPUTS, num_output_channels: P::DEFAULT_NUM_OUTPUTS,
}), }),
Cell::new(false), // bypass_state
Cell::new(ProcessStatus::Normal), // last_process_status
AtomicBool::new(false), // is_processing
RefCell::new(Vec::new()), // output_slices
HashMap::new(), // param_by_hash
Vec::new(), // param_hashes
Vec::new(), // param_defaults_normalized
HashMap::new(), // param_id_hashes
); );
// This is a mapping from the parameter IDs specified by the plugin to pointers to thsoe // This is a mapping from the parameter IDs specified by the plugin to pointers to thsoe
@ -292,9 +292,44 @@ impl<P: Plugin> IComponent for Wrapper<'_, P> {
kResultFalse kResultFalse
} }
unsafe fn get_state(&self, _state: *mut c_void) -> tresult { unsafe fn get_state(&self, state: *mut c_void) -> tresult {
// TODO: Implemnt state saving and restoring check_null_ptr!(state);
kResultFalse
let state: ComPtr<dyn IBStream> = ComPtr::new(state as *mut _);
// We'll serialize parmaeter values as a simple `string_param_id: display_value` map.
let params = self
.param_id_hashes
.iter()
.filter_map(|(hash, param_id_str)| {
let param_ptr = self.param_by_hash.get(hash)?;
Some((param_id_str, param_ptr))
})
.map(|(&param_id_str, &param_ptr)| match param_ptr {
ParamPtr::FloatParam(p) => (param_id_str, ParamValue::F32((*p).value)),
ParamPtr::IntParam(p) => (param_id_str, ParamValue::I32((*p).value)),
})
.collect();
let plugin_state = State { params };
match serde_json::to_vec(&plugin_state) {
Ok(serialized) => {
let mut num_bytes_written = 0;
let result = state.write(
serialized.as_ptr() as *const c_void,
serialized.len() as i32,
&mut num_bytes_written,
);
nih_debug_assert_eq!(result, kResultOk);
nih_debug_assert_eq!(num_bytes_written as usize, serialized.len());
kResultOk
}
Err(err) => {
nih_debug_assert_failure!("Could not save state: {}", err);
kResultFalse
}
}
} }
} }
@ -722,6 +757,7 @@ impl<P: Plugin> IAudioProcessor for Wrapper<'_, P> {
} }
} }
#[doc(hidden)]
#[VST3(implements(IPluginFactory, IPluginFactory2, IPluginFactory3))] #[VST3(implements(IPluginFactory, IPluginFactory2, IPluginFactory3))]
pub struct Factory<P: Vst3Plugin> { pub struct Factory<P: Vst3Plugin> {
/// The exposed plugin's GUID. Instead of generating this, we'll just let the programmer decide /// The exposed plugin's GUID. Instead of generating this, we'll just let the programmer decide