Skip to main content

pito_codecs/filter/
mix.rs

1use crate::media::{AudioBuffer, Error, Result};
2
3const H: f32 = std::f32::consts::FRAC_1_SQRT_2;
4
5fn stereo_weights(channels: usize) -> Vec<[f32; 2]> {
6    let side = |left: bool, g: f32| if left { [g, 0.0] } else { [0.0, g] };
7    match channels {
8        1 => vec![[1.0, 1.0]],
9        2 => vec![[1.0, 0.0], [0.0, 1.0]],
10        3 => vec![[1.0, 0.0], [0.0, 1.0], [H, H]],
11        4 => vec![[1.0, 0.0], [0.0, 1.0], [H, 0.0], [0.0, H]],
12        5 => vec![[1.0, 0.0], [0.0, 1.0], [H, H], [H, 0.0], [0.0, H]],
13        6 => vec![
14            [1.0, 0.0],
15            [0.0, 1.0],
16            [H, H],
17            [0.0, 0.0],
18            [H, 0.0],
19            [0.0, H],
20        ],
21        7 => vec![
22            [1.0, 0.0],
23            [0.0, 1.0],
24            [H, H],
25            [0.0, 0.0],
26            [0.5, 0.5],
27            [H, 0.0],
28            [0.0, H],
29        ],
30        8 => vec![
31            [1.0, 0.0],
32            [0.0, 1.0],
33            [H, H],
34            [0.0, 0.0],
35            [H, 0.0],
36            [0.0, H],
37            [H, 0.0],
38            [0.0, H],
39        ],
40        n => (0..n)
41            .map(|i| match i {
42                0 => [1.0, 0.0],
43                1 => [0.0, 1.0],
44                _ => side(i % 2 == 0, H),
45            })
46            .collect(),
47    }
48}
49
50pub fn downmix(input: &AudioBuffer, channels: u16) -> Result<AudioBuffer> {
51    let from = input.channels as usize;
52    if from == 0 {
53        return Err(Error::Invalid("audio with no channels".into()));
54    }
55    let mut out = AudioBuffer::new(input.rate, channels);
56    if from == channels as usize {
57        out.samples = input.samples.clone();
58        return Ok(out);
59    }
60    let mut w = stereo_weights(from);
61    let norm = |w: &[[f32; 2]], k: usize| w.iter().map(|c| c[k].abs()).sum::<f32>().max(1.0);
62    let (nl, nr) = (norm(&w, 0), norm(&w, 1));
63    for c in w.iter_mut() {
64        c[0] /= nl;
65        c[1] /= nr;
66    }
67    let frames = input.frames();
68    match channels {
69        2 => {
70            out.samples.resize(frames * 2, 0.0);
71            for (o, f) in out
72                .samples
73                .chunks_exact_mut(2)
74                .zip(input.samples.chunks_exact(from))
75            {
76                let (mut l, mut r) = (0.0, 0.0);
77                for (s, c) in f.iter().zip(&w) {
78                    l += s * c[0];
79                    r += s * c[1];
80                }
81                o[0] = l;
82                o[1] = r;
83            }
84        }
85        1 => {
86            let m: Vec<f32> = w.iter().map(|c| 0.5 * (c[0] + c[1])).collect();
87            out.samples.resize(frames, 0.0);
88            for (o, f) in out.samples.iter_mut().zip(input.samples.chunks_exact(from)) {
89                *o = f.iter().zip(&m).map(|(s, c)| s * c).sum();
90            }
91        }
92        n => {
93            return Err(Error::Unsupported(format!(
94                "downmix to {n} channels; only mono and stereo"
95            )));
96        }
97    }
98    Ok(out)
99}
100
101pub fn gain(buffer: &mut AudioBuffer, db: f32) {
102    let g = 10f32.powf(db / 20.0);
103    buffer.samples.iter_mut().for_each(|s| *s *= g);
104}