active_call/media/
ambiance.rs

1use super::processor::Processor;
2use crate::media::{AudioFrame, INTERNAL_SAMPLERATE, Samples};
3use anyhow::Result;
4use serde::{Deserialize, Serialize};
5use tracing::info;
6
7#[derive(Debug, Clone, Serialize, Deserialize, Default)]
8#[serde(rename_all = "camelCase")]
9pub struct AmbianceOption {
10    pub path: Option<String>,
11    pub duck_level: Option<f32>,
12    pub normal_level: Option<f32>,
13    pub transition_speed: Option<f32>,
14    pub enabled: Option<bool>,
15}
16
17impl AmbianceOption {
18    pub fn merge(&mut self, other: &AmbianceOption) {
19        if self.path.is_none() {
20            self.path = other.path.clone();
21        }
22        if self.duck_level.is_none() {
23            self.duck_level = other.duck_level;
24        }
25        if self.normal_level.is_none() {
26            self.normal_level = other.normal_level;
27        }
28        if self.transition_speed.is_none() {
29            self.transition_speed = other.transition_speed;
30        }
31        if self.enabled.is_none() {
32            self.enabled = other.enabled;
33        }
34    }
35}
36
37pub struct AmbianceProcessor {
38    samples: Vec<i16>,
39    cursor: usize,
40    duck_level: f32,
41    normal_level: f32,
42    enabled: bool,
43    current_level: f32,
44    transition_speed: f32,
45    resample_phase: u32,
46    resample_step: u32,
47}
48
49impl AmbianceProcessor {
50    pub async fn new(option: AmbianceOption) -> Result<Self> {
51        let path = option
52            .path
53            .ok_or_else(|| anyhow::anyhow!("Ambiance path required"))?;
54
55        let samples =
56            crate::media::loader::load_audio_as_pcm(&path, INTERNAL_SAMPLERATE, true).await?;
57
58        info!("Loading ambiance {}: samples={}", path, samples.len());
59
60        let normal_level = option.normal_level.unwrap_or(0.3);
61        Ok(Self {
62            samples,
63            cursor: 0,
64            duck_level: option.duck_level.unwrap_or(0.1),
65            normal_level,
66            enabled: option.enabled.unwrap_or(true),
67            current_level: normal_level,
68            transition_speed: option.transition_speed.unwrap_or(0.01),
69            resample_phase: 0,
70            resample_step: 1 << 16,
71        })
72    }
73
74    pub fn set_enabled(&mut self, enabled: bool) {
75        self.enabled = enabled;
76    }
77
78    pub fn set_levels(&mut self, normal: f32, duck: f32) {
79        self.normal_level = normal;
80        self.duck_level = duck;
81    }
82
83    #[inline]
84    fn get_ambient_sample_with_rate(&mut self, target_sample_rate: u32) -> i16 {
85        if self.samples.is_empty() {
86            return 0;
87        }
88
89        self.resample_step =
90            (((INTERNAL_SAMPLERATE as u64) << 16) / target_sample_rate as u64) as u32;
91        let sample = self.samples[self.cursor];
92
93        self.resample_phase += self.resample_step;
94        while self.resample_phase >= (1 << 16) {
95            self.resample_phase -= 1 << 16;
96            self.cursor = (self.cursor + 1) % self.samples.len();
97        }
98
99        sample
100    }
101
102    #[inline]
103    fn soft_mix(signal: i16, ambient: i16, level: f32) -> i16 {
104        let ambient_scaled = (ambient as i32 * (level * 256.0) as i32) >> 8;
105        let signal_i32 = signal as i32;
106        let mixed = signal_i32 + ambient_scaled;
107
108        if mixed > 32767 {
109            let over = mixed - 32767;
110            (32767 - (over >> 2)) as i16
111        } else if mixed < -32768 {
112            let under = -32768 - mixed;
113            (-32768 + (under >> 2)) as i16
114        } else {
115            mixed as i16
116        }
117    }
118}
119
120impl Processor for AmbianceProcessor {
121    fn process_frame(&mut self, frame: &mut AudioFrame) -> Result<()> {
122        if !self.enabled || self.samples.is_empty() {
123            return Ok(());
124        }
125
126        let is_server_side_speaking = match &frame.samples {
127            Samples::PCM { samples } => !samples.is_empty(),
128            Samples::RTP { .. } => true,
129            Samples::Empty => false,
130        };
131
132        let target_level = if is_server_side_speaking {
133            self.duck_level
134        } else {
135            self.normal_level
136        };
137
138        if (self.current_level - target_level).abs() > 0.001 {
139            if self.current_level < target_level {
140                self.current_level = (self.current_level + self.transition_speed).min(target_level);
141            } else {
142                self.current_level = (self.current_level - self.transition_speed).max(target_level);
143            }
144        }
145
146        let sample_rate = if frame.sample_rate > 0 {
147            frame.sample_rate
148        } else {
149            INTERNAL_SAMPLERATE
150        };
151        let channels = frame.channels.max(1) as usize;
152
153        match &mut frame.samples {
154            Samples::PCM { samples } => {
155                let frame_sample_count = samples.len() / channels;
156                for i in 0..frame_sample_count {
157                    let ambient = self.get_ambient_sample_with_rate(sample_rate);
158                    for c in 0..channels {
159                        let idx = i * channels + c;
160                        if idx < samples.len() {
161                            samples[idx] =
162                                Self::soft_mix(samples[idx], ambient, self.current_level);
163                        }
164                    }
165                }
166            }
167            Samples::Empty => {
168                let frame_size = (sample_rate as usize * 20) / 1000;
169                let mut ambient_samples = Vec::with_capacity(frame_size * channels);
170                for _ in 0..frame_size {
171                    let ambient = self.get_ambient_sample_with_rate(sample_rate);
172                    let ambient_scaled =
173                        ((ambient as i32 * (self.current_level * 256.0) as i32) >> 8) as i16;
174                    for _ in 0..channels {
175                        ambient_samples.push(ambient_scaled);
176                    }
177                }
178                frame.samples = Samples::PCM {
179                    samples: ambient_samples,
180                };
181                frame.sample_rate = sample_rate;
182                frame.channels = channels as u16;
183            }
184            _ => {}
185        }
186
187        Ok(())
188    }
189}