Audio mixing, fast way to sum of two (or more) buffers element-by-element

Hi all.

I'm playing around with Jack and I'm doing some audio mixing in my code. This is simplified version of my mixing funcion:

fn audio_mix_2_channels(length: u32, b1: &[f32], b2: &[f32], f1: f32, f2: f32, out: &mut [f32]) {
    
    assert!(b1.len() == length as usize);
    assert!(b2.len() == length as usize);
    assert!(out.len() == length as usize);

    for i in 0 .. length as usize {
        out[i] = b1[i] * f1 + b2[i] * f2;
    }

}

The gist of my question is obviously the for loop. What i wrote above is the most naive way to make a weighted sum of the elements of two slices, but it works perfectly. I'm not a machine code expert, but I looked at the resulting assembly code with https://godbolt.org/ and there's a lot of it for a simple calculation like this. I'm not surprised :slight_smile:

How can I help the compiler to do a better job, given the assert!s?
And what if I sum 3 or 4 buffers, like

out[i] = b1[i] * f1 + b2[i] * f2 + b3[i] * f3;
out[i] = b1[i] * f1 + b2[i] * f2 + b3[i] * f3 + b4[i] * f4;

Thanks in advance for any hint.

2 Likes

You can use zip to advance iterators in lockstep instead of indexing.

for (y, (&x1, &x2)) in out.iter_mut().zip(b1.iter().zip(b2)) {
    *y = x1 * f1 + x2 * f2;
}

This should optimize to no bounds checks and vectorize better, at least in general.

You should probably remove the length argument altogether, btw. It's redundant because slices know their own length.

4 Likes

You probably want Itertools::zip_eq from the itertools crate. That way, you don't have to take a length argument, it panics if the slices have differing lengths and iirc it gets optimized fairly well.

1 Like

I'll give it a shot, thanks!

It's a lot better! Thanks

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.