1use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU32, AtomicU64, Ordering};
2
3use crate::info::ParamInfo;
4use crate::sample::Float;
5use crate::smooth::{Smoother, SmoothingStyle};
6
7pub struct AtomicF64 {
9 bits: AtomicU64,
10}
11
12impl AtomicF64 {
13 pub fn new(value: f64) -> Self {
14 Self {
15 bits: AtomicU64::new(value.to_bits()),
16 }
17 }
18
19 #[inline]
20 pub fn load(&self) -> f64 {
21 f64::from_bits(self.bits.load(Ordering::Relaxed))
22 }
23
24 #[inline]
25 pub fn store(&self, value: f64) {
26 self.bits.store(value.to_bits(), Ordering::Relaxed);
27 }
28}
29
30pub struct FloatParam {
32 pub info: ParamInfo,
33 value: AtomicF64,
34 pub smoother: Smoother,
35}
36
37impl FloatParam {
38 #[must_use]
39 pub fn new(info: ParamInfo, smoothing: SmoothingStyle) -> Self {
40 let default = info.default_plain;
41 let smoother = Smoother::new(smoothing);
42 smoother.snap(default);
43 Self {
44 info,
45 value: AtomicF64::new(default),
46 smoother,
47 }
48 }
49
50 #[inline]
52 pub fn set_value(&self, v: f64) {
53 self.value.store(v);
54 }
55
56 #[doc(hidden)]
62 #[inline]
63 pub fn raw_target(&self) -> f64 {
64 self.value.load()
65 }
66
67 #[doc(hidden)]
70 #[inline]
71 pub fn raw_smoothed_next(&self) -> f32 {
72 let target = self.value.load();
73 self.smoother.next(target)
74 }
75
76 #[doc(hidden)]
79 #[inline]
80 pub fn raw_smoothed_current(&self) -> f32 {
81 self.smoother.current()
82 }
83
84 #[doc(hidden)]
88 #[inline]
89 pub fn raw_smoothed_next_block<const N: usize>(&self) -> [f32; N] {
90 let target = self.value.load();
91 self.smoother.next_block::<N>(target)
92 }
93
94 #[doc(hidden)]
99 #[inline]
100 pub fn raw_smoothed_next_after(&self, n_samples: usize) -> f32 {
101 let target = self.value.load();
102 self.smoother.next_after(target, n_samples)
103 }
104
105 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
109 #[inline]
110 pub fn value_usize(&self) -> usize {
111 let v = self.value.load().round();
112 if v <= 0.0 { 0 } else { v as usize }
113 }
114
115 #[allow(clippy::cast_possible_truncation)]
118 #[inline]
119 pub fn value_i32(&self) -> i32 {
120 self.value.load().round() as i32
121 }
122
123 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
126 #[inline]
127 pub fn value_u8(&self) -> u8 {
128 let v = self.value.load().round();
129 if v <= 0.0 {
130 0
131 } else if v >= 255.0 {
132 255
133 } else {
134 v as u8
135 }
136 }
137
138 #[inline]
157 #[must_use]
158 pub fn is_smoothing(&self) -> bool {
159 !self.smoother.is_converged(self.value.load())
160 }
161
162 pub fn id(&self) -> u32 {
164 self.info.id
165 }
166}
167
168pub trait FloatParamReadF32 {
186 #[must_use]
188 fn read(&self) -> f32;
189
190 #[must_use]
197 fn read_block<const N: usize>(&self) -> [f32; N];
198
199 #[must_use]
209 fn read_after(&self, n_samples: usize) -> f32;
210
211 #[must_use]
213 fn current(&self) -> f32;
214
215 #[must_use]
219 fn value(&self) -> f32;
220}
221
222pub trait FloatParamReadF64 {
225 #[must_use]
226 fn read(&self) -> f64;
227 #[must_use]
231 fn read_block<const N: usize>(&self) -> [f64; N];
232 #[must_use]
235 fn read_after(&self, n_samples: usize) -> f64;
236 #[must_use]
237 fn current(&self) -> f64;
238 #[must_use]
239 fn value(&self) -> f64;
240}
241
242impl FloatParamReadF32 for FloatParam {
243 #[inline]
244 fn read(&self) -> f32 {
245 self.raw_smoothed_next()
246 }
247
248 #[inline]
249 fn read_block<const N: usize>(&self) -> [f32; N] {
250 self.raw_smoothed_next_block::<N>()
251 }
252
253 #[inline]
254 fn read_after(&self, n_samples: usize) -> f32 {
255 self.raw_smoothed_next_after(n_samples)
256 }
257
258 #[inline]
259 fn current(&self) -> f32 {
260 self.raw_smoothed_current()
261 }
262
263 #[inline]
264 fn value(&self) -> f32 {
265 f32::from_f64(self.raw_target())
266 }
267}
268
269impl FloatParamReadF64 for FloatParam {
270 #[inline]
271 fn read(&self) -> f64 {
272 f64::from(self.raw_smoothed_next())
273 }
274
275 #[inline]
276 fn read_block<const N: usize>(&self) -> [f64; N] {
277 let block = self.raw_smoothed_next_block::<N>();
278 let mut out = [0.0_f64; N];
279 for (i, &v) in block.iter().enumerate() {
280 out[i] = f64::from(v);
281 }
282 out
283 }
284
285 #[inline]
286 fn read_after(&self, n_samples: usize) -> f64 {
287 f64::from(self.raw_smoothed_next_after(n_samples))
288 }
289
290 #[inline]
291 fn current(&self) -> f64 {
292 f64::from(self.raw_smoothed_current())
293 }
294
295 #[inline]
296 fn value(&self) -> f64 {
297 self.raw_target()
298 }
299}
300
301pub struct BoolParam {
303 pub info: ParamInfo,
304 value: AtomicBool,
305}
306
307impl BoolParam {
308 #[must_use]
315 pub fn new(info: ParamInfo) -> Self {
316 let default = match info.default_plain {
317 0.0 => false,
318 1.0 => true,
319 other => panic!(
320 "BoolParam '{}' default {} must be exactly 0.0 (false) \
321 or 1.0 (true) - bool params have no halfway value",
322 info.name, other,
323 ),
324 };
325 Self {
326 info,
327 value: AtomicBool::new(default),
328 }
329 }
330
331 pub fn value(&self) -> bool {
332 self.value.load(Ordering::Relaxed)
333 }
334
335 pub fn set_value(&self, v: bool) {
336 self.value.store(v, Ordering::Relaxed);
337 }
338
339 pub fn id(&self) -> u32 {
340 self.info.id
341 }
342}
343
344pub struct IntParam {
346 pub info: ParamInfo,
347 value: AtomicI64,
348}
349
350impl IntParam {
351 #[allow(
365 clippy::float_cmp,
366 clippy::cast_possible_truncation,
367 clippy::cast_precision_loss
368 )]
369 #[must_use]
370 pub fn new(info: ParamInfo) -> Self {
371 let default = info.default_plain;
372 assert!(
373 default.is_finite(),
374 "IntParam '{}' default {} is not finite",
375 info.name,
376 default,
377 );
378 let truncated = default as i64;
379 assert!(
380 truncated as f64 == default,
381 "IntParam '{}' default {} doesn't round-trip through i64 \
382 - supply an integer-valued default in the derive attribute",
383 info.name,
384 default,
385 );
386 let (lo, hi) = (info.range.min() as i64, info.range.max() as i64);
387 assert!(
388 truncated >= lo && truncated <= hi,
389 "IntParam '{}' default {} is outside range [{}, {}]",
390 info.name,
391 truncated,
392 lo,
393 hi,
394 );
395 Self {
396 info,
397 value: AtomicI64::new(truncated),
398 }
399 }
400
401 pub fn value(&self) -> i64 {
402 self.value.load(Ordering::Relaxed)
403 }
404
405 #[allow(clippy::cast_precision_loss)]
408 #[inline]
409 pub fn value_f32(&self) -> f32 {
410 self.value.load(Ordering::Relaxed) as f32
411 }
412
413 #[allow(clippy::cast_precision_loss)]
415 #[inline]
416 pub fn value_f64(&self) -> f64 {
417 self.value.load(Ordering::Relaxed) as f64
418 }
419
420 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
423 #[inline]
424 pub fn value_usize(&self) -> usize {
425 let v = self.value.load(Ordering::Relaxed);
426 if v <= 0 { 0 } else { v as usize }
427 }
428
429 #[allow(clippy::cast_possible_truncation)]
431 #[inline]
432 pub fn value_i32(&self) -> i32 {
433 self.value
434 .load(Ordering::Relaxed)
435 .clamp(i64::from(i32::MIN), i64::from(i32::MAX)) as i32
436 }
437
438 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
440 #[inline]
441 pub fn value_u8(&self) -> u8 {
442 self.value.load(Ordering::Relaxed).clamp(0, 255) as u8
443 }
444
445 pub fn set_value(&self, v: i64) {
446 self.value.store(v, Ordering::Relaxed);
447 }
448
449 pub fn id(&self) -> u32 {
450 self.info.id
451 }
452}
453
454pub trait ParamEnum: crate::__private::Sealed + Clone + Copy + Send + Sync + 'static {
456 fn from_index(index: usize) -> Self;
457 fn to_index(&self) -> usize;
458 fn name(&self) -> &'static str;
459 fn variant_count() -> usize;
460 fn variant_names() -> &'static [&'static str];
461}
462
463pub struct EnumParam<E: ParamEnum> {
465 pub info: ParamInfo,
466 value: AtomicU32,
467 _phantom: std::marker::PhantomData<E>,
468}
469
470impl<E: ParamEnum> EnumParam<E> {
471 #[allow(
483 clippy::float_cmp,
484 clippy::cast_possible_truncation,
485 clippy::cast_sign_loss
486 )]
487 #[must_use]
488 pub fn new(info: ParamInfo) -> Self {
489 let default = info.default_plain;
490 let count = E::variant_count();
491 assert!(
492 default.is_finite(),
493 "EnumParam '{}' default {} is not finite",
494 info.name,
495 default,
496 );
497 assert!(
498 default >= 0.0,
499 "EnumParam '{}' default {} is negative; enum variants are \
500 0-indexed",
501 info.name,
502 default,
503 );
504 let idx = default as u32;
505 assert!(
506 f64::from(idx) == default,
507 "EnumParam '{}' default {} is non-integer; supply a 0-indexed \
508 variant index",
509 info.name,
510 default,
511 );
512 assert!(
513 (idx as usize) < count,
514 "EnumParam '{}' default {} is out of range; only {} variant(s) \
515 defined",
516 info.name,
517 idx,
518 count,
519 );
520 Self {
521 info,
522 value: AtomicU32::new(idx),
523 _phantom: std::marker::PhantomData,
524 }
525 }
526
527 pub fn value(&self) -> E {
528 #[allow(clippy::cast_possible_truncation)]
531 let idx = self.value.load(Ordering::Relaxed) as usize;
532 E::from_index(idx)
533 }
534
535 pub fn set_value(&self, v: E) {
536 #[allow(clippy::cast_possible_truncation)]
540 let idx = v.to_index() as u32;
541 self.value.store(idx, Ordering::Relaxed);
542 }
543
544 pub fn set_index(&self, idx: u32) {
545 self.value.store(idx, Ordering::Relaxed);
546 }
547
548 pub fn index(&self) -> u32 {
549 self.value.load(Ordering::Relaxed)
550 }
551
552 pub fn id(&self) -> u32 {
553 self.info.id
554 }
555
556 #[must_use]
563 pub fn format_by_index(value: f64) -> String {
564 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
567 let idx = value.round() as usize;
568 E::from_index(idx).name().to_string()
569 }
570}
571
572pub struct MeterSlot {
592 #[doc(hidden)]
593 pub id: u32,
594}
595
596impl MeterSlot {
597 #[must_use]
598 pub fn id(&self) -> u32 {
599 self.id
600 }
601}
602
603impl From<MeterSlot> for u32 {
604 fn from(m: MeterSlot) -> u32 {
605 m.id
606 }
607}
608
609impl From<&MeterSlot> for u32 {
610 fn from(m: &MeterSlot) -> u32 {
611 m.id
612 }
613}
614
615#[cfg(test)]
616mod tests {
617 use super::*;
618 use crate::info::{ParamFlags, ParamUnit, ParamValueKind};
619 use crate::range::ParamRange;
620
621 fn info(name: &'static str, range: ParamRange, default_plain: f64) -> ParamInfo {
622 ParamInfo {
623 id: 0,
624 name,
625 short_name: name,
626 group: "",
627 range,
628 default_plain,
629 flags: ParamFlags::AUTOMATABLE,
630 unit: ParamUnit::None,
631 kind: ParamValueKind::Float,
632 }
633 }
634
635 #[derive(Clone, Copy)]
636 enum E4 {
637 A,
638 B,
639 C,
640 D,
641 }
642 impl crate::__private::Sealed for E4 {}
643 impl ParamEnum for E4 {
644 fn from_index(i: usize) -> Self {
645 match i {
646 0 => Self::A,
647 1 => Self::B,
648 2 => Self::C,
649 _ => Self::D,
650 }
651 }
652 fn to_index(&self) -> usize {
653 *self as usize
654 }
655 fn name(&self) -> &'static str {
656 match self {
657 Self::A => "A",
658 Self::B => "B",
659 Self::C => "C",
660 Self::D => "D",
661 }
662 }
663 fn variant_count() -> usize {
664 4
665 }
666 fn variant_names() -> &'static [&'static str] {
667 &["A", "B", "C", "D"]
668 }
669 }
670
671 #[test]
672 fn enum_param_accepts_in_range_default() {
673 let p: EnumParam<E4> = EnumParam::new(info("Mode", ParamRange::Enum { count: 4 }, 2.0));
674 assert_eq!(p.index(), 2);
675 }
676
677 #[test]
678 #[should_panic(expected = "negative")]
679 fn enum_param_rejects_negative_default() {
680 let _: EnumParam<E4> = EnumParam::new(info("Mode", ParamRange::Enum { count: 4 }, -1.0));
681 }
682
683 #[test]
684 #[should_panic(expected = "out of range")]
685 fn enum_param_rejects_overflow_default() {
686 let _: EnumParam<E4> = EnumParam::new(info("Mode", ParamRange::Enum { count: 4 }, 99.0));
687 }
688
689 #[test]
690 #[should_panic(expected = "non-integer")]
691 fn enum_param_rejects_fractional_default() {
692 let _: EnumParam<E4> = EnumParam::new(info("Mode", ParamRange::Enum { count: 4 }, 1.5));
693 }
694
695 #[test]
696 fn int_param_accepts_negative_default() {
697 let p = IntParam::new(info("N", ParamRange::Discrete { min: -10, max: 10 }, -3.0));
698 assert_eq!(p.value(), -3);
699 }
700
701 #[test]
702 #[should_panic(expected = "round-trip")]
703 fn int_param_rejects_fractional_default() {
704 let _ = IntParam::new(info("N", ParamRange::Discrete { min: 0, max: 10 }, 1.5));
705 }
706
707 #[test]
708 #[should_panic(expected = "outside range")]
709 fn int_param_rejects_out_of_range_default() {
710 let _ = IntParam::new(info("N", ParamRange::Discrete { min: 0, max: 5 }, 10.0));
711 }
712}