use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU32, AtomicU64, Ordering};
use crate::info::ParamInfo;
use crate::sample::Float;
use crate::smooth::{Smoother, SmoothingStyle};
pub struct AtomicF64 {
bits: AtomicU64,
}
impl AtomicF64 {
pub fn new(value: f64) -> Self {
Self {
bits: AtomicU64::new(value.to_bits()),
}
}
#[inline]
pub fn load(&self) -> f64 {
f64::from_bits(self.bits.load(Ordering::Relaxed))
}
#[inline]
pub fn store(&self, value: f64) {
self.bits.store(value.to_bits(), Ordering::Relaxed);
}
}
pub struct FloatParam {
pub info: ParamInfo,
value: AtomicF64,
pub smoother: Smoother,
}
impl FloatParam {
#[must_use]
pub fn new(info: ParamInfo, smoothing: SmoothingStyle) -> Self {
let default = info.default_plain;
let smoother = Smoother::new(smoothing);
smoother.snap(default);
Self {
info,
value: AtomicF64::new(default),
smoother,
}
}
#[inline]
pub fn set_value(&self, v: f64) {
self.value.store(v);
}
#[doc(hidden)]
#[inline]
pub fn raw_target(&self) -> f64 {
self.value.load()
}
#[doc(hidden)]
#[inline]
pub fn raw_smoothed_next(&self) -> f32 {
let target = self.value.load();
self.smoother.next(target)
}
#[doc(hidden)]
#[inline]
pub fn raw_smoothed_current(&self) -> f32 {
self.smoother.current()
}
#[doc(hidden)]
#[inline]
pub fn raw_smoothed_next_block<const N: usize>(&self) -> [f32; N] {
let target = self.value.load();
self.smoother.next_block::<N>(target)
}
#[doc(hidden)]
#[inline]
pub fn raw_smoothed_next_after(&self, n_samples: usize) -> f32 {
let target = self.value.load();
self.smoother.next_after(target, n_samples)
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
#[inline]
pub fn value_usize(&self) -> usize {
let v = self.value.load().round();
if v <= 0.0 { 0 } else { v as usize }
}
#[allow(clippy::cast_possible_truncation)]
#[inline]
pub fn value_i32(&self) -> i32 {
self.value.load().round() as i32
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
#[inline]
pub fn value_u8(&self) -> u8 {
let v = self.value.load().round();
if v <= 0.0 {
0
} else if v >= 255.0 {
255
} else {
v as u8
}
}
#[inline]
#[must_use]
pub fn is_smoothing(&self) -> bool {
!self.smoother.is_converged(self.value.load())
}
pub fn id(&self) -> u32 {
self.info.id
}
}
pub trait FloatParamReadF32 {
#[must_use]
fn read(&self) -> f32;
#[must_use]
fn read_block<const N: usize>(&self) -> [f32; N];
#[must_use]
fn read_after(&self, n_samples: usize) -> f32;
#[must_use]
fn current(&self) -> f32;
#[must_use]
fn value(&self) -> f32;
}
pub trait FloatParamReadF64 {
#[must_use]
fn read(&self) -> f64;
#[must_use]
fn read_block<const N: usize>(&self) -> [f64; N];
#[must_use]
fn read_after(&self, n_samples: usize) -> f64;
#[must_use]
fn current(&self) -> f64;
#[must_use]
fn value(&self) -> f64;
}
impl FloatParamReadF32 for FloatParam {
#[inline]
fn read(&self) -> f32 {
self.raw_smoothed_next()
}
#[inline]
fn read_block<const N: usize>(&self) -> [f32; N] {
self.raw_smoothed_next_block::<N>()
}
#[inline]
fn read_after(&self, n_samples: usize) -> f32 {
self.raw_smoothed_next_after(n_samples)
}
#[inline]
fn current(&self) -> f32 {
self.raw_smoothed_current()
}
#[inline]
fn value(&self) -> f32 {
f32::from_f64(self.raw_target())
}
}
impl FloatParamReadF64 for FloatParam {
#[inline]
fn read(&self) -> f64 {
f64::from(self.raw_smoothed_next())
}
#[inline]
fn read_block<const N: usize>(&self) -> [f64; N] {
let block = self.raw_smoothed_next_block::<N>();
let mut out = [0.0_f64; N];
for (i, &v) in block.iter().enumerate() {
out[i] = f64::from(v);
}
out
}
#[inline]
fn read_after(&self, n_samples: usize) -> f64 {
f64::from(self.raw_smoothed_next_after(n_samples))
}
#[inline]
fn current(&self) -> f64 {
f64::from(self.raw_smoothed_current())
}
#[inline]
fn value(&self) -> f64 {
self.raw_target()
}
}
pub struct BoolParam {
pub info: ParamInfo,
value: AtomicBool,
}
impl BoolParam {
#[must_use]
pub fn new(info: ParamInfo) -> Self {
let default = match info.default_plain {
0.0 => false,
1.0 => true,
other => panic!(
"BoolParam '{}' default {} must be exactly 0.0 (false) \
or 1.0 (true) - bool params have no halfway value",
info.name, other,
),
};
Self {
info,
value: AtomicBool::new(default),
}
}
pub fn value(&self) -> bool {
self.value.load(Ordering::Relaxed)
}
pub fn set_value(&self, v: bool) {
self.value.store(v, Ordering::Relaxed);
}
pub fn id(&self) -> u32 {
self.info.id
}
}
pub struct IntParam {
pub info: ParamInfo,
value: AtomicI64,
}
impl IntParam {
#[allow(
clippy::float_cmp,
clippy::cast_possible_truncation,
clippy::cast_precision_loss
)]
#[must_use]
pub fn new(info: ParamInfo) -> Self {
let default = info.default_plain;
assert!(
default.is_finite(),
"IntParam '{}' default {} is not finite",
info.name,
default,
);
let truncated = default as i64;
assert!(
truncated as f64 == default,
"IntParam '{}' default {} doesn't round-trip through i64 \
- supply an integer-valued default in the derive attribute",
info.name,
default,
);
let (lo, hi) = (info.range.min() as i64, info.range.max() as i64);
assert!(
truncated >= lo && truncated <= hi,
"IntParam '{}' default {} is outside range [{}, {}]",
info.name,
truncated,
lo,
hi,
);
Self {
info,
value: AtomicI64::new(truncated),
}
}
pub fn value(&self) -> i64 {
self.value.load(Ordering::Relaxed)
}
#[allow(clippy::cast_precision_loss)]
#[inline]
pub fn value_f32(&self) -> f32 {
self.value.load(Ordering::Relaxed) as f32
}
#[allow(clippy::cast_precision_loss)]
#[inline]
pub fn value_f64(&self) -> f64 {
self.value.load(Ordering::Relaxed) as f64
}
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
#[inline]
pub fn value_usize(&self) -> usize {
let v = self.value.load(Ordering::Relaxed);
if v <= 0 { 0 } else { v as usize }
}
#[allow(clippy::cast_possible_truncation)]
#[inline]
pub fn value_i32(&self) -> i32 {
self.value
.load(Ordering::Relaxed)
.clamp(i64::from(i32::MIN), i64::from(i32::MAX)) as i32
}
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
#[inline]
pub fn value_u8(&self) -> u8 {
self.value.load(Ordering::Relaxed).clamp(0, 255) as u8
}
pub fn set_value(&self, v: i64) {
self.value.store(v, Ordering::Relaxed);
}
pub fn id(&self) -> u32 {
self.info.id
}
}
pub trait ParamEnum: crate::__private::Sealed + Clone + Copy + Send + Sync + 'static {
fn from_index(index: usize) -> Self;
fn to_index(&self) -> usize;
fn name(&self) -> &'static str;
fn variant_count() -> usize;
fn variant_names() -> &'static [&'static str];
}
pub struct EnumParam<E: ParamEnum> {
pub info: ParamInfo,
value: AtomicU32,
_phantom: std::marker::PhantomData<E>,
}
impl<E: ParamEnum> EnumParam<E> {
#[allow(
clippy::float_cmp,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
#[must_use]
pub fn new(info: ParamInfo) -> Self {
let default = info.default_plain;
let count = E::variant_count();
assert!(
default.is_finite(),
"EnumParam '{}' default {} is not finite",
info.name,
default,
);
assert!(
default >= 0.0,
"EnumParam '{}' default {} is negative; enum variants are \
0-indexed",
info.name,
default,
);
let idx = default as u32;
assert!(
f64::from(idx) == default,
"EnumParam '{}' default {} is non-integer; supply a 0-indexed \
variant index",
info.name,
default,
);
assert!(
(idx as usize) < count,
"EnumParam '{}' default {} is out of range; only {} variant(s) \
defined",
info.name,
idx,
count,
);
Self {
info,
value: AtomicU32::new(idx),
_phantom: std::marker::PhantomData,
}
}
pub fn value(&self) -> E {
#[allow(clippy::cast_possible_truncation)]
let idx = self.value.load(Ordering::Relaxed) as usize;
E::from_index(idx)
}
pub fn set_value(&self, v: E) {
#[allow(clippy::cast_possible_truncation)]
let idx = v.to_index() as u32;
self.value.store(idx, Ordering::Relaxed);
}
pub fn set_index(&self, idx: u32) {
self.value.store(idx, Ordering::Relaxed);
}
pub fn index(&self) -> u32 {
self.value.load(Ordering::Relaxed)
}
pub fn id(&self) -> u32 {
self.info.id
}
#[must_use]
pub fn format_by_index(value: f64) -> String {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let idx = value.round() as usize;
E::from_index(idx).name().to_string()
}
}
pub struct MeterSlot {
#[doc(hidden)]
pub id: u32,
}
impl MeterSlot {
#[must_use]
pub fn id(&self) -> u32 {
self.id
}
}
impl From<MeterSlot> for u32 {
fn from(m: MeterSlot) -> u32 {
m.id
}
}
impl From<&MeterSlot> for u32 {
fn from(m: &MeterSlot) -> u32 {
m.id
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::info::{ParamFlags, ParamUnit, ParamValueKind};
use crate::range::ParamRange;
fn info(name: &'static str, range: ParamRange, default_plain: f64) -> ParamInfo {
ParamInfo {
id: 0,
name,
short_name: name,
group: "",
range,
default_plain,
flags: ParamFlags::AUTOMATABLE,
unit: ParamUnit::None,
kind: ParamValueKind::Float,
}
}
#[derive(Clone, Copy)]
enum E4 {
A,
B,
C,
D,
}
impl crate::__private::Sealed for E4 {}
impl ParamEnum for E4 {
fn from_index(i: usize) -> Self {
match i {
0 => Self::A,
1 => Self::B,
2 => Self::C,
_ => Self::D,
}
}
fn to_index(&self) -> usize {
*self as usize
}
fn name(&self) -> &'static str {
match self {
Self::A => "A",
Self::B => "B",
Self::C => "C",
Self::D => "D",
}
}
fn variant_count() -> usize {
4
}
fn variant_names() -> &'static [&'static str] {
&["A", "B", "C", "D"]
}
}
#[test]
fn enum_param_accepts_in_range_default() {
let p: EnumParam<E4> = EnumParam::new(info("Mode", ParamRange::Enum { count: 4 }, 2.0));
assert_eq!(p.index(), 2);
}
#[test]
#[should_panic(expected = "negative")]
fn enum_param_rejects_negative_default() {
let _: EnumParam<E4> = EnumParam::new(info("Mode", ParamRange::Enum { count: 4 }, -1.0));
}
#[test]
#[should_panic(expected = "out of range")]
fn enum_param_rejects_overflow_default() {
let _: EnumParam<E4> = EnumParam::new(info("Mode", ParamRange::Enum { count: 4 }, 99.0));
}
#[test]
#[should_panic(expected = "non-integer")]
fn enum_param_rejects_fractional_default() {
let _: EnumParam<E4> = EnumParam::new(info("Mode", ParamRange::Enum { count: 4 }, 1.5));
}
#[test]
fn int_param_accepts_negative_default() {
let p = IntParam::new(info("N", ParamRange::Discrete { min: -10, max: 10 }, -3.0));
assert_eq!(p.value(), -3);
}
#[test]
#[should_panic(expected = "round-trip")]
fn int_param_rejects_fractional_default() {
let _ = IntParam::new(info("N", ParamRange::Discrete { min: 0, max: 10 }, 1.5));
}
#[test]
#[should_panic(expected = "outside range")]
fn int_param_rejects_out_of_range_default() {
let _ = IntParam::new(info("N", ParamRange::Discrete { min: 0, max: 5 }, 10.0));
}
}