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