1use std::collections::HashMap;
4use std::sync::{Arc, RwLock};
5
6use crate::{BridgeError, ParameterHost};
7use wavecraft_protocol::{AudioRuntimeStatus, MeterFrame, OscilloscopeFrame, ParameterInfo};
8
9pub trait MeterProvider: Send + Sync {
11 fn get_meter_frame(&self) -> Option<MeterFrame>;
13}
14
15pub trait OscilloscopeProvider: Send + Sync {
17 fn get_oscilloscope_frame(&self) -> Option<OscilloscopeFrame>;
19}
20
21pub struct InMemoryParameterHost {
25 parameters: RwLock<Vec<ParameterInfo>>,
26 values: RwLock<HashMap<String, f32>>,
27 meter_provider: Option<Arc<dyn MeterProvider>>,
28 oscilloscope_provider: Option<Arc<dyn OscilloscopeProvider>>,
29}
30
31impl InMemoryParameterHost {
32 pub fn new(parameters: Vec<ParameterInfo>) -> Self {
34 let values = parameters
35 .iter()
36 .map(|p| (p.id.clone(), p.default))
37 .collect();
38
39 Self {
40 parameters: RwLock::new(parameters),
41 values: RwLock::new(values),
42 meter_provider: None,
43 oscilloscope_provider: None,
44 }
45 }
46
47 pub fn with_meter_provider(
49 parameters: Vec<ParameterInfo>,
50 meter_provider: Arc<dyn MeterProvider>,
51 ) -> Self {
52 let mut host = Self::new(parameters);
53 host.meter_provider = Some(meter_provider);
54 host
55 }
56
57 pub fn with_oscilloscope_provider(
59 parameters: Vec<ParameterInfo>,
60 oscilloscope_provider: Arc<dyn OscilloscopeProvider>,
61 ) -> Self {
62 let mut host = Self::new(parameters);
63 host.oscilloscope_provider = Some(oscilloscope_provider);
64 host
65 }
66
67 pub fn with_providers(
69 parameters: Vec<ParameterInfo>,
70 meter_provider: Option<Arc<dyn MeterProvider>>,
71 oscilloscope_provider: Option<Arc<dyn OscilloscopeProvider>>,
72 ) -> Self {
73 let mut host = Self::new(parameters);
74 host.meter_provider = meter_provider;
75 host.oscilloscope_provider = oscilloscope_provider;
76 host
77 }
78
79 pub fn replace_parameters(&self, new_params: Vec<ParameterInfo>) -> Result<(), String> {
96 let mut values = match self.values.write() {
98 Ok(guard) => guard,
99 Err(poisoned) => {
100 eprintln!("⚠ Recovering from poisoned values lock");
101 poisoned.into_inner()
102 }
103 };
104
105 let mut new_values = HashMap::new();
107 for param in &new_params {
108 let value = values.get(¶m.id).copied().unwrap_or(param.default);
109 new_values.insert(param.id.clone(), value);
110 }
111
112 *values = new_values;
113 drop(values); let mut params = match self.parameters.write() {
117 Ok(guard) => guard,
118 Err(poisoned) => {
119 eprintln!("⚠ Recovering from poisoned parameters lock");
120 poisoned.into_inner()
121 }
122 };
123
124 *params = new_params;
125 Ok(())
126 }
127
128 fn current_value(&self, id: &str, default: f32) -> f32 {
129 self.values
130 .read()
131 .ok()
132 .and_then(|values| values.get(id).copied())
133 .unwrap_or(default)
134 }
135
136 fn materialize_parameter(&self, param: &ParameterInfo) -> ParameterInfo {
137 ParameterInfo {
138 id: param.id.clone(),
139 name: param.name.clone(),
140 param_type: param.param_type,
141 value: self.current_value(¶m.id, param.default),
142 default: param.default,
143 min: param.min,
144 max: param.max,
145 unit: param.unit.clone(),
146 group: param.group.clone(),
147 variants: param.variants.clone(),
148 }
149 }
150}
151
152impl ParameterHost for InMemoryParameterHost {
153 fn get_parameter(&self, id: &str) -> Option<ParameterInfo> {
154 let parameters = self.parameters.read().ok()?;
155 let param = parameters.iter().find(|p| p.id == id)?;
156
157 Some(self.materialize_parameter(param))
158 }
159
160 fn set_parameter(&self, id: &str, value: f32) -> Result<(), BridgeError> {
161 let parameters = self.parameters.read().ok();
162 let param_exists = parameters
163 .as_ref()
164 .map(|p| p.iter().any(|param| param.id == id))
165 .unwrap_or(false);
166
167 if !param_exists {
168 return Err(BridgeError::ParameterNotFound(id.to_string()));
169 }
170
171 let Some(param) = parameters
172 .as_ref()
173 .and_then(|p| p.iter().find(|param| param.id == id))
174 else {
175 return Err(BridgeError::ParameterNotFound(id.to_string()));
176 };
177
178 if !(param.min..=param.max).contains(&value) {
179 return Err(BridgeError::ParameterOutOfRange {
180 id: id.to_string(),
181 value,
182 });
183 }
184
185 if let Ok(mut values) = self.values.write() {
186 values.insert(id.to_string(), value);
187 }
188
189 Ok(())
190 }
191
192 fn get_all_parameters(&self) -> Vec<ParameterInfo> {
193 let parameters = match self.parameters.read() {
194 Ok(guard) => guard,
195 Err(_) => return Vec::new(), };
197
198 parameters
199 .iter()
200 .map(|param| self.materialize_parameter(param))
201 .collect()
202 }
203
204 fn get_meter_frame(&self) -> Option<MeterFrame> {
205 self.meter_provider
206 .as_ref()
207 .and_then(|provider| provider.get_meter_frame())
208 }
209
210 fn get_oscilloscope_frame(&self) -> Option<OscilloscopeFrame> {
211 self.oscilloscope_provider
212 .as_ref()
213 .and_then(|provider| provider.get_oscilloscope_frame())
214 }
215
216 fn request_resize(&self, _width: u32, _height: u32) -> bool {
217 false
218 }
219
220 fn get_audio_status(&self) -> Option<AudioRuntimeStatus> {
221 None
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 use wavecraft_protocol::ParameterType;
229
230 struct StaticMeterProvider {
231 frame: MeterFrame,
232 }
233
234 struct StaticOscilloscopeProvider {
235 frame: OscilloscopeFrame,
236 }
237
238 impl MeterProvider for StaticMeterProvider {
239 fn get_meter_frame(&self) -> Option<MeterFrame> {
240 Some(self.frame)
241 }
242 }
243
244 impl OscilloscopeProvider for StaticOscilloscopeProvider {
245 fn get_oscilloscope_frame(&self) -> Option<OscilloscopeFrame> {
246 Some(self.frame.clone())
247 }
248 }
249
250 fn test_params() -> Vec<ParameterInfo> {
251 vec![
252 ParameterInfo {
253 id: "gain".to_string(),
254 name: "Gain".to_string(),
255 param_type: ParameterType::Float,
256 value: 0.5,
257 default: 0.5,
258 min: 0.0,
259 max: 1.0,
260 unit: Some("dB".to_string()),
261 group: Some("Input".to_string()),
262 variants: None,
263 },
264 ParameterInfo {
265 id: "mix".to_string(),
266 name: "Mix".to_string(),
267 param_type: ParameterType::Float,
268 value: 1.0,
269 default: 1.0,
270 min: 0.0,
271 max: 1.0,
272 unit: Some("%".to_string()),
273 group: None,
274 variants: None,
275 },
276 ]
277 }
278
279 #[test]
280 fn test_get_parameter() {
281 let host = InMemoryParameterHost::new(test_params());
282
283 let param = host.get_parameter("gain").expect("should find gain");
284 assert_eq!(param.id, "gain");
285 assert_eq!(param.name, "Gain");
286 assert!((param.value - 0.5).abs() < f32::EPSILON);
287 }
288
289 #[test]
290 fn test_set_parameter() {
291 let host = InMemoryParameterHost::new(test_params());
292
293 host.set_parameter("gain", 0.75).expect("should set gain");
294
295 let param = host.get_parameter("gain").expect("should find gain");
296 assert!((param.value - 0.75).abs() < f32::EPSILON);
297 }
298
299 #[test]
300 fn test_set_parameter_out_of_range() {
301 let host = InMemoryParameterHost::new(test_params());
302
303 let result = host.set_parameter("gain", 1.5);
304 assert!(result.is_err());
305
306 let result = host.set_parameter("gain", -0.1);
307 assert!(result.is_err());
308 }
309
310 #[test]
311 fn test_get_all_parameters() {
312 let host = InMemoryParameterHost::new(test_params());
313
314 let params = host.get_all_parameters();
315 assert_eq!(params.len(), 2);
316 assert!(params.iter().any(|p| p.id == "gain"));
317 assert!(params.iter().any(|p| p.id == "mix"));
318 }
319
320 #[test]
321 fn test_get_meter_frame() {
322 let frame = MeterFrame {
323 peak_l: 0.7,
324 rms_l: 0.5,
325 peak_r: 0.6,
326 rms_r: 0.4,
327 timestamp: 0,
328 };
329 let provider = Arc::new(StaticMeterProvider { frame });
330 let host = InMemoryParameterHost::with_meter_provider(test_params(), provider);
331
332 let read = host.get_meter_frame().expect("should have meter frame");
333 assert!((read.peak_l - 0.7).abs() < f32::EPSILON);
334 assert!((read.rms_r - 0.4).abs() < f32::EPSILON);
335 }
336
337 #[test]
338 fn test_get_oscilloscope_frame() {
339 let frame = OscilloscopeFrame {
340 points_l: vec![0.1; 1024],
341 points_r: vec![0.2; 1024],
342 sample_rate: 48_000.0,
343 timestamp: 99,
344 no_signal: false,
345 trigger_mode: wavecraft_protocol::OscilloscopeTriggerMode::RisingZeroCrossing,
346 };
347 let provider = Arc::new(StaticOscilloscopeProvider { frame });
348 let host = InMemoryParameterHost::with_oscilloscope_provider(test_params(), provider);
349
350 let read = host
351 .get_oscilloscope_frame()
352 .expect("should have oscilloscope frame");
353 assert_eq!(read.points_l.len(), 1024);
354 assert_eq!(read.points_r.len(), 1024);
355 assert_eq!(read.timestamp, 99);
356 }
357
358 #[test]
359 fn test_replace_parameters_preserves_values() {
360 let host = InMemoryParameterHost::new(test_params());
361
362 host.set_parameter("gain", 0.75).expect("should set gain");
364 host.set_parameter("mix", 0.5).expect("should set mix");
365
366 let new_params = vec![
368 ParameterInfo {
369 id: "gain".to_string(),
370 name: "Gain".to_string(),
371 param_type: ParameterType::Float,
372 value: 0.5,
373 default: 0.5,
374 min: 0.0,
375 max: 1.0,
376 unit: Some("dB".to_string()),
377 group: Some("Input".to_string()),
378 variants: None,
379 },
380 ParameterInfo {
381 id: "mix".to_string(),
382 name: "Mix".to_string(),
383 param_type: ParameterType::Float,
384 value: 1.0,
385 default: 1.0,
386 min: 0.0,
387 max: 1.0,
388 unit: Some("%".to_string()),
389 group: None,
390 variants: None,
391 },
392 ParameterInfo {
393 id: "freq".to_string(),
394 name: "Frequency".to_string(),
395 param_type: ParameterType::Float,
396 value: 440.0,
397 default: 440.0,
398 min: 20.0,
399 max: 20_000.0,
400 unit: Some("Hz".to_string()),
401 group: None,
402 variants: None,
403 },
404 ];
405
406 host.replace_parameters(new_params)
407 .expect("should replace parameters");
408
409 let gain = host.get_parameter("gain").expect("should find gain");
411 assert!((gain.value - 0.75).abs() < f32::EPSILON);
412
413 let mix = host.get_parameter("mix").expect("should find mix");
414 assert!((mix.value - 0.5).abs() < f32::EPSILON);
415
416 let freq = host.get_parameter("freq").expect("should find freq");
418 assert!((freq.value - 440.0).abs() < f32::EPSILON);
419 }
420
421 #[test]
422 fn test_replace_parameters_removes_old() {
423 let host = InMemoryParameterHost::new(test_params());
424
425 let new_params = vec![ParameterInfo {
427 id: "gain".to_string(),
428 name: "Gain".to_string(),
429 param_type: ParameterType::Float,
430 value: 0.5,
431 default: 0.5,
432 min: 0.0,
433 max: 1.0,
434 unit: Some("dB".to_string()),
435 group: Some("Input".to_string()),
436 variants: None,
437 }];
438
439 host.replace_parameters(new_params)
440 .expect("should replace parameters");
441
442 assert!(host.get_parameter("mix").is_none());
444
445 assert!(host.get_parameter("gain").is_some());
447 }
448
449 #[test]
450 fn test_set_parameter_uses_declared_range_not_normalized_range() {
451 let host = InMemoryParameterHost::new(vec![ParameterInfo {
452 id: "test_tone_frequency".to_string(),
453 name: "Frequency".to_string(),
454 param_type: ParameterType::Float,
455 value: 440.0,
456 default: 440.0,
457 min: 20.0,
458 max: 20_000.0,
459 unit: Some("Hz".to_string()),
460 group: Some("Test Tone".to_string()),
461 variants: None,
462 }]);
463
464 host.set_parameter("test_tone_frequency", 2_000.0)
465 .expect("frequency in declared range should be accepted");
466
467 let freq = host
468 .get_parameter("test_tone_frequency")
469 .expect("frequency should exist");
470 assert!((freq.value - 2_000.0).abs() < f32::EPSILON);
471
472 let too_low = host.set_parameter("test_tone_frequency", 10.0);
473 assert!(too_low.is_err(), "value below min should be rejected");
474
475 let too_high = host.set_parameter("test_tone_frequency", 30_000.0);
476 assert!(too_high.is_err(), "value above max should be rejected");
477 }
478}