#![forbid(unsafe_code)]
mod info;
mod range;
pub mod sample;
mod smooth;
mod types;
pub use info::{ParamFlags, ParamInfo, ParamUnit, ParamValueKind};
pub use range::ParamRange;
pub use sample::{Float, Sample};
pub use smooth::{Smoother, SmoothingStyle};
pub use types::{
BoolParam, EnumParam, FloatParam, FloatParamReadF32, FloatParamReadF64, IntParam, MeterSlot,
ParamEnum,
};
#[doc(hidden)]
pub const METER_ID_BASE: u32 = 1 << 24;
#[doc(hidden)]
pub mod __private {
pub trait Sealed {}
}
#[must_use]
pub fn format_param_value(info: &ParamInfo, value: f64) -> String {
let is_int = info.kind == ParamValueKind::Int;
#[allow(clippy::cast_possible_truncation)]
let int_value = value.round() as i64;
match info.unit {
ParamUnit::Db => {
if is_int {
format!("{int_value} dB")
} else {
format!("{value:.1} dB")
}
}
ParamUnit::Hz => {
if value >= 1000.0 {
format!("{:.1} kHz", value / 1000.0)
} else {
format!("{value:.0} Hz")
}
}
ParamUnit::Milliseconds => {
if is_int {
format!("{int_value} ms")
} else {
format!("{value:.1} ms")
}
}
ParamUnit::Seconds => {
if value >= 1.0 {
format!("{value:.2} s")
} else {
format!("{:.0} ms", value * 1000.0)
}
}
ParamUnit::Percent => format!("{:.0}%", value * 100.0),
ParamUnit::Semitones => {
if is_int {
format!("{int_value} st")
} else {
format!("{value:.1} st")
}
}
ParamUnit::Pan => {
#[allow(clippy::cast_possible_truncation)]
let pct = (value * 100.0).round() as i32;
match pct.cmp(&0) {
std::cmp::Ordering::Equal => "C".to_string(),
std::cmp::Ordering::Less => format!("{}L", -pct),
std::cmp::Ordering::Greater => format!("{pct}R"),
}
}
ParamUnit::None => {
if is_int {
format!("{int_value}")
} else {
format!("{value:.2}")
}
}
}
}
pub trait Params: __private::Sealed + Send + Sync + 'static {
fn param_infos(&self) -> Vec<ParamInfo>;
fn append_param_infos(&self, into: &mut Vec<ParamInfo>) {
into.extend(self.param_infos());
}
#[must_use]
fn param_infos_static() -> Vec<ParamInfo>
where
Self: Sized,
{
Vec::new()
}
fn count(&self) -> usize;
fn meter_ids(&self) -> Vec<u32> {
Vec::new()
}
fn get_normalized(&self, id: u32) -> Option<f64>;
fn set_normalized(&self, id: u32, value: f64);
fn set_normalized_returning_plain(&self, id: u32, value: f64) -> f64 {
self.set_normalized(id, value);
self.get_plain(id).unwrap_or(0.0)
}
fn set_normalized_returning_normalized(&self, id: u32, value: f64) -> f64 {
self.set_normalized(id, value);
self.get_normalized(id).unwrap_or(0.0)
}
fn get_plain(&self, id: u32) -> Option<f64>;
fn set_plain(&self, id: u32, value: f64);
fn format_value(&self, id: u32, value: f64) -> Option<String>;
fn parse_value(&self, id: u32, text: &str) -> Option<f64>;
fn snap_smoothers(&self);
fn set_sample_rate(&self, sample_rate: f64);
fn collect_values(&self) -> (Vec<u32>, Vec<f64>);
fn restore_values(&self, values: &[(u32, f64)]);
fn assert_no_id_collisions(&self) {
let mut all = self.param_infos();
let mut seen: Vec<(u32, &'static str)> = Vec::with_capacity(all.len());
for info in all.drain(..) {
for (prev_id, prev_name) in &seen {
assert!(
*prev_id != info.id,
"duplicate parameter ID {}: '{}' and '{}' (likely a \
parent / nested-struct collision; the per-struct \
compile-time check can't see across nested types)",
info.id,
prev_name,
info.name,
);
}
seen.push((info.id, info.name));
}
for meter_id in self.meter_ids() {
for (prev_id, prev_name) in &seen {
assert!(
*prev_id != meter_id,
"meter ID {meter_id} collides with parameter ID for '{prev_name}'",
);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::range::ParamRange;
fn pan_info() -> ParamInfo {
ParamInfo {
id: 0,
name: "Pan",
short_name: "Pan",
group: "",
range: ParamRange::Linear {
min: -1.0,
max: 1.0,
},
default_plain: 0.0,
flags: ParamFlags::empty(),
unit: ParamUnit::Pan,
kind: ParamValueKind::Float,
}
}
#[test]
fn pan_centre() {
let info = pan_info();
assert_eq!(format_param_value(&info, 0.0), "C");
assert_eq!(format_param_value(&info, 0.004), "C");
assert_eq!(format_param_value(&info, -0.004), "C");
}
#[test]
fn pan_left() {
let info = pan_info();
assert_eq!(format_param_value(&info, -0.5), "50L");
assert_eq!(format_param_value(&info, -1.0), "100L");
assert_eq!(format_param_value(&info, -0.006), "1L");
}
#[test]
fn pan_right() {
let info = pan_info();
assert_eq!(format_param_value(&info, 0.5), "50R");
assert_eq!(format_param_value(&info, 1.0), "100R");
assert_eq!(format_param_value(&info, 0.006), "1R");
}
fn int_info(unit: ParamUnit) -> ParamInfo {
ParamInfo {
id: 0,
name: "n",
short_name: "n",
group: "",
range: ParamRange::Discrete { min: -12, max: 12 },
default_plain: 0.0,
flags: ParamFlags::empty(),
unit,
kind: ParamValueKind::Int,
}
}
#[test]
fn int_param_no_fractional_zero() {
assert_eq!(
format_param_value(&int_info(ParamUnit::Semitones), 0.0),
"0 st"
);
assert_eq!(
format_param_value(&int_info(ParamUnit::Semitones), -5.0),
"-5 st"
);
assert_eq!(format_param_value(&int_info(ParamUnit::None), 0.0), "0");
assert_eq!(format_param_value(&int_info(ParamUnit::Db), 6.0), "6 dB");
assert_eq!(
format_param_value(&int_info(ParamUnit::Milliseconds), 50.0),
"50 ms"
);
}
}