Skip to main content

nice_plug_core/params/
float.rs

1//! Continuous (or discrete, with a step size) floating point parameters.
2
3use atomic_float::AtomicF32;
4use std::fmt::{Debug, Display};
5use std::sync::Arc;
6use std::sync::atomic::Ordering;
7
8use crate::nice_debug_assert;
9
10use super::internals::ParamPtr;
11use super::range::FloatRange;
12use super::smoothing::{Smoother, SmoothingStyle};
13use super::{InternalParamMut, Param, ParamFlags};
14
15/// A floating point parameter that's stored unnormalized. The range is used for the normalization
16/// process.
17pub struct FloatParam {
18    /// The field's current plain value, after monophonic modulation has been applied.
19    value: AtomicF32,
20    /// The field's current value normalized to the `[0, 1]` range.
21    normalized_value: AtomicF32,
22    /// The field's plain, unnormalized value before any monophonic automation coming from the host
23    /// has been applied. This will always be the same as `value` for VST3 plugins.
24    unmodulated_value: AtomicF32,
25    /// The field's value normalized to the `[0, 1]` range before any monophonic automation coming
26    /// from the host has been applied. This will always be the same as `value` for VST3 plugins.
27    unmodulated_normalized_value: AtomicF32,
28    /// A value in `[-1, 1]` indicating the amount of modulation applied to
29    /// `unmodulated_normalized_`. This needs to be stored separately since the normalized values are
30    /// clamped, and this value persists after new automation events.
31    modulation_offset: AtomicF32,
32    /// The field's default plain, unnormalized value.
33    default: f32,
34    /// An optional smoother that will automatically interpolate between the new automation values
35    /// set by the host.
36    pub smoothed: Smoother<f32>,
37
38    /// Flags to control the parameter's behavior. See [`ParamFlags`].
39    flags: ParamFlags,
40    /// Optional callback for listening to value changes. The argument passed to this function is
41    /// the parameter's new **plain** value. This should not do anything expensive as it may be
42    /// called multiple times in rapid succession.
43    ///
44    /// To use this, you'll probably want to store an `Arc<Atomic*>` alongside the parameter in the
45    /// parameters struct, move a clone of that `Arc` into this closure, and then modify that.
46    ///
47    /// TODO: We probably also want to pass the old value to this function.
48    value_changed: Option<Arc<dyn Fn(f32) + Send + Sync>>,
49
50    /// The distribution of the parameter's values.
51    range: FloatRange,
52    /// The distance between discrete steps in this parameter. Mostly useful for quantizing GUI
53    /// input. If this is set and if [`value_to_string`][Self::value_to_string] is not set, then
54    /// this is also used when formatting the parameter. This must be a positive, nonzero number.
55    step_size: Option<f32>,
56    /// The parameter's human readable display name.
57    name: String,
58    /// The parameter value's unit, added after [`value_to_string`][Self::value_to_string] if that
59    /// is set. nice-plug will not automatically add a space before the unit.
60    unit: &'static str,
61    /// If this parameter has been marked as polyphonically modulatable, then this will be a unique
62    /// integer identifying the parameter. Because this value is determined by the plugin itself,
63    /// the plugin can easily map
64    /// [`NoteEvent::PolyModulation`][crate::prelude::NoteEvent::PolyModulation] events to the
65    /// correct parameter by pattern matching on a constant.
66    poly_modulation_id: Option<u32>,
67    /// Optional custom conversion function from a plain **unnormalized** value to a string.
68    value_to_string: Option<Arc<dyn Fn(f32) -> String + Send + Sync>>,
69    /// Optional custom conversion function from a string to a plain **unnormalized** value. If the
70    /// string cannot be parsed, then this should return a `None`. If this happens while the
71    /// parameter is being updated then the update will be canceled.
72    ///
73    /// The input string may or may not contain the unit, so you will need to be able to handle
74    /// that.
75    string_to_value: Option<Arc<dyn Fn(&str) -> Option<f32> + Send + Sync>>,
76}
77
78impl Display for FloatParam {
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        match (&self.value_to_string, &self.step_size) {
81            (Some(func), _) => write!(f, "{}{}", func(self.value()), self.unit),
82            (None, Some(step_size)) => {
83                let num_digits = decimals_from_step_size(*step_size);
84                write!(f, "{:.num_digits$}{}", self.value(), self.unit)
85            }
86            _ => write!(f, "{}{}", self.value(), self.unit),
87        }
88    }
89}
90
91impl Debug for FloatParam {
92    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93        // This uses the above `Display` instance to show the value
94        if self.modulated_plain_value() != self.unmodulated_plain_value() {
95            write!(f, "{}: {} (modulated)", &self.name, &self)
96        } else {
97            write!(f, "{}: {}", &self.name, &self)
98        }
99    }
100}
101
102// `Params` can not be implemented outside of nice-plug itself because `ParamPtr` is also closed
103impl super::Sealed for FloatParam {}
104
105impl Param for FloatParam {
106    type Plain = f32;
107
108    fn name(&self) -> &str {
109        &self.name
110    }
111
112    fn unit(&self) -> &'static str {
113        self.unit
114    }
115
116    fn poly_modulation_id(&self) -> Option<u32> {
117        self.poly_modulation_id
118    }
119
120    #[inline]
121    fn modulated_plain_value(&self) -> Self::Plain {
122        self.value.load(Ordering::Relaxed)
123    }
124
125    #[inline]
126    fn modulated_normalized_value(&self) -> f32 {
127        self.normalized_value.load(Ordering::Relaxed)
128    }
129
130    #[inline]
131    fn unmodulated_plain_value(&self) -> Self::Plain {
132        self.unmodulated_value.load(Ordering::Relaxed)
133    }
134
135    #[inline]
136    fn unmodulated_normalized_value(&self) -> f32 {
137        self.unmodulated_normalized_value.load(Ordering::Relaxed)
138    }
139
140    #[inline]
141    fn default_plain_value(&self) -> Self::Plain {
142        self.default
143    }
144
145    fn step_count(&self) -> Option<usize> {
146        None
147    }
148
149    fn previous_step(&self, from: Self::Plain, finer: bool) -> Self::Plain {
150        self.range.previous_step(from, self.step_size, finer)
151    }
152
153    fn next_step(&self, from: Self::Plain, finer: bool) -> Self::Plain {
154        self.range.next_step(from, self.step_size, finer)
155    }
156
157    fn normalized_value_to_string(&self, normalized: f32, include_unit: bool) -> String {
158        let value = self.preview_plain(normalized);
159        match (&self.value_to_string, &self.step_size, include_unit) {
160            (Some(f), _, true) => format!("{}{}", f(value), self.unit),
161            (Some(f), _, false) => f(value),
162            (None, Some(step_size), true) => {
163                let num_digits = decimals_from_step_size(*step_size);
164                format!("{:.num_digits$}{}", value, self.unit)
165            }
166            (None, Some(step_size), false) => {
167                let num_digits = decimals_from_step_size(*step_size);
168                format!("{value:.num_digits$}")
169            }
170            (None, None, true) => format!("{}{}", value, self.unit),
171            (None, None, false) => format!("{value}"),
172        }
173    }
174
175    fn string_to_normalized_value(&self, string: &str) -> Option<f32> {
176        let value = match &self.string_to_value {
177            Some(f) => f(string.trim()),
178            // In the CLAP wrapper the unit will be included, so make sure to handle that
179            None => string.trim().trim_end_matches(self.unit).parse().ok(),
180        }?;
181
182        Some(self.preview_normalized(value))
183    }
184
185    #[inline]
186    fn preview_normalized(&self, plain: Self::Plain) -> f32 {
187        self.range.normalize(plain)
188    }
189
190    #[inline]
191    fn preview_plain(&self, normalized: f32) -> Self::Plain {
192        let value = self.range.unnormalize(normalized);
193        match &self.step_size {
194            Some(step_size) => self.range.snap_to_step(value, *step_size as Self::Plain),
195            None => value,
196        }
197    }
198
199    fn flags(&self) -> ParamFlags {
200        self.flags
201    }
202
203    fn as_ptr(&self) -> ParamPtr {
204        ParamPtr::FloatParam(self as *const _ as *mut _)
205    }
206}
207
208impl InternalParamMut for FloatParam {
209    unsafe fn _internal_set_plain_value(&self, plain: Self::Plain) -> bool {
210        let unmodulated_value = plain;
211        let unmodulated_normalized_value = self.preview_normalized(plain);
212
213        let modulation_offset = self.modulation_offset.load(Ordering::Relaxed);
214        let (value, normalized_value) = if modulation_offset == 0.0 {
215            (unmodulated_value, unmodulated_normalized_value)
216        } else {
217            let normalized_value =
218                (unmodulated_normalized_value + modulation_offset).clamp(0.0, 1.0);
219
220            (self.preview_plain(normalized_value), normalized_value)
221        };
222
223        // REAPER spams automation events with the same value. This prevents callbacks from firing
224        // multiple times. This can be problematic when they're used to trigger expensive
225        // computations when a parameter changes.
226        let old_value = self.value.swap(value, Ordering::Relaxed);
227        if value != old_value {
228            self.normalized_value
229                .store(normalized_value, Ordering::Relaxed);
230            self.unmodulated_value
231                .store(unmodulated_value, Ordering::Relaxed);
232            self.unmodulated_normalized_value
233                .store(unmodulated_normalized_value, Ordering::Relaxed);
234            if let Some(f) = &self.value_changed {
235                f(value);
236            }
237
238            true
239        } else {
240            false
241        }
242    }
243
244    unsafe fn _internal_set_normalized_value(&self, normalized: f32) -> bool {
245        // NOTE: The double conversion here is to make sure the state is reproducible. State is
246        //       saved and restored using plain values, and the new normalized value will be
247        //       different from `normalized`. This is not necessary for the modulation as these
248        //       values are never shown to the host.
249        unsafe { self._internal_set_plain_value(self.preview_plain(normalized)) }
250    }
251
252    unsafe fn _internal_modulate_value(&self, modulation_offset: f32) -> bool {
253        self.modulation_offset
254            .store(modulation_offset, Ordering::Relaxed);
255
256        // TODO: This renormalizes this value, which is not necessary
257        unsafe { self._internal_set_plain_value(self.unmodulated_plain_value()) }
258    }
259
260    unsafe fn _internal_update_smoother(&self, sample_rate: f32, reset: bool) {
261        if reset {
262            self.smoothed.reset(self.modulated_plain_value());
263        } else {
264            self.smoothed
265                .set_target(sample_rate, self.modulated_plain_value());
266        }
267    }
268}
269
270impl FloatParam {
271    /// Build a new [`FloatParam`]. Use the other associated functions to modify the behavior of the
272    /// parameter.
273    pub fn new(name: impl Into<String>, default: f32, range: FloatRange) -> Self {
274        range.assert_validity();
275
276        Self {
277            value: AtomicF32::new(default),
278            normalized_value: AtomicF32::new(range.normalize(default)),
279            unmodulated_value: AtomicF32::new(default),
280            unmodulated_normalized_value: AtomicF32::new(range.normalize(default)),
281            modulation_offset: AtomicF32::new(0.0),
282            default,
283            smoothed: Smoother::none(),
284
285            flags: ParamFlags::default(),
286            value_changed: None,
287
288            range,
289            step_size: None,
290            name: name.into(),
291            unit: "",
292            poly_modulation_id: None,
293            value_to_string: None,
294            string_to_value: None,
295        }
296    }
297
298    /// The field's current plain value, after monophonic modulation has been applied. Equivalent to
299    /// calling `param.plain_value()`.
300    #[inline]
301    pub fn value(&self) -> f32 {
302        self.modulated_plain_value()
303    }
304
305    /// The range of valid plain values for this parameter.
306    #[inline]
307    pub fn range(&self) -> FloatRange {
308        self.range
309    }
310
311    /// Enable polyphonic modulation for this parameter. The ID is used to uniquely identify this
312    /// parameter in [`NoteEvent::PolyModulation`][crate::midi::NoteEvent::PolyModulation]
313    /// events, and must thus be unique between _all_ polyphonically modulatable parameters. See the
314    /// event's documentation on how to use polyphonic modulation. Also consider configuring the
315    /// `ClapPlugin::CLAP_POLY_MODULATION_CONFIG` constant when enabling this.
316    ///
317    /// # Important
318    ///
319    /// After enabling polyphonic modulation, the plugin **must** start sending
320    /// [`NoteEvent::VoiceTerminated`][crate::midi::NoteEvent::VoiceTerminated] events to the
321    /// host when a voice has fully ended. This allows the host to reuse its modulation resources.
322    pub fn with_poly_modulation_id(mut self, id: u32) -> Self {
323        self.poly_modulation_id = Some(id);
324        self
325    }
326
327    /// Set up a smoother that can gradually interpolate changes made to this parameter, preventing
328    /// clicks and zipper noises.
329    pub fn with_smoother(mut self, style: SmoothingStyle) -> Self {
330        // Logarithmic smoothing will cause problems if the range goes through zero since then you
331        // end up multiplying by zero
332        let goes_through_zero = match (&style, &self.range) {
333            (
334                SmoothingStyle::Logarithmic(_),
335                FloatRange::Linear { min, max }
336                | FloatRange::Skewed { min, max, .. }
337                | FloatRange::SymmetricalSkewed { min, max, .. },
338            ) => *min == 0.0 || *max == 0.0 || min.signum() != max.signum(),
339            _ => false,
340        };
341        nice_debug_assert!(
342            !goes_through_zero,
343            "Logarithmic smoothing does not work with ranges that go through zero"
344        );
345
346        self.smoothed = Smoother::new(style);
347        self
348    }
349
350    /// Run a callback whenever this parameter's value changes. The argument passed to this function
351    /// is the parameter's new value. This should not do anything expensive as it may be called
352    /// multiple times in rapid succession, and it can be run from both the GUI and the audio
353    /// thread.
354    pub fn with_callback(mut self, callback: Arc<dyn Fn(f32) + Send + Sync>) -> Self {
355        self.value_changed = Some(callback);
356        self
357    }
358
359    /// Display a unit when rendering this parameter to a string. Appended after the
360    /// [`value_to_string`][Self::with_value_to_string()] function if that is also set. nice-plug
361    /// will not automatically add a space before the unit.
362    pub fn with_unit(mut self, unit: &'static str) -> Self {
363        self.unit = unit;
364        self
365    }
366
367    /// Set the distance between steps of a [`FloatParam`]. Mostly useful for quantizing GUI input. If
368    /// this is set and a [`value_to_string`][Self::with_value_to_string()] function is not set,
369    /// then this is also used when formatting the parameter. This must be a positive, nonzero
370    /// number.
371    pub fn with_step_size(mut self, step_size: f32) -> Self {
372        self.step_size = Some(step_size);
373        self
374    }
375
376    /// Use a custom conversion function to convert the plain, unnormalized value to a
377    /// string.
378    pub fn with_value_to_string(
379        mut self,
380        callback: Arc<dyn Fn(f32) -> String + Send + Sync>,
381    ) -> Self {
382        self.value_to_string = Some(callback);
383        self
384    }
385
386    /// Use a custom conversion function to convert from a string to a plain, unnormalized
387    /// value. If the string cannot be parsed, then this should return a `None`. If this
388    /// happens while the parameter is being updated then the update will be canceled.
389    ///
390    /// The input string may or may not contain the unit, so you will need to be able to handle
391    /// that.
392    pub fn with_string_to_value(
393        mut self,
394        callback: Arc<dyn Fn(&str) -> Option<f32> + Send + Sync>,
395    ) -> Self {
396        self.string_to_value = Some(callback);
397        self
398    }
399
400    /// Mark the parameter as non-automatable. This means that the parameter cannot be changed from
401    /// an automation lane. The parameter can however still be manually changed by the user from
402    /// either the plugin's own GUI or from the host's generic UI.
403    pub fn non_automatable(mut self) -> Self {
404        self.flags.insert(ParamFlags::NON_AUTOMATABLE);
405        self
406    }
407
408    /// Hide the parameter in the host's generic UI for this plugin. This also implies
409    /// `NON_AUTOMATABLE`. Setting this does not prevent you from changing the parameter in the
410    /// plugin's editor GUI.
411    pub fn hide(mut self) -> Self {
412        self.flags.insert(ParamFlags::HIDDEN);
413        self
414    }
415
416    /// Don't show this parameter when generating a generic UI for the plugin using one of
417    /// nice-plug's generic UI widgets.
418    pub fn hide_in_generic_ui(mut self) -> Self {
419        self.flags.insert(ParamFlags::HIDE_IN_GENERIC_UI);
420        self
421    }
422}
423
424/// Calculate how many decimals to round to when displaying a floating point value with a specific
425/// step size. We'll perform some rounding to ignore spurious extra precision caused by the floating
426/// point quantization.
427fn decimals_from_step_size(step_size: f32) -> usize {
428    const SCALE: f32 = 1_000_000.0; // 10.0f32.powi(f32::DIGITS as i32)
429    let step_size = (step_size * SCALE).round() / SCALE;
430
431    let mut num_digits = 0;
432    for decimals in 0..f32::DIGITS as i32 {
433        if step_size * 10.0f32.powi(decimals) >= 1.0 {
434            num_digits = decimals;
435            break;
436        }
437    }
438
439    num_digits as usize
440}