use super::registry::{CloneableEffect, EffectRegistry};
use crate::engine::audio::lfo::{LfoParams, LfoRate, LfoTarget, LfoWaveform};
use crate::language::syntax::ast::Value;
use std::collections::HashMap;
#[derive(Debug)]
pub struct EffectChain {
effects: Vec<Box<dyn CloneableEffect>>,
registry: EffectRegistry,
synth_context: bool,
}
impl EffectChain {
pub fn new(synth_context: bool) -> Self {
Self {
effects: Vec::new(),
registry: EffectRegistry::new(),
synth_context,
}
}
pub fn add_effect(&mut self, name: &str, params: Option<Value>) -> bool {
if !self.registry.is_effect_available(name, self.synth_context) {
return false;
}
if let Some(processor) =
build_effect_processor(&self.registry, name, params, self.synth_context)
{
self.effects.push(processor);
true
} else {
false
}
}
pub fn process(&mut self, samples: &mut [f32], sample_rate: u32) {
for effect in &mut self.effects {
effect.process(samples, sample_rate);
}
}
pub fn reset(&mut self) {
for effect in &mut self.effects {
effect.reset();
}
}
pub fn len(&self) -> usize {
self.effects.len()
}
pub fn is_empty(&self) -> bool {
self.effects.is_empty()
}
pub fn available_effects(&self) -> Vec<&'static str> {
self.registry.list_available_effects(self.synth_context)
}
pub fn is_effect_available(&self, name: &str) -> bool {
self.registry.is_effect_available(name, self.synth_context)
}
}
impl Default for EffectChain {
fn default() -> Self {
Self::new(true) }
}
pub fn build_effect_chain(effects_array: &[Value], synth_context: bool) -> EffectChain {
let mut chain = EffectChain::new(synth_context);
for effect_value in effects_array {
match effect_value {
Value::Map(map) => {
if let Some(effect_type) = map.get("type").or_else(|| map.get("effect")) {
if let Value::String(s) | Value::Identifier(s) = effect_type {
chain.add_effect(s, Some(Value::Map(map.clone())));
}
} else {
for (k, v) in map.iter() {
match v {
Value::Map(sm) => {
chain.add_effect(k, Some(Value::Map(sm.clone())));
}
_ => {
let mut sub = std::collections::HashMap::new();
sub.insert("value".to_string(), v.clone());
chain.add_effect(k, Some(Value::Map(sub.clone())));
}
}
}
}
}
Value::String(s) | Value::Identifier(s) => {
chain.add_effect(s, None);
}
_ => {}
}
}
chain
}
fn build_effect_processor(
registry: &EffectRegistry,
name: &str,
params: Option<Value>,
synth_context: bool,
) -> Option<Box<dyn CloneableEffect>> {
let base_processor = registry.get_effect(name, synth_context)?;
if let Some(Value::Map(params_map)) = params {
match name {
"chorus" => {
let depth = get_f32_param(¶ms_map, "depth", 0.7);
let rate = get_f32_param(¶ms_map, "rate", 0.5);
let mix = get_f32_param(¶ms_map, "mix", 0.5);
Some(Box::new(super::processors::ChorusProcessor::new(
depth, rate, mix,
)))
}
"flanger" => {
let depth = get_f32_param(¶ms_map, "depth", 0.7);
let rate = get_f32_param(¶ms_map, "rate", 0.5);
let feedback = get_f32_param(¶ms_map, "feedback", 0.5);
let mix = get_f32_param(¶ms_map, "mix", 0.5);
Some(Box::new(super::processors::FlangerProcessor::new(
depth, rate, feedback, mix,
)))
}
"phaser" => {
let stages = get_f32_param(¶ms_map, "stages", 4.0) as usize;
let rate = get_f32_param(¶ms_map, "rate", 0.5);
let depth = get_f32_param(¶ms_map, "depth", 0.7);
let feedback = get_f32_param(¶ms_map, "feedback", 0.5);
let mix = get_f32_param(¶ms_map, "mix", 0.5);
Some(Box::new(super::processors::PhaserProcessor::new(
stages, rate, depth, feedback, mix,
)))
}
"compressor" => {
let threshold = get_f32_param(¶ms_map, "threshold", -20.0);
let ratio = get_f32_param(¶ms_map, "ratio", 4.0);
let attack = get_f32_param(¶ms_map, "attack", 0.005);
let release = get_f32_param(¶ms_map, "release", 0.1);
Some(Box::new(super::processors::CompressorProcessor::new(
threshold, ratio, attack, release,
)))
}
"drive" => {
let amount = get_f32_param(¶ms_map, "amount", 0.7);
let mix = get_f32_param(¶ms_map, "mix", 0.5);
let tone = get_f32_param(¶ms_map, "tone", 0.5);
let color = get_f32_param(¶ms_map, "color", 0.5);
Some(Box::new(super::processors::DriveProcessor::new(
amount, tone, color, mix,
)))
}
"reverb" => {
let size = get_f32_param(¶ms_map, "size", 0.5);
let damping = get_f32_param(¶ms_map, "damping", 0.5);
let decay = get_f32_param(¶ms_map, "decay", 0.5);
let mix = get_f32_param(¶ms_map, "mix", 0.3);
Some(Box::new(super::processors::ReverbProcessor::new(
size, damping, decay, mix,
)))
}
"delay" => {
let time = get_f32_param(¶ms_map, "time", 250.0);
let feedback = get_f32_param(¶ms_map, "feedback", 0.4);
let mix = get_f32_param(¶ms_map, "mix", 0.3);
Some(Box::new(super::processors::DelayProcessor::new(
time, feedback, mix,
)))
}
"eq" | "equalizer" => {
let low = get_f32_param(¶ms_map, "low", 1.0);
let mid = get_f32_param(¶ms_map, "mid", 1.0);
let high = get_f32_param(¶ms_map, "high", 1.0);
Some(Box::new(super::processors::EqProcessor::new(
low, mid, high,
)))
}
"lowpass" | "lpf" => {
let cutoff = get_f32_param(¶ms_map, "cutoff", 5000.0);
let resonance = get_f32_param(¶ms_map, "resonance", 0.1);
Some(Box::new(super::processors::LowpassProcessor::new(
cutoff, resonance,
)))
}
"highpass" | "hpf" => {
let cutoff = get_f32_param(¶ms_map, "cutoff", 200.0);
let resonance = get_f32_param(¶ms_map, "resonance", 0.1);
Some(Box::new(super::processors::HighpassProcessor::new(
cutoff, resonance,
)))
}
"bandpass" | "bpf" => {
let cutoff = get_f32_param(¶ms_map, "cutoff", 1000.0);
let resonance = get_f32_param(¶ms_map, "resonance", 0.2);
Some(Box::new(super::processors::BandpassProcessor::new(
cutoff, resonance,
)))
}
"distortion" | "dist" => {
let amount = get_f32_param(¶ms_map, "amount", 0.5);
let mix = get_f32_param(¶ms_map, "mix", 0.5);
Some(Box::new(super::processors::DistortionProcessor::new(
amount, mix,
)))
}
"speed" => {
let speed = get_f32_param(
¶ms_map,
"speed",
get_f32_param(¶ms_map, "value", 1.0),
);
Some(Box::new(super::processors::SpeedProcessor::new(speed)))
}
"lfo" => {
let bpm = get_f32_param(¶ms_map, "bpm", 120.0);
let depth = get_f32_param(¶ms_map, "depth", 0.5).clamp(0.0, 1.0);
let phase = get_f32_param(¶ms_map, "phase", 0.0).fract();
let waveform = params_map
.get("waveform")
.and_then(|v| match v {
Value::String(s) | Value::Identifier(s) => Some(LfoWaveform::from_str(s)),
_ => None,
})
.unwrap_or(LfoWaveform::Sine);
let rate_value_opt = params_map.get("rate");
let rate = match rate_value_opt {
Some(Value::String(s)) | Some(Value::Identifier(s)) => LfoRate::from_value(s),
Some(Value::Number(n)) => LfoRate::Hz(*n),
_ => LfoRate::Hz(1.0),
};
let target = params_map
.get("target")
.and_then(|v| match v {
Value::String(s) | Value::Identifier(s) => LfoTarget::from_str(s),
_ => None,
})
.unwrap_or(LfoTarget::Volume);
let params = LfoParams {
rate,
depth,
waveform,
target,
phase,
};
let semitones = get_f32_param(¶ms_map, "semitones", 2.0);
let base_cutoff = get_f32_param(¶ms_map, "cutoff", 1000.0);
let cutoff_range = get_f32_param(¶ms_map, "cutoff_range", 1000.0);
Some(Box::new(super::processors::LfoProcessor::new(
params,
bpm,
semitones,
base_cutoff,
cutoff_range,
)))
}
"reverse" => {
let reverse = get_bool_param(
¶ms_map,
"reverse",
get_bool_param(¶ms_map, "value", true),
);
Some(Box::new(super::processors::ReverseProcessor::new(reverse)))
}
_ => Some(base_processor),
}
} else {
Some(base_processor)
}
}
fn get_f32_param(map: &HashMap<String, Value>, key: &str, default: f32) -> f32 {
map.get(key)
.and_then(|v| match v {
Value::Number(n) => Some(*n),
Value::String(s) => s.parse::<f32>().ok(),
_ => None,
})
.unwrap_or(default)
}
fn get_bool_param(map: &HashMap<String, Value>, key: &str, default: bool) -> bool {
map.get(key)
.and_then(|v| match v {
Value::Boolean(b) => Some(*b),
Value::Number(n) => Some(*n != 0.0),
Value::String(s) => s.parse::<bool>().ok(),
_ => None,
})
.unwrap_or(default)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_effect_chain_creation() {
let chain = EffectChain::new(true); assert_eq!(chain.len(), 0);
assert!(chain.is_empty());
let synth_effects = chain.available_effects();
assert!(synth_effects.contains(&"reverb"));
assert!(!synth_effects.contains(&"speed")); }
#[test]
fn test_effect_chain_trigger_context() {
let mut chain = EffectChain::new(false);
assert!(chain.is_effect_available("speed"));
assert!(chain.is_effect_available("reverse"));
assert!(chain.add_effect("speed", None));
assert_eq!(chain.len(), 1);
}
#[test]
fn test_effect_parameter_parsing() {
let mut chain = EffectChain::new(true);
let mut params = HashMap::new();
params.insert("depth".to_string(), Value::Number(0.8));
params.insert("rate".to_string(), Value::Number(1.0));
params.insert("mix".to_string(), Value::Number(0.6));
assert!(chain.add_effect("chorus", Some(Value::Map(params))));
assert_eq!(chain.len(), 1);
}
#[test]
fn test_filter_effects_accept_parameter_maps() {
let mut params = HashMap::new();
params.insert("cutoff".to_string(), Value::Number(900.0));
params.insert("resonance".to_string(), Value::Number(0.2));
let effects = vec![Value::Map({
let mut effect = HashMap::new();
effect.insert("type".to_string(), Value::String("lowpass".to_string()));
effect.extend(params);
effect
})];
let chain = build_effect_chain(&effects, true);
assert_eq!(chain.len(), 1);
}
#[test]
fn test_eq_effect_accepts_three_band_map() {
let effects = vec![Value::Map({
let mut effect = HashMap::new();
effect.insert("type".to_string(), Value::String("eq".to_string()));
effect.insert("low".to_string(), Value::Number(1.2));
effect.insert("mid".to_string(), Value::Number(0.8));
effect.insert("high".to_string(), Value::Number(1.1));
effect
})];
let chain = build_effect_chain(&effects, true);
assert_eq!(chain.len(), 1);
}
#[test]
fn test_effect_context_restrictions() {
let mut synth_chain = EffectChain::new(true);
let mut trigger_chain = EffectChain::new(false);
assert!(!synth_chain.add_effect("speed", None));
assert!(trigger_chain.add_effect("speed", None));
assert!(synth_chain.add_effect("reverb", None));
assert!(trigger_chain.add_effect("reverb", None));
}
}