use std::marker::PhantomData;
use crate::parameter_types::Parameters;
use crate::types::ParameterId;
#[derive(Debug, Clone, Copy)]
pub struct PresetInfo {
pub name: &'static str,
}
#[derive(Debug, Clone, Copy)]
pub struct PresetValue {
pub id: ParameterId,
pub plain_value: f64,
}
pub trait FactoryPresets: Send + Sync + 'static {
type Parameters: Parameters;
fn count() -> usize;
fn info(index: usize) -> Option<PresetInfo>;
fn values(index: usize) -> &'static [PresetValue];
fn apply(index: usize, parameters: &Self::Parameters) -> bool {
if index >= Self::count() {
return false;
}
let values = Self::values(index);
for value in values {
if let Some(param) = parameters.by_id(value.id) {
let normalized = param.plain_to_normalized(value.plain_value);
param.set_normalized(normalized);
}
}
true
}
}
pub struct NoPresets<P>(PhantomData<P>);
impl<P: Parameters + 'static> FactoryPresets for NoPresets<P> {
type Parameters = P;
fn count() -> usize {
0
}
fn info(_index: usize) -> Option<PresetInfo> {
None
}
fn values(_index: usize) -> &'static [PresetValue] {
&[]
}
}
unsafe impl<P> Send for NoPresets<P> {}
unsafe impl<P> Sync for NoPresets<P> {}
pub const fn fnv1a_hash(s: &str) -> u32 {
const FNV_OFFSET_BASIS: u32 = 2166136261;
const FNV_PRIME: u32 = 16777619;
let bytes = s.as_bytes();
let mut hash = FNV_OFFSET_BASIS;
let mut i = 0;
while i < bytes.len() {
hash ^= bytes[i] as u32;
hash = hash.wrapping_mul(FNV_PRIME);
i += 1;
}
hash
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parameter_groups::{GroupInfo, ParameterGroups};
use crate::parameter_info::{ParameterFlags, ParameterInfo, ParameterUnit};
use crate::parameter_types::{ParameterRef, Parameters};
use std::sync::atomic::{AtomicU64, Ordering};
struct MockParameter {
id: ParameterId,
name: &'static str,
value: AtomicU64,
info: ParameterInfo,
}
impl MockParameter {
fn new(id: ParameterId, name: &'static str) -> Self {
Self {
id,
name,
value: AtomicU64::new(0.0f64.to_bits()),
info: ParameterInfo {
id,
name,
short_name: name,
units: "",
unit: ParameterUnit::Generic,
step_count: 0,
default_normalized: 0.0,
flags: ParameterFlags::default(),
group_id: 0,
},
}
}
fn get_value(&self) -> f64 {
f64::from_bits(self.value.load(Ordering::Relaxed))
}
}
impl ParameterRef for MockParameter {
fn id(&self) -> ParameterId {
self.id
}
fn name(&self) -> &'static str {
self.name
}
fn short_name(&self) -> &'static str {
self.name
}
fn units(&self) -> &'static str {
""
}
fn flags(&self) -> &ParameterFlags {
&self.info.flags
}
fn default_normalized(&self) -> f64 {
0.0
}
fn step_count(&self) -> i32 {
0
}
fn get_normalized(&self) -> f64 {
self.get_value()
}
fn set_normalized(&self, value: f64) {
self.value.store(value.to_bits(), Ordering::Relaxed);
}
fn get_plain(&self) -> f64 {
self.get_normalized()
}
fn set_plain(&self, value: f64) {
self.set_normalized(value);
}
fn display_normalized(&self, normalized: f64) -> String {
format!("{:.2}", normalized)
}
fn parse(&self, s: &str) -> Option<f64> {
s.parse().ok()
}
fn normalized_to_plain(&self, normalized: f64) -> f64 {
normalized
}
fn plain_to_normalized(&self, plain: f64) -> f64 {
plain
}
fn info(&self) -> &ParameterInfo {
&self.info
}
}
struct MockParameters {
gain: MockParameter,
mix: MockParameter,
}
impl MockParameters {
fn new() -> Self {
Self {
gain: MockParameter::new(fnv1a_hash("gain"), "Gain"),
mix: MockParameter::new(fnv1a_hash("mix"), "Mix"),
}
}
}
impl ParameterGroups for MockParameters {
fn group_count(&self) -> usize {
1
}
fn group_info(&self, index: usize) -> Option<GroupInfo> {
if index == 0 {
Some(GroupInfo::root())
} else {
None
}
}
}
impl Parameters for MockParameters {
fn count(&self) -> usize {
2
}
fn iter(&self) -> Box<dyn Iterator<Item = &dyn ParameterRef> + '_> {
Box::new(
[&self.gain as &dyn ParameterRef, &self.mix as &dyn ParameterRef].into_iter(),
)
}
fn by_id(&self, id: ParameterId) -> Option<&dyn ParameterRef> {
if id == self.gain.id {
Some(&self.gain)
} else if id == self.mix.id {
Some(&self.mix)
} else {
None
}
}
}
struct TestPresets;
const TEST_PRESET_VALUES_0: &[PresetValue] = &[
PresetValue {
id: fnv1a_hash("gain"),
plain_value: 0.5,
},
PresetValue {
id: fnv1a_hash("mix"),
plain_value: 1.0,
},
];
const TEST_PRESET_VALUES_1: &[PresetValue] = &[PresetValue {
id: fnv1a_hash("gain"),
plain_value: 0.0,
}];
impl FactoryPresets for TestPresets {
type Parameters = MockParameters;
fn count() -> usize {
2
}
fn info(index: usize) -> Option<PresetInfo> {
match index {
0 => Some(PresetInfo { name: "Full Mix" }),
1 => Some(PresetInfo { name: "Silent" }),
_ => None,
}
}
fn values(index: usize) -> &'static [PresetValue] {
match index {
0 => TEST_PRESET_VALUES_0,
1 => TEST_PRESET_VALUES_1,
_ => &[],
}
}
}
#[test]
fn no_presets_count_returns_zero() {
assert_eq!(NoPresets::<MockParameters>::count(), 0);
}
#[test]
fn no_presets_info_returns_none() {
assert!(NoPresets::<MockParameters>::info(0).is_none());
assert!(NoPresets::<MockParameters>::info(1).is_none());
assert!(NoPresets::<MockParameters>::info(usize::MAX).is_none());
}
#[test]
fn no_presets_values_returns_empty_slice() {
assert!(NoPresets::<MockParameters>::values(0).is_empty());
assert!(NoPresets::<MockParameters>::values(1).is_empty());
}
#[test]
fn no_presets_apply_returns_false() {
let params = MockParameters::new();
assert!(!NoPresets::<MockParameters>::apply(0, ¶ms));
assert!(!NoPresets::<MockParameters>::apply(1, ¶ms));
assert!(!NoPresets::<MockParameters>::apply(usize::MAX, ¶ms));
}
#[test]
fn preset_info_can_be_created_with_name() {
let info = PresetInfo { name: "My Preset" };
assert_eq!(info.name, "My Preset");
}
#[test]
fn preset_info_supports_empty_name() {
let info = PresetInfo { name: "" };
assert_eq!(info.name, "");
}
#[test]
fn preset_info_is_copy() {
let info = PresetInfo { name: "Test" };
let info2 = info; assert_eq!(info.name, info2.name);
}
#[test]
fn preset_info_is_clone() {
let info = PresetInfo { name: "Test" };
let info2 = Clone::clone(&info);
assert_eq!(info.name, info2.name);
}
#[test]
fn preset_value_stores_id_and_plain_value() {
let value = PresetValue {
id: 12345,
plain_value: 0.75,
};
assert_eq!(value.id, 12345);
assert!((value.plain_value - 0.75).abs() < f64::EPSILON);
}
#[test]
fn preset_value_is_copy() {
let value = PresetValue {
id: 100,
plain_value: 0.5,
};
let value2 = value; assert_eq!(value.id, value2.id);
assert!((value.plain_value - value2.plain_value).abs() < f64::EPSILON);
}
#[test]
fn test_presets_count() {
assert_eq!(TestPresets::count(), 2);
}
#[test]
fn test_presets_info_valid_index() {
let info0 = TestPresets::info(0);
assert!(info0.is_some());
assert_eq!(info0.unwrap().name, "Full Mix");
let info1 = TestPresets::info(1);
assert!(info1.is_some());
assert_eq!(info1.unwrap().name, "Silent");
}
#[test]
fn test_presets_info_invalid_index() {
assert!(TestPresets::info(2).is_none());
assert!(TestPresets::info(usize::MAX).is_none());
}
#[test]
fn test_presets_values_valid_index() {
let values0 = TestPresets::values(0);
assert_eq!(values0.len(), 2);
assert_eq!(values0[0].id, fnv1a_hash("gain"));
assert!((values0[0].plain_value - 0.5).abs() < f64::EPSILON);
assert_eq!(values0[1].id, fnv1a_hash("mix"));
assert!((values0[1].plain_value - 1.0).abs() < f64::EPSILON);
let values1 = TestPresets::values(1);
assert_eq!(values1.len(), 1);
assert_eq!(values1[0].id, fnv1a_hash("gain"));
assert!((values1[0].plain_value - 0.0).abs() < f64::EPSILON);
}
#[test]
fn test_presets_values_invalid_index() {
assert!(TestPresets::values(2).is_empty());
assert!(TestPresets::values(usize::MAX).is_empty());
}
#[test]
fn test_presets_apply_sets_parameters() {
let params = MockParameters::new();
assert!((params.gain.get_value() - 0.0).abs() < f64::EPSILON);
assert!((params.mix.get_value() - 0.0).abs() < f64::EPSILON);
let result = TestPresets::apply(0, ¶ms);
assert!(result);
assert!((params.gain.get_value() - 0.5).abs() < f64::EPSILON);
assert!((params.mix.get_value() - 1.0).abs() < f64::EPSILON);
}
#[test]
fn test_presets_apply_sparse_preset() {
let params = MockParameters::new();
params.gain.set_normalized(0.75);
params.mix.set_normalized(0.5);
let result = TestPresets::apply(1, ¶ms);
assert!(result);
assert!((params.gain.get_value() - 0.0).abs() < f64::EPSILON);
assert!((params.mix.get_value() - 0.5).abs() < f64::EPSILON); }
#[test]
fn test_presets_apply_invalid_index_returns_false() {
let params = MockParameters::new();
params.gain.set_normalized(0.75);
params.mix.set_normalized(0.5);
let result = TestPresets::apply(2, ¶ms);
assert!(!result);
assert!((params.gain.get_value() - 0.75).abs() < f64::EPSILON);
assert!((params.mix.get_value() - 0.5).abs() < f64::EPSILON);
}
#[test]
fn test_presets_apply_with_unknown_parameter_id() {
struct PresetsWithUnknownParam;
const UNKNOWN_PARAM_VALUES: &[PresetValue] = &[
PresetValue {
id: fnv1a_hash("gain"),
plain_value: 0.5,
},
PresetValue {
id: fnv1a_hash("unknown"),
plain_value: 0.9,
},
];
impl FactoryPresets for PresetsWithUnknownParam {
type Parameters = MockParameters;
fn count() -> usize {
1
}
fn info(index: usize) -> Option<PresetInfo> {
if index == 0 {
Some(PresetInfo { name: "Test" })
} else {
None
}
}
fn values(index: usize) -> &'static [PresetValue] {
if index == 0 {
UNKNOWN_PARAM_VALUES
} else {
&[]
}
}
}
let params = MockParameters::new();
let result = PresetsWithUnknownParam::apply(0, ¶ms);
assert!(result);
assert!((params.gain.get_value() - 0.5).abs() < f64::EPSILON);
}
#[test]
fn fnv1a_hash_produces_consistent_values() {
assert_eq!(fnv1a_hash("gain"), fnv1a_hash("gain"));
assert_eq!(fnv1a_hash("mix"), fnv1a_hash("mix"));
}
#[test]
fn fnv1a_hash_different_inputs_produce_different_hashes() {
assert_ne!(fnv1a_hash("gain"), fnv1a_hash("mix"));
assert_ne!(fnv1a_hash("a"), fnv1a_hash("b"));
}
#[test]
fn fnv1a_hash_empty_string() {
let hash = fnv1a_hash("");
assert_eq!(hash, 2166136261); }
#[test]
fn fnv1a_hash_is_const() {
const GAIN_HASH: u32 = fnv1a_hash("gain");
assert_eq!(GAIN_HASH, fnv1a_hash("gain"));
}
}