use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use crate::{BridgeError, ParameterHost};
use wavecraft_protocol::{AudioRuntimeStatus, MeterFrame, OscilloscopeFrame, ParameterInfo};
pub trait MeterProvider: Send + Sync {
fn get_meter_frame(&self) -> Option<MeterFrame>;
}
pub trait OscilloscopeProvider: Send + Sync {
fn get_oscilloscope_frame(&self) -> Option<OscilloscopeFrame>;
}
pub struct InMemoryParameterHost {
parameters: RwLock<Vec<ParameterInfo>>,
values: RwLock<HashMap<String, f32>>,
meter_provider: Option<Arc<dyn MeterProvider>>,
oscilloscope_provider: Option<Arc<dyn OscilloscopeProvider>>,
}
impl InMemoryParameterHost {
pub fn new(parameters: Vec<ParameterInfo>) -> Self {
let values = parameters
.iter()
.map(|p| (p.id.clone(), p.default))
.collect();
Self {
parameters: RwLock::new(parameters),
values: RwLock::new(values),
meter_provider: None,
oscilloscope_provider: None,
}
}
pub fn with_meter_provider(
parameters: Vec<ParameterInfo>,
meter_provider: Arc<dyn MeterProvider>,
) -> Self {
let mut host = Self::new(parameters);
host.meter_provider = Some(meter_provider);
host
}
pub fn with_oscilloscope_provider(
parameters: Vec<ParameterInfo>,
oscilloscope_provider: Arc<dyn OscilloscopeProvider>,
) -> Self {
let mut host = Self::new(parameters);
host.oscilloscope_provider = Some(oscilloscope_provider);
host
}
pub fn with_providers(
parameters: Vec<ParameterInfo>,
meter_provider: Option<Arc<dyn MeterProvider>>,
oscilloscope_provider: Option<Arc<dyn OscilloscopeProvider>>,
) -> Self {
let mut host = Self::new(parameters);
host.meter_provider = meter_provider;
host.oscilloscope_provider = oscilloscope_provider;
host
}
pub fn replace_parameters(&self, new_params: Vec<ParameterInfo>) -> Result<(), String> {
let mut values = match self.values.write() {
Ok(guard) => guard,
Err(poisoned) => {
eprintln!("⚠ Recovering from poisoned values lock");
poisoned.into_inner()
}
};
let mut new_values = HashMap::new();
for param in &new_params {
let value = values.get(¶m.id).copied().unwrap_or(param.default);
new_values.insert(param.id.clone(), value);
}
*values = new_values;
drop(values);
let mut params = match self.parameters.write() {
Ok(guard) => guard,
Err(poisoned) => {
eprintln!("⚠ Recovering from poisoned parameters lock");
poisoned.into_inner()
}
};
*params = new_params;
Ok(())
}
fn current_value(&self, id: &str, default: f32) -> f32 {
self.values
.read()
.ok()
.and_then(|values| values.get(id).copied())
.unwrap_or(default)
}
}
impl ParameterHost for InMemoryParameterHost {
fn get_parameter(&self, id: &str) -> Option<ParameterInfo> {
let parameters = self.parameters.read().ok()?;
let param = parameters.iter().find(|p| p.id == id)?;
Some(ParameterInfo {
id: param.id.clone(),
name: param.name.clone(),
param_type: param.param_type,
value: self.current_value(¶m.id, param.default),
default: param.default,
min: param.min,
max: param.max,
unit: param.unit.clone(),
group: param.group.clone(),
})
}
fn set_parameter(&self, id: &str, value: f32) -> Result<(), BridgeError> {
let parameters = self.parameters.read().ok();
let param_exists = parameters
.as_ref()
.map(|p| p.iter().any(|param| param.id == id))
.unwrap_or(false);
if !param_exists {
return Err(BridgeError::ParameterNotFound(id.to_string()));
}
let Some(param) = parameters
.as_ref()
.and_then(|p| p.iter().find(|param| param.id == id))
else {
return Err(BridgeError::ParameterNotFound(id.to_string()));
};
if !(param.min..=param.max).contains(&value) {
return Err(BridgeError::ParameterOutOfRange {
id: id.to_string(),
value,
});
}
if let Ok(mut values) = self.values.write() {
values.insert(id.to_string(), value);
}
Ok(())
}
fn get_all_parameters(&self) -> Vec<ParameterInfo> {
let parameters = match self.parameters.read() {
Ok(guard) => guard,
Err(_) => return Vec::new(), };
parameters
.iter()
.map(|param| ParameterInfo {
id: param.id.clone(),
name: param.name.clone(),
param_type: param.param_type,
value: self.current_value(¶m.id, param.default),
default: param.default,
min: param.min,
max: param.max,
unit: param.unit.clone(),
group: param.group.clone(),
})
.collect()
}
fn get_meter_frame(&self) -> Option<MeterFrame> {
self.meter_provider
.as_ref()
.and_then(|provider| provider.get_meter_frame())
}
fn get_oscilloscope_frame(&self) -> Option<OscilloscopeFrame> {
self.oscilloscope_provider
.as_ref()
.and_then(|provider| provider.get_oscilloscope_frame())
}
fn request_resize(&self, _width: u32, _height: u32) -> bool {
false
}
fn get_audio_status(&self) -> Option<AudioRuntimeStatus> {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use wavecraft_protocol::ParameterType;
struct StaticMeterProvider {
frame: MeterFrame,
}
struct StaticOscilloscopeProvider {
frame: OscilloscopeFrame,
}
impl MeterProvider for StaticMeterProvider {
fn get_meter_frame(&self) -> Option<MeterFrame> {
Some(self.frame)
}
}
impl OscilloscopeProvider for StaticOscilloscopeProvider {
fn get_oscilloscope_frame(&self) -> Option<OscilloscopeFrame> {
Some(self.frame.clone())
}
}
fn test_params() -> Vec<ParameterInfo> {
vec![
ParameterInfo {
id: "gain".to_string(),
name: "Gain".to_string(),
param_type: ParameterType::Float,
value: 0.5,
default: 0.5,
min: 0.0,
max: 1.0,
unit: Some("dB".to_string()),
group: Some("Input".to_string()),
},
ParameterInfo {
id: "mix".to_string(),
name: "Mix".to_string(),
param_type: ParameterType::Float,
value: 1.0,
default: 1.0,
min: 0.0,
max: 1.0,
unit: Some("%".to_string()),
group: None,
},
]
}
#[test]
fn test_get_parameter() {
let host = InMemoryParameterHost::new(test_params());
let param = host.get_parameter("gain").expect("should find gain");
assert_eq!(param.id, "gain");
assert_eq!(param.name, "Gain");
assert!((param.value - 0.5).abs() < f32::EPSILON);
}
#[test]
fn test_set_parameter() {
let host = InMemoryParameterHost::new(test_params());
host.set_parameter("gain", 0.75).expect("should set gain");
let param = host.get_parameter("gain").expect("should find gain");
assert!((param.value - 0.75).abs() < f32::EPSILON);
}
#[test]
fn test_set_parameter_out_of_range() {
let host = InMemoryParameterHost::new(test_params());
let result = host.set_parameter("gain", 1.5);
assert!(result.is_err());
let result = host.set_parameter("gain", -0.1);
assert!(result.is_err());
}
#[test]
fn test_get_all_parameters() {
let host = InMemoryParameterHost::new(test_params());
let params = host.get_all_parameters();
assert_eq!(params.len(), 2);
assert!(params.iter().any(|p| p.id == "gain"));
assert!(params.iter().any(|p| p.id == "mix"));
}
#[test]
fn test_get_meter_frame() {
let frame = MeterFrame {
peak_l: 0.7,
rms_l: 0.5,
peak_r: 0.6,
rms_r: 0.4,
timestamp: 0,
};
let provider = Arc::new(StaticMeterProvider { frame });
let host = InMemoryParameterHost::with_meter_provider(test_params(), provider);
let read = host.get_meter_frame().expect("should have meter frame");
assert!((read.peak_l - 0.7).abs() < f32::EPSILON);
assert!((read.rms_r - 0.4).abs() < f32::EPSILON);
}
#[test]
fn test_get_oscilloscope_frame() {
let frame = OscilloscopeFrame {
points_l: vec![0.1; 1024],
points_r: vec![0.2; 1024],
sample_rate: 48_000.0,
timestamp: 99,
no_signal: false,
trigger_mode: wavecraft_protocol::OscilloscopeTriggerMode::RisingZeroCrossing,
};
let provider = Arc::new(StaticOscilloscopeProvider { frame });
let host = InMemoryParameterHost::with_oscilloscope_provider(test_params(), provider);
let read = host
.get_oscilloscope_frame()
.expect("should have oscilloscope frame");
assert_eq!(read.points_l.len(), 1024);
assert_eq!(read.points_r.len(), 1024);
assert_eq!(read.timestamp, 99);
}
#[test]
fn test_replace_parameters_preserves_values() {
let host = InMemoryParameterHost::new(test_params());
host.set_parameter("gain", 0.75).expect("should set gain");
host.set_parameter("mix", 0.5).expect("should set mix");
let new_params = vec![
ParameterInfo {
id: "gain".to_string(),
name: "Gain".to_string(),
param_type: ParameterType::Float,
value: 0.5,
default: 0.5,
min: 0.0,
max: 1.0,
unit: Some("dB".to_string()),
group: Some("Input".to_string()),
},
ParameterInfo {
id: "mix".to_string(),
name: "Mix".to_string(),
param_type: ParameterType::Float,
value: 1.0,
default: 1.0,
min: 0.0,
max: 1.0,
unit: Some("%".to_string()),
group: None,
},
ParameterInfo {
id: "freq".to_string(),
name: "Frequency".to_string(),
param_type: ParameterType::Float,
value: 440.0,
default: 440.0,
min: 20.0,
max: 5_000.0,
unit: Some("Hz".to_string()),
group: None,
},
];
host.replace_parameters(new_params)
.expect("should replace parameters");
let gain = host.get_parameter("gain").expect("should find gain");
assert!((gain.value - 0.75).abs() < f32::EPSILON);
let mix = host.get_parameter("mix").expect("should find mix");
assert!((mix.value - 0.5).abs() < f32::EPSILON);
let freq = host.get_parameter("freq").expect("should find freq");
assert!((freq.value - 440.0).abs() < f32::EPSILON);
}
#[test]
fn test_replace_parameters_removes_old() {
let host = InMemoryParameterHost::new(test_params());
let new_params = vec![ParameterInfo {
id: "gain".to_string(),
name: "Gain".to_string(),
param_type: ParameterType::Float,
value: 0.5,
default: 0.5,
min: 0.0,
max: 1.0,
unit: Some("dB".to_string()),
group: Some("Input".to_string()),
}];
host.replace_parameters(new_params)
.expect("should replace parameters");
assert!(host.get_parameter("mix").is_none());
assert!(host.get_parameter("gain").is_some());
}
#[test]
fn test_set_parameter_uses_declared_range_not_normalized_range() {
let host = InMemoryParameterHost::new(vec![ParameterInfo {
id: "oscillator_frequency".to_string(),
name: "Frequency".to_string(),
param_type: ParameterType::Float,
value: 440.0,
default: 440.0,
min: 20.0,
max: 5_000.0,
unit: Some("Hz".to_string()),
group: Some("Oscillator".to_string()),
}]);
host.set_parameter("oscillator_frequency", 2_000.0)
.expect("frequency in declared range should be accepted");
let freq = host
.get_parameter("oscillator_frequency")
.expect("frequency should exist");
assert!((freq.value - 2_000.0).abs() < f32::EPSILON);
let too_low = host.set_parameter("oscillator_frequency", 10.0);
assert!(too_low.is_err(), "value below min should be rejected");
let too_high = host.set_parameter("oscillator_frequency", 10_000.0);
assert!(too_high.is_err(), "value above max should be rejected");
}
}