diff --git a/src/wrapper/state.rs b/src/wrapper/state.rs index 6feb7da1..db37f22e 100644 --- a/src/wrapper/state.rs +++ b/src/wrapper/state.rs @@ -29,10 +29,9 @@ pub(crate) enum ParamValue { /// 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>, + pub params: HashMap, } diff --git a/src/wrapper/vst3.rs b/src/wrapper/vst3.rs index 0c8199c7..d8de33e9 100644 --- a/src/wrapper/vst3.rs +++ b/src/wrapper/vst3.rs @@ -35,7 +35,7 @@ use vst3_sys::vst::{ use vst3_sys::{ComPtr, VST3}; use widestring::U16CStr; -use crate::params::ParamPtr; +use crate::params::{Param, ParamPtr}; use crate::plugin::{BufferConfig, BusConfig, Plugin, ProcessStatus, Vst3Plugin}; use crate::wrapper::state::{ParamValue, State}; use crate::wrapper::util::{hash_param_id, strlcpy, u16strlcpy}; @@ -103,7 +103,9 @@ pub(crate) struct Wrapper<'a, P: Plugin> { param_defaults_normalized: Vec, /// Mappings from parameter hashes back to string parameter indentifiers. Useful for debug /// logging and when handling plugin state. - param_id_hashes: HashMap, + param_id_to_hash: HashMap, + /// The inverse mapping from `param_id_hashes`. Used only for restoring state. + param_hash_to_id: HashMap<&'static str, u32>, } impl Wrapper<'_, P> { @@ -125,7 +127,8 @@ impl Wrapper<'_, P> { HashMap::new(), // param_by_hash Vec::new(), // param_hashes Vec::new(), // param_defaults_normalized - HashMap::new(), // param_id_hashes + HashMap::new(), // param_id_to_hash + HashMap::new(), // param_hash_to_id ); // This is a mapping from the parameter IDs specified by the plugin to pointers to thsoe @@ -147,10 +150,15 @@ impl Wrapper<'_, P> { .iter() .map(|hash| unsafe { wrapper.param_by_hash[hash].normalized_value() }) .collect(); - wrapper.param_id_hashes = param_map + wrapper.param_id_to_hash = param_map .into_keys() .map(|id| (hash_param_id(id), id)) .collect(); + wrapper.param_hash_to_id = wrapper + .param_id_to_hash + .iter() + .map(|(&hash, &id)| (id, hash)) + .collect(); wrapper } @@ -287,9 +295,78 @@ impl IComponent for Wrapper<'_, P> { kResultOk } - unsafe fn set_state(&self, _state: *mut c_void) -> tresult { - // TODO: Implemnt state saving and restoring - kResultFalse + unsafe fn set_state(&self, state: *mut c_void) -> tresult { + check_null_ptr!(state); + + let state: ComPtr = ComPtr::new(state as *mut _); + + // We need to know how large the state is before we can read it. The current position can be + // zero, but it can also be something else. Bitwig prepends the preset header in the stream, + // while some other hosts don't expose that to the plugin. + let mut current_pos = 0; + let mut eof_pos = 0; + if state.tell(&mut current_pos) != kResultOk + || state.seek(0, vst3_sys::base::kIBSeekEnd, &mut eof_pos) != kResultOk + || state.seek(current_pos, vst3_sys::base::kIBSeekSet, ptr::null_mut()) != kResultOk + { + nih_debug_assert_failure!("Could not get the stream length"); + return kResultFalse; + } + + let stream_byte_size = (eof_pos - current_pos) as i32; + let mut num_bytes_read = 0; + let mut read_buffer: Vec = Vec::with_capacity(stream_byte_size as usize); + state.read( + read_buffer.as_mut_ptr() as *mut c_void, + read_buffer.capacity() as i32, + &mut num_bytes_read, + ); + read_buffer.set_len(num_bytes_read as usize); + + // If the size is zero, some hsots will always return `kResultFalse` even if the read was + // 'successful', so we can't check the return value but we can check the number of bytes + // read. + if read_buffer.len() != stream_byte_size as usize { + nih_debug_assert_failure!("Unexpected stream length"); + 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; + } + }; + + for (param_id_str, param_value) in state.params { + let param_ptr = match self + .param_hash_to_id + .get(param_id_str.as_str()) + .and_then(|hash| self.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), + (param_ptr, param_value) => { + nih_debug_assert_failure!( + "Invalid serialized value {:?} for parameter {} ({:?})", + param_value, + param_id_str, + param_ptr, + ); + } + } + } + + kResultOk } unsafe fn get_state(&self, state: *mut c_void) -> tresult { @@ -299,15 +376,15 @@ impl IComponent for Wrapper<'_, P> { // We'll serialize parmaeter values as a simple `string_param_id: display_value` map. let params = self - .param_id_hashes + .param_id_to_hash .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)), + ParamPtr::FloatParam(p) => (param_id_str.to_string(), ParamValue::F32((*p).value)), + ParamPtr::IntParam(p) => (param_id_str.to_string(), ParamValue::I32((*p).value)), }) .collect(); let plugin_state = State { params };