diff --git a/src/params.rs b/src/params.rs index b4a9b4a2..f5d8d49b 100644 --- a/src/params.rs +++ b/src/params.rs @@ -185,7 +185,7 @@ pub trait Params { } /// Internal pointers to parameters. This is an implementation detail used by the wrappers. -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum ParamPtr { FloatParam(*mut FloatParam), IntParam(*mut IntParam), diff --git a/src/wrapper/vst3.rs b/src/wrapper/vst3.rs index 5c38abe4..c7fcbfa5 100644 --- a/src/wrapper/vst3.rs +++ b/src/wrapper/vst3.rs @@ -55,20 +55,18 @@ pub struct Wrapper { /// trait. bypass_state: Cell, - /// A mapping from parameter IDs to pointers to parameters belonging to the plugin. As long as - /// `plugin` does not get recreated, these addresses will remain stable, as they are obtained - /// from a pinned object. - param_map: HashMap<&'static str, ParamPtr>, + /// A mapping from parameter ID hashes (obtained from the string parameter IDs) to pointers to + /// parameters belonging to the plugin. As long as `plugin` does not get recreated, these + /// addresses will remain stable, as they are obtained from a pinned object. + param_by_hash: HashMap, /// The keys from `param_map` in a stable order. - param_ids: Vec<&'static str>, - /// Mappings from parameter hashes back to string parameter indentifiers. - /// - /// TODO: To avoid needing two pointer dereferences each time, maybe restructure `param_map` in - /// this wrapper to be indexed by the numerical hash. - param_id_hashes: HashMap, + param_hashes: Vec, /// The default normalized parameter value for every parameter in `param_ids`. We need to store /// this in case the host requeries the parmaeter later. 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, /// The current bus configuration, modified through `IAudioProcessor::setBusArrangements()`. current_bus_config: RefCell, @@ -79,10 +77,10 @@ impl Wrapper

