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}