Skip to main content

nice_plug_core/params/
integer.rs

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