Skip to main content

wavecraft_protocol/
params.rs

1//! Parameter definitions - the single source of truth for all plugin parameters.
2//!
3//! This module defines parameter IDs, specifications, conversion functions,
4//! and the ParamSet trait for user-defined parameter sets.
5
6/// Trait for user-defined parameter sets.
7///
8/// Implement this trait to define custom parameters for your plugin.
9/// The trait provides type-safe access to parameter specifications.
10///
11/// # Example
12///
13/// ```rust
14/// use wavecraft_protocol::{ParamSet, ParamSpec, ParamId};
15///
16/// #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17/// #[repr(u32)]
18/// pub enum MyParamId {
19///     Volume = 0,
20///     Pan = 1,
21/// }
22///
23/// impl From<MyParamId> for ParamId {
24///     fn from(id: MyParamId) -> Self {
25///         ParamId(id as u32)
26///     }
27/// }
28///
29/// pub struct MyParams;
30///
31/// impl ParamSet for MyParams {
32///     type Id = MyParamId;
33///     
34///     const SPECS: &'static [ParamSpec] = &[
35///         ParamSpec {
36///             id: ParamId(0),
37///             name: "Volume",
38///             short_name: "Vol",
39///             unit: "dB",
40///             default: 0.0,
41///             min: -60.0,
42///             max: 12.0,
43///             step: 0.1,
44///         },
45///         ParamSpec {
46///             id: ParamId(1),
47///             name: "Pan",
48///             short_name: "Pan",
49///             unit: "",
50///             default: 0.0,
51///             min: -1.0,
52///             max: 1.0,
53///             step: 0.01,
54///         },
55///     ];
56///     
57///     fn spec(id: Self::Id) -> Option<&'static ParamSpec> {
58///         Self::SPECS.iter().find(|s| s.id.0 == id as u32)
59///     }
60///     
61///     fn iter() -> impl Iterator<Item = &'static ParamSpec> {
62///         Self::SPECS.iter()
63///     }
64/// }
65/// ```
66pub trait ParamSet: 'static + Send + Sync {
67    /// The parameter ID type (typically an enum).
68    type Id: Copy + Into<ParamId>;
69
70    /// All parameter specifications for this set.
71    const SPECS: &'static [ParamSpec];
72
73    /// Get the specification for a parameter by ID.
74    ///
75    /// # Arguments
76    /// * `id` - The parameter ID
77    ///
78    /// # Returns
79    /// The parameter specification, or `None` if the ID is invalid.
80    fn spec(id: Self::Id) -> Option<&'static ParamSpec>;
81
82    /// Iterate over all parameter specifications.
83    fn iter() -> impl Iterator<Item = &'static ParamSpec>;
84
85    /// Get the number of parameters in this set.
86    fn count() -> usize {
87        Self::SPECS.len()
88    }
89}
90
91/// Unique identifier for each parameter.
92///
93/// This wraps a u32 ID for maximum flexibility. Plugin-specific parameter
94/// enums can convert to this type via `Into<ParamId>`.
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
96#[repr(transparent)]
97pub struct ParamId(pub u32);
98
99impl From<u32> for ParamId {
100    fn from(id: u32) -> Self {
101        ParamId(id)
102    }
103}
104
105/// Legacy parameter IDs for the VstKit reference implementation.
106///
107/// This enum is kept for backward compatibility with the existing plugin code.
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
109#[repr(u32)]
110pub enum WavecraftParamId {
111    Gain = 0,
112}
113
114impl From<WavecraftParamId> for ParamId {
115    fn from(id: WavecraftParamId) -> Self {
116        ParamId(id as u32)
117    }
118}
119
120/// Specification for a single parameter.
121#[derive(Debug, Clone)]
122pub struct ParamSpec {
123    /// Unique parameter ID
124    pub id: ParamId,
125    /// Full display name (e.g., "Gain")
126    pub name: &'static str,
127    /// Short name for compact displays (e.g., "Gain")
128    pub short_name: &'static str,
129    /// Unit suffix (e.g., "dB")
130    pub unit: &'static str,
131    /// Default value
132    pub default: f32,
133    /// Minimum value
134    pub min: f32,
135    /// Maximum value
136    pub max: f32,
137    /// Step size for UI controls (0.0 = continuous)
138    pub step: f32,
139}
140
141/// Canonical parameter specifications.
142///
143/// This is the single source of truth for all parameter metadata.
144pub const PARAM_SPECS: &[ParamSpec] = &[ParamSpec {
145    id: ParamId(0),
146    name: "Gain",
147    short_name: "Gain",
148    unit: "dB",
149    default: 0.0,
150    min: -24.0,
151    max: 24.0,
152    step: 0.1,
153}];
154
155/// Default parameter set for VstKit reference implementation.
156pub struct WavecraftParams;
157
158impl ParamSet for WavecraftParams {
159    type Id = WavecraftParamId;
160
161    const SPECS: &'static [ParamSpec] = PARAM_SPECS;
162
163    fn spec(id: Self::Id) -> Option<&'static ParamSpec> {
164        Self::SPECS.iter().find(|s| s.id.0 == id as u32)
165    }
166
167    fn iter() -> impl Iterator<Item = &'static ParamSpec> {
168        Self::SPECS.iter()
169    }
170}
171
172/// Convert decibels to linear gain.
173///
174/// # Arguments
175/// * `db` - Gain value in decibels
176///
177/// # Returns
178/// Linear gain multiplier (e.g., 0 dB → 1.0, -6 dB → ~0.5)
179///
180/// # Performance
181/// This function is marked `#[inline]` for use on the audio thread.
182#[inline]
183pub fn db_to_linear(db: f32) -> f32 {
184    10.0_f32.powf(db / 20.0)
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190
191    #[test]
192    fn test_db_to_linear_unity() {
193        let result = db_to_linear(0.0);
194        assert!((result - 1.0).abs() < 1e-6, "0 dB should equal 1.0 linear");
195    }
196
197    #[test]
198    fn test_db_to_linear_minus_6db() {
199        let result = db_to_linear(-6.0);
200        // -6 dB ≈ 0.501187
201        assert!(
202            (result - 0.501187).abs() < 0.001,
203            "-6 dB should be approximately 0.5"
204        );
205    }
206
207    #[test]
208    fn test_db_to_linear_plus_6db() {
209        let result = db_to_linear(6.0);
210        // +6 dB ≈ 1.9953
211        assert!(
212            (result - 1.9953).abs() < 0.001,
213            "+6 dB should be approximately 2.0"
214        );
215    }
216
217    #[test]
218    fn test_db_to_linear_minus_infinity() {
219        let result = db_to_linear(-100.0);
220        assert!(result < 1e-4, "-100 dB should be nearly silent");
221    }
222}