1#[derive(Clone, Debug)]
3pub enum ParamRange {
4 Linear { min: f64, max: f64 },
5 Logarithmic { min: f64, max: f64 },
6 Discrete { min: i64, max: i64 },
7 Enum { count: usize },
8}
9
10impl ParamRange {
11 pub fn normalize(&self, plain: f64) -> f64 {
13 match self {
14 Self::Linear { min, max } => ((plain - min) / (max - min)).clamp(0.0, 1.0),
15 Self::Logarithmic { min, max } => {
16 if *min <= 0.0 || *max <= 0.0 {
17 return 0.0;
18 }
19 let min_log = min.ln();
20 let max_log = max.ln();
21 ((plain.ln() - min_log) / (max_log - min_log)).clamp(0.0, 1.0)
22 }
23 Self::Discrete { min, max } => {
24 ((plain - *min as f64) / (*max as f64 - *min as f64)).clamp(0.0, 1.0)
25 }
26 Self::Enum { count } => {
27 if *count <= 1 {
28 return 0.0;
29 }
30 (plain / (*count as f64 - 1.0)).clamp(0.0, 1.0)
31 }
32 }
33 }
34
35 pub fn denormalize(&self, normalized: f64) -> f64 {
37 let n = normalized.clamp(0.0, 1.0);
38 match self {
39 Self::Linear { min, max } => min + n * (max - min),
40 Self::Logarithmic { min, max } => {
41 if *min <= 0.0 || *max <= 0.0 {
42 return *min;
43 }
44 let min_log = min.ln();
45 let max_log = max.ln();
46 (min_log + n * (max_log - min_log)).exp()
47 }
48 Self::Discrete { min, max } => {
49 ((*min as f64) + n * (*max as f64 - *min as f64)).round()
50 }
51 Self::Enum { count } => (n * (*count as f64 - 1.0)).round(),
52 }
53 }
54
55 pub fn min(&self) -> f64 {
57 match self {
58 Self::Linear { min, .. } | Self::Logarithmic { min, .. } => *min,
59 Self::Discrete { min, .. } => *min as f64,
60 Self::Enum { .. } => 0.0,
61 }
62 }
63
64 pub fn max(&self) -> f64 {
66 match self {
67 Self::Linear { max, .. } | Self::Logarithmic { max, .. } => *max,
68 Self::Discrete { max, .. } => *max as f64,
69 Self::Enum { count } => (*count as f64 - 1.0).max(0.0),
70 }
71 }
72
73 pub fn step_count(&self) -> u32 {
75 match self {
76 Self::Linear { .. } | Self::Logarithmic { .. } => 0,
77 Self::Discrete { min, max } => (*max - *min) as u32,
78 Self::Enum { count } => (*count as u32).saturating_sub(1),
79 }
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn linear_round_trip() {
89 let range = ParamRange::Linear {
90 min: -60.0,
91 max: 24.0,
92 };
93 for plain in [-60.0, -30.0, 0.0, 12.0, 24.0] {
94 let norm = range.normalize(plain);
95 let back = range.denormalize(norm);
96 assert!(
97 (back - plain).abs() < 1e-10,
98 "plain={plain}, norm={norm}, back={back}"
99 );
100 }
101 }
102
103 #[test]
104 fn log_round_trip() {
105 let range = ParamRange::Logarithmic {
106 min: 20.0,
107 max: 20000.0,
108 };
109 for plain in [20.0, 100.0, 1000.0, 10000.0, 20000.0] {
110 let norm = range.normalize(plain);
111 let back = range.denormalize(norm);
112 assert!(
113 (back - plain).abs() < 0.01,
114 "plain={plain}, norm={norm}, back={back}"
115 );
116 }
117 }
118
119 #[test]
120 fn enum_round_trip() {
121 let range = ParamRange::Enum { count: 4 };
122 for idx in 0..4 {
123 let norm = range.normalize(idx as f64);
124 let back = range.denormalize(norm);
125 assert_eq!(back as usize, idx);
126 }
127 }
128}