lc3-codec 0.2.0

An implementation of the LC3 audio codec for embedded systems (no_std)
Documentation
use crate::common::{complex::Scaler, config::FrameDuration};

use super::side_info::Bandwidth;

// checked against spec

/// Applies stable noise to the spectral lines in the frame given the noise filling seed
///
/// # Arguments
///
/// * `is_zero_frame` - If this is a zero frame, noise filling is skipped
/// * `noise_filling_seed` - The noise filling seed (e.g. 56909) is the seed for psudo-random noise
/// * `bandwidth` - Bandwidth cutoff index (e.g. 4) (P_BW)
/// * `duration` - Frame duration (7.5 ms or 10 ms) (N_MS)
/// * `noise_factor` - Transmitted noise factor (e.g. 3) which is quantised from the calculated noise level (F_NF)
/// * `spec_lines_int` - Input integer spectral lines (used for checking for zeros only)
/// * `spec_lines_float` - Input / Output floating point spectral lines
pub fn apply_noise_filling(
    is_zero_frame: bool,
    noise_filling_seed: i32,
    bandwidth: Bandwidth,
    duration: FrameDuration,
    noise_factor: usize,
    spec_lines_int: &[i32],
    spec_lines_float: &mut [Scaler],
) {
    if !is_zero_frame {
        const BW_STOP_TABLE_7P5MS: [usize; 5] = [60, 120, 180, 240, 300];
        const BW_STOP_TABLE_10MS: [usize; 5] = [80, 160, 240, 320, 400];

        let (bw_stop, nf_start, nf_width) = match duration {
            FrameDuration::SevenPointFiveMs => (BW_STOP_TABLE_7P5MS[bandwidth as usize], 18, 2),
            FrameDuration::TenMs => (BW_STOP_TABLE_10MS[bandwidth as usize], 24, 3),
        };

        let mut noise_fill = noise_filling_seed;
        let noise_level = (8.0 - noise_factor as Scaler) / 16.0;

        // loop though spectral lines between nf_start and bw_stop
        for (k, spec_line) in spec_lines_float.iter_mut().enumerate().take(bw_stop).skip(nf_start) {
            let from = k - nf_width;
            let to = (bw_stop - 1).min(k + nf_width);

            // NOTE: it is safe to use spec_lines_int for the check for a zero sample
            // because residual_spectrum::decode ignores zero samples.
            // It is faster to compare integers than floats and this is a hot loop
            let apply_noise_fill = &spec_lines_int[from..=to].iter().all(|x| *x == 0);

            // noise filling is only applied if all spectral lines in the range above are zero
            if *apply_noise_fill {
                noise_fill = (13849 + noise_fill * 31821) & 0xFFFF;
                *spec_line = if noise_fill < 0x8000 { noise_level } else { -noise_level };
            }
        }
    }
}

#[cfg(test)]
mod tests {
    extern crate std;
    use super::*;
    use crate::common::{config::FrameDuration, constants::MAX_LEN_SPECTRAL};

