Skip to main content

active_call/media/
volume_control.rs

1use super::processor::Processor;
2use crate::media::AudioFrame;
3use anyhow::Result;
4use std::sync::{
5    Arc,
6    atomic::{AtomicBool, AtomicU32, Ordering},
7};
8
9/// Volume control processor for audio streams
10#[derive(Debug, Clone)]
11pub struct VolumeControlProcessor {
12    /// Volume level stored as bits of f32 (0.0 to 2.0, where 1.0 is normal volume)
13    volume_level: Arc<AtomicU32>,
14    /// Whether audio is muted
15    muted: Arc<AtomicBool>,
16}
17
18impl Default for VolumeControlProcessor {
19    fn default() -> Self {
20        Self::new()
21    }
22}
23
24impl VolumeControlProcessor {
25    pub fn new() -> Self {
26        Self {
27            volume_level: Arc::new(AtomicU32::new(1.0_f32.to_bits())),
28            muted: Arc::new(AtomicBool::new(false)),
29        }
30    }
31
32    pub fn set_volume(&self, level: f32) {
33        let clamped_level = level.clamp(0.0, 2.0);
34        self.volume_level
35            .store(clamped_level.to_bits(), Ordering::Relaxed);
36    }
37
38    pub fn get_volume(&self) -> f32 {
39        f32::from_bits(self.volume_level.load(Ordering::Relaxed))
40    }
41
42    pub fn set_muted(&self, muted: bool) {
43        self.muted.store(muted, Ordering::Relaxed);
44    }
45
46    pub fn is_muted(&self) -> bool {
47        self.muted.load(Ordering::Relaxed)
48    }
49
50    pub fn toggle_mute(&self) -> bool {
51        // Use fetch_xor to atomically toggle the boolean
52        !self.muted.fetch_xor(true, Ordering::Relaxed)
53    }
54}
55
56impl Processor for VolumeControlProcessor {
57    fn process_frame(&mut self, frame: &mut AudioFrame) -> Result<()> {
58        // Check if muted
59        if self.is_muted() {
60            // Mute the audio by zeroing out samples
61            if let crate::media::Samples::PCM { samples } = &mut frame.samples {
62                for sample in samples.iter_mut() {
63                    *sample = 0;
64                }
65            }
66            return Ok(());
67        }
68
69        // Apply volume control
70        let volume = self.get_volume();
71        if (volume - 1.0).abs() > f32::EPSILON {
72            if let crate::media::Samples::PCM { samples } = &mut frame.samples {
73                for sample in samples.iter_mut() {
74                    let adjusted = (*sample as f32 * volume) as i16;
75                    *sample = adjusted.clamp(i16::MIN, i16::MAX);
76                }
77            }
78        }
79
80        Ok(())
81    }
82}
83
84/// Hold/Unhold processor for audio streams
85#[derive(Debug, Clone)]
86pub struct HoldProcessor {
87    /// Whether the call is on hold
88    on_hold: Arc<AtomicBool>,
89}
90
91impl Default for HoldProcessor {
92    fn default() -> Self {
93        Self::new()
94    }
95}
96
97impl HoldProcessor {
98    pub fn new() -> Self {
99        Self {
100            on_hold: Arc::new(AtomicBool::new(false)),
101        }
102    }
103
104    pub fn set_hold(&self, hold: bool) {
105        self.on_hold.store(hold, Ordering::Relaxed);
106    }
107
108    pub fn is_on_hold(&self) -> bool {
109        self.on_hold.load(Ordering::Relaxed)
110    }
111
112    pub fn toggle_hold(&self) -> bool {
113        // Use fetch_xor to atomically toggle the boolean
114        !self.on_hold.fetch_xor(true, Ordering::Relaxed)
115    }
116}
117
118impl Processor for HoldProcessor {
119    fn process_frame(&mut self, frame: &mut AudioFrame) -> Result<()> {
120        if self.is_on_hold() {
121            // When on hold, replace audio with silence (set all samples to 0)
122            // IMPORTANT: We still pass the frame through to subsequent processors (e.g., ASR)
123            // to maintain stream continuity. ASR needs to receive silence frames to avoid errors.
124            if let crate::media::Samples::PCM { samples } = &mut frame.samples {
125                // Replace with silence (keep the frame structure intact)
126                // The frame is still sent to ASR and other processors
127                for sample in samples.iter_mut() {
128                    *sample = 0;
129                }
130                // Note: Frame continues to flow through processor chain - this is intentional
131                // to prevent ASR and other processors from experiencing stream interruption
132            }
133        }
134        Ok(())
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use crate::media::Samples;
142
143    #[test]
144    fn test_volume_control() {
145        let processor = VolumeControlProcessor::new();
146
147        // Test default volume
148        assert!((processor.get_volume() - 1.0).abs() < f32::EPSILON);
149        assert!(!processor.is_muted());
150
151        // Test volume setting
152        processor.set_volume(0.5);
153        assert!((processor.get_volume() - 0.5).abs() < f32::EPSILON);
154
155        // Test mute
156        processor.set_muted(true);
157        assert!(processor.is_muted());
158    }
159
160    #[test]
161    fn test_volume_processing() {
162        let mut processor = VolumeControlProcessor::new();
163        processor.set_volume(0.5);
164
165        let mut frame = AudioFrame {
166            track_id: "test".to_string(),
167            samples: Samples::PCM {
168                samples: vec![1000, -1000, 500, -500],
169            },
170            timestamp: 0,
171            sample_rate: 16000,
172            channels: 1,
173            ..Default::default()
174        };
175
176        processor.process_frame(&mut frame).unwrap();
177
178        if let Samples::PCM { samples } = frame.samples {
179            assert_eq!(samples, vec![500, -500, 250, -250]);
180        } else {
181            panic!("Expected PCM samples");
182        }
183    }
184
185    #[test]
186    fn test_mute_processing() {
187        let mut processor = VolumeControlProcessor::new();
188        processor.set_muted(true);
189
190        let mut frame = AudioFrame {
191            track_id: "test".to_string(),
192            samples: Samples::PCM {
193                samples: vec![1000, -1000, 500, -500],
194            },
195            timestamp: 0,
196            sample_rate: 16000,
197            channels: 1,
198            ..Default::default()
199        };
200
201        processor.process_frame(&mut frame).unwrap();
202
203        if let Samples::PCM { samples } = frame.samples {
204            assert_eq!(samples, vec![0, 0, 0, 0]);
205        } else {
206            panic!("Expected PCM samples");
207        }
208    }
209
210    #[test]
211    fn test_hold_processor() {
212        let processor = HoldProcessor::new();
213
214        // Test default state
215        assert!(!processor.is_on_hold());
216
217        // Test hold setting
218        processor.set_hold(true);
219        assert!(processor.is_on_hold());
220
221        // Test toggle
222        let new_state = processor.toggle_hold();
223        assert!(!new_state);
224        assert!(!processor.is_on_hold());
225    }
226}