use std::{
fmt::{Debug, Display},
ops::RangeInclusive,
sync::Arc,
};
use four_cc::FourCC;
use super::{
formatters, Parameter, ParameterPolarity, ParameterScaling, ParameterType, ParameterValueUpdate,
};
#[derive(Clone)]
pub struct FloatParameter {
id: FourCC,
name: &'static str,
range: RangeInclusive<f32>,
default: f32,
scaling: ParameterScaling,
unit: &'static str,
value_to_string: Option<fn(f32) -> String>,
string_to_value: Option<fn(&str) -> Option<f32>>,
}
impl Debug for FloatParameter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FloatParameter")
.field("id", &self.id)
.field("name", &self.name)
.field("range", &self.range)
.field("default", &self.default)
.field("value_to_string", &self.value_to_string.is_some())
.field("string_to_value", &self.string_to_value.is_some())
.finish()
}
}
impl FloatParameter {
pub const fn new(
id: FourCC,
name: &'static str,
range: RangeInclusive<f32>,
default: f32,
) -> Self {
assert!(
default >= *range.start() && default <= *range.end(),
"Invalid parameter default value"
);
Self {
id,
name,
range,
default,
scaling: ParameterScaling::Linear,
unit: "",
value_to_string: None,
string_to_value: None,
}
}
pub const fn with_unit(mut self, unit: &'static str) -> Self {
self.unit = unit;
self
}
pub const fn with_scaling(mut self, scaling: ParameterScaling) -> Self {
scaling.validate();
self.scaling = scaling;
self
}
pub const fn with_formatter(mut self, formatter: formatters::FloatFormatter) -> Self {
self.value_to_string = Some(formatter.0);
self.string_to_value = Some(formatter.1);
self
}
#[must_use]
pub fn value_update(&self, value: f32) -> (FourCC, ParameterValueUpdate) {
debug_assert!(
self.range.contains(&value),
"Float value for parameter '{}' must be in range {}..={}, but is {}",
self.id,
self.range.start(),
self.range.end(),
value
);
(self.id, ParameterValueUpdate::Raw(Arc::new(value)))
}
pub const fn id(&self) -> FourCC {
self.id
}
pub const fn range(&self) -> &RangeInclusive<f32> {
&self.range
}
pub const fn scaling(&self) -> &ParameterScaling {
&self.scaling
}
pub const fn default_value(&self) -> f32 {
self.default
}
pub fn clamp_value(&self, value: f32) -> f32 {
value.clamp(*self.range.start(), *self.range.end())
}
pub fn normalize_value(&self, value: f32) -> f32 {
let (min, max) = (*self.range.start(), *self.range.end());
self.scaling.unscale((value - min) / (max - min))
}
pub fn denormalize_value(&self, normalized: f32) -> f32 {
assert!((0.0..=1.0).contains(&normalized));
let (min, max) = (*self.range.start(), *self.range.end());
min + self.scaling.scale(normalized) * (max - min)
}
pub fn value_to_string(&self, value: f32, include_unit: bool) -> String {
match (&self.value_to_string, include_unit && !self.unit.is_empty()) {
(Some(f), true) => format!("{} {}", f(value), self.unit),
(Some(f), false) => f(value),
(None, true) => format!("{:.2} {}", value, self.unit),
(None, false) => format!("{:.2}", value),
}
}
pub fn string_to_value(&self, string: &str) -> Option<f32> {
let string = string.trim();
let value = match &self.string_to_value {
Some(f) => f(string).filter(|v| !v.is_nan()),
None => string
.to_lowercase()
.trim_end_matches(self.unit.to_lowercase().as_str())
.trim_end()
.parse::<f32>()
.ok()
.filter(|v| !v.is_nan()),
}?;
Some(self.clamp_value(value))
}
}
impl Parameter for FloatParameter {
fn id(&self) -> FourCC {
self.id
}
fn name(&self) -> &'static str {
self.name
}
fn parameter_type(&self) -> ParameterType {
ParameterType::Float {
step: 0.0,
polarity: if *self.range().start() == -*self.range().end() {
ParameterPolarity::Bipolar
} else {
ParameterPolarity::Unipolar
},
}
}
fn default_value(&self) -> f32 {
self.normalize_value(self.default)
}
fn value_to_string(&self, normalized: f32, include_unit: bool) -> String {
let value = self.denormalize_value(normalized.clamp(0.0, 1.0));
self.value_to_string(value, include_unit)
}
fn string_to_value(&self, string: String) -> Option<f32> {
let value = self.string_to_value(&string)?;
Some(self.normalize_value(value))
}
fn dyn_clone(&self) -> Box<dyn Parameter> {
Box::new(self.clone())
}
}
#[derive(Debug, Clone)]
pub struct FloatParameterValue {
description: FloatParameter,
value: f32,
}
impl FloatParameterValue {
pub fn from_description(description: FloatParameter) -> Self {
let value = description.default_value();
Self { value, description }
}
pub fn with_value(&self, value: f32) -> Self {
assert!(
self.description.range().contains(&value),
"Value out of bounds"
);
Self {
value,
description: self.description.clone(),
}
}
pub fn description(&self) -> &FloatParameter {
&self.description
}
#[inline(always)]
pub fn value(&self) -> f32 {
self.value
}
pub fn set_value(&mut self, value: f32) {
assert!(
self.description.range().contains(&value),
"Value out of bounds"
);
self.value = value;
}
pub fn set_value_clamped(&mut self, value: f32) {
self.value = self.description.clamp_value(value);
}
pub fn apply_update(&mut self, update: &ParameterValueUpdate) {
match update {
ParameterValueUpdate::Raw(raw) => {
if let Some(value) = (*raw).downcast_ref::<f32>() {
self.set_value_clamped(*value);
} else if let Some(value) = (*raw).downcast_ref::<f64>() {
self.set_value_clamped(*value as f32);
} else {
log::warn!(
"Invalid value type for float parameter '{}'",
self.description.id()
);
}
}
ParameterValueUpdate::Normalized(normalized) => {
let value = self
.description
.denormalize_value(normalized.clamp(0.0, 1.0));
self.set_value(value);
}
}
}
}
impl Display for FloatParameterValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let include_unit = true;
f.write_str(&self.description.value_to_string(self.value, include_unit))
}
}