1use clasp_core::Value;
4use evalexpr::{eval_with_context_mut, ContextWithMutableVariables, HashMapContext};
5use std::collections::HashMap;
6use tracing::warn;
7
8#[derive(Debug, Clone)]
10pub struct AddressMapping {
11 pub from: String,
13 pub to: String,
15 pub transform: Option<ValueTransform>,
17}
18
19impl AddressMapping {
20 pub fn new(from: &str, to: &str) -> Self {
21 Self {
22 from: from.to_string(),
23 to: to.to_string(),
24 transform: None,
25 }
26 }
27
28 pub fn with_transform(mut self, transform: ValueTransform) -> Self {
29 self.transform = Some(transform);
30 self
31 }
32
33 pub fn map_address(&self, addr: &str) -> Option<String> {
35 if self.from.contains('*') {
37 let from_parts: Vec<&str> = self.from.split('*').collect();
39 let to_parts: Vec<&str> = self.to.split('*').collect();
40
41 if from_parts.len() != to_parts.len() {
42 return None;
43 }
44
45 let mut result = self.to.clone();
46 let mut remaining = addr;
47
48 for (i, part) in from_parts.iter().enumerate() {
49 if !part.is_empty() {
50 if let Some(pos) = remaining.find(part) {
51 if i > 0 {
52 let captured = &remaining[..pos];
53 result = result.replacen('*', captured, 1);
54 }
55 remaining = &remaining[pos + part.len()..];
56 } else {
57 return None;
58 }
59 }
60 }
61
62 if self.from.ends_with('*') && !remaining.is_empty() {
64 result = result.replacen('*', remaining, 1);
65 }
66
67 Some(result)
68 } else if addr == self.from {
69 Some(self.to.clone())
70 } else {
71 None
72 }
73 }
74}
75
76#[derive(Debug, Clone)]
78pub enum ValueTransform {
79 Identity,
81 Scale {
83 from_min: f64,
84 from_max: f64,
85 to_min: f64,
86 to_max: f64,
87 },
88 Clamp { min: f64, max: f64 },
90 Invert,
92 ToInt,
94 ToFloat,
96 Expression(String),
98}
99
100impl ValueTransform {
101 pub fn scale(from_min: f64, from_max: f64, to_min: f64, to_max: f64) -> Self {
102 Self::Scale {
103 from_min,
104 from_max,
105 to_min,
106 to_max,
107 }
108 }
109
110 pub fn apply(&self, value: &Value) -> Value {
112 match self {
113 ValueTransform::Identity => value.clone(),
114 ValueTransform::Scale {
115 from_min,
116 from_max,
117 to_min,
118 to_max,
119 } => {
120 if let Some(v) = value.as_f64() {
121 let normalized = (v - from_min) / (from_max - from_min);
122 let scaled = to_min + normalized * (to_max - to_min);
123 Value::Float(scaled)
124 } else {
125 value.clone()
126 }
127 }
128 ValueTransform::Clamp { min, max } => {
129 if let Some(v) = value.as_f64() {
130 Value::Float(v.clamp(*min, *max))
131 } else {
132 value.clone()
133 }
134 }
135 ValueTransform::Invert => {
136 if let Some(v) = value.as_f64() {
137 Value::Float(1.0 - v)
138 } else {
139 value.clone()
140 }
141 }
142 ValueTransform::ToInt => {
143 if let Some(v) = value.as_f64() {
144 Value::Int(v as i64)
145 } else {
146 value.clone()
147 }
148 }
149 ValueTransform::ToFloat => {
150 if let Some(v) = value.as_i64() {
151 Value::Float(v as f64)
152 } else {
153 value.clone()
154 }
155 }
156 ValueTransform::Expression(expr) => {
157 let mut context = HashMapContext::new();
158
159 if let Some(v) = value.as_f64() {
161 let _ = context.set_value("value".to_string(), evalexpr::Value::Float(v));
162 let _ = context.set_value("x".to_string(), evalexpr::Value::Float(v));
163 } else if let Some(v) = value.as_i64() {
164 let _ = context.set_value("value".to_string(), evalexpr::Value::Int(v));
165 let _ = context.set_value("x".to_string(), evalexpr::Value::Int(v));
166 }
167
168 let _ = context.set_value(
170 "PI".to_string(),
171 evalexpr::Value::Float(std::f64::consts::PI),
172 );
173 let _ =
174 context.set_value("E".to_string(), evalexpr::Value::Float(std::f64::consts::E));
175
176 match eval_with_context_mut(expr, &mut context) {
177 Ok(evalexpr::Value::Float(f)) => Value::Float(f),
178 Ok(evalexpr::Value::Int(i)) => Value::Int(i),
179 Ok(evalexpr::Value::Boolean(b)) => Value::Bool(b),
180 Ok(evalexpr::Value::String(s)) => Value::String(s),
181 Ok(_) => value.clone(),
182 Err(e) => {
183 warn!("Expression evaluation failed: {}", e);
184 value.clone()
185 }
186 }
187 }
188 }
189 }
190}
191
192#[derive(Debug, Clone, Default)]
194pub struct MappingTable {
195 mappings: Vec<AddressMapping>,
196 cache: HashMap<String, String>,
197}
198
199impl MappingTable {
200 pub fn new() -> Self {
201 Self::default()
202 }
203
204 pub fn add(&mut self, mapping: AddressMapping) {
205 self.mappings.push(mapping);
206 self.cache.clear();
207 }
208
209 pub fn map(&mut self, addr: &str) -> Option<String> {
210 if let Some(cached) = self.cache.get(addr) {
212 return Some(cached.clone());
213 }
214
215 for mapping in &self.mappings {
217 if let Some(result) = mapping.map_address(addr) {
218 self.cache.insert(addr.to_string(), result.clone());
219 return Some(result);
220 }
221 }
222
223 None
224 }
225
226 pub fn transform(&self, addr: &str, value: &Value) -> Value {
228 for mapping in &self.mappings {
229 if mapping.map_address(addr).is_some() {
230 if let Some(ref transform) = mapping.transform {
231 return transform.apply(value);
232 }
233 }
234 }
235 value.clone()
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242
243 #[test]
244 fn test_simple_mapping() {
245 let mapping = AddressMapping::new("/osc/synth/cutoff", "/synth/cutoff");
246 assert_eq!(
247 mapping.map_address("/osc/synth/cutoff"),
248 Some("/synth/cutoff".to_string())
249 );
250 assert_eq!(mapping.map_address("/osc/synth/other"), None);
251 }
252
253 #[test]
254 fn test_wildcard_mapping() {
255 let _mapping = AddressMapping::new("/midi/*/cc/*", "/midi/*/*/*");
256 }
258
259 #[test]
260 fn test_scale_transform() {
261 let transform = ValueTransform::scale(0.0, 127.0, 0.0, 1.0);
262 let result = transform.apply(&Value::Float(63.5));
263 assert!((result.as_f64().unwrap() - 0.5).abs() < 0.01);
264 }
265
266 #[test]
267 fn test_invert_transform() {
268 let transform = ValueTransform::Invert;
269 let result = transform.apply(&Value::Float(0.3));
270 assert!((result.as_f64().unwrap() - 0.7).abs() < 0.001);
271 }
272}