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)]
89 #[inline]
90 pub fn raw_smoothed_next_into(&self, out: &mut [f32]) {
91 let target = self.value.load();
92 self.smoother.next_into(target, out);
93 }
94
95 #[doc(hidden)]
100 #[inline]
101 pub fn raw_smoothed_next_after(&self, n_samples: usize) -> f32 {
102 let target = self.value.load();
103 self.smoother.next_after(target, n_samples)
104 }
105
106 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
110 #[inline]
111 pub fn value_usize(&self) -> usize {
112 let v = self.value.load().round();
113 if v <= 0.0 { 0 } else { v as usize }
114 }
115
116 #[allow(clippy::cast_possible_truncation)]
119 #[inline]
120 pub fn value_i32(&self) -> i32 {
121 self.value.load().round() as i32
122 }
123
124 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
127 #[inline]
128 pub fn value_u8(&self) -> u8 {
129 let v = self.value.load().round();
130 if v <= 0.0 {
131 0
132 } else if v >= 255.0 {
133 255
134 } else {
135 v as u8
136 }
137 }
138
139 #[inline]
158 #[must_use]
159 pub fn is_smoothing(&self) -> bool {
160 !self.smoother.is_converged(self.value.load())
161 }
162
163 pub fn id(&self) -> u32 {
165 self.info.id
166 }
167}
168
169pub trait FloatParamReadF32 {
187 #[must_use]
189 fn read(&self) -> f32;
190
191 fn read_into(&self, out: &mut [f32]);
207
208 #[must_use]
217 fn read_after(&self, n_samples: usize) -> f32;
218
219 #[must_use]
221 fn current(&self) -> f32;
222
223 #[must_use]
227 fn value(&self) -> f32;
228}
229
230pub trait FloatParamReadF64 {
233 #[must_use]
234 fn read(&self) -> f64;
235 fn read_into(&self, out: &mut [f64]);
238 #[must_use]
241 fn read_after(&self, n_samples: usize) -> f64;
242 #[must_use]
243 fn current(&self) -> f64;
244 #[must_use]
245 fn value(&self) -> f64;
246}
247
248impl FloatParamReadF32 for FloatParam {
249 #[inline]
250 fn read(&self) -> f32 {
251 self.raw_smoothed_next()
252 }
253
254 #[inline]
255 fn read_into(&self, out: &mut [f32]) {
256 self.raw_smoothed_next_into(out);
257 }
258
259 #[inline]
260 fn read_after(&self, n_samples: usize) -> f32 {
261 self.raw_smoothed_next_after(n_samples)
262 }
263
264 #[inline]
265 fn current(&self) -> f32 {
266 self.raw_smoothed_current()
267 }
268
269 #[inline]
270 fn value(&self) -> f32 {
271 f32::from_f64(self.raw_target())
272 }
273}
274
275impl FloatParamReadF64 for FloatParam {
276 #[inline]
277 fn read(&self) -> f64 {
278 f64::from(self.raw_smoothed_next())
279 }
280
281 #[inline]
282 fn read_into(&self, out: &mut [f64]) {
283 const SCRATCH: usize = 1024;
288 let mut scratch = [0.0_f32; SCRATCH];
289 let mut remaining = out;
290 while !remaining.is_empty() {
291 let take = remaining.len().min(SCRATCH);
292 self.raw_smoothed_next_into(&mut scratch[..take]);
293 for (dst, &src) in remaining[..take].iter_mut().zip(&scratch[..take]) {
294 *dst = f64::from(src);
295 }
296 remaining = &mut remaining[take..];
297 }
298 }
299
300 #[inline]
301 fn read_after(&self, n_samples: usize) -> f64 {
302 f64::from(self.raw_smoothed_next_after(n_samples))
303 }
304
305 #[inline]
306 fn current(&self) -> f64 {
307 f64::from(self.raw_smoothed_current())
308 }
309
310 #[inline]
311 fn value(&self) -> f64 {
312 self.raw_target()
313 }
314}
315
316pub struct BoolParam {
318 pub info: ParamInfo,
319 value: AtomicBool,
320}
321
322impl BoolParam {
323 #[must_use]
330 pub fn new(info: ParamInfo) -> Self {
331 let default = match info.default_plain {
332 0.0 => false,
333 1.0 => true,
334 other => panic!(
335 "BoolParam '{}' default {} must be exactly 0.0 (false) \
336 or 1.0 (true) - bool params have no halfway value",
337 info.name, other,
338 ),
339 };
340 Self {
341 info,
342 value: AtomicBool::new(default),
343 }
344 }
345
346 pub fn value(&self) -> bool {
347 self.value.load(Ordering::Relaxed)
348 }
349
350 pub fn set_value(&self, v: bool) {
351 self.value.store(v, Ordering::Relaxed);
352 }
353
354 pub fn id(&self) -> u32 {
355 self.info.id
356 }
357}
358
359pub struct IntParam {
361 pub info: ParamInfo,
362 value: AtomicI64,
363}
364
365impl IntParam {
366 #[allow(
380 clippy::float_cmp,
381 clippy::cast_possible_truncation,
382 clippy::cast_precision_loss
383 )]
384 #[must_use]
385 pub fn new(info: ParamInfo) -> Self {
386 let default = info.default_plain;
387 assert!(
388 default.is_finite(),
389 "IntParam '{}' default {} is not finite",
390 info.name,
391 default,
392 );
393 let truncated = default as i64;
394 assert!(
395 truncated as f64 == default,
396 "IntParam '{}' default {} doesn't round-trip through i64 \
397 - supply an integer-valued default in the derive attribute",
398 info.name,
399 default,
400 );
401 let (lo, hi) = (info.range.min() as i64, info.range.max() as i64);
402 assert!(
403 truncated >= lo && truncated <= hi,
404 "IntParam '{}' default {} is outside range [{}, {}]",
405 info.name,
406 truncated,
407 lo,
408 hi,
409 );
410 Self {
411 info,
412 value: AtomicI64::new(truncated),
413 }
414 }
415
416 pub fn value(&self) -> i64 {
417 self.value.load(Ordering::Relaxed)
418 }
419
420 #[allow(clippy::cast_precision_loss)]
423 #[inline]
424 pub fn value_f32(&self) -> f32 {
425 self.value.load(Ordering::Relaxed) as f32
426 }
427
428 #[allow(clippy::cast_precision_loss)]
430 #[inline]
431 pub fn value_f64(&self) -> f64 {
432 self.value.load(Ordering::Relaxed) as f64
433 }
434
435 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
438 #[inline]
439 pub fn value_usize(&self) -> usize {
440 let v = self.value.load(Ordering::Relaxed);
441 if v <= 0 { 0 } else { v as usize }
442 }
443
444 #[allow(clippy::cast_possible_truncation)]
446 #[inline]
447 pub fn value_i32(&self) -> i32 {
448 self.value
449 .load(Ordering::Relaxed)
450 .clamp(i64::from(i32::MIN), i64::from(i32::MAX)) as i32
451 }
452
453 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
455 #[inline]
456 pub fn value_u8(&self) -> u8 {
457 self.value.load(Ordering::Relaxed).clamp(0, 255) as u8
458 }
459
460 pub fn set_value(&self, v: i64) {
461 self.value.store(v, Ordering::Relaxed);
462 }
463
464 pub fn id(&self) -> u32 {
465 self.info.id
466 }
467}
468
469pub trait ParamEnum: crate::__private::Sealed + Clone + Copy + Send + Sync + 'static {
471 fn from_index(index: usize) -> Self;
472 fn to_index(&self) -> usize;
473 fn name(&self) -> &'static str;
474 fn variant_count() -> usize;
475 fn variant_names() -> &'static [&'static str];
476}
477
478pub struct EnumParam<E: ParamEnum> {
480 pub info: ParamInfo,
481 value: AtomicU32,
482 _phantom: std::marker::PhantomData<E>,
483}
484
485impl<E: ParamEnum> EnumParam<E> {
486 #[allow(
498 clippy::float_cmp,
499 clippy::cast_possible_truncation,
500 clippy::cast_sign_loss
501 )]
502 #[must_use]
503 pub fn new(info: ParamInfo) -> Self {
504 let default = info.default_plain;
505 let count = E::variant_count();
506 assert!(
507 default.is_finite(),
508 "EnumParam '{}' default {} is not finite",
509 info.name,
510 default,
511 );
512 assert!(
513 default >= 0.0,
514 "EnumParam '{}' default {} is negative; enum variants are \
515 0-indexed",
516 info.name,
517 default,
518 );
519 let idx = default as u32;
520 assert!(
521 f64::from(idx) == default,
522 "EnumParam '{}' default {} is non-integer; supply a 0-indexed \
523 variant index",
524 info.name,
525 default,
526 );
527 assert!(
528 (idx as usize) < count,
529 "EnumParam '{}' default {} is out of range; only {} variant(s) \
530 defined",
531 info.name,
532 idx,
533 count,
534 );
535 Self {
536 info,
537 value: AtomicU32::new(idx),
538 _phantom: std::marker::PhantomData,
539 }
540 }
541
542 pub fn value(&self) -> E {
543 #[allow(clippy::cast_possible_truncation)]
546 let idx = self.value.load(Ordering::Relaxed) as usize;
547 E::from_index(idx)
548 }
549
550 pub fn set_value(&self, v: E) {
551 #[allow(clippy::cast_possible_truncation)]
555 let idx = v.to_index() as u32;
556 self.value.store(idx, Ordering::Relaxed);
557 }
558
559 pub fn set_index(&self, idx: u32) {
560 self.value.store(idx, Ordering::Relaxed);
561 }
562
563 pub fn index(&self) -> u32 {
564 self.value.load(Ordering::Relaxed)
565 }
566
567 pub fn id(&self) -> u32 {
568 self.info.id
569 }
570
571 #[must_use]
578 pub fn format_by_index(value: f64) -> String {
579 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
582 let idx = value.round() as usize;
583 E::from_index(idx).name().to_string()
584 }
585}
586
587pub struct MeterSlot {
607 #[doc(hidden)]
608 pub id: u32,
609}
610
611impl MeterSlot {
612 #[must_use]
613 pub fn id(&self) -> u32 {
614 self.id
615 }
616}
617
618impl From<MeterSlot> for u32 {
619 fn from(m: MeterSlot) -> u32 {
620 m.id
621 }
622}
623
624impl From<&MeterSlot> for u32 {
625 fn from(m: &MeterSlot) -> u32 {
626 m.id
627 }
628}
629
630#[cfg(test)]
631mod tests {
632 use super::*;
633 use crate::info::{ParamFlags, ParamUnit, ParamValueKind};
634 use crate::range::ParamRange;
635
636 fn info(name: &'static str, range: ParamRange, default_plain: f64) -> ParamInfo {
637 ParamInfo {
638 id: 0,
639 name,
640 short_name: name,
641 group: "",
642 range,
643 default_plain,
644 flags: ParamFlags::AUTOMATABLE,
645 unit: ParamUnit::None,
646 kind: ParamValueKind::Float,
647 midi_map: None,
648 midi_channel: None,
649 }
650 }
651
652 #[derive(Clone, Copy)]
653 enum E4 {
654 A,
655 B,
656 C,
657 D,
658 }
659 impl crate::__private::Sealed for E4 {}
660 impl ParamEnum for E4 {
661 fn from_index(i: usize) -> Self {
662 match i {
663 0 => Self::A,
664 1 => Self::B,
665 2 => Self::C,
666 _ => Self::D,
667 }
668 }
669 fn to_index(&self) -> usize {
670 *self as usize
671 }
672 fn name(&self) -> &'static str {
673 match self {
674 Self::A => "A",
675 Self::B => "B",
676 Self::C => "C",
677 Self::D => "D",
678 }
679 }
680 fn variant_count() -> usize {
681 4
682 }
683 fn variant_names() -> &'static [&'static str] {
684 &["A", "B", "C", "D"]
685 }
686 }
687
688 #[test]
689 fn enum_param_accepts_in_range_default() {
690 let p: EnumParam<E4> = EnumParam::new(info("Mode", ParamRange::Enum { count: 4 }, 2.0));
691 assert_eq!(p.index(), 2);
692 }
693
694 #[test]
695 #[should_panic(expected = "negative")]
696 fn enum_param_rejects_negative_default() {
697 let _: EnumParam<E4> = EnumParam::new(info("Mode", ParamRange::Enum { count: 4 }, -1.0));
698 }
699
700 #[test]
701 #[should_panic(expected = "out of range")]
702 fn enum_param_rejects_overflow_default() {
703 let _: EnumParam<E4> = EnumParam::new(info("Mode", ParamRange::Enum { count: 4 }, 99.0));
704 }
705
706 #[test]
707 #[should_panic(expected = "non-integer")]
708 fn enum_param_rejects_fractional_default() {
709 let _: EnumParam<E4> = EnumParam::new(info("Mode", ParamRange::Enum { count: 4 }, 1.5));
710 }
711
712 #[test]
713 fn int_param_accepts_negative_default() {
714 let p = IntParam::new(info("N", ParamRange::Discrete { min: -10, max: 10 }, -3.0));
715 assert_eq!(p.value(), -3);
716 }
717
718 #[test]
719 #[should_panic(expected = "round-trip")]
720 fn int_param_rejects_fractional_default() {
721 let _ = IntParam::new(info("N", ParamRange::Discrete { min: 0, max: 10 }, 1.5));
722 }
723
724 #[test]
725 #[should_panic(expected = "outside range")]
726 fn int_param_rejects_out_of_range_default() {
727 let _ = IntParam::new(info("N", ParamRange::Discrete { min: 0, max: 5 }, 10.0));
728 }
729}