Skip to main content

nice_plug_core/params/
boolean.rs

1//! Simple boolean parameters.
2
3use atomic_float::AtomicF32;
4use std::fmt::{Debug, Display};
5use std::sync::Arc;
6use std::sync::atomic::{AtomicBool, Ordering};
7
8use super::internals::ParamPtr;
9use super::{InternalParamMut, Param, ParamFlags};
10
11/// A simple boolean parameter.
12pub struct BoolParam {
13    /// The field's current value, after monophonic modulation has been applied.
14    value: AtomicBool,
15    /// The field's current value normalized to the `[0, 1]` range.
16    normalized_value: AtomicF32,
17    /// The field's value before any monophonic automation coming from the host has been applied.
18    /// This will always be the same as `value` for VST3 plugins.
19    unmodulated_value: AtomicBool,
20    /// The field's value normalized to the `[0, 1]` range before any monophonic automation coming
21    /// from the host has been applied. This will always be the same as `value` for VST3 plugins.
22    unmodulated_normalized_value: AtomicF32,
23    /// A value in `[-1, 1]` indicating the amount of modulation applied to
24    /// `unmodulated_normalized_`. This needs to be stored separately since the normalized values are
25    /// clamped, and this value persists after new automation events.
26    modulation_offset: AtomicF32,
27    /// The field's default value.
28    default: bool,
29
30    /// Flags to control the parameter's behavior. See [`ParamFlags`].
31    flags: ParamFlags,
32    /// Optional callback for listening to value changes. The argument passed to this function is
33    /// the parameter's new value. This should not do anything expensive as it may be called
34    /// multiple times in rapid succession, and it can be run from both the GUI and the audio
35    /// thread.
36    value_changed: Option<Arc<dyn Fn(bool) + Send + Sync>>,
37
38    /// The parameter's human readable display name.
39    name: String,
40    /// If this parameter has been marked as polyphonically modulatable, then this will be a unique
41    /// integer identifying the parameter. Because this value is determined by the plugin itself,
42    /// the plugin can easily map
43    /// [`NoteEvent::PolyModulation`][crate::prelude::NoteEvent::PolyModulation] events to the
44    /// correct parameter by pattern matching on a constant.
45    poly_modulation_id: Option<u32>,
46    /// Optional custom conversion function from a boolean value to a string.
47    value_to_string: Option<Arc<dyn Fn(bool) -> String + Send + Sync>>,
48    /// Optional custom conversion function from a string to a boolean value. If the string cannot
49    /// be parsed, then this should return a `None`. If this happens while the parameter is being
50    /// updated then the update will be canceled.
51    string_to_value: Option<Arc<dyn Fn(&str) -> Option<bool> + Send + Sync>>,
52}
53
54impl Display for BoolParam {
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        match (self.value(), &self.value_to_string) {
57            (v, Some(func)) => write!(f, "{}", func(v)),
58            (true, None) => write!(f, "On"),
59            (false, None) => write!(f, "Off"),
60        }
61    }
62}
63
64impl Debug for BoolParam {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        // This uses the above `Display` instance to show the value
67        if self.value.load(Ordering::Relaxed) != self.unmodulated_value.load(Ordering::Relaxed) {
68            write!(f, "{}: {} (modulated)", self.name, self)
69        } else {
70            write!(f, "{}: {}", self.name, self)
71        }
72    }
73}
74
75// `Params` can not be implemented outside of nice-plug itself because `ParamPtr` is also closed
76impl super::Sealed for BoolParam {}
77
78impl Param for BoolParam {
79    type Plain = bool;
80
81    fn name(&self) -> &str {
82        &self.name
83    }
84
85    fn unit(&self) -> &'static str {
86        ""
87    }
88
89    fn poly_modulation_id(&self) -> Option<u32> {
90        self.poly_modulation_id
91    }
92
93    #[inline]
94    fn modulated_plain_value(&self) -> Self::Plain {
95        self.value.load(Ordering::Relaxed)
96    }
97
98    #[inline]
99    fn modulated_normalized_value(&self) -> f32 {
100        self.normalized_value.load(Ordering::Relaxed)
101    }
102
103    #[inline]
104    fn unmodulated_plain_value(&self) -> Self::Plain {
105        self.unmodulated_value.load(Ordering::Relaxed)
106    }
107
108    #[inline]
109    fn unmodulated_normalized_value(&self) -> f32 {
110        self.unmodulated_normalized_value.load(Ordering::Relaxed)
111    }
112
113    #[inline]
114    fn default_plain_value(&self) -> Self::Plain {
115        self.default
116    }
117
118    fn step_count(&self) -> Option<usize> {
119        Some(1)
120    }
121
122    fn previous_step(&self, _from: Self::Plain, _finer: bool) -> Self::Plain {
123        false
124    }
125
126    fn next_step(&self, _from: Self::Plain, _finer: bool) -> Self::Plain {
127        true
128    }
129
130    fn normalized_value_to_string(&self, normalized: f32, _include_unit: bool) -> String {
131        let value = self.preview_plain(normalized);
132        match (value, &self.value_to_string) {
133            (v, Some(f)) => f(v),
134            (true, None) => String::from("On"),
135            (false, None) => String::from("Off"),
136        }
137    }
138
139    fn string_to_normalized_value(&self, string: &str) -> Option<f32> {
140        let string = string.trim();
141        let value = match &self.string_to_value {
142            Some(f) => f(string),
143            None => Some(string.eq_ignore_ascii_case("true") || string.eq_ignore_ascii_case("on")),
144        }?;
145
146        Some(self.preview_normalized(value))
147    }
148
149    #[inline]
150    fn preview_normalized(&self, plain: Self::Plain) -> f32 {
151        if plain { 1.0 } else { 0.0 }
152    }
153
154    #[inline]
155    fn preview_plain(&self, normalized: f32) -> Self::Plain {
156        normalized > 0.5
157    }
158
159    fn flags(&self) -> ParamFlags {
160        self.flags
161    }
162
163    fn as_ptr(&self) -> ParamPtr {
164        ParamPtr::BoolParam(self as *const BoolParam as *mut BoolParam)
165    }
166}
167
168impl InternalParamMut for BoolParam {
169    unsafe fn _internal_set_plain_value(&self, plain: Self::Plain) -> bool {
170        let unmodulated_value = plain;
171        let unmodulated_normalized_value = self.preview_normalized(plain);
172
173        let modulation_offset = self.modulation_offset.load(Ordering::Relaxed);
174        let (value, normalized_value) = if modulation_offset == 0.0 {
175            (unmodulated_value, unmodulated_normalized_value)
176        } else {
177            let normalized_value =
178                (unmodulated_normalized_value + modulation_offset).clamp(0.0, 1.0);
179
180            (self.preview_plain(normalized_value), normalized_value)
181        };
182
183        // REAPER spams automation events with the same value. This prevents callbacks from firing
184        // multiple times. This can be problematic when they're used to trigger expensive
185        // computations when a parameter changes.
186        let old_value = self.value.swap(value, Ordering::Relaxed);
187        if value != old_value {
188            self.normalized_value
189                .store(normalized_value, Ordering::Relaxed);
190            self.unmodulated_value
191                .store(unmodulated_value, Ordering::Relaxed);
192            self.unmodulated_normalized_value
193                .store(unmodulated_normalized_value, Ordering::Relaxed);
194            if let Some(f) = &self.value_changed {
195                f(value);
196            }
197
198            true
199        } else {
200            false
201        }
202    }
203
204    unsafe fn _internal_set_normalized_value(&self, normalized: f32) -> bool {
205        // NOTE: The double conversion here is to make sure the state is reproducible. State is
206        //       saved and restored using plain values, and the new normalized value will be
207        //       different from `normalized`. This is not necessary for the modulation as these
208        //       values are never shown to the host.
209        unsafe { self._internal_set_plain_value(self.preview_plain(normalized)) }
210    }
211
212    unsafe fn _internal_modulate_value(&self, modulation_offset: f32) -> bool {
213        self.modulation_offset
214            .store(modulation_offset, Ordering::Relaxed);
215
216        // TODO: This renormalizes this value, which is not necessary
217        unsafe { self._internal_set_plain_value(self.unmodulated_plain_value()) }
218    }
219
220    unsafe fn _internal_update_smoother(&self, _sample_rate: f32, _init: bool) {
221        // Can't really smooth a binary parameter now can you
222    }
223}
224
225impl BoolParam {
226    /// Build a new [`BoolParam`]. Use the other associated functions to modify the behavior of the
227    /// parameter.
228    pub fn new(name: impl Into<String>, default: bool) -> Self {
229        Self {
230            value: AtomicBool::new(default),
231            normalized_value: AtomicF32::new(if default { 1.0 } else { 0.0 }),
232            unmodulated_value: AtomicBool::new(default),
233            unmodulated_normalized_value: AtomicF32::new(if default { 1.0 } else { 0.0 }),
234            modulation_offset: AtomicF32::new(0.0),
235            default,
236
237            flags: ParamFlags::default(),
238            value_changed: None,
239
240            name: name.into(),
241            poly_modulation_id: None,
242            value_to_string: None,
243            string_to_value: None,
244        }
245    }
246
247    /// The field's current plain value, after monophonic modulation has been applied. Equivalent to
248    /// calling `param.plain_value()`.
249    #[inline]
250    pub fn value(&self) -> bool {
251        self.modulated_plain_value()
252    }
253
254    /// Enable polyphonic modulation for this parameter. The ID is used to uniquely identify this
255    /// parameter in [`NoteEvent::PolyModulation`][crate::midi::NoteEvent::PolyModulation]
256    /// events, and must thus be unique between _all_ polyphonically modulatable parameters. See the
257    /// event's documentation on how to use polyphonic modulation. Also consider configuring the
258    /// `ClapPlugin::CLAP_POLY_MODULATION_CONFIG` constant when enabling this.
259    ///
260    /// # Important
261    ///
262    /// After enabling polyphonic modulation, the plugin **must** start sending
263    /// [`NoteEvent::VoiceTerminated`][crate::midi::NoteEvent::VoiceTerminated] events to the
264    /// host when a voice has fully ended. This allows the host to reuse its modulation resources.
265    pub fn with_poly_modulation_id(mut self, id: u32) -> Self {
266        self.poly_modulation_id = Some(id);
267        self
268    }
269
270    /// Run a callback whenever this parameter's value changes. The argument passed to this function
271    /// is the parameter's new value. This should not do anything expensive as it may be called
272    /// multiple times in rapid succession, and it can be run from both the GUI and the audio
273    /// thread.
274    pub fn with_callback(mut self, callback: Arc<dyn Fn(bool) + Send + Sync>) -> Self {
275        self.value_changed = Some(callback);
276        self
277    }
278
279    /// Use a custom conversion function to convert the boolean value to a string.
280    pub fn with_value_to_string(
281        mut self,
282        callback: Arc<dyn Fn(bool) -> String + Send + Sync>,
283    ) -> Self {
284        self.value_to_string = Some(callback);
285        self
286    }
287
288    /// Use a custom conversion function to convert from a string to a boolean value. If the string
289    /// cannot be parsed, then this should return a `None`. If this happens while the parameter is
290    /// being updated then the update will be canceled.
291    pub fn with_string_to_value(
292        mut self,
293        callback: Arc<dyn Fn(&str) -> Option<bool> + Send + Sync>,
294    ) -> Self {
295        self.string_to_value = Some(callback);
296        self
297    }
298
299    /// Mark this parameter as a bypass parameter. Plugin hosts can integrate this parameter into
300    /// their UI. Only a single [`BoolParam`] can be a bypass parameter, and nice-plug will add one
301    /// if you don't create one yourself. You will need to implement this yourself if your plugin
302    /// introduces latency.
303    pub fn make_bypass(mut self) -> Self {
304        self.flags.insert(ParamFlags::BYPASS);
305        self
306    }
307
308    /// Mark the parameter as non-automatable. This means that the parameter cannot be changed from
309    /// an automation lane. The parameter can however still be manually changed by the user from
310    /// either the plugin's own GUI or from the host's generic UI.
311    pub fn non_automatable(mut self) -> Self {
312        self.flags.insert(ParamFlags::NON_AUTOMATABLE);
313        self
314    }
315
316    /// Hide the parameter in the host's generic UI for this plugin. This also implies
317    /// `NON_AUTOMATABLE`. Setting this does not prevent you from changing the parameter in the
318    /// plugin's editor GUI.
319    pub fn hide(mut self) -> Self {
320        self.flags.insert(ParamFlags::HIDDEN);
321        self
322    }
323
324    /// Don't show this parameter when generating a generic UI for the plugin using one of
325    /// nice-plug's generic UI widgets.
326    pub fn hide_in_generic_ui(mut self) -> Self {
327        self.flags.insert(ParamFlags::HIDE_IN_GENERIC_UI);
328        self
329    }
330}