    #[test]
    fn decode_noise_filling() {
        let is_zero_frame = false;
        let noise_filling_seed = 56909;
        let bandwidth = Bandwidth::FullBand;
        let duration = FrameDuration::TenMs;
        let noise_factor = 3;
        let mut spec_lines_float: [f32; MAX_LEN_SPECTRAL] = [
            -9.3125, 32.3125, 65.3125, 40.3125, -4.3125, -48.3125, -36.3125, 2.3125, 20.8125, -6.3125, 27.3125, 9.3125,
            -8.8125, -26.3125, 8.8125, -4.3125, -0.8125, 12.3125, -7.8125, -2.3125, 23.3125, -33.3125, -9.8125, 3.3125,
            -15.3125, -3.3125, 5.3125, 17.3125, -1.3125, 12.3125, 27.3125, -4.3125, 8.3125, 2.3125, 10.3125, -7.3125,
            8.3125, -6.3125, 3.3125, -21.8125, 23.8125, -6.3125, -0.8125, 2.3125, 0.0, 1.3125, -8.0, 3.0, -2.0, 0.0,
            0.0, 2.0, -6.0, -2.0, -4.0, -3.0, 0.0, 1.0, 0.0, -7.0, -4.0, -11.0, -7.0, -6.0, 0.0, 3.0, 10.0, 2.0, 9.0,
            3.0, 7.0, -2.0, 2.0, -2.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 4.0, -1.0, -1.0, -1.0, 2.0, 1.0, 3.0, 3.0,
            1.0, -2.0, -2.0, 1.0, 5.0, 2.0, -2.0, -5.0, -4.0, -2.0, 0.0, 0.0, 0.0, -1.0, 1.0, -1.0, 1.0, 1.0, 1.0,
            -1.0, -3.0, -1.0, 1.0, 3.0, 0.0, 0.0, 1.0, 2.0, -1.0, -1.0, 1.0, 2.0, 2.0, 1.0, 2.0, 0.0, -2.0, -3.0, 0.0,
            3.0, 2.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, -1.0, 0.0, 0.0, 0.0, -1.0, -1.0, 1.0, 2.0, 0.0, 0.0, 0.0, 0.0,
            0.0, 0.0, 0.0, -1.0, 2.0, 0.0, -1.0, -1.0, 0.0, 2.0, 0.0, 2.0, 0.0, 0.0, -2.0, -1.0, 0.0, 1.0, 0.0, -1.0,
            0.0, 1.0, 2.0, 0.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0, -1.0, -1.0, 0.0,
            0.0, 1.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0,
            0.0, -1.0, 1.0, 2.0, -1.0, 0.0, 2.0, 0.0, 1.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, 2.0, -1.0, -1.0, 0.0, 0.0,
            0.0, 0.0, -1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0,
            0.0, 1.0, 2.0, -2.0, 0.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 1.0, 1.0, 1.0, -2.0, -1.0,
            -1.0, 0.0, 1.0, -1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, -1.0, 1.0, -1.0, 0.0, 0.0, -1.0, 1.0, 0.0, 0.0, 0.0,
            1.0, 1.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0.0, -1.0, -1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 1.0, 1.0,
            0.0, 0.0, 0.0, 0.0, -1.0, -1.0, -1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0, 0.0,
            0.0, -1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
            0.0, 0.0, 0.0, 0.0, -1.0, -1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0,
            -1.0, 0.0, 0.0, 1.0, -1.0, -1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0,
            0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 1.0, 1.0,
        ];
        let spec_lines_int: [i32; MAX_LEN_SPECTRAL] = [
            -9, 32, 65, 40, -4, -48, -36, 2, 21, -6, 27, 9, -9, -26, 9, -4, -1, 12, -8, -2, 23, -33, -10, 3, -15, -3,
            5, 17, -1, 12, 27, -4, 8, 2, 10, -7, 8, -6, 3, -22, 24, -6, -1, 2, 0, 1, -8, 3, -2, 0, 0, 2, -6, -2, -4,
            -3, 0, 1, 0, -7, -4, -11, -7, -6, 0, 3, 10, 2, 9, 3, 7, -2, 2, -2, -1, -1, 0, 0, 0, 0, 1, 4, -1, -1, -1, 2,
            1, 3, 3, 1, -2, -2, 1, 5, 2, -2, -5, -4, -2, 0, 0, 0, -1, 1, -1, 1, 1, 1, -1, -3, -1, 1, 3, 0, 0, 1, 2, -1,
            -1, 1, 2, 2, 1, 2, 0, -2, -3, 0, 3, 2, 0, 0, 0, 0, 1, 1, 0, -1, 0, 0, 0, -1, -1, 1, 2, 0, 0, 0, 0, 0, 0, 0,
            -1, 2, 0, -1, -1, 0, 2, 0, 2, 0, 0, -2, -1, 0, 1, 0, -1, 0, 1, 2, 0, -1, 0, 1, 0, 1, 0, 1, -1, -1, 0, 0, 0,
            0, -1, -1, 0, 0, 1, 0, -1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, -1, 1, 2, -1, 0, 2, 0, 1,
            -1, 0, 0, -1, 0, 0, 2, -1, -1, 0, 0, 0, 0, -1, 1, -1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1,
            2, -2, 0, -1, -1, 0, 0, 0, 0, -1, 0, 0, -1, 1, 1, 1, -2, -1, -1, 0, 1, -1, 0, 0, 1, 0, 0, 0, -1, 1, -1, 0,
            0, -1, 1, 0, 0, 0, 1, 1, 0, -1, -1, 0, -1, 0, -1, -1, 0, 1, 0, 0, 0, 0, -1, 0, 1, 1, 0, 0, 0, 0, -1, -1,
            -1, 0, 0, 0, -1, 0, 0, 1, 0, -1, 0, 1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1, 0, 0, 0, 0, -1, 1, 0, 0, 0, 0,
            1, 0, 0, 0, 0, -1, -1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 1, -1, -1, 0, 0, 1, 0, 0, 0,
            0, -1, 0, 1, 0, 1, 0, 0, 0, 0, 0, -1, 0, 0, 1, 1,
        ];

        apply_noise_filling(
            is_zero_frame,
            noise_filling_seed,
            bandwidth,
            duration,
            noise_factor,
            &spec_lines_int,
            &mut spec_lines_float,
        );

        let x_hat_expected: [f32; MAX_LEN_SPECTRAL] = [
            -9.3125, 32.3125, 65.3125, 40.3125, -4.3125, -48.3125, -36.3125, 2.3125, 20.8125, -6.3125, 27.3125, 9.3125,
            -8.8125, -26.3125, 8.8125, -4.3125, -0.8125, 12.3125, -7.8125, -2.3125, 23.3125, -33.3125, -9.8125, 3.3125,
            -15.3125, -3.3125, 5.3125, 17.3125, -1.3125, 12.3125, 27.3125, -4.3125, 8.3125, 2.3125, 10.3125, -7.3125,
            8.3125, -6.3125, 3.3125, -21.8125, 23.8125, -6.3125, -0.8125, 2.3125, 0.0, 1.3125, -8.0, 3.0, -2.0, 0.0,
            0.0, 2.0, -6.0, -2.0, -4.0, -3.0, 0.0, 1.0, 0.0, -7.0, -4.0, -11.0, -7.0, -6.0, 0.0, 3.0, 10.0, 2.0, 9.0,
            3.0, 7.0, -2.0, 2.0, -2.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 4.0, -1.0, -1.0, -1.0, 2.0, 1.0, 3.0, 3.0,
            1.0, -2.0, -2.0, 1.0, 5.0, 2.0, -2.0, -5.0, -4.0, -2.0, 0.0, 0.0, 0.0, -1.0, 1.0, -1.0, 1.0, 1.0, 1.0,
            -1.0, -3.0, -1.0, 1.0, 3.0, 0.0, 0.0, 1.0, 2.0, -1.0, -1.0, 1.0, 2.0, 2.0, 1.0, 2.0, 0.0, -2.0, -3.0, 0.0,
            3.0, 2.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, -1.0, 0.0, 0.0, 0.0, -1.0, -1.0, 1.0, 2.0, 0.0, 0.0, 0.0,
            0.3125, 0.0, 0.0, 0.0, -1.0, 2.0, 0.0, -1.0, -1.0, 0.0, 2.0, 0.0, 2.0, 0.0, 0.0, -2.0, -1.0, 0.0, 1.0, 0.0,
            -1.0, 0.0, 1.0, 2.0, 0.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0, -1.0, -1.0,
            0.0, 0.0, 1.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0,
            0.0, 0.0, -1.0, 1.0, 2.0, -1.0, 0.0, 2.0, 0.0, 1.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, 2.0, -1.0, -1.0, 0.0,
            0.0, 0.0, 0.0, -1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0,
            0.0, 0.0, 1.0, 2.0, -2.0, 0.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 1.0, 1.0, 1.0, -2.0,
            -1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, -1.0, 1.0, -1.0, 0.0, 0.0, -1.0, 1.0, 0.0, 0.0,
            0.0, 1.0, 1.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0.0, -1.0, -1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 1.0,
            1.0, 0.0, 0.0, 0.0, 0.0, -1.0, -1.0, -1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0,
            0.0, 0.0, -1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 1.0, 0.0, 0.0, 0.0, 0.0,
            1.0, 0.0, 0.0, 0.0, 0.0, -1.0, -1.0, 1.0, 0.0, 0.0, 0.0, -0.3125, -0.3125, -0.3125, -0.3125, 0.3125,
            -0.3125, 0.3125, 0.0, 0.0, 0.0, -1.0, -1.0, 0.0, 0.0, 1.0, -1.0, -1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0,
            -1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 1.0, 1.0,
        ];
        assert_eq![spec_lines_float, x_hat_expected];
    }
}