diff --git a/src/wrapper/clap/plugin.rs b/src/wrapper/clap/plugin.rs index 564bb9c9..4c92d4a2 100644 --- a/src/wrapper/clap/plugin.rs +++ b/src/wrapper/clap/plugin.rs @@ -19,6 +19,7 @@ use clap_sys::ext::params::{ clap_param_info, clap_plugin_params, CLAP_EXT_PARAMS, CLAP_PARAM_IS_BYPASS, 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::host::clap_host; 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_ERROR, }; +use clap_sys::stream::{clap_istream, clap_ostream}; use crossbeam::atomic::AtomicCell; use crossbeam::queue::ArrayQueue; use lazy_static::lazy_static; @@ -124,13 +126,15 @@ pub struct Wrapper { /// can retrieve them later for the UI if needed. param_defaults_normalized: HashMap, /// 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>, /// 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 /// add a setter function to the parameter (or even worse, have it be completely untyped). param_ptr_to_hash: HashMap, + clap_plugin_state: clap_plugin_state, + /// 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 /// implementations. Instead, we'll post tasks to this queue, ask the host to call @@ -273,6 +277,11 @@ impl Wrapper

{ param_id_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), main_thread_id: thread::current().id(), }; @@ -1055,6 +1064,23 @@ impl Wrapper

{ // 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. diff --git a/src/wrapper/state.rs b/src/wrapper/state.rs index 81c6802c..81d6c347 100644 --- a/src/wrapper/state.rs +++ b/src/wrapper/state.rs @@ -7,7 +7,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use crate::param::internals::ParamPtr; use crate::param::Param; -use crate::Params; +use crate::{BufferConfig, Params}; /// A plain, unnormalized value for a parameter. #[derive(Debug, Serialize, Deserialize)] @@ -85,3 +85,83 @@ pub(crate) unsafe fn serialize( let plugin_state = State { params, fields }; 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, + 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 +} diff --git a/src/wrapper/vst3/wrapper.rs b/src/wrapper/vst3/wrapper.rs index f774da4a..49052e95 100644 --- a/src/wrapper/vst3/wrapper.rs +++ b/src/wrapper/vst3/wrapper.rs @@ -1,5 +1,4 @@ use std::cmp; -use std::collections::HashMap; use std::ffi::c_void; use std::mem::{self, MaybeUninit}; use std::ptr; @@ -18,10 +17,8 @@ use widestring::U16CStr; use super::inner::WrapperInner; use super::util::{VstPtr, BYPASS_PARAM_HASH, BYPASS_PARAM_ID}; use super::view::WrapperView; -use crate::param::internals::ParamPtr; -use crate::param::Param; 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}; // Alias needed for the VST3 attribute macro @@ -220,80 +217,20 @@ impl IComponent for Wrapper

{ return kResultFalse; } - let state: State = match serde_json::from_slice(&read_buffer) { - Ok(s) => s, - Err(err) => { - nih_debug_assert_failure!("Error while deserializing state: {}", err); - return kResultFalse; - } - }; - - let sample_rate = self - .inner - .current_buffer_config - .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); - } + let success = state::deserialize( + &read_buffer, + self.inner.plugin.read().params(), + &self.inner.param_by_hash, + &self.inner.param_id_to_hash, + self.inner.current_buffer_config.load().as_ref(), + BYPASS_PARAM_ID, + &self.inner.bypass_state, + ); + if !success { + return kResultFalse; } - // The plugin can also persist arbitrary fields alongside its parameters. This is useful for - // 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 + // Reinitialize the plugin after loading state so it can respond to the new parameter values let bus_config = self.inner.current_bus_config.load(); if let Some(buffer_config) = self.inner.current_buffer_config.load() { self.inner.plugin.write().initialize(