Skip to main content

clasp_bridge/
mapping.rs

1//! Address mapping and value transformation
2
3use clasp_core::Value;
4use evalexpr::{eval_with_context_mut, ContextWithMutableVariables, HashMapContext};
5use std::collections::HashMap;
6use tracing::warn;
7
8/// Maps between protocol addresses and Clasp addresses
9#[derive(Debug, Clone)]
10pub struct AddressMapping {
11    /// Protocol address pattern
12    pub from: String,
13    /// Clasp address pattern
14    pub to: String,
15    /// Value transformation
16    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    /// Apply mapping to convert protocol address to Clasp address
34    pub fn map_address(&self, addr: &str) -> Option<String> {
35        // Simple pattern matching
36        if self.from.contains('*') {
37            // Extract wildcards
38            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            // Handle trailing wildcard
63            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/// Value transformation
77#[derive(Debug, Clone)]
78pub enum ValueTransform {
79    /// No transformation
80    Identity,
81    /// Scale from one range to another
82    Scale {
83        from_min: f64,
84        from_max: f64,
85        to_min: f64,
86        to_max: f64,
87    },
88    /// Clamp to range
89    Clamp { min: f64, max: f64 },
90    /// Invert (1 - x for normalized values)
91    Invert,
92    /// Convert to integer
93    ToInt,
94    /// Convert to float
95    ToFloat,
96    /// Custom expression (for advanced use)
97    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    /// Apply the transformation to a value
111    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                // Set value variable
160                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                // Add math constants
169                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/// Collection of address mappings
193#[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        // Check cache
211        if let Some(cached) = self.cache.get(addr) {
212            return Some(cached.clone());
213        }
214
215        // Find matching mapping
216        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    /// Transform a value using the mapping for an address
227    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        // This is a simplified test - real implementation would be more sophisticated
257    }
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}