1#[cfg(feature = "simd")]
4use bbx_core::simd::apply_gain;
5
6use crate::{
7 block::{Block, DEFAULT_EFFECTOR_INPUT_COUNT, DEFAULT_EFFECTOR_OUTPUT_COUNT},
8 context::DspContext,
9 parameter::{ModulationOutput, Parameter},
10 sample::Sample,
11 smoothing::LinearSmoothedValue,
12};
13
14const MAX_BUFFER_SIZE: usize = 4096;
16
17pub struct GainBlock<S: Sample> {
21 pub level_db: Parameter<S>,
23
24 pub base_gain: S,
26
27 gain_smoother: LinearSmoothedValue<S>,
29}
30
31impl<S: Sample> GainBlock<S> {
32 const MIN_DB: f64 = -80.0;
34 const MAX_DB: f64 = 30.0;
36
37 pub fn new(level_db: f64, base_gain: Option<f64>) -> Self {
39 let clamped_db = level_db.clamp(Self::MIN_DB, Self::MAX_DB);
40 let initial_gain = Self::db_to_linear(clamped_db);
41
42 Self {
43 level_db: Parameter::Constant(S::from_f64(level_db)),
44 base_gain: S::from_f64(base_gain.unwrap_or(1.0)),
45 gain_smoother: LinearSmoothedValue::new(S::from_f64(initial_gain)),
46 }
47 }
48
49 pub fn unity() -> Self {
51 Self::new(0.0, None)
52 }
53
54 #[inline]
56 fn db_to_linear(db: f64) -> f64 {
57 let clamped = db.clamp(Self::MIN_DB, Self::MAX_DB);
58 10.0_f64.powf(clamped / 20.0)
59 }
60}
61
62impl<S: Sample> Block<S> for GainBlock<S> {
63 fn process(&mut self, inputs: &[&[S]], outputs: &mut [&mut [S]], modulation_values: &[S], context: &DspContext) {
64 let level_db = self.level_db.get_value(modulation_values).to_f64();
65 let target_gain = S::from_f64(Self::db_to_linear(level_db));
66
67 let current_target = self.gain_smoother.target();
68 if (target_gain - current_target).abs() > S::EPSILON {
69 self.gain_smoother.set_target_value(target_gain);
70 }
71
72 let num_channels = inputs.len().min(outputs.len());
73
74 if !self.gain_smoother.is_smoothing() {
75 let gain = self.gain_smoother.current() * self.base_gain;
76
77 #[cfg(feature = "simd")]
78 {
79 for ch in 0..num_channels {
80 let len = inputs[ch].len().min(outputs[ch].len());
81 apply_gain(&inputs[ch][..len], &mut outputs[ch][..len], gain);
82 }
83 return;
84 }
85
86 #[cfg(not(feature = "simd"))]
87 {
88 for ch in 0..num_channels {
89 let len = inputs[ch].len().min(outputs[ch].len());
90 for i in 0..len {
91 outputs[ch][i] = inputs[ch][i] * gain;
92 }
93 }
94 return;
95 }
96 }
97
98 let len = inputs.first().map_or(0, |ch| ch.len().min(context.buffer_size));
99 debug_assert!(len <= MAX_BUFFER_SIZE, "buffer_size exceeds MAX_BUFFER_SIZE");
100
101 let mut gain_values: [S; MAX_BUFFER_SIZE] = [S::ZERO; MAX_BUFFER_SIZE];
102 for gain_value in gain_values.iter_mut().take(len) {
103 *gain_value = self.gain_smoother.get_next_value() * self.base_gain;
104 }
105
106 for ch in 0..num_channels {
107 let ch_len = inputs[ch].len().min(outputs[ch].len()).min(len);
108 for (i, &gain) in gain_values.iter().enumerate().take(ch_len) {
109 outputs[ch][i] = inputs[ch][i] * gain;
110 }
111 }
112 }
113
114 #[inline]
115 fn input_count(&self) -> usize {
116 DEFAULT_EFFECTOR_INPUT_COUNT
117 }
118
119 #[inline]
120 fn output_count(&self) -> usize {
121 DEFAULT_EFFECTOR_OUTPUT_COUNT
122 }
123
124 #[inline]
125 fn modulation_outputs(&self) -> &[ModulationOutput] {
126 &[]
127 }
128
129 fn set_smoothing(&mut self, sample_rate: f64, ramp_time_ms: f64) {
130 self.gain_smoother.reset(sample_rate, ramp_time_ms);
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use crate::channel::ChannelLayout;
138
139 fn test_context(buffer_size: usize) -> DspContext {
140 DspContext {
141 sample_rate: 44100.0,
142 num_channels: 2,
143 buffer_size,
144 current_sample: 0,
145 channel_layout: ChannelLayout::Stereo,
146 }
147 }
148
149 #[test]
150 fn test_gain_input_output_counts_f32() {
151 let gain = GainBlock::<f32>::new(0.0, None);
152 assert_eq!(gain.input_count(), DEFAULT_EFFECTOR_INPUT_COUNT);
153 assert_eq!(gain.output_count(), DEFAULT_EFFECTOR_OUTPUT_COUNT);
154 }
155
156 #[test]
157 fn test_gain_input_output_counts_f64() {
158 let gain = GainBlock::<f64>::new(0.0, None);
159 assert_eq!(gain.input_count(), DEFAULT_EFFECTOR_INPUT_COUNT);
160 assert_eq!(gain.output_count(), DEFAULT_EFFECTOR_OUTPUT_COUNT);
161 }
162
163 #[test]
164 fn test_unity_gain_passthrough_f32() {
165 let mut gain = GainBlock::<f32>::unity();
166 let context = test_context(4);
167
168 let input = [0.5f32, -0.5, 0.25, -0.25];
169 let mut output = [0.0f32; 4];
170
171 let inputs: [&[f32]; 1] = [&input];
172 let mut outputs: [&mut [f32]; 1] = [&mut output];
173
174 for _ in 0..10 {
175 gain.process(&inputs, &mut outputs, &[], &context);
176 }
177
178 for (i, (&inp, &out)) in input.iter().zip(output.iter()).enumerate() {
179 assert!(
180 (inp - out).abs() < 1e-5,
181 "Unity gain should passthrough: input[{}]={}, output[{}]={}",
182 i,
183 inp,
184 i,
185 out
186 );
187 }
188 }
189
190 #[test]
191 fn test_unity_gain_passthrough_f64() {
192 let mut gain = GainBlock::<f64>::unity();
193 let context = test_context(4);
194
195 let input = [0.5f64, -0.5, 0.25, -0.25];
196 let mut output = [0.0f64; 4];
197
198 let inputs: [&[f64]; 1] = [&input];
199 let mut outputs: [&mut [f64]; 1] = [&mut output];
200
201 for _ in 0..10 {
202 gain.process(&inputs, &mut outputs, &[], &context);
203 }
204
205 for (i, (&inp, &out)) in input.iter().zip(output.iter()).enumerate() {
206 assert!(
207 (inp - out).abs() < 1e-10,
208 "Unity gain should passthrough: input[{}]={}, output[{}]={}",
209 i,
210 inp,
211 i,
212 out
213 );
214 }
215 }
216
217 #[test]
218 fn test_silence_at_min_db_f32() {
219 let mut gain = GainBlock::<f32>::new(-80.0, None);
220 let context = test_context(4);
221
222 let input = [1.0f32; 4];
223 let mut output = [1.0f32; 4];
224
225 let inputs: [&[f32]; 1] = [&input];
226 let mut outputs: [&mut [f32]; 1] = [&mut output];
227
228 for _ in 0..10 {
229 gain.process(&inputs, &mut outputs, &[], &context);
230 }
231
232 for (i, &out) in output.iter().enumerate() {
233 assert!(
234 out.abs() < 0.001,
235 "Output should be nearly silent at -80dB: output[{}]={}",
236 i,
237 out
238 );
239 }
240 }
241
242 #[test]
243 fn test_silence_at_min_db_f64() {
244 let mut gain = GainBlock::<f64>::new(-80.0, None);
245 let context = test_context(4);
246
247 let input = [1.0f64; 4];
248 let mut output = [1.0f64; 4];
249
250 let inputs: [&[f64]; 1] = [&input];
251 let mut outputs: [&mut [f64]; 1] = [&mut output];
252
253 for _ in 0..10 {
254 gain.process(&inputs, &mut outputs, &[], &context);
255 }
256
257 for (i, &out) in output.iter().enumerate() {
258 assert!(
259 out.abs() < 0.001,
260 "Output should be nearly silent at -80dB: output[{}]={}",
261 i,
262 out
263 );
264 }
265 }
266
267 #[test]
268 fn test_amplification_at_positive_db_f32() {
269 let mut gain = GainBlock::<f32>::new(6.0, None);
270 let context = test_context(4);
271
272 let input = [0.5f32; 4];
273 let mut output = [0.0f32; 4];
274
275 let inputs: [&[f32]; 1] = [&input];
276 let mut outputs: [&mut [f32]; 1] = [&mut output];
277
278 for _ in 0..10 {
279 gain.process(&inputs, &mut outputs, &[], &context);
280 }
281
282 let expected_linear = 10.0_f32.powf(6.0 / 20.0);
283 for (i, &out) in output.iter().enumerate() {
284 let expected = 0.5 * expected_linear;
285 assert!(
286 (out - expected).abs() < 0.05,
287 "Output should be amplified at +6dB: expected={}, output[{}]={}",
288 expected,
289 i,
290 out
291 );
292 }
293 }
294
295 #[test]
296 fn test_amplification_at_positive_db_f64() {
297 let mut gain = GainBlock::<f64>::new(6.0, None);
298 let context = test_context(4);
299
300 let input = [0.5f64; 4];
301 let mut output = [0.0f64; 4];
302
303 let inputs: [&[f64]; 1] = [&input];
304 let mut outputs: [&mut [f64]; 1] = [&mut output];
305
306 for _ in 0..10 {
307 gain.process(&inputs, &mut outputs, &[], &context);
308 }
309
310 let expected_linear = 10.0_f64.powf(6.0 / 20.0);
311 for (i, &out) in output.iter().enumerate() {
312 let expected = 0.5 * expected_linear;
313 assert!(
314 (out - expected).abs() < 0.05,
315 "Output should be amplified at +6dB: expected={}, output[{}]={}",
316 expected,
317 i,
318 out
319 );
320 }
321 }
322
323 #[test]
324 fn test_attenuation_at_negative_db_f32() {
325 let mut gain = GainBlock::<f32>::new(-6.0, None);
326 let context = test_context(4);
327
328 let input = [1.0f32; 4];
329 let mut output = [0.0f32; 4];
330
331 let inputs: [&[f32]; 1] = [&input];
332 let mut outputs: [&mut [f32]; 1] = [&mut output];
333
334 for _ in 0..10 {
335 gain.process(&inputs, &mut outputs, &[], &context);
336 }
337
338 let expected_linear = 10.0_f32.powf(-6.0 / 20.0);
339 for (i, &out) in output.iter().enumerate() {
340 assert!(
341 (out - expected_linear).abs() < 0.05,
342 "Output should be attenuated at -6dB: expected={}, output[{}]={}",
343 expected_linear,
344 i,
345 out
346 );
347 }
348 }
349
350 #[test]
351 fn test_attenuation_at_negative_db_f64() {
352 let mut gain = GainBlock::<f64>::new(-6.0, None);
353 let context = test_context(4);
354
355 let input = [1.0f64; 4];
356 let mut output = [0.0f64; 4];
357
358 let inputs: [&[f64]; 1] = [&input];
359 let mut outputs: [&mut [f64]; 1] = [&mut output];
360
361 for _ in 0..10 {
362 gain.process(&inputs, &mut outputs, &[], &context);
363 }
364
365 let expected_linear = 10.0_f64.powf(-6.0 / 20.0);
366 for (i, &out) in output.iter().enumerate() {
367 assert!(
368 (out - expected_linear).abs() < 0.05,
369 "Output should be attenuated at -6dB: expected={}, output[{}]={}",
370 expected_linear,
371 i,
372 out
373 );
374 }
375 }
376
377 #[test]
378 fn test_base_gain_multiplier_f32() {
379 let mut gain = GainBlock::<f32>::new(0.0, Some(0.5));
380 let context = test_context(4);
381
382 let input = [1.0f32; 4];
383 let mut output = [0.0f32; 4];
384
385 let inputs: [&[f32]; 1] = [&input];
386 let mut outputs: [&mut [f32]; 1] = [&mut output];
387
388 for _ in 0..10 {
389 gain.process(&inputs, &mut outputs, &[], &context);
390 }
391
392 for (i, &out) in output.iter().enumerate() {
393 assert!(
394 (out - 0.5).abs() < 0.05,
395 "Base gain of 0.5 should halve output: output[{}]={}",
396 i,
397 out
398 );
399 }
400 }
401
402 #[test]
403 fn test_multichannel_processing_f32() {
404 let mut gain = GainBlock::<f32>::new(0.0, None);
405 let context = test_context(4);
406
407 let input_l = [1.0f32, 0.5, 0.25, 0.125];
408 let input_r = [0.8f32, 0.4, 0.2, 0.1];
409 let mut output_l = [0.0f32; 4];
410 let mut output_r = [0.0f32; 4];
411
412 let inputs: [&[f32]; 2] = [&input_l, &input_r];
413 let mut outputs: [&mut [f32]; 2] = [&mut output_l, &mut output_r];
414
415 for _ in 0..10 {
416 gain.process(&inputs, &mut outputs, &[], &context);
417 }
418
419 for (i, (&inp, &out)) in input_l.iter().zip(output_l.iter()).enumerate() {
420 assert!(
421 (inp - out).abs() < 0.05,
422 "Left channel passthrough: input[{}]={}, output[{}]={}",
423 i,
424 inp,
425 i,
426 out
427 );
428 }
429 for (i, (&inp, &out)) in input_r.iter().zip(output_r.iter()).enumerate() {
430 assert!(
431 (inp - out).abs() < 0.05,
432 "Right channel passthrough: input[{}]={}, output[{}]={}",
433 i,
434 inp,
435 i,
436 out
437 );
438 }
439 }
440
441 #[test]
442 fn test_db_clamping_below_min_f32() {
443 let mut gain = GainBlock::<f32>::new(-100.0, None);
444 let context = test_context(4);
445
446 let input = [1.0f32; 4];
447 let mut output = [0.0f32; 4];
448
449 let inputs: [&[f32]; 1] = [&input];
450 let mut outputs: [&mut [f32]; 1] = [&mut output];
451
452 for _ in 0..10 {
453 gain.process(&inputs, &mut outputs, &[], &context);
454 }
455
456 let expected = 10.0_f32.powf(-80.0 / 20.0);
457 for &out in &output {
458 assert!(out.abs() < expected * 2.0, "Should clamp to -80dB minimum");
459 }
460 }
461
462 #[test]
463 fn test_db_clamping_above_max_f32() {
464 let mut gain = GainBlock::<f32>::new(50.0, None);
465 let context = test_context(4);
466
467 let input = [0.1f32; 4];
468 let mut output = [0.0f32; 4];
469
470 let inputs: [&[f32]; 1] = [&input];
471 let mut outputs: [&mut [f32]; 1] = [&mut output];
472
473 for _ in 0..10 {
474 gain.process(&inputs, &mut outputs, &[], &context);
475 }
476
477 let max_gain = 10.0_f32.powf(30.0 / 20.0);
478 for &out in &output {
479 assert!(
480 out <= 0.1 * max_gain * 1.1,
481 "Should clamp to +30dB maximum, got {}",
482 out
483 );
484 }
485 }
486
487 #[test]
488 fn test_silence_input_f32() {
489 let mut gain = GainBlock::<f32>::new(20.0, None);
490 let context = test_context(4);
491
492 let input = [0.0f32; 4];
493 let mut output = [1.0f32; 4];
494
495 let inputs: [&[f32]; 1] = [&input];
496 let mut outputs: [&mut [f32]; 1] = [&mut output];
497
498 gain.process(&inputs, &mut outputs, &[], &context);
499
500 for (i, &out) in output.iter().enumerate() {
501 assert!(
502 out.abs() < 1e-10,
503 "Silence input should produce silence: output[{}]={}",
504 i,
505 out
506 );
507 }
508 }
509
510 #[test]
511 fn test_silence_input_f64() {
512 let mut gain = GainBlock::<f64>::new(20.0, None);
513 let context = test_context(4);
514
515 let input = [0.0f64; 4];
516 let mut output = [1.0f64; 4];
517
518 let inputs: [&[f64]; 1] = [&input];
519 let mut outputs: [&mut [f64]; 1] = [&mut output];
520
521 gain.process(&inputs, &mut outputs, &[], &context);
522
523 for (i, &out) in output.iter().enumerate() {
524 assert!(
525 out.abs() < 1e-15,
526 "Silence input should produce silence: output[{}]={}",
527 i,
528 out
529 );
530 }
531 }
532}