1use rill_core::math::Transcendental;
7use rill_core::traits::ProcessResult;
8use rill_core::traits::{Algorithm, AlgorithmCategory, AlgorithmMetadata};
9
10#[derive(Debug, Clone, Copy, PartialEq)]
12pub enum MappingStrategy {
13 Linear,
15 Exponential {
17 exponent: f32,
20 },
21 Logarithmic,
23 Inverted,
25}
26
27impl MappingStrategy {
28 pub fn map<T: Transcendental>(&self, x: T, min: T, max: T) -> T {
30 let xf: f32 = x.to_f32();
31 let minf: f32 = min.to_f32();
32 let maxf: f32 = max.to_f32();
33 let range = maxf - minf;
34 let result = match self {
35 MappingStrategy::Linear => minf + xf * range,
36 MappingStrategy::Exponential { exponent } => minf + xf.powf(*exponent) * range,
37 MappingStrategy::Logarithmic => {
38 let one = 1.0f32;
39 let mapped =
40 (one + xf * (core::f32::consts::E - one)).ln() / core::f32::consts::E.ln();
41 minf + mapped * range
42 }
43 MappingStrategy::Inverted => maxf - xf * range,
44 };
45 T::from_f32(result)
46 }
47}
48
49#[derive(Debug, Clone)]
74pub struct ControlMapper<T: Transcendental> {
75 min: T,
77 max: T,
79 strategy: MappingStrategy,
81 value: T,
83}
84
85impl<T: Transcendental> ControlMapper<T> {
86 pub fn new(min: T, max: T, strategy: MappingStrategy) -> Self {
88 Self {
89 min,
90 max,
91 strategy,
92 value: T::ZERO,
93 }
94 }
95
96 pub fn set_range(&mut self, min: T, max: T) {
98 self.min = min;
99 self.max = max;
100 }
101
102 pub fn set_strategy(&mut self, strategy: MappingStrategy) {
104 self.strategy = strategy;
105 }
106
107 pub fn current_mapped(&self) -> T {
109 self.strategy.map(self.value, self.min, self.max)
110 }
111}
112
113impl<T: Transcendental> Algorithm<T> for ControlMapper<T> {
114 fn process(&mut self, input: Option<&[T]>, output: &mut [T]) -> ProcessResult<()> {
115 for (i, sample) in output.iter_mut().enumerate() {
116 let normalized = match input {
119 Some(buf) => {
120 if i < buf.len() {
121 buf[i]
122 } else {
123 self.value
124 }
125 }
126 None => self.value,
127 };
128 *sample = self.strategy.map(normalized, self.min, self.max);
129 }
130 Ok(())
131 }
132
133 fn apply_command(&mut self, value: T) {
134 self.value = value;
135 }
136
137 fn init(&mut self, _sample_rate: f32) {}
138
139 fn reset(&mut self) {
140 self.value = T::ZERO;
141 }
142
143 fn metadata(&self) -> AlgorithmMetadata {
144 AlgorithmMetadata {
145 name: "ControlMapper",
146 category: AlgorithmCategory::Utility,
147 description: "Maps normalized [0,1] control values to a parameter range",
148 author: "Rill",
149 version: "0.1.0",
150 }
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn test_linear_mapping() {
160 let mapper = ControlMapper::new(0.0f32, 100.0, MappingStrategy::Linear);
161 assert!((mapper.current_mapped() - 0.0).abs() < 1e-6);
162 }
163
164 #[test]
165 fn test_mapping_strategies() {
166 let mut mapper = ControlMapper::new(0.0f32, 100.0, MappingStrategy::Linear);
167 mapper.apply_command(0.5);
168 let mut out = [0.0f32];
169 mapper.process(None, &mut out).unwrap();
170 assert!((out[0] - 50.0).abs() < 1e-6);
171
172 mapper.set_strategy(MappingStrategy::Inverted);
173 mapper.apply_command(0.5);
174 mapper.process(None, &mut out).unwrap();
175 assert!((out[0] - 50.0).abs() < 1e-6);
176
177 mapper.set_strategy(MappingStrategy::Exponential { exponent: 2.0 });
178 mapper.apply_command(0.5); mapper.process(None, &mut out).unwrap();
180 assert!((out[0] - 25.0).abs() < 1e-6);
181 }
182
183 #[test]
184 fn test_mapping_with_input() {
185 let mut mapper = ControlMapper::new(0.0f32, 100.0, MappingStrategy::Linear);
186 let input = [0.25f32, 0.75f32];
187 let mut output = [0.0f32; 2];
188 mapper.process(Some(&input), &mut output).unwrap();
189 assert!((output[0] - 25.0).abs() < 1e-6);
190 assert!((output[1] - 75.0).abs() < 1e-6);
191 }
192
193 #[test]
194 fn test_log_mapping_bounds() {
195 let mut mapper = ControlMapper::new(20.0f32, 20000.0, MappingStrategy::Logarithmic);
196 mapper.apply_command(0.0);
197 let mut out = [0.0f32];
198 mapper.process(None, &mut out).unwrap();
199 assert!((out[0] - 20.0).abs() < 1.0);
200
201 mapper.apply_command(1.0);
202 mapper.process(None, &mut out).unwrap();
203 assert!((out[0] - 20000.0).abs() < 1.0);
204 }
205}