1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use std::fmt::Debug;
use crate::utils::{db_to_linear, linear_to_db};
// -------------------------------------------------------------------------------------------------
/// Effect parameter scaling for float parameters, applied to convert normalized UI or automation
/// values to the internal values.
#[derive(Default, Debug, Clone, Copy, PartialEq)]
pub enum ParameterScaling {
#[default]
/// Linear scaling: `y = x` (no transformation applied)
Linear,
/// Exponential scaling: `y = x^factor`
/// Factor must be > 0.0.
///
/// Values < 1.0 create a curve that rises slowly at first then quickly.
/// Values > 1.0 create a curve that rises quickly at first then slowly.
///
/// Factors between 2.0 - 3.0 are typically used for Hz (e.g. filter cutoffs).
///
Exponential(f32),
/// Decibel scaling: maps normalized value to dB range, then converts to linear gain
/// Parameters are (min_db, max_db). max_db must be > min_db.
///
/// The normalized value \[0, 1\] is mapped to the dB range \[min_db, max_db\], then converted
/// to linear gain. The scaling returns a 0-1 position within the linear gain range.
///
/// Allows storing/applying a linear gain value while displaying a dB value with proper scaling.
/// The internal value range should use `db_to_lin(minDb)..=db_to_lin(maxDb)`.
Decibel(f32, f32),
/// Sigmoid (S-curve) scaling: `y = 1 / (1 + e^(-steepness * (x - 0.5)))`
/// Steepness must be > 0.0.
///
/// Creates an S-shaped curve, normalized to map \[0,1\] -> \[0,1\]. Higher steepness values create
/// a sharper transition around the midpoint.
Sigmoid(f32),
}
impl ParameterScaling {
/// Apply scaling to a normalized f32 value.
pub fn scale(&self, value: f32) -> f32 {
assert!(
(0.0..=1.0).contains(&value),
"Expecting a normalized value here"
);
match self {
ParameterScaling::Linear => value,
ParameterScaling::Exponential(factor) => {
// Exponential curve: y = x^factor
value.powf(*factor)
}
ParameterScaling::Sigmoid(steepness) => {
// Sigmoid curve: y = 1 / (1 + e^(-steepness * (x - 0.5)))
let sigmoid = |x: f32| 1.0 / (1.0 + (-steepness * (x - 0.5)).exp());
let y = sigmoid(value);
let y_min = sigmoid(0.0);
let y_max = sigmoid(1.0);
(y - y_min) / (y_max - y_min)
}
ParameterScaling::Decibel(min_db, max_db) => {
// Map normalized value to dB range
let db_value = min_db + value * (max_db - min_db);
// Convert dB to linear gain
let linear_gain = db_to_linear(db_value);
// Map to [0,1] range relative to [db_to_lin(min_db), db_to_lin(max_db)]
let (min_linear, max_linear) = (db_to_linear(*min_db), db_to_linear(*max_db));
(linear_gain - min_linear) / (max_linear - min_linear)
}
}
}
/// Apply inverse scaling to a normalized f32 value.
pub fn unscale(&self, value: f32) -> f32 {
assert!(
(0.0..=1.0).contains(&value),
"Expecting a normalized value here"
);
match self {
ParameterScaling::Linear => value,
ParameterScaling::Exponential(factor) => {
// Inverse of exponential: x = y^(1/factor)
let factor = factor.abs().max(0.001);
value.powf(1.0 / factor)
}
ParameterScaling::Sigmoid(steepness) => {
// Inverse of sigmoid: x = 0.5 - ln((1/y) - 1) / steepness
let sigmoid = |x: f32| 1.0 / (1.0 + (-steepness * (x - 0.5)).exp());
let y_min = sigmoid(0.0);
let y_max = sigmoid(1.0);
let y = value * (y_max - y_min) + y_min;
let y_clamped = y.clamp(0.0001, 0.9999); // Avoid log(0)
0.5 - ((1.0 / y_clamped) - 1.0).ln() / steepness
}
ParameterScaling::Decibel(min_db, max_db) => {
// value is a 0-1 position in the [db_to_lin(min_db), db_to_lin(max_db)] range
let (min_linear, max_linear) = (db_to_linear(*min_db), db_to_linear(*max_db));
let linear_gain = min_linear + value * (max_linear - min_linear);
// Convert linear gain to dB
let db_value = linear_to_db(linear_gain);
// Map dB back to normalized range
(db_value - min_db) / (max_db - min_db)
}
}
}
pub(crate) const fn validate(&self) {
match self {
ParameterScaling::Linear => {}
ParameterScaling::Exponential(factor) => {
assert!(
*factor > 0.0,
"Invalid exponential parameter scaling factor (must be > 0)"
);
}
ParameterScaling::Sigmoid(steepness) => {
assert!(
*steepness > 0.0,
"Invalid sigmoid parameter scaling steepness (must be > 0)",
);
}
ParameterScaling::Decibel(min_db, max_db) => {
assert!(
*min_db < *max_db,
"Invalid decibel parameter scaling range (min_db must be < max_db)"
);
}
}
}
}