beamer_core/parameter_store.rs
1//! Low-level parameter store for host communication.
2//!
3//! This module provides the [`ParameterStore`] trait for direct host communication.
4//! It exposes the raw normalized value interface that hosts expect.
5//!
6//! # Choosing Between `Parameters` and `ParameterStore`
7//!
8//! Beamer provides two parameter traits that work together:
9//!
10//! - **[`Parameters`](crate::parameter_types::Parameters)** (recommended): High-level trait with
11//! type-erased iteration, automatic state serialization, and support for parameter
12//! types like `FloatParameter`, `IntParameter`, and `BoolParameter`. Use `#[derive(Parameters)]`
13//! for automatic implementation.
14//!
15//! - **[`ParameterStore`]**: Low-level trait for direct host communication. Provides
16//! raw access to normalized values and parameter metadata. Useful when you need
17//! fine-grained control over parameter handling or are building custom parameter
18//! systems.
19//!
20//! For most plugins, use `#[derive(Parameters)]` which automatically implements both traits.
21//! The `Parameters` trait builds on top of `ParameterStore` to provide a more ergonomic API.
22//!
23//! # Thread Safety
24//!
25//! The [`ParameterStore`] trait requires `Send + Sync` because parameters may be
26//! accessed from multiple threads:
27//! - Audio thread: reads parameter values during processing
28//! - UI thread: displays and modifies parameter values
29//! - Host thread: automation playback and recording
30//!
31//! Use atomic types (e.g., `AtomicU64` with `to_bits`/`from_bits`) for lock-free access.
32
33use crate::parameter_groups::ParameterGroups;
34use crate::parameter_info::ParameterInfo;
35use crate::types::{ParameterId, ParameterValue};
36
37/// Low-level trait for plugin parameter collections (host interface).
38///
39/// Implement this trait to declare your plugin's parameters. The format wrappers
40/// (AU, VST3) use this to communicate parameter information and values to the host.
41///
42/// # Example
43///
44/// ```ignore
45/// use std::sync::atomic::{AtomicU64, Ordering};
46/// use beamer_core::{ParameterStore, ParameterInfo, ParameterId, ParameterValue};
47///
48/// pub struct MyParameters {
49/// gain: AtomicU64,
50/// gain_info: ParameterInfo,
51/// }
52///
53/// impl ParameterStore for MyParameters {
54/// fn count(&self) -> usize { 1 }
55///
56/// fn info(&self, index: usize) -> Option<&ParameterInfo> {
57/// match index {
58/// 0 => Some(&self.gain_info),
59/// _ => None,
60/// }
61/// }
62///
63/// fn get_normalized(&self, id: ParameterId) -> ParameterValue {
64/// match id {
65/// 0 => f64::from_bits(self.gain.load(Ordering::Relaxed)),
66/// _ => 0.0,
67/// }
68/// }
69///
70/// fn set_normalized(&self, id: ParameterId, value: ParameterValue) {
71/// match id {
72/// 0 => self.gain.store(value.to_bits(), Ordering::Relaxed),
73/// _ => {}
74/// }
75/// }
76///
77/// // ... implement other methods
78/// }
79/// ```
80pub trait ParameterStore: Send + Sync {
81 /// Returns the number of parameters.
82 fn count(&self) -> usize;
83
84 /// Returns parameter info by index (0 to count-1).
85 ///
86 /// Returns `None` if index is out of bounds.
87 fn info(&self, index: usize) -> Option<&ParameterInfo>;
88
89 /// Gets the current normalized value (0.0 to 1.0) for a parameter.
90 ///
91 /// This must be lock-free and safe to call from the audio thread.
92 fn get_normalized(&self, id: ParameterId) -> ParameterValue;
93
94 /// Sets the normalized value (0.0 to 1.0) for a parameter.
95 ///
96 /// This must be lock-free and safe to call from the audio thread.
97 /// Implementations should clamp the value to [0.0, 1.0].
98 fn set_normalized(&self, id: ParameterId, value: ParameterValue);
99
100 /// Converts a normalized value to a display string.
101 ///
102 /// Used by the host to display parameter values in automation lanes,
103 /// tooltips, etc.
104 fn normalized_to_string(&self, id: ParameterId, normalized: ParameterValue) -> String;
105
106 /// Parses a display string to a normalized value.
107 ///
108 /// Used when the user types a value directly. Returns `None` if
109 /// the string cannot be parsed.
110 fn string_to_normalized(&self, id: ParameterId, string: &str) -> Option<ParameterValue>;
111
112 /// Converts a normalized value (0.0-1.0) to a plain/real value.
113 ///
114 /// For example, a frequency parameter might map 0.0-1.0 to 20-20000 Hz.
115 fn normalized_to_plain(&self, id: ParameterId, normalized: ParameterValue) -> ParameterValue;
116
117 /// Converts a plain/real value to a normalized value (0.0-1.0).
118 ///
119 /// Inverse of `normalized_to_plain`.
120 fn plain_to_normalized(&self, id: ParameterId, plain: ParameterValue) -> ParameterValue;
121
122 /// Find parameter info by ID.
123 ///
124 /// Default implementation searches linearly through all parameters.
125 fn info_by_id(&self, id: ParameterId) -> Option<&ParameterInfo> {
126 (0..self.count()).find_map(|i| {
127 let info = self.info(i)?;
128 if info.id == id {
129 Some(info)
130 } else {
131 None
132 }
133 })
134 }
135}
136
137/// Empty parameter collection for plugins with no parameters.
138#[derive(Debug, Clone, Copy, Default)]
139pub struct NoParameters;
140
141impl ParameterGroups for NoParameters {}
142
143impl ParameterStore for NoParameters {
144 fn count(&self) -> usize {
145 0
146 }
147
148 fn info(&self, _index: usize) -> Option<&ParameterInfo> {
149 None
150 }
151
152 fn get_normalized(&self, _id: ParameterId) -> ParameterValue {
153 0.0
154 }
155
156 fn set_normalized(&self, _id: ParameterId, _value: ParameterValue) {}
157
158 fn normalized_to_string(&self, _id: ParameterId, _normalized: ParameterValue) -> String {
159 String::new()
160 }
161
162 fn string_to_normalized(&self, _id: ParameterId, _string: &str) -> Option<ParameterValue> {
163 None
164 }
165
166 fn normalized_to_plain(&self, _id: ParameterId, normalized: ParameterValue) -> ParameterValue {
167 normalized
168 }
169
170 fn plain_to_normalized(&self, _id: ParameterId, plain: ParameterValue) -> ParameterValue {
171 plain
172 }
173}
174
175impl crate::parameter_types::Parameters for NoParameters {
176 fn count(&self) -> usize {
177 0
178 }
179
180 fn iter(&self) -> Box<dyn Iterator<Item = &dyn crate::parameter_types::ParameterRef> + '_> {
181 Box::new(std::iter::empty())
182 }
183
184 fn by_id(&self, _id: ParameterId) -> Option<&dyn crate::parameter_types::ParameterRef> {
185 None
186 }
187}