Skip to main content

nice_plug_core/params/
enums.rs

1//! Enum parameters. `enum` is a keyword, so `enums` it is.
2
3use std::fmt::{Debug, Display};
4use std::marker::PhantomData;
5use std::sync::Arc;
6
7use super::internals::ParamPtr;
8use super::range::IntRange;
9use super::{IntParam, InternalParamMut, Param, ParamFlags};
10
11// Re-export the derive macro
12pub use nice_plug_derive::Enum;
13
14/// An enum usable with `EnumParam`. This trait can be derived. Variants are identified either by a
15/// stable _id_ (see below), or if those are not set then they are identifier by their **declaration
16/// order**. If you don't provide IDs then you can freely rename the variant names, but reordering
17/// them will break compatibility with existing presets. The variant's name is used as the display
18/// name by default. If you want to override this, for instance, because it needs to contain spaces,
19/// then you can use the `#[name = "..."]` attribute:
20///
21/// ```ignore
22/// #[derive(Enum)]
23/// enum Foo {
24///     Bar,
25///     Baz,
26///     #[name = "Contains Spaces"]
27///     ContainsSpaces,
28/// }
29/// ```
30///
31/// IDs can be added by adding the `#[id = "..."]` attribute to each variant:
32///
33/// ```ignore
34/// #[derive(Enum)]
35/// enum Foo {
36///     #[id = "bar"],
37///     Bar,
38///     #[id = "baz"],
39///     Baz,
40///     #[id = "contains-spaces"],
41///     #[name = "Contains Spaces"]
42///     ContainsSpaces,
43/// }
44/// ```
45///
46/// You can safely move from not using IDs to using IDs without breaking patches, but you cannot go
47/// back to not using IDs after that.
48pub trait Enum {
49    /// The human readable names for the variants. These are displayed in the GUI or parameter list,
50    /// and also used for parsing text back to a parameter value. The length of this slice
51    /// determines how many variants there are.
52    fn variants() -> &'static [&'static str];
53
54    /// Optional identifiers for each variant. This makes it possible to reorder variants while
55    /// maintaining save compatibility (automation will still break of course). The length of this
56    /// slice needs to be equal to [`variants()`][Self::variants()].
57    fn ids() -> Option<&'static [&'static str]>;
58
59    /// Get the variant index (which may not be the same as the discriminator) corresponding to the
60    /// active variant. The index needs to correspond to the name in
61    /// [`variants()`][Self::variants()].
62    fn to_index(self) -> usize;
63
64    /// Get the variant corresponding to the variant with the same index in
65    /// [`variants()`][Self::variants()]. This must always return a value. If the index is out of
66    /// range, return the first variant.
67    fn from_index(index: usize) -> Self;
68}
69
70/// An [`IntParam`]-backed categorical parameter that allows convenient conversion to and from a
71/// simple enum. This enum must derive the re-exported [Enum] trait. Check the trait's documentation
72/// for more information on how this works.
73pub struct EnumParam<T: Enum + PartialEq> {
74    /// A type-erased version of this parameter so the wrapper can do its thing without needing to
75    /// know about `T`.
76    inner: EnumParamInner,
77
78    /// `T` is only used on the plugin side to convert back to an enum variant. Internally
79    /// everything works through the variants field on [`EnumParamInner`].
80    _marker: PhantomData<T>,
81}
82
83/// The type-erased internals for [`EnumParam`] so that the wrapper can interact with it. Acts like
84/// an [`IntParam`] but with different conversions from strings to values.
85pub struct EnumParamInner {
86    /// The integer parameter backing this enum parameter.
87    pub(crate) inner: IntParam,
88    /// The human readable variant names, obtained from [Enum::variants()].
89    variants: &'static [&'static str],
90    /// Stable identifiers for the enum variants, obtained from [Enum::ids()]. These are optional,
91    /// but if they are set (they're either not set for any variant, or set for all variants) then
92    /// these identifiers are used when saving enum parameter values to the state. Otherwise the
93    /// index is used.
94    ids: Option<&'static [&'static str]>,
95}
96
97impl<T: Enum + PartialEq> Display for EnumParam<T> {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        Display::fmt(&self.inner, f)
100    }
101}
102
103impl Display for EnumParamInner {
104    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105        write!(
106            f,
107            "{}",
108            self.variants[self.inner.modulated_plain_value() as usize]
109        )
110    }
111}
112
113impl<T: Enum + PartialEq> Debug for EnumParam<T> {
114    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115        Debug::fmt(&self.inner, f)
116    }
117}
118
119impl Debug for EnumParamInner {
120    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121        // This uses the above `Display` instance to show the value
122        if self.inner.modulated_plain_value() != self.inner.unmodulated_plain_value() {
123            write!(f, "{}: {} (modulated)", self.name(), self)
124        } else {
125            write!(f, "{}: {}", self.name(), self)
126        }
127    }
128}
129
130// `Params` can not be implemented outside of nice-plug itself because `ParamPtr` is also closed
131impl<T: Enum + PartialEq> super::Sealed for EnumParam<T> {}
132
133impl<T: Enum + PartialEq> Param for EnumParam<T> {
134    type Plain = T;
135
136    fn name(&self) -> &str {
137        self.inner.name()
138    }
139
140    fn unit(&self) -> &'static str {
141        self.inner.unit()
142    }
143
144    fn poly_modulation_id(&self) -> Option<u32> {
145        self.inner.poly_modulation_id()
146    }
147
148    #[inline]
149    fn modulated_plain_value(&self) -> Self::Plain {
150        T::from_index(self.inner.modulated_plain_value() as usize)
151    }
152
153    #[inline]
154    fn modulated_normalized_value(&self) -> f32 {
155        self.inner.modulated_normalized_value()
156    }
157
158    #[inline]
159    fn unmodulated_plain_value(&self) -> Self::Plain {
160        T::from_index(self.inner.unmodulated_plain_value() as usize)
161    }
162
163    #[inline]
164    fn unmodulated_normalized_value(&self) -> f32 {
165        self.inner.unmodulated_normalized_value()
166    }
167
168    #[inline]
169    fn default_plain_value(&self) -> Self::Plain {
170        T::from_index(self.inner.default_plain_value() as usize)
171    }
172
173    fn step_count(&self) -> Option<usize> {
174        self.inner.step_count()
175    }
176
177    fn previous_step(&self, from: Self::Plain, finer: bool) -> Self::Plain {
178        T::from_index(self.inner.previous_step(T::to_index(from) as i32, finer) as usize)
179    }
180
181    fn next_step(&self, from: Self::Plain, finer: bool) -> Self::Plain {
182        T::from_index(self.inner.next_step(T::to_index(from) as i32, finer) as usize)
183    }
184
185    fn normalized_value_to_string(&self, normalized: f32, include_unit: bool) -> String {
186        self.inner
187            .normalized_value_to_string(normalized, include_unit)
188    }
189
190    fn string_to_normalized_value(&self, string: &str) -> Option<f32> {
191        self.inner.string_to_normalized_value(string)
192    }
193
194    #[inline]
195    fn preview_normalized(&self, plain: Self::Plain) -> f32 {
196        self.inner.preview_normalized(T::to_index(plain) as i32)
197    }
198
199    #[inline]
200    fn preview_plain(&self, normalized: f32) -> Self::Plain {
201        T::from_index(self.inner.preview_plain(normalized) as usize)
202    }
203
204    fn flags(&self) -> ParamFlags {
205        self.inner.flags()
206    }
207
208    fn as_ptr(&self) -> ParamPtr {
209        self.inner.as_ptr()
210    }
211}
212
213// `Params` can not be implemented outside of nice-plug itself because `ParamPtr` is also closed
214impl super::Sealed for EnumParamInner {}
215
216impl Param for EnumParamInner {
217    type Plain = i32;
218
219    fn name(&self) -> &str {
220        self.inner.name()
221    }
222
223    fn unit(&self) -> &'static str {
224        ""
225    }
226
227    fn poly_modulation_id(&self) -> Option<u32> {
228        self.inner.poly_modulation_id()
229    }
230
231    #[inline]
232    fn modulated_plain_value(&self) -> Self::Plain {
233        self.inner.modulated_plain_value()
234    }
235
236    #[inline]
237    fn modulated_normalized_value(&self) -> f32 {
238        self.inner.modulated_normalized_value()
239    }
240
241    #[inline]
242    fn default_plain_value(&self) -> Self::Plain {
243        self.inner.default_plain_value()
244    }
245
246    #[inline]
247    fn unmodulated_plain_value(&self) -> Self::Plain {
248        self.inner.unmodulated_plain_value()
249    }
250
251    #[inline]
252    fn unmodulated_normalized_value(&self) -> f32 {
253        self.inner.unmodulated_normalized_value()
254    }
255
256    fn step_count(&self) -> Option<usize> {
257        Some(self.len() - 1)
258    }
259
260    fn previous_step(&self, from: Self::Plain, finer: bool) -> Self::Plain {
261        self.inner.previous_step(from, finer)
262    }
263
264    fn next_step(&self, from: Self::Plain, finer: bool) -> Self::Plain {
265        self.inner.next_step(from, finer)
266    }
267
268    fn normalized_value_to_string(&self, normalized: f32, _include_unit: bool) -> String {
269        let index = self.preview_plain(normalized);
270        self.variants[index as usize].to_string()
271    }
272
273    fn string_to_normalized_value(&self, string: &str) -> Option<f32> {
274        let string = string.trim();
275        self.variants
276            .iter()
277            .position(|variant| variant == &string)
278            .map(|idx| self.preview_normalized(idx as i32))
279    }
280
281    #[inline]
282    fn preview_normalized(&self, plain: Self::Plain) -> f32 {
283        self.inner.preview_normalized(plain)
284    }
285
286    #[inline]
287    fn preview_plain(&self, normalized: f32) -> Self::Plain {
288        self.inner.preview_plain(normalized)
289    }
290
291    fn flags(&self) -> ParamFlags {
292        self.inner.flags()
293    }
294
295    fn as_ptr(&self) -> ParamPtr {
296        ParamPtr::EnumParam(self as *const EnumParamInner as *mut EnumParamInner)
297    }
298}
299
300impl<T: Enum + PartialEq> InternalParamMut for EnumParam<T> {
301    unsafe fn _internal_set_plain_value(&self, plain: Self::Plain) -> bool {
302        unsafe {
303            self.inner
304                ._internal_set_plain_value(T::to_index(plain) as i32)
305        }
306    }
307
308    unsafe fn _internal_set_normalized_value(&self, normalized: f32) -> bool {
309        unsafe { self.inner._internal_set_normalized_value(normalized) }
310    }
311
312    unsafe fn _internal_modulate_value(&self, modulation_offset: f32) -> bool {
313        unsafe { self.inner._internal_modulate_value(modulation_offset) }
314    }
315
316    unsafe fn _internal_update_smoother(&self, sample_rate: f32, reset: bool) {
317        unsafe { self.inner._internal_update_smoother(sample_rate, reset) }
318    }
319}
320
321impl InternalParamMut for EnumParamInner {
322    unsafe fn _internal_set_plain_value(&self, plain: Self::Plain) -> bool {
323        unsafe { self.inner._internal_set_plain_value(plain) }
324    }
325
326    unsafe fn _internal_set_normalized_value(&self, normalized: f32) -> bool {
327        unsafe { self.inner._internal_set_normalized_value(normalized) }
328    }
329
330    unsafe fn _internal_modulate_value(&self, modulation_offset: f32) -> bool {
331        unsafe { self.inner._internal_modulate_value(modulation_offset) }
332    }
333
334    unsafe fn _internal_update_smoother(&self, sample_rate: f32, reset: bool) {
335        unsafe { self.inner._internal_update_smoother(sample_rate, reset) }
336    }
337}
338
339impl<T: Enum + PartialEq + 'static> EnumParam<T> {
340    /// Build a new [Self]. Use the other associated functions to modify the behavior of the
341    /// parameter.
342    pub fn new(name: impl Into<String>, default: T) -> Self {
343        let variants = T::variants();
344        let ids = T::ids();
345
346        Self {
347            inner: EnumParamInner {
348                inner: IntParam::new(
349                    name,
350                    T::to_index(default) as i32,
351                    IntRange::Linear {
352                        min: 0,
353                        max: variants.len() as i32 - 1,
354                    },
355                ),
356                variants,
357                ids,
358            },
359            _marker: PhantomData,
360        }
361    }
362
363    /// Get the active enum variant.
364    #[inline]
365    pub fn value(&self) -> T {
366        self.modulated_plain_value()
367    }
368
369    /// Enable polyphonic modulation for this parameter. The ID is used to uniquely identify this
370    /// parameter in [`NoteEvent::PolyModulation`][crate::midi::NoteEvent::PolyModulation]
371    /// events, and must thus be unique between _all_ polyphonically modulatable parameters. See the
372    /// event's documentation on how to use polyphonic modulation. Also consider configuring the
373    /// `ClapPlugin::CLAP_POLY_MODULATION_CONFIG` constant when enabling this.
374    ///
375    /// # Important
376    ///
377    /// After enabling polyphonic modulation, the plugin **must** start sending
378    /// [`NoteEvent::VoiceTerminated`][crate::midi::NoteEvent::VoiceTerminated] events to the
379    /// host when a voice has fully ended. This allows the host to reuse its modulation resources.
380    pub fn with_poly_modulation_id(mut self, id: u32) -> Self {
381        self.inner.inner = self.inner.inner.with_poly_modulation_id(id);
382        self
383    }
384
385    /// Run a callback whenever this parameter's value changes. The argument passed to this function
386    /// is the parameter's new value. This should not do anything expensive as it may be called
387    /// multiple times in rapid succession, and it can be run from both the GUI and the audio
388    /// thread.
389    pub fn with_callback(mut self, callback: Arc<dyn Fn(T) + Send + Sync>) -> Self {
390        self.inner.inner = self.inner.inner.with_callback(Arc::new(move |value| {
391            callback(T::from_index(value as usize))
392        }));
393        self
394    }
395
396    /// Mark the parameter as non-automatable. This means that the parameter cannot be changed from
397    /// an automation lane. The parameter can however still be manually changed by the user from
398    /// either the plugin's own GUI or from the host's generic UI.
399    pub fn non_automatable(mut self) -> Self {
400        self.inner.inner = self.inner.inner.non_automatable();
401        self
402    }
403
404    /// Hide the parameter in the host's generic UI for this plugin. This also implies
405    /// `NON_AUTOMATABLE`. Setting this does not prevent you from changing the parameter in the
406    /// plugin's editor GUI.
407    pub fn hide(mut self) -> Self {
408        self.inner.inner = self.inner.inner.hide();
409        self
410    }
411
412    /// Don't show this parameter when generating a generic UI for the plugin using one of
413    /// nice-plug's generic UI widgets.
414    pub fn hide_in_generic_ui(mut self) -> Self {
415        self.inner.inner = self.inner.inner.hide_in_generic_ui();
416        self
417    }
418}
419
420impl EnumParamInner {
421    /// Get the number of variants for this enum.
422    #[allow(clippy::len_without_is_empty)]
423    pub fn len(&self) -> usize {
424        self.variants.len()
425    }
426
427    /// Get the stable ID for the parameter's current value according to
428    /// [`unmodulated_plain_value()`][Param::unmodulated_plain_value()]. Returns `None` if this enum
429    /// parameter doesn't have any stable IDs.
430    pub fn unmodulated_plain_id(&self) -> Option<&'static str> {
431        let ids = &self.ids?;
432
433        // The `Enum` trait is supposed to make sure this contains enough values
434        Some(ids[self.unmodulated_plain_value() as usize])
435    }
436
437    /// Set the parameter based on a serialized stable string identifier. Return whether the ID was
438    /// known and the parameter was set.
439    pub fn set_from_id(&self, id: &str) -> bool {
440        match self
441            .ids
442            .and_then(|ids| ids.iter().position(|candidate| *candidate == id))
443        {
444            Some(index) => {
445                unsafe {
446                    self._internal_set_plain_value(index as i32);
447                }
448                true
449            }
450            None => false,
451        }
452    }
453}