//! Gain adjustment: apply a dB offset with clipping protection. /// Apply a gain offset in dB to all samples, clamping to [-1.0, 1.0]. pub fn apply_gain(samples: &mut [f32], db: f64) { if samples.is_empty() || db == 0.0 || !db.is_finite() { return; } let scale = 10.0_f64.powf(db / 20.0) as f32; for sample in samples.iter_mut() { *sample = (*sample * scale).clamp(-1.0, 1.0); } } #[cfg(test)] mod tests { use super::*; #[test] fn gain_plus_6db() { let mut samples = vec![0.25, -0.25, 0.1]; apply_gain(&mut samples, 6.0); // +6 dB ≈ 2x let scale = 10.0_f64.powf(6.0 / 20.0) as f32; assert!((samples[0] - 0.25 * scale).abs() < 0.001); assert!((samples[1] - (-0.25 * scale)).abs() < 0.001); } #[test] fn gain_minus_6db() { let mut samples = vec![1.0, -1.0, 0.5]; apply_gain(&mut samples, -6.0); let scale = 10.0_f64.powf(-6.0 / 20.0) as f32; assert!((samples[0] - scale).abs() < 0.001); } #[test] fn gain_zero_is_noop() { let original = vec![0.5, -0.5, 0.25]; let mut samples = original.clone(); apply_gain(&mut samples, 0.0); assert_eq!(samples, original); } #[test] fn gain_clips_to_bounds() { let mut samples = vec![0.5, -0.5]; apply_gain(&mut samples, 24.0); // +24 dB ≈ 15.85x, will clip assert_eq!(samples[0], 1.0); assert_eq!(samples[1], -1.0); } #[test] fn gain_empty() { let mut samples: Vec = vec![]; apply_gain(&mut samples, 6.0); assert!(samples.is_empty()); } }