use crate::{AudioChunk, Recording};
use crossbeam_channel::bounded;
use std::thread;
pub fn mix_pcm(a: &[i16], b: &[i16]) -> Vec<i16> {
let len = a.len().min(b.len());
let mut out = Vec::with_capacity(len);
for i in 0..len {
let sum = a[i] as i32 + b[i] as i32;
out.push(sum.clamp(i16::MIN as i32, i16::MAX as i32) as i16);
}
if a.len() > len { out.extend_from_slice(&a[len..]); }
if b.len() > len { out.extend_from_slice(&b[len..]); }
out
}
pub fn mix_recordings(mut a: Recording, mut b: Recording, _sample_rate: u32) -> Recording {
let (tx, rx) = bounded::<AudioChunk>(64);
let stop_a = a.stop_fn.take();
let stop_b = b.stop_fn.take();
let rx_a = a.rx.clone();
let rx_b = b.rx.clone();
drop(a);
drop(b);
thread::spawn(move || {
let mut buf_a: Vec<i16> = Vec::new();
let mut buf_b: Vec<i16> = Vec::new();
loop {
if let Ok(chunk) = rx_a.try_recv() {
buf_a.extend_from_slice(&chunk.pcm);
}
if let Ok(chunk) = rx_b.try_recv() {
buf_b.extend_from_slice(&chunk.pcm);
}
let mix_len = buf_a.len().min(buf_b.len());
if mix_len > 0 {
let mixed = mix_pcm(&buf_a[..mix_len], &buf_b[..mix_len]);
buf_a.drain(..mix_len);
buf_b.drain(..mix_len);
if tx.send(AudioChunk { pcm: mixed }).is_err() {
break;
}
} else {
crossbeam_channel::select! {
recv(rx_a) -> msg => match msg {
Ok(chunk) => buf_a.extend_from_slice(&chunk.pcm),
Err(_) => break, },
recv(rx_b) -> msg => match msg {
Ok(chunk) => buf_b.extend_from_slice(&chunk.pcm),
Err(_) => break, },
}
}
}
});
Recording {
rx,
stop_fn: Some(Box::new(move || {
if let Some(f) = stop_a { f(); }
if let Some(f) = stop_b { f(); }
})),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mix_sums_and_clamps() {
let a = vec![20000i16, -20000, 0];
let b = vec![20000i16, -20000, 1000];
let out = mix_pcm(&a, &b);
assert_eq!(out[0], 32767); assert_eq!(out[1], -32768); assert_eq!(out[2], 1000); }
#[test]
fn mix_handles_length_mismatch() {
let a = vec![1i16, 2, 3, 4, 5];
let b = vec![1i16, 2, 3];
let out = mix_pcm(&a, &b);
assert_eq!(out.len(), 5);
assert_eq!(out[3], 4); }
}