Implement saving parameter state
Restoring is next.
This commit is contained in:
parent
ef021915e5
commit
97a88e0db2
45
Cargo.lock
generated
45
Cargo.lock
generated
|
@ -9,6 +9,12 @@ dependencies = [
|
|||
"nih_plug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
|
@ -21,6 +27,8 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"lazy_static",
|
||||
"nih_plug_derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"vst3-sys",
|
||||
"widestring",
|
||||
]
|
||||
|
@ -51,6 +59,43 @@ dependencies = [
|
|||
"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]]
|
||||
name = "syn"
|
||||
version = "1.0.86"
|
||||
|
|
|
@ -11,6 +11,8 @@ members = ["nih_plug_derive", "plugins/gain"]
|
|||
[dependencies]
|
||||
nih_plug_derive = { path = "nih_plug_derive" }
|
||||
lazy_static = "1.4"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
# 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" }
|
||||
widestring = "1.0.0-beta.1"
|
||||
|
|
|
@ -17,5 +17,6 @@
|
|||
//! 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.
|
||||
|
||||
pub mod util;
|
||||
pub(crate) mod state;
|
||||
pub(crate) mod util;
|
||||
pub mod vst3;
|
||||
|
|
38
src/wrapper/state.rs
Normal file
38
src/wrapper/state.rs
Normal 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>,
|
||||
}
|
|
@ -28,16 +28,16 @@ use std::mem;
|
|||
use std::ptr;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use vst3_sys::base::{kInvalidArgument, kNoInterface, kResultFalse, kResultOk, tresult, TBool};
|
||||
use vst3_sys::base::{IPluginBase, IPluginFactory, IPluginFactory2, IPluginFactory3};
|
||||
use vst3_sys::vst::TChar;
|
||||
use vst3_sys::base::{IBStream, IPluginBase, IPluginFactory, IPluginFactory2, IPluginFactory3};
|
||||
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 crate::params::ParamPtr;
|
||||
use crate::plugin::{BufferConfig, BusConfig, Plugin, ProcessStatus, Vst3Plugin};
|
||||
use crate::wrapper::state::{ParamValue, State};
|
||||
use crate::wrapper::util::{hash_param_id, strlcpy, u16strlcpy};
|
||||
|
||||
// Alias needed for the VST3 attribute macro
|
||||
|
@ -74,9 +74,12 @@ macro_rules! check_null_ptr_msg {
|
|||
}
|
||||
|
||||
#[VST3(implements(IComponent, IEditController, IAudioProcessor))]
|
||||
pub struct Wrapper<'a, P: Plugin> {
|
||||
pub(crate) struct Wrapper<'a, P: Plugin> {
|
||||
/// The wrapped plugin instance.
|
||||
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`
|
||||
/// trait.
|
||||
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
|
||||
/// logging and when handling plugin state.
|
||||
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> {
|
||||
pub fn new() -> Box<Self> {
|
||||
let mut wrapper = Self::allocate(
|
||||
RefCell::new(P::default()), // plugin
|
||||
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
|
||||
RefCell::new(P::default()),
|
||||
// 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
|
||||
// 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_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
|
||||
|
@ -292,9 +292,44 @@ impl<P: Plugin> IComponent for Wrapper<'_, P> {
|
|||
kResultFalse
|
||||
}
|
||||
|
||||
unsafe fn get_state(&self, _state: *mut c_void) -> tresult {
|
||||
// TODO: Implemnt state saving and restoring
|
||||
kResultFalse
|
||||
unsafe fn get_state(&self, state: *mut c_void) -> tresult {
|
||||
check_null_ptr!(state);
|
||||
|
||||
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(|(¶m_id_str, ¶m_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))]
|
||||
pub struct Factory<P: Vst3Plugin> {
|
||||
/// The exposed plugin's GUID. Instead of generating this, we'll just let the programmer decide
|
||||
|
|
Loading…
Reference in a new issue