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}