{ let mut wrapper = Self::allocate( P::default(), // plugin Cell::new(false), // bypass_state - HashMap::new(), // param_map - Vec::new(), // param_ids - HashMap::new(), // param_id_hashes + 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, // 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 @@ -96,24 +94,27 @@ impl Wrapper

{ // This is a mapping from the parameter IDs specified by the plugin to pointers to thsoe // parameters. Since the object returned by `params()` is pinned, these pointers are safe to // dereference as long as `wrapper.plugin` is alive - wrapper.param_map = wrapper.plugin.params().param_map(); - wrapper.param_ids = wrapper.param_map.keys().copied().collect(); - wrapper.param_defaults_normalized = wrapper - .param_ids - .iter() - .map(|id| unsafe { wrapper.param_map[id].normalized_value() }) - .collect(); - wrapper.param_id_hashes = wrapper - .param_ids - .iter() - .map(|id| (hash_param_id(id), *id)) - .collect(); - + let param_map = wrapper.plugin.params().param_map(); nih_debug_assert!( - !wrapper.param_map.contains_key(BYPASS_PARAM_ID), + !param_map.contains_key(BYPASS_PARAM_ID), "The wrapper alread yadds its own bypass parameter" ); + wrapper.param_by_hash = param_map + .iter() + .map(|(id, p)| (hash_param_id(id), *p)) + .collect(); + wrapper.param_hashes = wrapper.param_by_hash.keys().copied().collect(); + wrapper.param_defaults_normalized = wrapper + .param_hashes + .iter() + .map(|hash| unsafe { wrapper.param_by_hash[hash].normalized_value() }) + .collect(); + wrapper.param_id_hashes = param_map + .into_keys() + .map(|id| (hash_param_id(id), id)) + .collect(); + wrapper } } @@ -264,7 +265,7 @@ impl IEditController for Wrapper

{ unsafe fn get_parameter_count(&self) -> i32 { // NOTE: We add a bypass parameter ourselves on index `self.param_ids.len()`, so these // indices are all off by one - self.param_ids.len() as i32 + 1 + self.param_hashes.len() as i32 + 1 } unsafe fn get_parameter_info( @@ -273,14 +274,14 @@ impl IEditController for Wrapper

{ info: *mut vst3_sys::vst::ParameterInfo, ) -> tresult { // Parameter index `self.param_ids.len()` is our own bypass parameter - if param_index < 0 || param_index > self.param_ids.len() as i32 { + if param_index < 0 || param_index > self.param_hashes.len() as i32 { return kInvalidArgument; } *info = std::mem::zeroed(); let info = &mut *info; - if param_index == self.param_ids.len() as i32 { + if param_index == self.param_hashes.len() as i32 { info.id = hash_param_id(BYPASS_PARAM_ID); u16strlcpy(&mut info.title, "Bypass"); u16strlcpy(&mut info.short_title, "Bypass"); @@ -291,11 +292,11 @@ impl IEditController for Wrapper

{ info.flags = vst3_sys::vst::ParameterFlags::kCanAutomate as i32 | vst3_sys::vst::ParameterFlags::kIsBypass as i32; } else { - let param_id = &self.param_ids[param_index as usize]; + let param_hash = &self.param_hashes[param_index as usize]; let default_value = &self.param_defaults_normalized[param_index as usize]; - let param_ptr = &self.param_map[param_id]; + let param_ptr = &self.param_by_hash[param_hash]; - info.id = hash_param_id(param_id); + info.id = *param_hash; u16strlcpy(&mut info.title, param_ptr.name()); u16strlcpy(&mut info.short_title, param_ptr.name()); u16strlcpy(&mut info.units, param_ptr.unit()); @@ -326,8 +327,7 @@ impl IEditController for Wrapper

{ } kResultOk - } else if let Some(param_id) = self.param_id_hashes.get(&id) { - let param_ptr = &self.param_map[param_id]; + } else if let Some(param_ptr) = self.param_by_hash.get(&id) { u16strlcpy( dest, ¶m_ptr.normalized_value_to_string(value_normalized as f32), @@ -359,8 +359,7 @@ impl IEditController for Wrapper

{ *value_normalized = value; kResultOk - } else if let Some(param_id) = self.param_id_hashes.get(&id) { - let param_ptr = &self.param_map[param_id]; + } else if let Some(param_ptr) = self.param_by_hash.get(&id) { let value = match param_ptr.string_to_normalized_value(&string) { Some(v) => v as f64, None => return kResultFalse, @@ -376,8 +375,7 @@ impl IEditController for Wrapper

{ unsafe fn normalized_param_to_plain(&self, id: u32, value_normalized: f64) -> f64 { if id == *BYPASS_PARAM_HASH { value_normalized - } else if let Some(param_id) = self.param_id_hashes.get(&id) { - let param_ptr = &self.param_map[param_id]; + } else if let Some(param_ptr) = self.param_by_hash.get(&id) { param_ptr.preview_unnormalized(value_normalized as f32) as f64 } else { 0.5 @@ -387,8 +385,7 @@ impl IEditController for Wrapper

{ unsafe fn plain_param_to_normalized(&self, id: u32, plain_value: f64) -> f64 { if id == *BYPASS_PARAM_HASH { plain_value.clamp(0.0, 1.0) - } else if let Some(param_id) = self.param_id_hashes.get(&id) { - let param_ptr = &self.param_map[param_id]; + } else if let Some(param_ptr) = self.param_by_hash.get(&id) { param_ptr.preview_normalized(plain_value as f32) as f64 } else { 0.5 @@ -402,8 +399,7 @@ impl IEditController for Wrapper

{ } else { 0.0 } - } else if let Some(param_id) = self.param_id_hashes.get(&id) { - let param_ptr = &self.param_map[param_id]; + } else if let Some(param_ptr) = self.param_by_hash.get(&id) { param_ptr.normalized_value() as f64 } else { 0.5 @@ -415,8 +411,7 @@ impl IEditController for Wrapper

{ self.bypass_state.set(value >= 0.5); kResultOk - } else if let Some(param_id) = self.param_id_hashes.get(&id) { - let param_ptr = &self.param_map[param_id]; + } else if let Some(param_ptr) = self.param_by_hash.get(&id) { param_ptr.set_normalized_value(value as f32); kResultOk