devalang_wasm/engine/audio/
automation.rs1use crate::language::syntax::ast::Value;
2use std::collections::HashMap;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum AutomationCurve {
9 Linear,
10 Exponential,
11 Logarithmic,
12 Smooth, }
14
15impl AutomationCurve {
16 pub fn from_str(s: &str) -> Self {
17 match s.to_lowercase().as_str() {
18 "linear" | "lin" => AutomationCurve::Linear,
19 "exponential" | "exp" => AutomationCurve::Exponential,
20 "logarithmic" | "log" => AutomationCurve::Logarithmic,
21 "smooth" | "ease" => AutomationCurve::Smooth,
22 _ => AutomationCurve::Linear,
23 }
24 }
25}
26
27#[derive(Debug, Clone)]
29pub struct AutomationParam {
30 pub param_name: String,
31 pub from_value: f32,
32 pub to_value: f32,
33 pub start_time: f32, pub duration: f32, pub curve: AutomationCurve,
36}
37
38#[derive(Debug, Clone)]
40pub struct AutomationEnvelope {
41 pub target: String, pub params: Vec<AutomationParam>,
43}
44
45impl AutomationEnvelope {
46 pub fn new(target: String) -> Self {
47 Self {
48 target,
49 params: Vec::new(),
50 }
51 }
52
53 pub fn add_param(&mut self, param: AutomationParam) {
55 self.params.push(param);
56 }
57
58 pub fn get_value(&self, param_name: &str, time_seconds: f32) -> Option<f32> {
60 let matching: Vec<&AutomationParam> = self
62 .params
63 .iter()
64 .filter(|p| p.param_name == param_name)
65 .collect();
66
67 if matching.is_empty() {
68 return None;
69 }
70
71 for param in matching.iter().rev() {
73 let end_time = param.start_time + param.duration;
74
75 if time_seconds >= param.start_time && time_seconds <= end_time {
76 let progress = (time_seconds - param.start_time) / param.duration;
78 let value =
79 interpolate_value(param.from_value, param.to_value, progress, param.curve);
80 return Some(value);
81 } else if time_seconds > end_time {
82 return Some(param.to_value);
84 }
85 }
86
87 Some(matching[0].from_value)
89 }
90}
91
92fn interpolate_value(from: f32, to: f32, progress: f32, curve: AutomationCurve) -> f32 {
94 let t = progress.clamp(0.0, 1.0);
95
96 let interpolated = match curve {
97 AutomationCurve::Linear => t,
98 AutomationCurve::Exponential => {
99 if (to - from).abs() < 0.0001 { t } else { t * t }
101 }
102 AutomationCurve::Logarithmic => {
103 1.0 - (1.0 - t) * (1.0 - t)
105 }
106 AutomationCurve::Smooth => {
107 if t < 0.5 {
109 2.0 * t * t
110 } else {
111 1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
112 }
113 }
114 };
115
116 from + (to - from) * interpolated
117}
118
119pub fn parse_automation_from_value(value: &Value) -> Option<AutomationEnvelope> {
121 if let Value::Map(map) = value {
122 let target = map.get("target").and_then(|v| match v {
123 Value::String(s) | Value::Identifier(s) => Some(s.clone()),
124 _ => None,
125 })?;
126
127 let mut envelope = AutomationEnvelope::new(target);
128
129 if let Some(Value::Array(params_array)) = map.get("params") {
131 for param_value in params_array {
132 if let Some(param) = parse_automation_param(param_value) {
133 envelope.add_param(param);
134 }
135 }
136 }
137
138 Some(envelope)
139 } else {
140 None
141 }
142}
143
144fn parse_automation_param(value: &Value) -> Option<AutomationParam> {
146 if let Value::Map(map) = value {
147 let param_name = map.get("name").and_then(|v| match v {
148 Value::String(s) | Value::Identifier(s) => Some(s.clone()),
149 _ => None,
150 })?;
151
152 let from_value = map.get("from").and_then(|v| match v {
153 Value::Number(n) => Some(*n),
154 _ => None,
155 })?;
156
157 let to_value = map.get("to").and_then(|v| match v {
158 Value::Number(n) => Some(*n),
159 _ => None,
160 })?;
161
162 let start_time = map
163 .get("start")
164 .and_then(|v| match v {
165 Value::Number(n) => Some(*n),
166 _ => None,
167 })
168 .unwrap_or(0.0);
169
170 let duration = map.get("duration").and_then(|v| match v {
171 Value::Number(n) => Some(*n),
172 _ => None,
173 })?;
174
175 let curve = map
176 .get("curve")
177 .and_then(|v| match v {
178 Value::String(s) | Value::Identifier(s) => Some(AutomationCurve::from_str(s)),
179 _ => None,
180 })
181 .unwrap_or(AutomationCurve::Linear);
182
183 Some(AutomationParam {
184 param_name,
185 from_value,
186 to_value,
187 start_time,
188 duration,
189 curve,
190 })
191 } else {
192 None
193 }
194}
195
196#[derive(Debug, Clone, Default)]
198pub struct AutomationRegistry {
199 envelopes: HashMap<String, AutomationEnvelope>,
200}
201
202impl AutomationRegistry {
203 pub fn new() -> Self {
204 Self {
205 envelopes: HashMap::new(),
206 }
207 }
208
209 pub fn register(&mut self, envelope: AutomationEnvelope) {
211 self.envelopes.insert(envelope.target.clone(), envelope);
212 }
213
214 pub fn get_value(&self, target: &str, param_name: &str, time_seconds: f32) -> Option<f32> {
216 self.envelopes
217 .get(target)
218 .and_then(|env| env.get_value(param_name, time_seconds))
219 }
220
221 pub fn has_automation(&self, target: &str) -> bool {
223 self.envelopes.contains_key(target)
224 }
225
226 pub fn targets(&self) -> Vec<String> {
228 self.envelopes.keys().cloned().collect()
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn test_linear_interpolation() {
238 let param = AutomationParam {
239 param_name: "volume".to_string(),
240 from_value: 0.0,
241 to_value: 1.0,
242 start_time: 0.0,
243 duration: 2.0,
244 curve: AutomationCurve::Linear,
245 };
246
247 let mut envelope = AutomationEnvelope::new("synth1".to_string());
248 envelope.add_param(param);
249
250 assert_eq!(envelope.get_value("volume", 0.0), Some(0.0));
252
253 assert!((envelope.get_value("volume", 1.0).unwrap() - 0.5).abs() < 0.001);
255
256 assert_eq!(envelope.get_value("volume", 2.0), Some(1.0));
258
259 assert_eq!(envelope.get_value("volume", 3.0), Some(1.0));
261 }
262
263 #[test]
264 fn test_smooth_curve() {
265 let value_start = interpolate_value(0.0, 1.0, 0.0, AutomationCurve::Smooth);
266 let value_mid = interpolate_value(0.0, 1.0, 0.5, AutomationCurve::Smooth);
267 let value_end = interpolate_value(0.0, 1.0, 1.0, AutomationCurve::Smooth);
268
269 assert_eq!(value_start, 0.0);
270 assert_eq!(value_end, 1.0);
271 assert!(value_mid > 0.4 && value_mid < 0.6); }
273
274 #[test]
275 fn test_automation_registry() {
276 let mut registry = AutomationRegistry::new();
277
278 let mut envelope = AutomationEnvelope::new("synth1".to_string());
279 envelope.add_param(AutomationParam {
280 param_name: "volume".to_string(),
281 from_value: 0.0,
282 to_value: 1.0,
283 start_time: 0.0,
284 duration: 2.0,
285 curve: AutomationCurve::Linear,
286 });
287
288 registry.register(envelope);
289
290 assert!(registry.has_automation("synth1"));
291 assert!(!registry.has_automation("synth2"));
292
293 let value = registry.get_value("synth1", "volume", 1.0);
294 assert!(value.is_some());
295 assert!((value.unwrap() - 0.5).abs() < 0.001);
296 }
297}