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",
|
"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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
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::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(|(¶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))]
|
#[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
|
||||||
|
|
Loading…
Reference in a new issue