proteus_lib/dsp/effects/
gain.rs1use serde::{Deserialize, Serialize};
4
5use super::level::deserialize_linear_gain;
6use super::EffectContext;
7
8const DEFAULT_GAIN: f32 = 1.0;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12#[serde(default)]
13pub struct GainSettings {
14 #[serde(deserialize_with = "deserialize_linear_gain")]
15 pub gain: f32,
16}
17
18impl GainSettings {
19 pub fn new(gain: f32) -> Self {
21 Self { gain }
22 }
23}
24
25impl Default for GainSettings {
26 fn default() -> Self {
27 Self { gain: DEFAULT_GAIN }
28 }
29}
30
31#[derive(Clone, Serialize, Deserialize)]
33#[serde(default)]
34pub struct GainEffect {
35 pub enabled: bool,
36 #[serde(flatten)]
37 pub settings: GainSettings,
38}
39
40impl std::fmt::Debug for GainEffect {
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 f.debug_struct("GainEffect")
43 .field("enabled", &self.enabled)
44 .field("settings", &self.settings)
45 .finish()
46 }
47}
48
49impl Default for GainEffect {
50 fn default() -> Self {
51 Self {
52 enabled: false,
53 settings: GainSettings::default(),
54 }
55 }
56}
57
58impl GainEffect {
59 pub fn process(&mut self, samples: &[f32], _context: &EffectContext, _drain: bool) -> Vec<f32> {
69 if !self.enabled {
70 return samples.to_vec();
71 }
72
73 let gain = sanitize_gain(self.settings.gain);
74 if samples.is_empty() {
75 return Vec::new();
76 }
77
78 let mut out = Vec::with_capacity(samples.len());
79 for &sample in samples {
80 out.push(sample * gain);
81 }
82
83 out
84 }
85
86 pub fn reset_state(&mut self) {}
88}
89
90#[cfg(test)]
91mod tests {
92 use super::super::level::db_to_linear;
93 use super::*;
94
95 fn context() -> EffectContext {
96 EffectContext {
97 sample_rate: 44_100,
98 channels: 1,
99 container_path: None,
100 impulse_response_spec: None,
101 impulse_response_tail_db: -60.0,
102 }
103 }
104
105 #[test]
106 fn gain_disabled_passthrough() {
107 let mut effect = GainEffect::default();
108 let samples = vec![0.25_f32, -0.25, 0.5, -0.5];
109 let output = effect.process(&samples, &context(), false);
110 assert_eq!(output, samples);
111 }
112
113 #[test]
114 fn gain_scales_samples() {
115 let mut effect = GainEffect::default();
116 effect.enabled = true;
117 effect.settings.gain = 2.0;
118 let samples = vec![0.25_f32, -0.25, 0.5, -0.5];
119 let output = effect.process(&samples, &context(), false);
120 assert_eq!(output, vec![0.5_f32, -0.5, 1.0, -1.0]);
121 }
122
123 #[test]
124 fn gain_deserializes_db_strings() {
125 let json = r#"{"enabled":true,"gain":"6db"}"#;
126 let effect: GainEffect = serde_json::from_str(json).expect("deserialize gain");
127 let expected = db_to_linear(6.0);
128 assert!((effect.settings.gain - expected).abs() < 1e-6);
129 }
130
131 #[test]
132 fn gain_deserializes_negative_db_strings() {
133 let json = r#"{"enabled":true,"gain":"-2db"}"#;
134 let effect: GainEffect = serde_json::from_str(json).expect("deserialize gain");
135 let expected = db_to_linear(-2.0);
136 assert!((effect.settings.gain - expected).abs() < 1e-6);
137 }
138}
139
140fn sanitize_gain(gain: f32) -> f32 {
141 if gain.is_finite() {
142 gain
143 } else {
144 DEFAULT_GAIN
145 }
146}