Skip to main content

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}