1use std::sync::atomic::{AtomicBool, Ordering};
12use std::sync::Arc;
13
14use arc_swap::ArcSwapOption;
15
16use super::convolver::FFTConvolver;
17use super::crossfeed::Crossfeed;
18use super::dsp::NoiseShaper;
19use super::dynamic_loudness::DynamicLoudness;
20use super::eq::Equalizer;
21use super::lockfree_params::*;
22use super::loudness::PeakLimiter;
23use super::saturation::Saturation;
24use super::traits::{AudioProcessor, ProcessResult};
25pub struct EqProcessor {
31 eq: Equalizer,
33 channels: usize,
35 params: Arc<AtomicEqParams>,
37 cached_params: Arc<EqParamsSnapshot>,
38 cached_generation: u64,
39 cached: EqParamsSnapshot,
41 sample_rate: f64,
43}
44
45impl EqProcessor {
46 pub fn new(channels: usize, sample_rate: f64, params: Arc<AtomicEqParams>) -> Self {
48 let (cached_params, cached_generation) = params.load_with_generation();
49 let cached = *cached_params;
50 let mut eq = Equalizer::new(channels, sample_rate);
51 eq.set_all_bands(&cached.gains, sample_rate);
52 eq.set_enabled(cached.enabled);
53 Self {
54 eq,
55 channels,
56 params,
57 cached_params,
58 cached_generation,
59 cached,
60 sample_rate,
61 }
62 }
63
64 fn sync_params(&mut self) {
66 if let Some((current, generation)) =
67 self.params.load_if_changed_since(self.cached_generation)
68 {
69 self.cached = *current;
70 self.cached_params = current;
71 self.cached_generation = generation;
72
73 self.eq.set_all_bands(&self.cached.gains, self.sample_rate);
75 self.eq.set_enabled(self.cached.enabled);
76 }
77 }
78}
79
80impl AudioProcessor for EqProcessor {
81 fn name(&self) -> &'static str {
82 "Equalizer"
83 }
84
85 fn process(&mut self, buffer: &mut [f64], _channels: usize) -> ProcessResult {
86 self.sync_params();
87
88 if !self.cached.enabled {
89 return ProcessResult::Bypassed;
90 }
91
92 self.eq.process(buffer);
93 ProcessResult::Ok
94 }
95
96 fn reset(&mut self) {
97 self.eq.reset();
98 }
99
100 fn is_enabled(&self) -> bool {
101 self.cached.enabled
102 }
103
104 fn set_enabled(&mut self, enabled: bool) {
105 self.params.set_enabled(enabled);
106 }
107
108 fn set_sample_rate(&mut self, sample_rate: f64) {
109 self.sample_rate = sample_rate;
110 self.eq = Equalizer::new(self.channels, sample_rate);
111 self.eq.set_all_bands(&self.cached.gains, sample_rate);
112 self.eq.set_enabled(self.cached.enabled);
113 }
114}
115
116pub struct SaturationProcessor {
122 saturation: Saturation,
123 params: Arc<AtomicSaturationParams>,
124 cached_params: Arc<SaturationParamsSnapshot>,
125 cached_generation: u64,
126 cached: SaturationParamsSnapshot,
127 sample_rate: f64,
128}
129
130impl SaturationProcessor {
131 pub fn new(channels: usize, params: Arc<AtomicSaturationParams>) -> Self {
132 let (cached_params, cached_generation) = params.load_with_generation();
133 let cached = *cached_params;
134 let mut saturation = Saturation::new();
135 saturation.set_channel_count(channels);
138 saturation.set_drive(cached.drive);
139 saturation.set_threshold(cached.threshold);
140 saturation.set_mix(cached.mix);
141 saturation.set_input_gain(cached.input_gain_db);
142 saturation.set_output_gain(cached.output_gain_db);
143 saturation.set_highpass_mode(cached.highpass_mode);
144 saturation.set_highpass_cutoff(cached.highpass_cutoff);
145 saturation.set_enabled(cached.enabled);
146 saturation.set_type(super::saturation::SaturationType::from(cached.sat_type));
147 Self {
148 saturation,
149 params,
150 cached_params,
151 cached_generation,
152 cached,
153 sample_rate: 44100.0,
154 }
155 }
156
157 fn sync_params(&mut self) {
158 if let Some((current, generation)) =
159 self.params.load_if_changed_since(self.cached_generation)
160 {
161 self.cached = *current;
162 self.cached_params = current;
163 self.cached_generation = generation;
164
165 self.saturation.set_drive(self.cached.drive);
167 self.saturation.set_threshold(self.cached.threshold);
168 self.saturation.set_mix(self.cached.mix);
169 self.saturation.set_input_gain(self.cached.input_gain_db);
170 self.saturation.set_output_gain(self.cached.output_gain_db);
171 self.saturation.set_highpass_mode(self.cached.highpass_mode);
172 self.saturation
173 .set_highpass_cutoff(self.cached.highpass_cutoff);
174 self.saturation.set_enabled(self.cached.enabled);
175
176 self.saturation
178 .set_type(super::saturation::SaturationType::from(
179 self.cached.sat_type,
180 ));
181 }
182 }
183}
184
185impl AudioProcessor for SaturationProcessor {
186 fn name(&self) -> &'static str {
187 "Saturation"
188 }
189
190 fn process(&mut self, buffer: &mut [f64], channels: usize) -> ProcessResult {
191 self.sync_params();
192
193 if !self.cached.enabled {
194 return ProcessResult::Bypassed;
195 }
196
197 self.saturation.process_with_channels(buffer, channels);
198 ProcessResult::Ok
199 }
200
201 fn reset(&mut self) {
202 self.saturation.reset();
203 }
204
205 fn is_enabled(&self) -> bool {
206 self.cached.enabled
207 }
208
209 fn set_enabled(&mut self, enabled: bool) {
210 self.params.set_enabled(enabled);
211 }
212
213 fn set_sample_rate(&mut self, sample_rate: f64) {
214 self.sample_rate = sample_rate;
215 self.saturation.set_sample_rate(sample_rate);
216 }
217}
218
219pub struct CrossfeedProcessor {
225 crossfeed: Crossfeed,
226 params: Arc<AtomicCrossfeedParams>,
227 cached_params: Arc<CrossfeedParamsSnapshot>,
228 cached_generation: u64,
229 cached: CrossfeedParamsSnapshot,
230 sample_rate: f64,
231}
232
233impl CrossfeedProcessor {
234 pub fn new(sample_rate: f64, params: Arc<AtomicCrossfeedParams>) -> Self {
235 let (cached_params, cached_generation) = params.load_with_generation();
236 let cached = *cached_params;
237 let mut crossfeed = Crossfeed::new(sample_rate);
238 crossfeed.set_mix(cached.mix);
239 crossfeed.set_enabled(cached.enabled);
240 crossfeed.set_sample_rate(sample_rate, cached.cutoff_hz);
241 Self {
242 crossfeed,
243 params,
244 cached_params,
245 cached_generation,
246 cached,
247 sample_rate,
248 }
249 }
250
251 fn sync_params(&mut self) {
252 if let Some((current, generation)) =
253 self.params.load_if_changed_since(self.cached_generation)
254 {
255 self.cached = *current;
256 self.cached_params = current;
257 self.cached_generation = generation;
258 self.crossfeed.set_mix(self.cached.mix);
259 self.crossfeed.set_enabled(self.cached.enabled);
260 self.crossfeed
262 .set_sample_rate(self.sample_rate, self.cached.cutoff_hz);
263 }
264 }
265}
266
267impl AudioProcessor for CrossfeedProcessor {
268 fn name(&self) -> &'static str {
269 "Crossfeed"
270 }
271
272 fn process(&mut self, buffer: &mut [f64], channels: usize) -> ProcessResult {
273 self.sync_params();
274
275 if !self.cached.enabled {
276 return ProcessResult::Bypassed;
277 }
278
279 self.crossfeed.process(buffer, channels);
280 ProcessResult::Ok
281 }
282
283 fn reset(&mut self) {
284 self.crossfeed.reset();
285 }
286
287 fn is_enabled(&self) -> bool {
288 self.cached.enabled
289 }
290
291 fn set_enabled(&mut self, enabled: bool) {
292 self.params.set_enabled(enabled);
293 }
294
295 fn set_sample_rate(&mut self, sample_rate: f64) {
296 self.sample_rate = sample_rate;
297 self.crossfeed
298 .set_sample_rate(sample_rate, self.cached.cutoff_hz);
299 }
300}
301
302pub struct PeakLimiterProcessor {
308 limiter: PeakLimiter,
309 params: Arc<AtomicPeakLimiterParams>,
310 cached_params: Arc<PeakLimiterParamsSnapshot>,
311 cached_generation: u64,
312 cached: PeakLimiterParamsSnapshot,
313 sample_rate: u32,
314 channels: usize,
315}
316
317impl PeakLimiterProcessor {
318 pub fn new(channels: usize, sample_rate: u32, params: Arc<AtomicPeakLimiterParams>) -> Self {
319 let (cached_params, cached_generation) = params.load_with_generation();
320 let cached = *cached_params;
321 Self {
322 limiter: PeakLimiter::new(
323 channels,
324 sample_rate,
325 cached.threshold_db,
326 10.0,
327 cached.release_ms,
328 ),
329 params,
330 cached_params,
331 cached_generation,
332 cached,
333 sample_rate,
334 channels,
335 }
336 }
337
338 fn sync_params(&mut self) {
339 if let Some((current, generation)) =
340 self.params.load_if_changed_since(self.cached_generation)
341 {
342 self.cached = *current;
343 self.cached_params = current;
344 self.cached_generation = generation;
345
346 self.limiter.set_threshold(self.cached.threshold_db);
348 self.limiter.set_release_ms(self.cached.release_ms);
349 if self.cached.enabled != self.limiter.is_enabled() {
351 self.limiter.reset();
352 }
353 }
354 }
355}
356
357impl AudioProcessor for PeakLimiterProcessor {
358 fn name(&self) -> &'static str {
359 "PeakLimiter"
360 }
361
362 fn process(&mut self, buffer: &mut [f64], _channels: usize) -> ProcessResult {
363 self.sync_params();
364
365 if !self.cached.enabled {
366 return ProcessResult::Bypassed;
367 }
368
369 self.limiter.process(buffer);
370 ProcessResult::Ok
371 }
372
373 fn reset(&mut self) {
374 self.limiter.reset();
375 }
376
377 fn is_enabled(&self) -> bool {
378 self.cached.enabled
379 }
380
381 fn set_enabled(&mut self, enabled: bool) {
382 self.params.set_enabled(enabled);
383 }
384
385 fn set_sample_rate(&mut self, sample_rate: f64) {
386 self.sample_rate = sample_rate as u32;
387 self.limiter = PeakLimiter::new(
388 self.channels,
389 self.sample_rate,
390 self.cached.threshold_db,
391 10.0,
392 self.cached.release_ms,
393 );
394 }
395}
396
397pub struct VolumeProcessor {
407 params: Arc<AtomicVolumeParams>,
408 cached_params: Arc<VolumeParamsSnapshot>,
409 cached_generation: u64,
410 cached: VolumeParamsSnapshot,
411 current_volume: f64,
413 smoothing_coeff: f64,
415 one_minus_smoothing_coeff: f64,
417 sample_rate: f64,
419}
420
421impl VolumeProcessor {
422 const SETTLE_EPSILON: f64 = 1.0e-6;
423
424 pub fn new(params: Arc<AtomicVolumeParams>) -> Self {
425 let smoothing_coeff = Self::calc_smoothing_coeff(44100.0);
426 let one_minus_smoothing_coeff = 1.0 - smoothing_coeff;
427 let (cached_params, cached_generation) = params.load_with_generation();
428 let cached = *cached_params;
429 Self {
430 params,
431 cached_params,
432 cached_generation,
433 cached,
434 current_volume: 1.0,
435 smoothing_coeff,
436 one_minus_smoothing_coeff,
437 sample_rate: 44100.0,
438 }
439 }
440
441 fn calc_smoothing_coeff(sample_rate: f64) -> f64 {
443 let smoothing_time_ms = 5.0;
444 let smoothing_samples = (smoothing_time_ms / 1000.0) * sample_rate;
445 (-1.0 / smoothing_samples).exp()
446 }
447
448 fn sync_params(&mut self) {
449 if let Some((current, generation)) =
450 self.params.load_if_changed_since(self.cached_generation)
451 {
452 self.cached = *current;
453 self.cached_params = current;
454 self.cached_generation = generation;
455 }
456 }
457}
458
459impl AudioProcessor for VolumeProcessor {
460 fn name(&self) -> &'static str {
461 "Volume"
462 }
463
464 fn process(&mut self, buffer: &mut [f64], channels: usize) -> ProcessResult {
465 self.sync_params();
466
467 if self.cached.muted {
470 let coeff = self.smoothing_coeff;
472 let mut current_volume = self.current_volume;
473 for sample in buffer.iter_mut() {
474 current_volume *= coeff;
475 *sample *= current_volume;
476 }
477 self.current_volume = current_volume;
478 return ProcessResult::Ok;
479 }
480
481 let target = self.cached.volume;
483 if self.current_volume == target {
484 if target != 1.0 {
485 for sample in buffer.iter_mut() {
486 *sample *= target;
487 }
488 }
489 return ProcessResult::Ok;
490 }
491
492 let one_minus_coeff = self.one_minus_smoothing_coeff;
493 let mut current_volume = self.current_volume;
494 let frames = buffer.len() / channels;
495 let mut frame = 0;
496
497 while frame < frames {
498 if (target - current_volume).abs() <= Self::SETTLE_EPSILON {
499 current_volume = target;
500 break;
501 }
502
503 current_volume += (target - current_volume) * one_minus_coeff;
505 for ch in 0..channels {
506 buffer[frame * channels + ch] *= current_volume;
507 }
508 frame += 1;
509 }
510
511 if frame < frames && target != 1.0 {
512 for sample in &mut buffer[(frame * channels)..] {
513 *sample *= target;
514 }
515 }
516 self.current_volume = current_volume;
517
518 ProcessResult::Ok
519 }
520
521 fn reset(&mut self) {
522 self.current_volume = self.cached.volume;
523 }
524
525 fn is_enabled(&self) -> bool {
526 true }
528
529 fn set_enabled(&mut self, _enabled: bool) {
530 }
532
533 fn set_sample_rate(&mut self, sample_rate: f64) {
534 if (self.sample_rate - sample_rate).abs() > 1.0 {
535 self.sample_rate = sample_rate;
536 self.smoothing_coeff = Self::calc_smoothing_coeff(sample_rate);
537 self.one_minus_smoothing_coeff = 1.0 - self.smoothing_coeff;
538 }
539 }
540}
541
542pub struct NoiseShaperProcessor {
548 noise_shaper: NoiseShaper,
549 params: Arc<AtomicNoiseShaperParams>,
550 cached_params: Arc<NoiseShaperParamsSnapshot>,
551 cached_generation: u64,
552 cached: NoiseShaperParamsSnapshot,
553 sample_rate: u32,
554 channels: usize,
555}
556
557impl NoiseShaperProcessor {
558 pub fn new(channels: usize, sample_rate: u32, params: Arc<AtomicNoiseShaperParams>) -> Self {
559 let (cached_params, cached_generation) = params.load_with_generation();
560 let cached = *cached_params;
561 let mut noise_shaper = NoiseShaper::new(channels, sample_rate, cached.bits);
562 noise_shaper.set_enabled(cached.enabled);
563 noise_shaper.set_curve(cached.curve);
564
565 Self {
566 noise_shaper,
567 params,
568 cached_params,
569 cached_generation,
570 cached,
571 sample_rate,
572 channels,
573 }
574 }
575
576 pub fn refresh_is_enabled(&mut self) -> bool {
577 self.sync_params();
578 self.cached.enabled
579 }
580
581 pub fn process_cached(&mut self, buffer: &mut [f64], _channels: usize) -> ProcessResult {
582 if !self.cached.enabled {
583 return ProcessResult::Bypassed;
584 }
585
586 self.noise_shaper.process(buffer, self.channels);
587 ProcessResult::Ok
588 }
589
590 fn sync_params(&mut self) {
591 if let Some((current, generation)) =
592 self.params.load_if_changed_since(self.cached_generation)
593 {
594 self.cached = *current;
595 self.cached_params = current;
596 self.cached_generation = generation;
597 self.noise_shaper.set_enabled(self.cached.enabled);
598 self.noise_shaper.set_bits(self.cached.bits);
599 self.noise_shaper.set_curve(self.cached.curve);
600 }
601 }
602}
603
604impl AudioProcessor for NoiseShaperProcessor {
605 fn name(&self) -> &'static str {
606 "NoiseShaper"
607 }
608
609 fn process(&mut self, buffer: &mut [f64], _channels: usize) -> ProcessResult {
610 self.sync_params();
611
612 if !self.cached.enabled {
613 return ProcessResult::Bypassed;
614 }
615
616 self.noise_shaper.process(buffer, self.channels);
617 ProcessResult::Ok
618 }
619
620 fn reset(&mut self) {
621 self.noise_shaper.reset();
622 }
623
624 fn is_enabled(&self) -> bool {
625 self.cached.enabled
626 }
627
628 fn set_enabled(&mut self, enabled: bool) {
629 self.params.set_enabled(enabled);
630 }
631
632 fn set_sample_rate(&mut self, sample_rate: f64) {
633 self.sample_rate = sample_rate as u32;
634 self.noise_shaper = NoiseShaper::new(self.channels, self.sample_rate, self.cached.bits);
635 self.noise_shaper.set_enabled(self.cached.enabled);
636 self.noise_shaper.set_curve(self.cached.curve);
637 }
638}
639
640pub struct DynamicLoudnessProcessor {
646 dynamic_loudness: DynamicLoudness,
647 params: Arc<AtomicDynamicLoudnessParams>,
648 telemetry: Arc<AtomicDynamicLoudnessTelemetry>,
649 cached_params: Arc<DynamicLoudnessParamsSnapshot>,
650 cached_generation: u64,
651 cached: DynamicLoudnessParamsSnapshot,
652 sample_rate: u32,
653 channels: usize,
654}
655
656impl DynamicLoudnessProcessor {
657 pub fn new(
658 channels: usize,
659 sample_rate: u32,
660 params: Arc<AtomicDynamicLoudnessParams>,
661 telemetry: Arc<AtomicDynamicLoudnessTelemetry>,
662 ) -> Self {
663 let (cached_params, cached_generation) = params.load_with_generation();
664 let cached = *cached_params;
665 let mut dynamic_loudness = DynamicLoudness::new(channels, sample_rate as f64);
666 dynamic_loudness.set_volume(cached.volume);
667 dynamic_loudness.set_strength(cached.strength);
668 Self {
669 dynamic_loudness,
670 params,
671 telemetry,
672 cached_params,
673 cached_generation,
674 cached,
675 sample_rate,
676 channels,
677 }
678 }
679
680 fn sync_params(&mut self) {
681 if let Some((current, generation)) =
682 self.params.load_if_changed_since(self.cached_generation)
683 {
684 self.cached = *current;
685 self.cached_params = current;
686 self.cached_generation = generation;
687 self.dynamic_loudness.set_volume(self.cached.volume);
688 self.dynamic_loudness.set_strength(self.cached.strength);
689 }
690 }
691}
692
693impl AudioProcessor for DynamicLoudnessProcessor {
694 fn name(&self) -> &'static str {
695 "DynamicLoudness"
696 }
697
698 fn process(&mut self, buffer: &mut [f64], _channels: usize) -> ProcessResult {
699 self.sync_params();
700
701 if !self.cached.enabled {
702 self.telemetry.update(0.0, [0.0; 7]);
703 return ProcessResult::Bypassed;
704 }
705
706 self.dynamic_loudness.process(buffer);
707 self.telemetry.update(
708 self.dynamic_loudness.loudness_factor(),
709 self.dynamic_loudness.get_band_gains(),
710 );
711 ProcessResult::Ok
712 }
713
714 fn reset(&mut self) {
715 self.dynamic_loudness.reset();
716 }
717
718 fn is_enabled(&self) -> bool {
719 self.cached.enabled
720 }
721
722 fn set_enabled(&mut self, enabled: bool) {
723 self.params.set_enabled(enabled);
724 }
725
726 fn set_sample_rate(&mut self, sample_rate: f64) {
727 self.sample_rate = sample_rate as u32;
728 self.dynamic_loudness = DynamicLoudness::new(self.channels, self.sample_rate as f64);
729 }
730}
731
732pub struct ConvolverProcessor {
738 owned: Option<FFTConvolver>,
739 swap: Arc<ArcSwapOption<FFTConvolver>>,
740 enabled: Arc<AtomicBool>,
741}
742
743impl ConvolverProcessor {
744 pub fn new(swap: Arc<ArcSwapOption<FFTConvolver>>, enabled: Arc<AtomicBool>) -> Self {
745 Self {
746 owned: None,
747 swap,
748 enabled,
749 }
750 }
751
752 fn sync_convolver(&mut self) {
753 if !self.enabled.load(Ordering::Acquire) {
754 self.owned = None;
755 let _ = self.swap.swap(None);
756 return;
757 }
758
759 let new_conv = self.swap.swap(None);
760 if let Some(arc_conv) = new_conv {
761 match Arc::try_unwrap(arc_conv) {
762 Ok(conv) => self.owned = Some(conv),
763 Err(arc) => self.owned = Some((*arc).clone()),
764 }
765 }
766 }
767}
768
769impl AudioProcessor for ConvolverProcessor {
770 fn name(&self) -> &'static str {
771 "Convolver"
772 }
773
774 fn process(&mut self, buffer: &mut [f64], _channels: usize) -> ProcessResult {
775 self.sync_convolver();
776
777 if let Some(ref mut convolver) = self.owned {
778 convolver.process_inplace(buffer);
779 ProcessResult::Ok
780 } else {
781 ProcessResult::Bypassed
782 }
783 }
784
785 fn reset(&mut self) {
786 if let Some(ref mut convolver) = self.owned {
787 convolver.reset();
788 }
789 }
790
791 fn is_enabled(&self) -> bool {
792 self.enabled.load(Ordering::Acquire)
793 }
794
795 fn set_enabled(&mut self, enabled: bool) {
796 if !enabled {
797 self.owned = None;
798 }
799 self.enabled.store(enabled, Ordering::Release);
800 }
801}
802
803#[cfg(test)]
804mod tests {
805 use super::*;
806
807 #[test]
808 fn test_convolver_processor_swaps_in_and_processes() {
809 let swap = Arc::new(ArcSwapOption::empty());
810 let enabled = Arc::new(AtomicBool::new(false));
811 let mut proc = ConvolverProcessor::new(Arc::clone(&swap), Arc::clone(&enabled));
812 let mut buffer = vec![1.0, 2.0, 3.0, 4.0];
813
814 assert_eq!(proc.process(&mut buffer, 1), ProcessResult::Bypassed);
815
816 swap.store(Some(Arc::new(FFTConvolver::new(&[0.5], 1))));
817 enabled.store(true, Ordering::Release);
818 assert_eq!(proc.process(&mut buffer, 1), ProcessResult::Ok);
819 assert_eq!(buffer, vec![0.5, 1.0, 1.5, 2.0]);
820 }
821
822 #[test]
823 fn test_convolver_processor_clear_disables_owned_convolver() {
824 let swap = Arc::new(ArcSwapOption::empty());
825 let enabled = Arc::new(AtomicBool::new(true));
826 let mut proc = ConvolverProcessor::new(Arc::clone(&swap), Arc::clone(&enabled));
827 let mut buffer = vec![1.0, 2.0, 3.0, 4.0];
828
829 swap.store(Some(Arc::new(FFTConvolver::new(&[0.5], 1))));
830 assert_eq!(proc.process(&mut buffer, 1), ProcessResult::Ok);
831
832 enabled.store(false, Ordering::Release);
833 let mut bypassed = vec![1.0, 2.0, 3.0, 4.0];
834 assert_eq!(proc.process(&mut bypassed, 1), ProcessResult::Bypassed);
835 assert_eq!(bypassed, vec![1.0, 2.0, 3.0, 4.0]);
836 }
837
838 #[test]
839 fn test_eq_processor() {
840 let params = Arc::new(AtomicEqParams::new());
841 let mut proc = EqProcessor::new(2, 44100.0, Arc::clone(¶ms));
842
843 let gains = [2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
845 params.write(&gains, true);
846
847 let mut buffer = vec![0.5; 4096];
849 let result = proc.process(&mut buffer, 2);
850
851 assert_eq!(result, ProcessResult::Ok);
852 assert!(buffer.iter().any(|&sample| (sample - 0.5).abs() > 1e-6));
854 }
855
856 #[test]
857 fn test_volume_processor_muted() {
858 let params = Arc::new(AtomicVolumeParams::new());
859 let mut proc = VolumeProcessor::new(Arc::clone(¶ms));
860
861 params.set_volume(0.5);
862 params.set_muted(true);
863
864 let mut buffer = vec![1.0; 4096];
865 proc.process(&mut buffer, 2);
866
867 assert!(buffer[0] < 1.0);
869 assert!(buffer[buffer.len() - 1] < 0.001);
870 }
871
872 #[test]
873 fn test_volume_processor_writes_back_smoothed_volume() {
874 let params = Arc::new(AtomicVolumeParams::new());
875 let mut proc = VolumeProcessor::new(Arc::clone(¶ms));
876
877 params.set_volume(0.25);
878 let mut buffer = vec![1.0; 128];
879 proc.process(&mut buffer, 2);
880
881 let first_pass_volume = proc.current_volume;
882 assert!(first_pass_volume < 1.0);
883 assert!(first_pass_volume > 0.25);
884
885 proc.process(&mut buffer, 2);
886
887 assert!(proc.current_volume < first_pass_volume);
888 assert!(proc.current_volume > 0.25);
889 }
890
891 #[test]
892 fn test_volume_processor_steady_state_fast_path_preserves_unity() {
893 let params = Arc::new(AtomicVolumeParams::new());
894 let mut proc = VolumeProcessor::new(Arc::clone(¶ms));
895 proc.reset();
896
897 let mut buffer = vec![0.25, -0.5, 0.75, -1.0];
898 let original = buffer.clone();
899
900 assert_eq!(proc.process(&mut buffer, 2), ProcessResult::Ok);
901 assert_eq!(buffer, original);
902 assert_eq!(proc.current_volume, 1.0);
903 }
904
905 #[test]
906 fn test_volume_processor_steady_state_fast_path_applies_target() {
907 let params = Arc::new(AtomicVolumeParams::new());
908 params.set_volume(0.5);
909 let mut proc = VolumeProcessor::new(Arc::clone(¶ms));
910 proc.sync_params();
911 proc.reset();
912
913 let mut buffer = vec![0.25, -0.5, 0.75, -1.0];
914
915 assert_eq!(proc.process(&mut buffer, 2), ProcessResult::Ok);
916 assert_eq!(buffer, vec![0.125, -0.25, 0.375, -0.5]);
917 assert_eq!(proc.current_volume, 0.5);
918 }
919
920 #[test]
921 fn volume_lazy_settle_dc_null_residual_stays_below_snap_floor() {
922 let input = vec![0.8; 32_768 * 2];
923
924 assert_lazy_settle_residual_bounds("dc", &input, 2);
925 }
926
927 #[test]
928 fn volume_lazy_settle_sweep_null_residual_stays_below_snap_floor() {
929 let input = sweep_signal(32_768, 2);
930
931 assert_lazy_settle_residual_bounds("sweep", &input, 2);
932 }
933
934 #[test]
935 fn volume_lazy_settle_abrupt_step_null_residual_stays_below_snap_floor() {
936 let input = abrupt_step_signal(32_768, 2);
937
938 assert_lazy_settle_residual_bounds("abrupt_step", &input, 2);
939 }
940
941 #[test]
942 fn test_saturation_processor() {
943 let params = Arc::new(AtomicSaturationParams::new());
944 let mut proc = SaturationProcessor::new(2, Arc::clone(¶ms));
945
946 params.set_drive(1.0);
947 params.set_mix(1.0);
948 params.set_enabled(true);
949
950 let mut buffer = vec![0.9, 0.9];
951 proc.process(&mut buffer, 2);
952
953 assert!(buffer[0].abs() < 0.9 * 2.0);
955 }
956
957 fn assert_lazy_settle_residual_bounds(name: &str, input: &[f64], channels: usize) {
958 const RESIDUAL_DELTA_LIMIT: f64 = 2.0e-6;
959 const RESIDUAL_RMS_LIMIT: f64 = 2.0e-7;
960
961 let mut exact = input.to_vec();
962 let mut lazy = input.to_vec();
963 process_volume_exact_kernel(&mut exact, channels, 48_000.0, 0.25);
964 process_volume_lazy_settle_kernel(
965 &mut lazy,
966 channels,
967 48_000.0,
968 0.25,
969 VolumeProcessor::SETTLE_EPSILON,
970 );
971
972 let mut max_abs = 0.0_f64;
973 let mut sum_sq = 0.0_f64;
974 let mut max_delta = 0.0_f64;
975 let mut prev_residual = 0.0_f64;
976
977 for (idx, (left, right)) in lazy.iter().zip(&exact).enumerate() {
978 let residual = left - right;
979 max_abs = max_abs.max(residual.abs());
980 sum_sq += residual * residual;
981 if idx > 0 {
982 max_delta = max_delta.max((residual - prev_residual).abs());
983 }
984 prev_residual = residual;
985 }
986
987 let rms = (sum_sq / input.len() as f64).sqrt();
988 assert!(
989 max_abs <= VolumeProcessor::SETTLE_EPSILON,
990 "{name} lazy-settle max residual {max_abs:.3e} exceeds {:.3e}",
991 VolumeProcessor::SETTLE_EPSILON
992 );
993 assert!(
994 max_delta <= RESIDUAL_DELTA_LIMIT,
995 "{name} lazy-settle residual delta {max_delta:.3e} exceeds {RESIDUAL_DELTA_LIMIT:.3e}"
996 );
997 assert!(
998 rms <= RESIDUAL_RMS_LIMIT,
999 "{name} lazy-settle residual rms {rms:.3e} exceeds {RESIDUAL_RMS_LIMIT:.3e}"
1000 );
1001 }
1002
1003 fn process_volume_exact_kernel(
1004 buffer: &mut [f64],
1005 channels: usize,
1006 sample_rate: f64,
1007 target: f64,
1008 ) -> f64 {
1009 let smoothing_coeff = VolumeProcessor::calc_smoothing_coeff(sample_rate);
1010 let one_minus_coeff = 1.0 - smoothing_coeff;
1011 let mut current_volume = 1.0;
1012 let frames = buffer.len() / channels;
1013
1014 for frame in 0..frames {
1015 current_volume += (target - current_volume) * one_minus_coeff;
1016 for ch in 0..channels {
1017 buffer[frame * channels + ch] *= current_volume;
1018 }
1019 }
1020
1021 current_volume
1022 }
1023
1024 fn process_volume_lazy_settle_kernel(
1025 buffer: &mut [f64],
1026 channels: usize,
1027 sample_rate: f64,
1028 target: f64,
1029 settle_epsilon: f64,
1030 ) -> f64 {
1031 let smoothing_coeff = VolumeProcessor::calc_smoothing_coeff(sample_rate);
1032 let one_minus_coeff = 1.0 - smoothing_coeff;
1033 let mut current_volume = 1.0;
1034 let frames = buffer.len() / channels;
1035 let mut frame = 0;
1036
1037 while frame < frames {
1038 if (target - current_volume).abs() <= settle_epsilon {
1039 current_volume = target;
1040 break;
1041 }
1042
1043 current_volume += (target - current_volume) * one_minus_coeff;
1044 for ch in 0..channels {
1045 buffer[frame * channels + ch] *= current_volume;
1046 }
1047 frame += 1;
1048 }
1049
1050 if frame < frames && target != 1.0 {
1051 for sample in &mut buffer[(frame * channels)..] {
1052 *sample *= target;
1053 }
1054 }
1055
1056 current_volume
1057 }
1058
1059 fn sweep_signal(frames: usize, channels: usize) -> Vec<f64> {
1060 let mut out = Vec::with_capacity(frames * channels);
1061 let sample_rate = 48_000.0;
1062 let start_hz = 20.0_f64;
1063 let end_hz = 20_000.0_f64;
1064 let mut phase = 0.0_f64;
1065
1066 for frame in 0..frames {
1067 let progress = frame as f64 / frames.saturating_sub(1).max(1) as f64;
1068 let hz = start_hz * (end_hz / start_hz).powf(progress);
1069 phase += std::f64::consts::TAU * hz / sample_rate;
1070 let sample = phase.sin() * 0.9;
1071 for ch in 0..channels {
1072 out.push(sample * (1.0 - ch as f64 * 0.05));
1073 }
1074 }
1075
1076 out
1077 }
1078
1079 fn abrupt_step_signal(frames: usize, channels: usize) -> Vec<f64> {
1080 let mut out = Vec::with_capacity(frames * channels);
1081
1082 for frame in 0..frames {
1083 let sample = match frame * 4 / frames.max(1) {
1084 0 => 0.0,
1085 1 => 1.0,
1086 2 => -1.0,
1087 _ => {
1088 if frame % 2 == 0 {
1089 1.0
1090 } else {
1091 -1.0
1092 }
1093 }
1094 };
1095 for _ in 0..channels {
1096 out.push(sample);
1097 }
1098 }
1099
1100 out
1101 }
1102}