use crate::{
audio::AudioBuffer,
error::Result,
plugins::{AudioEffect, ParameterDefinition, ParameterType, ParameterValue, VoirsPlugin},
VoirsError,
};
use async_trait::async_trait;
use std::{collections::HashMap, sync::RwLock};
pub struct SpatialAudioEffect {
pub azimuth: RwLock<f32>,
pub elevation: RwLock<f32>,
pub distance: RwLock<f32>,
pub room_size: RwLock<f32>,
}
impl SpatialAudioEffect {
pub fn new() -> Self {
Self {
azimuth: RwLock::new(0.0),
elevation: RwLock::new(0.0),
distance: RwLock::new(1.0),
room_size: RwLock::new(0.5),
}
}
fn calculate_stereo_pan(&self) -> (f32, f32) {
let azimuth = *self.azimuth.read().expect("lock should not be poisoned");
let distance = *self.distance.read().expect("lock should not be poisoned");
let angle_rad = azimuth.to_radians();
let left =
((angle_rad + std::f32::consts::PI / 2.0) / std::f32::consts::PI).clamp(0.0, 1.0);
let right = 1.0 - left;
let attenuation = 1.0 / (1.0 + distance);
(left * attenuation, right * attenuation)
}
}
impl Default for SpatialAudioEffect {
fn default() -> Self {
Self::new()
}
}
impl VoirsPlugin for SpatialAudioEffect {
fn name(&self) -> &str {
"Spatial Audio"
}
fn version(&self) -> &str {
"1.0.0"
}
fn description(&self) -> &str {
"3D spatial audio positioning effect"
}
fn author(&self) -> &str {
"VoiRS Team"
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
#[async_trait]
impl AudioEffect for SpatialAudioEffect {
async fn process_audio(&self, audio: &AudioBuffer) -> Result<AudioBuffer> {
let (left_gain, right_gain) = self.calculate_stereo_pan();
let mut processed = audio.clone();
let samples = processed.samples_mut();
for sample in samples.iter_mut() {
let input = *sample;
*sample = input * ((left_gain + right_gain) / 2.0);
}
Ok(processed)
}
fn get_parameters(&self) -> HashMap<String, ParameterValue> {
let mut params = HashMap::new();
params.insert(
"azimuth".to_string(),
ParameterValue::Float(*self.azimuth.read().expect("lock should not be poisoned")),
);
params.insert(
"elevation".to_string(),
ParameterValue::Float(*self.elevation.read().expect("lock should not be poisoned")),
);
params.insert(
"distance".to_string(),
ParameterValue::Float(*self.distance.read().expect("lock should not be poisoned")),
);
params.insert(
"room_size".to_string(),
ParameterValue::Float(*self.room_size.read().expect("lock should not be poisoned")),
);
params
}
fn set_parameter(&self, name: &str, value: ParameterValue) -> Result<()> {
match name {
"azimuth" => {
if let Some(v) = value.as_f32() {
*self.azimuth.write().expect("lock should not be poisoned") =
v.clamp(-180.0, 180.0);
Ok(())
} else {
Err(VoirsError::internal(
"plugins",
"Invalid azimuth parameter type",
))
}
}
"elevation" => {
if let Some(v) = value.as_f32() {
*self.elevation.write().expect("lock should not be poisoned") =
v.clamp(-90.0, 90.0);
Ok(())
} else {
Err(VoirsError::internal(
"plugins",
"Invalid elevation parameter type",
))
}
}
"distance" => {
if let Some(v) = value.as_f32() {
*self.distance.write().expect("lock should not be poisoned") =
v.clamp(0.1, 100.0);
Ok(())
} else {
Err(VoirsError::internal(
"plugins",
"Invalid distance parameter type",
))
}
}
"room_size" => {
if let Some(v) = value.as_f32() {
*self.room_size.write().expect("lock should not be poisoned") =
v.clamp(0.0, 1.0);
Ok(())
} else {
Err(VoirsError::internal(
"plugins",
"Invalid room_size parameter type",
))
}
}
_ => Err(VoirsError::internal(
"plugins",
format!("Unknown parameter: {name}"),
)),
}
}
fn get_parameter_definition(&self, name: &str) -> Option<ParameterDefinition> {
match name {
"azimuth" => Some(ParameterDefinition {
name: "azimuth".to_string(),
description: "Horizontal angle in degrees".to_string(),
parameter_type: ParameterType::Float,
default_value: ParameterValue::Float(0.0),
min_value: Some(ParameterValue::Float(-180.0)),
max_value: Some(ParameterValue::Float(180.0)),
step_size: Some(1.0),
realtime_safe: true,
}),
"elevation" => Some(ParameterDefinition {
name: "elevation".to_string(),
description: "Vertical angle in degrees".to_string(),
parameter_type: ParameterType::Float,
default_value: ParameterValue::Float(0.0),
min_value: Some(ParameterValue::Float(-90.0)),
max_value: Some(ParameterValue::Float(90.0)),
step_size: Some(1.0),
realtime_safe: true,
}),
"distance" => Some(ParameterDefinition {
name: "distance".to_string(),
description: "Distance in meters".to_string(),
parameter_type: ParameterType::Float,
default_value: ParameterValue::Float(1.0),
min_value: Some(ParameterValue::Float(0.1)),
max_value: Some(ParameterValue::Float(100.0)),
step_size: Some(0.1),
realtime_safe: true,
}),
"room_size" => Some(ParameterDefinition {
name: "room_size".to_string(),
description: "Virtual room size".to_string(),
parameter_type: ParameterType::Float,
default_value: ParameterValue::Float(0.5),
min_value: Some(ParameterValue::Float(0.0)),
max_value: Some(ParameterValue::Float(1.0)),
step_size: Some(0.01),
realtime_safe: false,
}),
_ => None,
}
}
}