const HOLD_SAMPLES: usize = 160;
pub struct Mixer {
pub mic_gain: f32,
pub loopback_gain: f32,
last_mic: Option<i16>,
last_loopback: Option<i16>,
pub drift_frames: u64,
}
impl Default for Mixer {
fn default() -> Self {
Self {
mic_gain: 0.7,
loopback_gain: 0.7,
last_mic: None,
last_loopback: None,
drift_frames: 0,
}
}
}
impl Mixer {
#[allow(dead_code)]
pub fn mix(&mut self, mic: &[i16], loopback: &[i16]) -> Vec<i16> {
let mut out = Vec::with_capacity(mic.len().max(loopback.len()));
self.mix_into(mic, loopback, &mut out);
out
}
pub fn mix_into(&mut self, mic: &[i16], loopback: &[i16], out: &mut Vec<i16>) {
let mic_gain = self.mic_gain;
let lp_gain = self.loopback_gain;
let len = mic.len().max(loopback.len());
if let Some(&last) = mic.last() {
self.last_mic = Some(last);
}
if let Some(&last) = loopback.last() {
self.last_loopback = Some(last);
}
let mic_short = len.saturating_sub(mic.len());
let lp_short = len.saturating_sub(loopback.len());
if mic_short > HOLD_SAMPLES || lp_short > HOLD_SAMPLES {
self.drift_frames = self.drift_frames.wrapping_add(1);
}
let mic_hold = self.last_mic.unwrap_or(0);
let lp_hold = self.last_loopback.unwrap_or(0);
out.clear();
out.reserve(len);
for i in 0..len {
let a = mic.get(i).copied().unwrap_or(mic_hold) as f32 * mic_gain;
let b = loopback.get(i).copied().unwrap_or(lp_hold) as f32 * lp_gain;
let sum = (a + b).clamp(i16::MIN as f32, i16::MAX as f32);
out.push(sum as i16);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sample_and_hold_when_loopback_short() {
let mut mixer = Mixer::default();
let _ = mixer.mix(&vec![100i16; 160], &vec![500i16; 160]);
let mic = vec![100i16; 160];
let lp = vec![500i16; 80];
let out = mixer.mix(&mic, &lp);
assert_eq!(out.len(), 160);
let expected = ((100.0f32 + 500.0) * 0.7).round() as i16;
assert_eq!(out[100], expected);
assert_eq!(out[159], expected);
}
#[test]
fn drift_counter_increments_on_large_misalignment() {
let mut mixer = Mixer::default();
let out = mixer.mix(&vec![0i16; 1600], &vec![0i16; 32]);
assert_eq!(out.len(), 1600);
assert_eq!(mixer.drift_frames, 1);
}
#[test]
fn drift_counter_silent_on_jitter() {
let mut mixer = Mixer::default();
let _ = mixer.mix(&vec![0i16; 1600], &vec![0i16; 1500]);
assert_eq!(mixer.drift_frames, 0);
let _ = mixer.mix(&vec![0i16; 1600], &vec![0i16; 1550]);
assert_eq!(mixer.drift_frames, 0);
}
#[test]
fn empty_loopback_yields_mic_only() {
let mut mixer = Mixer::default();
let mic = vec![1000i16; 1600];
let out = mixer.mix(&mic, &[]);
assert_eq!(out.len(), 1600);
let expected = (1000.0 * 0.7) as i16;
assert!(
out.iter().all(|&s| (s - expected).abs() <= 1),
"all samples should be ~700"
);
}
#[test]
fn additive_mix_clamps_at_i16_max() {
let mut mixer = Mixer::default();
let mic = vec![i16::MAX; 100];
let lp = vec![i16::MAX; 100];
let out = mixer.mix(&mic, &lp);
for &s in &out {
assert_eq!(s, i16::MAX);
}
}
#[test]
fn mix_into_reuses_buffer() {
let mut mixer = Mixer::default();
let mut out = vec![999i16; 100]; let mic = vec![10i16; 1600];
let lp = vec![20i16; 1600];
mixer.mix_into(&mic, &lp, &mut out);
assert_eq!(out.len(), 1600);
let expected = ((10.0 + 20.0) * 0.7) as i16;
assert_eq!(out[0], expected);
assert!(out.capacity() >= 1600);
}
#[test]
fn drift_counter_wraps_safely() {
let mut mixer = Mixer::default();
mixer.drift_frames = u64::MAX;
let _ = mixer.mix(&vec![0i16; 1600], &vec![0i16; 0]);
assert_eq!(mixer.drift_frames, 0); }
}