1
0
Fork 0

Also move state deserialization to a function

This commit is contained in:
Robbert van der Helm 2022-03-02 16:00:11 +01:00
parent 7a3d3b8c8e
commit ec452bd41d
3 changed files with 121 additions and 78 deletions

View file

@ -19,6 +19,7 @@ use clap_sys::ext::params::{
clap_param_info, clap_plugin_params, CLAP_EXT_PARAMS, CLAP_PARAM_IS_BYPASS, clap_param_info, clap_plugin_params, CLAP_EXT_PARAMS, CLAP_PARAM_IS_BYPASS,
CLAP_PARAM_IS_STEPPED, CLAP_PARAM_IS_STEPPED,
}; };
use clap_sys::ext::state::clap_plugin_state;
use clap_sys::ext::thread_check::{clap_host_thread_check, CLAP_EXT_THREAD_CHECK}; use clap_sys::ext::thread_check::{clap_host_thread_check, CLAP_EXT_THREAD_CHECK};
use clap_sys::host::clap_host; use clap_sys::host::clap_host;
use clap_sys::id::{clap_id, CLAP_INVALID_ID}; use clap_sys::id::{clap_id, CLAP_INVALID_ID};
@ -27,6 +28,7 @@ use clap_sys::process::{
clap_process, clap_process_status, CLAP_PROCESS_CONTINUE, CLAP_PROCESS_CONTINUE_IF_NOT_QUIET, clap_process, clap_process_status, CLAP_PROCESS_CONTINUE, CLAP_PROCESS_CONTINUE_IF_NOT_QUIET,
CLAP_PROCESS_ERROR, CLAP_PROCESS_ERROR,
}; };
use clap_sys::stream::{clap_istream, clap_ostream};
use crossbeam::atomic::AtomicCell; use crossbeam::atomic::AtomicCell;
use crossbeam::queue::ArrayQueue; use crossbeam::queue::ArrayQueue;
use lazy_static::lazy_static; use lazy_static::lazy_static;
@ -124,13 +126,15 @@ pub struct Wrapper<P: ClapPlugin> {
/// can retrieve them later for the UI if needed. /// can retrieve them later for the UI if needed.
param_defaults_normalized: HashMap<u32, f32>, param_defaults_normalized: HashMap<u32, f32>,
/// Mappings from string parameter indentifiers to parameter hashes. Useful for debug logging /// Mappings from string parameter indentifiers to parameter hashes. Useful for debug logging
/// and when storing and restorign plugin state. /// and when storing and restoring plugin state.
param_id_to_hash: HashMap<&'static str, u32>, param_id_to_hash: HashMap<&'static str, u32>,
/// The inverse mapping from [Self::param_by_hash]. This is needed to be able to have an /// The inverse mapping from [Self::param_by_hash]. This is needed to be able to have an
/// ergonomic parameter setting API that uses references to the parameters instead of having to /// ergonomic parameter setting API that uses references to the parameters instead of having to
/// add a setter function to the parameter (or even worse, have it be completely untyped). /// add a setter function to the parameter (or even worse, have it be completely untyped).
param_ptr_to_hash: HashMap<ParamPtr, u32>, param_ptr_to_hash: HashMap<ParamPtr, u32>,
clap_plugin_state: clap_plugin_state,
/// A queue of tasks that still need to be performed. Because CLAP lets the plugin request a /// A queue of tasks that still need to be performed. Because CLAP lets the plugin request a
/// host callback directly, we don't need to use the OsEventLoop we use in our other plugin /// host callback directly, we don't need to use the OsEventLoop we use in our other plugin
/// implementations. Instead, we'll post tasks to this queue, ask the host to call /// implementations. Instead, we'll post tasks to this queue, ask the host to call
@ -273,6 +277,11 @@ impl<P: ClapPlugin> Wrapper<P> {
param_id_to_hash: HashMap::new(), param_id_to_hash: HashMap::new(),
param_ptr_to_hash: HashMap::new(), param_ptr_to_hash: HashMap::new(),
clap_plugin_state: clap_plugin_state {
save: Self::ext_state_save,
load: Self::ext_state_load,
},
tasks: ArrayQueue::new(TASK_QUEUE_CAPACITY), tasks: ArrayQueue::new(TASK_QUEUE_CAPACITY),
main_thread_id: thread::current().id(), main_thread_id: thread::current().id(),
}; };
@ -1055,6 +1064,23 @@ impl<P: ClapPlugin> Wrapper<P> {
// TODO: Handle automation/outputs // TODO: Handle automation/outputs
} }
unsafe extern "C" fn ext_state_save(
plugin: *const clap_plugin,
stream: *mut clap_ostream,
) -> bool {
check_null_ptr!(false, plugin, stream);
let wrapper = &*(plugin as *const Self);
todo!()
}
unsafe extern "C" fn ext_state_load(
plugin: *const clap_plugin,
stream: *mut clap_istream,
) -> bool {
todo!()
}
} }
/// Convenience function to query an extennsion from the host. /// Convenience function to query an extennsion from the host.

