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}