use super::filters::{AllpassFilter, CombFilter};
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 ReverbEffect {
pub mix: RwLock<f32>,
pub room_size: RwLock<f32>,
pub damping: RwLock<f32>,
pub decay_time: RwLock<f32>,
comb_filters: RwLock<Vec<CombFilter>>,
allpass_filters: RwLock<Vec<AllpassFilter>>,
sample_rate: RwLock<Option<u32>>,
}
impl ReverbEffect {
pub fn new() -> Self {
Self {
mix: RwLock::new(0.3),
room_size: RwLock::new(0.5),
damping: RwLock::new(0.5),
decay_time: RwLock::new(2.0),
comb_filters: RwLock::new(Vec::new()),
allpass_filters: RwLock::new(Vec::new()),
sample_rate: RwLock::new(None),
}
}
fn initialize_filters(&self, sample_rate: u32) {
let mut comb_filters = self
.comb_filters
.write()
.expect("lock should not be poisoned");
let mut allpass_filters = self
.allpass_filters
.write()
.expect("lock should not be poisoned");
let comb_tunings = [1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617];
let allpass_tunings = [556, 441, 341, 225];
let scale = sample_rate as f32 / 44100.0;
if comb_filters.is_empty() {
comb_filters.clear();
for &tuning in &comb_tunings {
let size = (tuning as f32 * scale) as usize;
comb_filters.push(CombFilter::new(size));
}
}
if allpass_filters.is_empty() {
allpass_filters.clear();
for &tuning in &allpass_tunings {
let size = (tuning as f32 * scale) as usize;
allpass_filters.push(AllpassFilter::new(size));
}
}
*self
.sample_rate
.write()
.expect("lock should not be poisoned") = Some(sample_rate);
}
fn update_parameters(&self) {
let room_size = *self.room_size.read().expect("lock should not be poisoned");
let damping = *self.damping.read().expect("lock should not be poisoned");
let decay = *self.decay_time.read().expect("lock should not be poisoned");
let feedback = 0.28 + (room_size * 0.7) * (decay / 10.0).min(1.0);
let mut comb_filters = self
.comb_filters
.write()
.expect("lock should not be poisoned");
for filter in comb_filters.iter_mut() {
filter.set_feedback(feedback);
filter.set_damp(damping);
}
}
}
impl Default for ReverbEffect {
fn default() -> Self {
Self::new()
}
}
impl VoirsPlugin for ReverbEffect {
fn name(&self) -> &str {
"Reverb"
}
fn version(&self) -> &str {
"1.0.0"
}
fn description(&self) -> &str {
"High-quality reverb effect for spatial audio enhancement"
}
fn author(&self) -> &str {
"VoiRS Team"
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
#[async_trait]
impl AudioEffect for ReverbEffect {
async fn process_audio(&self, audio: &AudioBuffer) -> Result<AudioBuffer> {
let current_sample_rate = *self
.sample_rate
.read()
.expect("lock should not be poisoned");
if current_sample_rate.is_none()
|| current_sample_rate.expect("value should be present") != audio.sample_rate()
{
self.initialize_filters(audio.sample_rate());
}
self.update_parameters();
let mut processed = audio.clone();
let samples = processed.samples_mut();
let mix = *self.mix.read().expect("lock should not be poisoned");
let mut comb_filters = self
.comb_filters
.write()
.expect("lock should not be poisoned");
let mut allpass_filters = self
.allpass_filters
.write()
.expect("lock should not be poisoned");
for sample in samples.iter_mut() {
let input = *sample;
let mut comb_output = 0.0;
for filter in comb_filters.iter_mut() {
comb_output += filter.process(input);
}
let mut allpass_output = comb_output;
for filter in allpass_filters.iter_mut() {
allpass_output = filter.process(allpass_output);
}
*sample = input * (1.0 - mix) + allpass_output * mix;
*sample = sample.clamp(-1.0, 1.0);
}
Ok(processed)
}
fn get_parameters(&self) -> HashMap<String, ParameterValue> {
let mut params = HashMap::new();
params.insert(
"mix".to_string(),
ParameterValue::Float(*self.mix.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.insert(
"damping".to_string(),
ParameterValue::Float(*self.damping.read().expect("lock should not be poisoned")),
);
params.insert(
"decay_time".to_string(),
ParameterValue::Float(*self.decay_time.read().expect("lock should not be poisoned")),
);
params
}
fn set_parameter(&self, name: &str, value: ParameterValue) -> Result<()> {
match name {
"mix" => {
if let Some(v) = value.as_f32() {
*self.mix.write().expect("lock should not be poisoned") = v.clamp(0.0, 1.0);
Ok(())
} else {
Err(VoirsError::internal(
"plugins",
"Invalid mix 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",
))
}
}
"damping" => {
if let Some(v) = value.as_f32() {
*self.damping.write().expect("lock should not be poisoned") = v.clamp(0.0, 1.0);
Ok(())
} else {
Err(VoirsError::internal(
"plugins",
"Invalid damping parameter type",
))
}
}
"decay_time" => {
if let Some(v) = value.as_f32() {
*self
.decay_time
.write()
.expect("lock should not be poisoned") = v.clamp(0.1, 10.0);
Ok(())
} else {
Err(VoirsError::internal(
"plugins",
"Invalid decay_time parameter type",
))
}
}
_ => Err(VoirsError::internal(
"plugins",
format!("Unknown parameter: {name}"),
)),
}
}
fn get_parameter_definition(&self, name: &str) -> Option<ParameterDefinition> {
match name {
"mix" => Some(ParameterDefinition {
name: "mix".to_string(),
description: "Wet/dry mix level".to_string(),
parameter_type: ParameterType::Float,
default_value: ParameterValue::Float(0.3),
min_value: Some(ParameterValue::Float(0.0)),
max_value: Some(ParameterValue::Float(1.0)),
step_size: Some(0.01),
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,
}),
"damping" => Some(ParameterDefinition {
name: "damping".to_string(),
description: "High frequency damping".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: true,
}),
"decay_time" => Some(ParameterDefinition {
name: "decay_time".to_string(),
description: "Reverb decay time in seconds".to_string(),
parameter_type: ParameterType::Float,
default_value: ParameterValue::Float(2.0),
min_value: Some(ParameterValue::Float(0.1)),
max_value: Some(ParameterValue::Float(10.0)),
step_size: Some(0.1),
realtime_safe: false,
}),
_ => None,
}
}
fn get_latency_samples(&self) -> usize {
512
}
}