View file

@ -7,7 +7,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
use crate::param::internals::ParamPtr; use crate::param::internals::ParamPtr;
use crate::param::Param; use crate::param::Param;
use crate::Params; use crate::{BufferConfig, Params};
/// A plain, unnormalized value for a parameter. /// A plain, unnormalized value for a parameter.
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@ -85,3 +85,83 @@ pub(crate) unsafe fn serialize(
let plugin_state = State { params, fields }; let plugin_state = State { params, fields };
serde_json::to_vec(&plugin_state) serde_json::to_vec(&plugin_state)
} }
/// Serialize a plugin's state to a vector containing JSON data. This can (and should) be shared
/// across plugin formats. Returns `false` and logs an error if the state could not be deserialized.
///
/// Make sure to reinitialize plugin after deserializing the state so it can react to the new
/// parameter values. The smoothers have already been reset by this function.
pub(crate) unsafe fn deserialize(
state: &[u8],
plugin_params: Pin<&dyn Params>,
param_by_hash: &HashMap<u32, ParamPtr>,
param_id_to_hash: &HashMap<&'static str, u32>,
current_buffer_config: Option<&BufferConfig>,
bypass_param_id: &str,
bypass_state: &AtomicBool,
) -> bool {
let state: State = match serde_json::from_slice(state) {
Ok(s) => s,
Err(err) => {
nih_debug_assert_failure!("Error while deserializing state: {}", err);
return false;
}
};
let sample_rate = current_buffer_config.map(|c| c.sample_rate);
for (param_id_str, param_value) in state.params {
// Handle the bypass parameter separately
if param_id_str == bypass_param_id {
match param_value {
ParamValue::Bool(b) => bypass_state.store(b, Ordering::SeqCst),
_ => nih_debug_assert_failure!(
"Invalid serialized value {:?} for parameter \"{}\"",
param_value,
param_id_str,
),
};
continue;
}
let param_ptr = match param_id_to_hash
.get(param_id_str.as_str())
.and_then(|hash| param_by_hash.get(hash))
{
Some(ptr) => ptr,
None => {
nih_debug_assert_failure!("Unknown parameter: {}", param_id_str);
continue;
}
};
match (param_ptr, param_value) {
(ParamPtr::FloatParam(p), ParamValue::F32(v)) => (**p).set_plain_value(v),
(ParamPtr::IntParam(p), ParamValue::I32(v)) => (**p).set_plain_value(v),
(ParamPtr::BoolParam(p), ParamValue::Bool(v)) => (**p).set_plain_value(v),
// Enums are serialized based on the active variant's index (which may not be the
// same as the discriminator)
(ParamPtr::EnumParam(p), ParamValue::I32(variant_idx)) => {
(**p).set_plain_value(variant_idx)
}
(param_ptr, param_value) => {
nih_debug_assert_failure!(
"Invalid serialized value {:?} for parameter \"{}\" ({:?})",
param_value,
param_id_str,
param_ptr,
);
}
}
// Make sure everything starts out in sync
if let Some(sample_rate) = sample_rate {
param_ptr.update_smoother(sample_rate, true);
}
}
// The plugin can also persist arbitrary fields alongside its parameters. This is useful for
// storing things like sample data.
plugin_params.deserialize_fields(&state.fields);
true
}

View file

@ -1,5 +1,4 @@
use std::cmp; use std::cmp;
use std::collections::HashMap;
use std::ffi::c_void; use std::ffi::c_void;
use std::mem::{self, MaybeUninit}; use std::mem::{self, MaybeUninit};
use std::ptr; use std::ptr;
@ -18,10 +17,8 @@ use widestring::U16CStr;
use super::inner::WrapperInner; use super::inner::WrapperInner;
use super::util::{VstPtr, BYPASS_PARAM_HASH, BYPASS_PARAM_ID}; use super::util::{VstPtr, BYPASS_PARAM_HASH, BYPASS_PARAM_ID};
use super::view::WrapperView; use super::view::WrapperView;
use crate::param::internals::ParamPtr;
use crate::param::Param;
use crate::plugin::{BufferConfig, BusConfig, NoteEvent, ProcessStatus, Vst3Plugin}; use crate::plugin::{BufferConfig, BusConfig, NoteEvent, ProcessStatus, Vst3Plugin};
use crate::wrapper::state::{self, ParamValue, State}; use crate::wrapper::state;
use crate::wrapper::util::{process_wrapper, u16strlcpy}; use crate::wrapper::util::{process_wrapper, u16strlcpy};
// Alias needed for the VST3 attribute macro // Alias needed for the VST3 attribute macro
@ -220,80 +217,20 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
return kResultFalse; return kResultFalse;
} }
let state: State = match serde_json::from_slice(&read_buffer) { let success = state::deserialize(
Ok(s) => s, &read_buffer,
Err(err) => { self.inner.plugin.read().params(),
nih_debug_assert_failure!("Error while deserializing state: {}", err); &self.inner.param_by_hash,
return kResultFalse; &self.inner.param_id_to_hash,
} self.inner.current_buffer_config.load().as_ref(),
}; BYPASS_PARAM_ID,
&self.inner.bypass_state,
let sample_rate = self );
.inner if !success {
.current_buffer_config return kResultFalse;
.load()
.map(|c| c.sample_rate);
for (param_id_str, param_value) in state.params {
// Handle the bypass parameter separately
if param_id_str == BYPASS_PARAM_ID {
match param_value {
ParamValue::Bool(b) => self.inner.bypass_state.store(b, Ordering::SeqCst),
_ => nih_debug_assert_failure!(
"Invalid serialized value {:?} for parameter \"{}\"",
param_value,
param_id_str,
),
};
continue;
}
let param_ptr = match self
.inner
.param_id_to_hash
.get(param_id_str.as_str())
.and_then(|hash| self.inner.param_by_hash.get(hash))
{
Some(ptr) => ptr,
None => {
nih_debug_assert_failure!("Unknown parameter: {}", param_id_str);
continue;
}
};
match (param_ptr, param_value) {
(ParamPtr::FloatParam(p), ParamValue::F32(v)) => (**p).set_plain_value(v),
(ParamPtr::IntParam(p), ParamValue::I32(v)) => (**p).set_plain_value(v),
(ParamPtr::BoolParam(p), ParamValue::Bool(v)) => (**p).set_plain_value(v),
// Enums are serialized based on the active variant's index (which may not be the
// same as the discriminator)
(ParamPtr::EnumParam(p), ParamValue::I32(variant_idx)) => {
(**p).set_plain_value(variant_idx)
}
(param_ptr, param_value) => {
nih_debug_assert_failure!(
"Invalid serialized value {:?} for parameter \"{}\" ({:?})",
param_value,
param_id_str,
param_ptr,
);
}
}
// Make sure everything starts out in sync
if let Some(sample_rate) = sample_rate {
param_ptr.update_smoother(sample_rate, true);
}
} }
// The plugin can also persist arbitrary fields alongside its parameters. This is useful for // Reinitialize the plugin after loading state so it can respond to the new parameter values
// storing things like sample data.
self.inner
.plugin
.read()
.params()
.deserialize_fields(&state.fields);
// Reinitialize the plugin after loading state so it can respond to the new parmaeters
let bus_config = self.inner.current_bus_config.load(); let bus_config = self.inner.current_bus_config.load();
if let Some(buffer_config) = self.inner.current_buffer_config.load() { if let Some(buffer_config) = self.inner.current_buffer_config.load() {
self.inner.plugin.write().initialize( self.inner.plugin.write().initialize(