]> git.nihav.org Git - nihav.git/commitdiff
nihav_qt: add PureVoice decoder
authorKostya Shishkov <kostya.shishkov@gmail.com>
Wed, 5 Feb 2025 17:12:12 +0000 (18:12 +0100)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Wed, 5 Feb 2025 17:12:12 +0000 (18:12 +0100)
nihav-qt/Cargo.toml
nihav-qt/src/codecs/mod.rs
nihav-qt/src/codecs/qcelp.rs [new file with mode: 0644]

index 2f5783db05523d31a6d2fa4b5b65ca931af55d6d..a5c78d57c1db6953e0a6b931a07344445af68634 100644 (file)
@@ -27,9 +27,10 @@ decoder_rpza = ["decoders"]
 decoder_svq1 = ["decoders"]
 decoder_svq3 = ["decoders"]
 
-all_audio_decoders = ["decoder_ima_adpcm_qt", "decoder_mace", "decoder_qdm", "decoder_qdm2", "decoder_alac"]
+all_audio_decoders = ["decoder_ima_adpcm_qt", "decoder_mace", "decoder_qcelp", "decoder_qdm", "decoder_qdm2", "decoder_alac"]
 decoder_ima_adpcm_qt = ["decoders"]
 decoder_mace = ["decoders"]
+decoder_qcelp = ["decoders"]
 decoder_qdm = ["decoders"]
 decoder_qdm2 = ["decoders"]
 decoder_alac = ["decoders"]
index 5c1ddc4d710d67c28adc6392f7e0a42d85c817cf..5757c37a07a6b0dd7710297a11456adaffa60347 100644 (file)
@@ -42,6 +42,10 @@ mod imaadpcm;
 #[cfg(feature="decoder_mace")]
 mod mace;
 
+#[cfg(feature="decoder_qcelp")]
+#[allow(clippy::excessive_precision)]
+mod qcelp;
+
 #[cfg(any(feature="decoder_qdm",feature="decoder_qdm2"))]
 mod qdmcommon;
 #[cfg(feature="decoder_qdm")]
@@ -75,6 +79,8 @@ const QT_CODECS: &[DecoderInfo] = &[
     DecoderInfo { name: "mace-3", get_decoder: mace::get_decoder_3 },
 #[cfg(feature="decoder_mace")]
     DecoderInfo { name: "mace-6", get_decoder: mace::get_decoder_6 },
+#[cfg(feature="decoder_qcelp")]
+    DecoderInfo { name: "qualcomm-purevoice", get_decoder: qcelp::get_decoder },
 #[cfg(feature="decoder_qdm")]
     DecoderInfo { name: "qdesign-music", get_decoder: qdmc::get_decoder },
 #[cfg(feature="decoder_qdm2")]
diff --git a/nihav-qt/src/codecs/qcelp.rs b/nihav-qt/src/codecs/qcelp.rs
new file mode 100644 (file)
index 0000000..e8ccd97
--- /dev/null
@@ -0,0 +1,952 @@
+use nihav_core::codecs::*;
+use nihav_core::io::bitreader::*;
+
+const ORDER: usize = 10;
+const BLOCK_SIZE: usize = 160;
+const MAX_PITCH: usize = 143;
+
+#[derive(Clone,Copy,Debug,PartialEq)]
+enum QcelpMode {
+    Full,
+    Half,
+    Quarter,
+    Octavo,
+    Silence,
+}
+
+fn get_mode(src: &[u8]) -> DecoderResult<QcelpMode> {
+    match src.len() {
+        35 => Ok(QcelpMode::Full),
+        17 => Ok(QcelpMode::Half),
+         8 => Ok(QcelpMode::Quarter),
+         4 => Ok(QcelpMode::Octavo),
+         1 => Ok(QcelpMode::Silence),
+         _ => Err(DecoderError::InvalidData),
+    }
+}
+
+#[derive(Copy,Clone,Default)]
+struct LSP {
+    data:   [f32; ORDER],
+}
+
+impl LSP {
+    fn new_init() -> Self {
+        let mut data = [0.0; ORDER];
+        for (i, el) in data.iter_mut().enumerate() {
+            *el = ((i + 1) as f32) / 11.0;
+        }
+        Self { data }
+    }
+    fn from_cb(lsp_idx: &[usize; 5]) -> Self {
+        const LSP_SCALE: f32 = 0.0001;
+        let mut data = [0.0; ORDER];
+
+        let mut acc = 0.0;
+        for (pair, (&cb, &idx)) in data.chunks_exact_mut(2)
+                .zip(LSP_CBS.iter().zip(lsp_idx.iter())) {
+            acc += (cb[idx][0] as f32) * LSP_SCALE;
+            pair[0] = acc;
+            acc += (cb[idx][1] as f32) * LSP_SCALE;
+            pair[1] = acc;
+        }
+
+        Self { data }
+    }
+    fn blend(&mut self, other: &Self, weight: f32) {
+        for (dst, &src) in self.data.iter_mut().zip(other.data.iter()) {
+            *dst = *dst * weight + src * (1.0 - weight);
+        }
+    }
+    fn get_lpc(&self) -> [f32; ORDER] {
+        const BANDWIDTH_EXPANSION: f64 = 0.9883;
+        let mut tmp = [0.0f64; ORDER];
+
+        for (dst, &src) in tmp.iter_mut().zip(self.data.iter()) {
+            *dst = (f64::from(src) * std::f64::consts::PI).cos();
+        }
+
+        // LSP to LPC
+        let mut p = [0.0f64; ORDER / 2 + 1];
+        let mut q = [0.0f64; ORDER / 2 + 1];
+
+        Self::get_lsp_coeffs(&tmp, &mut p);
+        Self::get_lsp_coeffs(&tmp[1..], &mut q);
+
+        let (half0, half1) = tmp.split_at_mut(ORDER / 2);
+        for ((dst1, dst2), (pp, qp)) in half0.iter_mut().rev().zip(half1.iter_mut())
+                .zip(p.windows(2).rev().zip(q.windows(2).rev())) {
+            let p1 = pp[1] + pp[0];
+            let q1 = qp[1] - qp[0];
+            *dst1 = (p1 + q1) * 0.5;
+            *dst2 = (p1 - q1) * 0.5;
+        }
+
+        let mut ret = [0.0; ORDER];
+        let mut scale = BANDWIDTH_EXPANSION;
+        for (dst, &src) in ret.iter_mut().zip(tmp.iter()) {
+            *dst = (src * scale) as f32;
+            scale *= BANDWIDTH_EXPANSION;
+        }
+
+        ret
+    }
+    fn get_lsp_coeffs(src: &[f64], dst: &mut [f64; ORDER / 2 + 1]) {
+        dst[0] = 1.0;
+        dst[1] = -2.0 * src[0];
+        for (i, src_coef) in src[2..].chunks(2).enumerate() {
+            let scale = -2.0 * src_coef[0];
+            dst[i + 2] = scale * dst[i + 1] + 2.0 * dst[i];
+            for j in (2..=i+1).rev() {
+                dst[j] += scale * dst[j - 1] + dst[j - 2];
+            }
+            dst[1] += scale;
+        }
+    }
+}
+
+#[derive(Default)]
+struct QcelpFrame {
+    cb_sign:    [bool; 16],
+    cb_gain:    [u8; 16],
+    cb_index:   [usize; 16],
+    lag:        [u8; 4],
+    lag_frac:   [bool; 4],
+    gain:       [u8; 4],
+    lsp_data:   [usize; ORDER / 2],
+    oct_seed:   u16,
+    cb_seed:    u8,
+}
+
+impl QcelpFrame {
+    fn read(&mut self, src: &[u8], mode: QcelpMode) -> DecoderResult<()> {
+        let mut br = BitReader::new(src, BitReaderMode::BE);
+
+        br.skip(8)?; // mode ID
+        match mode {
+            QcelpMode::Full => {
+                for &(idx, nbits) in [(2u8, 3u8), (1, 7), (0, 6), (4, 6), (3, 6)].iter() {
+                    self.lsp_data[usize::from(idx)] = br.read(nbits)? as usize;
+                }
+                self.lsp_data[2] |= (br.read(4)? as usize) << 3;
+
+                self.cb_sign[0]     = br.read_bool()?;
+                self.cb_gain[0]     = br.read(4)? as u8;
+                self.lag_frac[0]    = br.read_bool()?;
+                self.lag[0]         = br.read(7)? as u8;
+                self.gain[0]        = br.read(3)? as u8;
+                self.cb_index[1]    = br.read(4)? as usize;
+                self.cb_sign[1]     = br.read_bool()?;
+                self.cb_gain[1]     = br.read(4)? as u8;
+                self.cb_index[0]    = br.read(7)? as usize;
+                self.cb_gain[3]     = br.read(1)? as u8;
+                self.cb_index[2]    = br.read(7)? as usize;
+                self.cb_sign[2]     = br.read_bool()?;
+                self.cb_gain[2]     = br.read(4)? as u8;
+                self.cb_index[1]   |= (br.read(3)? as usize) << 4;
+                self.lag[1]         = br.read(3)? as u8;
+                self.gain[1]        = br.read(3)? as u8;
+                self.cb_index[3]    = br.read(7)? as usize;
+                self.cb_sign[3]     = br.read_bool()?;
+                self.cb_gain[3]    |= (br.read(2)? as u8) << 1;
+                self.cb_index[4]    = br.read(6)? as usize;
+                self.cb_sign[4]     = br.read_bool()?;
+                self.cb_gain[4]     = br.read(4)? as u8;
+                self.lag_frac[1]    = br.read_bool()?;
+                self.lag[1]        |= (br.read(4)? as u8) << 3;
+                self.cb_gain[6]     = br.read(3)? as u8;
+                self.cb_index[5]    = br.read(7)? as usize;
+                self.cb_sign[5]     = br.read_bool()?;
+                self.cb_gain[5]     = br.read(4)? as u8;
+                self.cb_index[4]   |= (br.read(1)? as usize) << 6;
+                self.cb_index[7]    = br.read(3)? as usize;
+                self.cb_sign[7]     = br.read_bool()?;
+                self.cb_gain[7]     = br.read(3)? as u8;
+                self.cb_index[6]    = br.read(7)? as usize;
+                self.cb_sign[6]     = br.read_bool()?;
+                self.cb_gain[6]    |= (br.read(1)? as u8) << 3;
+                self.cb_gain[8]     = br.read(1)? as u8;
+                self.lag_frac[2]    = br.read_bool()?;
+                self.lag[2]         = br.read(7)? as u8;
+                self.gain[2]        = br.read(3)? as u8;
+                self.cb_index[7]   |= (br.read(4)? as usize) << 3;
+                self.cb_sign[9]     = br.read_bool()?;
+                self.cb_gain[9]     = br.read(4)? as u8;
+                self.cb_index[8]    = br.read(7)? as usize;
+                self.cb_sign[8]     = br.read_bool()?;
+                self.cb_gain[8]    |= (br.read(3)? as u8) << 1;
+                self.cb_index[10]   = br.read(4)? as usize;
+                self.cb_sign[10]    = br.read_bool()?;
+                self.cb_gain[10]    = br.read(4)? as u8;
+                self.cb_index[9]    = br.read(7)? as usize;
+                self.gain[3]        = br.read(2)? as u8;
+                self.cb_index[11]   = br.read(7)? as usize;
+                self.cb_sign[11]    = br.read_bool()?;
+                self.cb_gain[11]    = br.read(3)? as u8;
+                self.cb_index[10]  |= (br.read(3)? as usize) << 4;
+                self.cb_index[12]   = br.read(2)? as usize;
+                self.cb_sign[12]    = br.read_bool()?;
+                self.cb_gain[12]    = br.read(4)? as u8;
+                self.lag_frac[3]    = br.read_bool()?;
+                self.lag[3]         = br.read(7)? as u8;
+                self.gain[3]       |= (br.read(1)? as u8) << 2;
+                self.cb_index[13]   = br.read(6)? as usize;
+                self.cb_sign[13]    = br.read_bool()?;
+                self.cb_gain[13]    = br.read(4)? as u8;
+                self.cb_index[12]  |= (br.read(5)? as usize) << 2;
+                self.cb_gain[15]    = br.read(3)? as u8;
+                self.cb_index[14]   = br.read(7)? as usize;
+                self.cb_sign[14]    = br.read_bool()?;
+                self.cb_gain[14]    = br.read(4)? as u8;
+                self.cb_index[13]  |= (br.read(1)? as usize) << 6;
+                let reserved        = br.read(2)? as u8;
+                validate!(reserved == 0);
+                self.cb_index[15]   = br.read(7)? as usize;
+                self.cb_sign[15]    = br.read_bool()?;
+            },
+            QcelpMode::Half => {
+                for &(idx, nbits) in [(2u8, 3u8), (1, 7), (0, 6), (4, 6), (3, 6)].iter() {
+                    self.lsp_data[usize::from(idx)] = br.read(nbits)? as usize;
+                }
+                self.lsp_data[2] |= (br.read(4)? as usize) << 3;
+
+                self.cb_sign[0]     = br.read_bool()?;
+                self.cb_gain[0]     = br.read(4)? as u8;
+                self.lag_frac[0]    = br.read_bool()?;
+                self.lag[0]         = br.read(7)? as u8;
+                self.gain[0]        = br.read(3)? as u8;
+                self.lag[1]         = br.read(6)? as u8;
+                self.gain[1]        = br.read(3)? as u8;
+                self.cb_index[0]    = br.read(7)? as usize;
+                self.gain[2]        = br.read(2)? as u8;
+                self.cb_index[1]    = br.read(7)? as usize;
+                self.cb_sign[1]     = br.read_bool()?;
+                self.cb_gain[1]     = br.read(4)? as u8;
+                self.lag_frac[1]    = br.read_bool()?;
+                self.lag[1]        |= (br.read(1)? as u8) << 6;
+                self.cb_index[2]    = br.read(2)? as usize;
+                self.cb_sign[2]     = br.read_bool()?;
+                self.cb_gain[2]     = br.read(4)? as u8;
+                self.lag_frac[2]    = br.read_bool()?;
+                self.lag[2]         = br.read(7)? as u8;
+                self.gain[2]       |= (br.read(1)? as u8) << 2;
+                self.lag_frac[3]    = br.read_bool()?;
+                self.lag[3]         = br.read(7)? as u8;
+                self.gain[3]        = br.read(3)? as u8;
+                self.cb_index[2]   |= (br.read(5)? as usize) << 2;
+                self.cb_index[3]    = br.read(7)? as usize;
+                self.cb_sign[3]     = br.read_bool()?;
+                self.cb_gain[3]     = br.read(4)? as u8;
+            },
+            QcelpMode::Quarter => {
+                self.lsp_data[2] = br.read(3)? as usize;
+                self.lsp_data[1] = br.read(7)? as usize;
+                self.lsp_data[0] = br.read(6)? as usize;
+                self.lsp_data[4] = br.read(6)? as usize;
+                self.lsp_data[3] = br.read(6)? as usize;
+                self.lsp_data[2] |= (br.read(4)? as usize) << 3;
+                self.cb_gain[3] = br.read(4)? as u8;
+                self.cb_gain[2] = br.read(4)? as u8;
+                self.cb_gain[1] = br.read(4)? as u8;
+                self.cb_gain[0] = br.read(4)? as u8;
+                let reserved    = br.read(2)? as u8;
+                validate!(reserved == 0);
+                self.cb_gain[4] = br.read(4)? as u8;
+
+                let mut pdiff = 0;
+                for pair in self.cb_gain[..5].windows(2) {
+                    let diff = (pair[1] as i8) - (pair[0] as i8);
+                    validate!(diff.abs() <= 10);
+                    validate!((diff - pdiff).abs() <= 12);
+                    pdiff = diff;
+                }
+            },
+            QcelpMode::Octavo => {
+                self.oct_seed = br.peek(16) as u16;
+                validate!(self.oct_seed != 0xFFFF);
+                self.cb_seed    |= (br.read(1)? as u8) << 3;
+                self.lsp_data[0] = br.read(1)? as usize;
+                self.lsp_data[1] = br.read(1)? as usize;
+                self.lsp_data[2] = br.read(1)? as usize;
+                self.cb_seed    |= (br.read(1)? as u8) << 2;
+                self.lsp_data[3] = br.read(1)? as usize;
+                self.lsp_data[4] = br.read(1)? as usize;
+                self.lsp_data[5] = br.read(1)? as usize;
+                self.cb_seed    |= (br.read(1)? as u8) << 1;
+                self.lsp_data[6] = br.read(1)? as usize;
+                self.lsp_data[7] = br.read(1)? as usize;
+                self.lsp_data[8] = br.read(1)? as usize;
+                self.cb_seed    |= br.read(1)? as u8;
+                self.lsp_data[9] = br.read(1)? as usize;
+                self.cb_gain[0]  = br.read(2)? as u8;
+                let reserved     = br.read(4)? as u8;
+                validate!(reserved == 0);
+            },
+            QcelpMode::Silence => {},
+        }
+
+        // check for invalid pitch lag
+        if matches!(mode, QcelpMode::Full | QcelpMode::Half) {
+            for (&lag, &lag_frac) in self.lag.iter().zip(self.lag_frac.iter()) {
+                validate!(lag < 124 || !lag_frac);
+            }
+        }
+
+        Ok(())
+    }
+}
+
+trait Rnd {
+    fn next_rnd(&mut self) -> f32;
+}
+
+impl Rnd for u16 {
+    fn next_rnd(&mut self) -> f32 {
+        *self = self.wrapping_mul(521).wrapping_add(259);
+        *self as i16 as f32
+    }
+}
+
+struct QcelpDecoder {
+    ainfo:              NAAudioInfo,
+    chmap:              NAChannelMap,
+    prev_lsp:           LSP,
+    predictor:          LSP,
+    quant_lsp:          LSP,
+    gain:               [f32; 16],
+    prev_gain_idx:      usize,
+    pprev_gain_idx:     usize,
+    last_cb_gain:       f32,
+    rnd:                [f32; BLOCK_SIZE + 20],
+    prev_mode:          QcelpMode,
+    oct_count:          usize,
+    pitch_gain:         [f32; 4],
+    pitch_lag:          [usize; 4],
+    pitch_synth_mem:    [f32; BLOCK_SIZE + MAX_PITCH],
+    pitch_prefilt_mem:  [f32; BLOCK_SIZE + MAX_PITCH],
+    formant_mem:        [f32; BLOCK_SIZE + ORDER],
+    last_lpc:           [f32; ORDER],
+    postfilt_hist:      [f32; ORDER],
+    postfilt_agc:       f32,
+    postfilt_tilt:      f32,
+}
+
+impl QcelpDecoder {
+    fn new() -> Self {
+        Self {
+            ainfo:              NAAudioInfo::new(0, 1, SND_F32P_FORMAT, 1),
+            chmap:              NAChannelMap::new(),
+            prev_lsp:           LSP::new_init(),
+            predictor:          LSP::default(),
+            quant_lsp:          LSP::default(),
+            gain:               [0.0; 16],
+            prev_gain_idx:      0,
+            pprev_gain_idx:     0,
+            last_cb_gain:       0.0,
+            rnd:                [0.0; BLOCK_SIZE + 20],
+            prev_mode:          QcelpMode::Silence,
+            oct_count:          0,
+            pitch_gain:         [0.0; 4],
+            pitch_lag:          [0; 4],
+            pitch_synth_mem:    [0.0; 303],
+            pitch_prefilt_mem:  [0.0; 303],
+            formant_mem:        [0.0; BLOCK_SIZE + ORDER],
+            last_lpc:           [0.0; ORDER],
+            postfilt_hist:      [0.0; ORDER],
+            postfilt_agc:       0.0,
+            postfilt_tilt:      0.0,
+        }
+    }
+    fn decode_gain(&mut self, mode: QcelpMode, frame: &mut QcelpFrame) -> DecoderResult<()> {
+        match mode {
+            QcelpMode::Full | QcelpMode::Half | QcelpMode::Quarter => {
+                let subframes = match mode {
+                        QcelpMode::Full => 16,
+                        QcelpMode::Half => 4,
+                        QcelpMode::Quarter => 5,
+                        _ => unreachable!(),
+                    };
+
+                let mut tmp = [0; 16];
+                for (i, gain) in self.gain[..subframes].iter_mut().enumerate() {
+                    tmp[i] = usize::from(frame.cb_gain[i]) * 4;
+                    if mode == QcelpMode::Full && ((i & 3) == 3) {
+                        tmp[i] += ((tmp[i - 1] + tmp[i - 2] + tmp[i - 3]) / 3).saturating_sub(6).min(32);
+                    }
+                    validate!(tmp[i] < QCELP_GAINS.len());
+                    *gain = QCELP_GAINS[tmp[i]];
+                    if frame.cb_sign[i] {
+                        *gain = -*gain;
+                        frame.cb_index[i]  = frame.cb_index[i].wrapping_sub(89) & 0x7F;
+                    }
+                }
+                self.pprev_gain_idx = tmp[subframes - 2];
+                self.prev_gain_idx  = tmp[subframes - 1];
+                self.last_cb_gain = QCELP_GAINS[self.prev_gain_idx];
+
+                if mode == QcelpMode::Quarter {
+                    self.gain[7] = self.gain[4];
+                    self.gain[6] = self.gain[4] * 0.6 + self.gain[3] * 0.4;
+                    self.gain[5] = self.gain[3];
+                    self.gain[4] = self.gain[3] * 0.2 + self.gain[2] * 0.8;
+                    self.gain[3] = self.gain[2] * 0.8 + self.gain[1] * 0.2;
+                    self.gain[2] = self.gain[1];
+                    self.gain[1] = self.gain[1] * 0.4 + self.gain[0] * 0.6;
+                }
+            },
+            QcelpMode::Octavo => {
+                let idx = 2 * usize::from(frame.cb_gain[0]) + ((self.prev_gain_idx + self.pprev_gain_idx) / 2).saturating_sub(5).max(54);
+
+                let delta = (QCELP_GAINS[self.pprev_gain_idx] - self.last_cb_gain) / 8.0;
+                for (i, gain) in self.gain[..=8].iter_mut().enumerate().skip(1) {
+                    *gain = self.last_cb_gain + delta * (i as f32);
+                }
+                self.last_cb_gain = self.gain[7];
+                self.pprev_gain_idx = self.prev_gain_idx;
+                self.prev_gain_idx = idx;
+            },
+            QcelpMode::Silence => {},
+        }
+        Ok(())
+    }
+    fn compute_cb_vector(&mut self, mode: QcelpMode, frame: &QcelpFrame, dst: &mut [f32]) {
+        match mode {
+            QcelpMode::Full => {
+                for ((&gain, &cb_idx), seg) in self.gain.iter().zip(frame.cb_index.iter())
+                        .zip(dst.chunks_exact_mut(10)).take(16) {
+                    let scale = gain * 0.01;
+                    let mut idx = (!cb_idx).wrapping_add(1);
+                    for el in seg.iter_mut() {
+                        *el = scale * (QCELP_FULL_MODE_CB[idx & 0x7F] as f32);
+                        idx = idx.wrapping_add(1);
+                    }
+                }
+            },
+            QcelpMode::Half => {
+                for ((&gain, &cb_idx), seg) in self.gain.iter().zip(frame.cb_index.iter())
+                        .zip(dst.chunks_exact_mut(40)).take(4) {
+                    let scale = gain * 0.5;
+                    let mut idx = (!cb_idx).wrapping_add(1);
+                    for el in seg.iter_mut() {
+                        *el = scale * (QCELP_HALF_MODE_CB[idx & 0x7F] as f32);
+                        idx = idx.wrapping_add(1);
+                    }
+                }
+            },
+            QcelpMode::Quarter => {
+                let mut seed = (((frame.lsp_data[4] & 0x03) << 14) |
+                                ((frame.lsp_data[3] & 0x3F) <<  8) |
+                                ((frame.lsp_data[2] & 0x60) <<  1) |
+                                ((frame.lsp_data[1] & 0x07) <<  3) |
+                                ((frame.lsp_data[0] & 0x38) >>  3)) as u16;
+
+                let mut rnd_idx = 20;
+                for (&gain, seg) in self.gain.iter().zip(dst.chunks_exact_mut(20)) {
+                    let scale = gain * 1.373681186 / 32768.0;
+                    for el in seg.iter_mut() {
+                        self.rnd[rnd_idx] = seed.next_rnd();
+
+                        let mut sum = self.rnd[rnd_idx - 20..].iter()
+                                .zip(self.rnd[..rnd_idx].iter().rev())
+                                .zip(RND_FIR_COEFFS.iter())
+                                .fold(0.0f32, |acc, ((&a, &b), &coef)| acc + coef * (a + b));
+                        sum += RND_FIR_COEF10 * self.rnd[rnd_idx - 10];
+                        *el = scale * sum;
+                        rnd_idx += 1;
+                    }
+                }
+                let (head, tail) = self.rnd.split_at_mut(20);
+                head.copy_from_slice(&tail[BLOCK_SIZE - 20..]);
+            },
+            QcelpMode::Octavo => {
+                let mut seed = frame.oct_seed;
+                for (&gain, seg) in self.gain.iter().zip(dst.chunks_exact_mut(40)).take(8) {
+                    let scale = gain * 1.373681186 / 32768.0;
+                    for el in seg.iter_mut() {
+                        *el = scale * seed.next_rnd();
+                    }
+                }
+            },
+            QcelpMode::Silence => {},
+        }
+    }
+    #[allow(clippy::neg_cmp_op_on_partial_ord)]
+    fn decode_lsp(&mut self, mode: QcelpMode, frame: &QcelpFrame) -> DecoderResult<()> {
+        const SPREAD_FACTOR: f32 = 0.02;
+        const OCTAVO_PRED_WEIGHT: f32 = 29.0 / 32.0;
+        match mode {
+            QcelpMode::Full | QcelpMode::Half | QcelpMode::Quarter => {
+                self.quant_lsp = LSP::from_cb(&frame.lsp_data);
+
+                let lsp = &self.quant_lsp;
+                if mode != QcelpMode::Quarter {
+                    validate!(lsp.data[9] > 0.66 && lsp.data[9] < 0.985);
+                    for i in 4..10 {
+                        validate!((lsp.data[i] - lsp.data[i - 4]).abs() >= 0.0931);
+                    }
+                } else {
+                    validate!(lsp.data[9] > 0.80 && lsp.data[9] < 0.97);
+                    for i in 3..10 {
+                        validate!((lsp.data[i] - lsp.data[i - 2]).abs() >= 0.08);
+                    }
+                }
+            },
+            QcelpMode::Octavo => {
+                let src = if self.prev_mode == QcelpMode::Octavo { &mut self.predictor } else { &mut self.prev_lsp };
+                let mut lsp = LSP::new_init();
+                for ((el, &src), &f_lsp) in lsp.data.iter_mut()
+                        .zip(src.data.iter()).zip(frame.lsp_data.iter()) {
+                    *el = (if f_lsp != 0 { SPREAD_FACTOR } else { -SPREAD_FACTOR }) +
+                          src * OCTAVO_PRED_WEIGHT + *el * (1.0 - OCTAVO_PRED_WEIGHT);
+                }
+                self.predictor = lsp;
+
+                let smooth = if self.oct_count < 10 { 0.875 } else { 0.1 };
+                lsp.data[0] = lsp.data[0].max(SPREAD_FACTOR);
+                for i in 1..10 {
+                    lsp.data[i] = lsp.data[i].max(lsp.data[i - 1] + SPREAD_FACTOR);
+                }
+                lsp.data[9] = lsp.data[9].min(1.0 - SPREAD_FACTOR);
+                for i in (1..10).rev() {
+                    lsp.data[i - 1] = lsp.data[i - 1].max(lsp.data[i] - SPREAD_FACTOR);
+                }
+
+                lsp.blend(&self.prev_lsp, smooth);
+                self.quant_lsp = lsp;
+            },
+            QcelpMode::Silence => {},
+        }
+        Ok(())
+    }
+    fn apply_pitch_filters(&mut self, mode: QcelpMode, frame: &mut QcelpFrame, buf: &mut [f32]) {
+        match mode {
+            QcelpMode::Full | QcelpMode::Half => {
+                for ((pgain, plag), (&lag, &gain)) in self.pitch_gain.iter_mut()
+                        .zip(self.pitch_lag.iter_mut())
+                        .zip(frame.lag.iter().zip(frame.gain.iter())) {
+                    *pgain = if lag > 0 { ((gain + 1) as f32) * 0.25 } else { 0.0 };
+                    *plag  = usize::from(lag) + 16;
+                }
+            },
+            QcelpMode::Quarter | QcelpMode::Octavo => {
+                self.pitch_synth_mem[..MAX_PITCH].copy_from_slice(&buf[BLOCK_SIZE - MAX_PITCH..]);
+                self.pitch_prefilt_mem[..MAX_PITCH].copy_from_slice(&buf[BLOCK_SIZE - MAX_PITCH..]);
+                self.pitch_gain = [0.0; 4];
+                self.pitch_lag = [0; 4];
+                return;
+            },
+            QcelpMode::Silence => {
+                for el in self.pitch_gain.iter_mut() {
+                    *el = el.min(1.0);
+                }
+                frame.lag_frac = [false; 4];
+            },
+        }
+
+        Self::pitch_filter(&mut self.pitch_synth_mem, buf, &self.pitch_gain, &self.pitch_lag, &frame.lag_frac);
+
+        for gain in self.pitch_gain.iter_mut() {
+            *gain = gain.min(1.0) * 0.5;
+        }
+
+        Self::pitch_filter(&mut self.pitch_prefilt_mem, &self.pitch_synth_mem[MAX_PITCH..], &self.pitch_gain, &self.pitch_lag, &frame.lag_frac);
+
+        for (dst, (src1, src2)) in buf.chunks_exact_mut(BLOCK_SIZE / 4)
+                .zip(self.pitch_synth_mem[MAX_PITCH..].chunks_exact(BLOCK_SIZE / 4)
+                    .zip(self.pitch_prefilt_mem[MAX_PITCH..].chunks_exact(BLOCK_SIZE / 4))) {
+            let energy1 = src1.iter().fold(0.0f32, |acc, &a| acc + a * a);
+            let energy2 = src2.iter().fold(0.0f32, |acc, &a| acc + a * a);
+            let scale = (energy1 / energy2).sqrt();
+            for (el, &src) in dst.iter_mut().zip(src2.iter()) {
+                *el = src * scale;
+            }
+        }
+    }
+    fn pitch_filter(mem: &mut [f32; BLOCK_SIZE + MAX_PITCH], src: &[f32], gain: &[f32; 4], lags: &[usize; 4], pitch_frac: &[bool; 4]) {
+        const HAMMSINC_FILTER: [f32; 4] = [ -0.006822,  0.041249, -0.143459,  0.588863 ];
+
+        let mut out_idx = MAX_PITCH;
+        for (sf, in_blk) in src.chunks_exact(BLOCK_SIZE / 4).enumerate() {
+            if gain[sf] != 0.0 {
+                let lag_idx = out_idx - lags[sf];
+                if !pitch_frac[sf] {
+                    for (i, &src) in in_blk.iter().enumerate() {
+                        mem[out_idx + i] = mem[lag_idx + i] * gain[sf] + src;
+                    }
+                } else {
+                    for (i, &src) in in_blk.iter().enumerate() {
+                        let sum = mem[lag_idx + i - 4..].iter()
+                                    .zip(mem[..lag_idx + i + 4].iter().rev())
+                                    .zip(HAMMSINC_FILTER.iter())
+                                    .fold(0.0f32, |acc, ((&a, &b), &coef)| acc + (a + b) * coef);
+                        mem[out_idx + i] = sum * gain[sf] + src;
+                    }
+                }
+            } else {
+                mem[out_idx..][..BLOCK_SIZE / 4].copy_from_slice(in_blk);
+            }
+            out_idx += BLOCK_SIZE / 4;
+        }
+
+        let (head, tail) = mem.split_at_mut(BLOCK_SIZE);
+        head[..MAX_PITCH].copy_from_slice(tail);
+    }
+    fn interpolate_lpc(&mut self, mode: QcelpMode, sf: usize) -> [f32; ORDER] {
+        let weight = match mode {
+                QcelpMode::Full | QcelpMode::Half | QcelpMode::Quarter => ((sf + 1) as f32) * 0.25,
+                QcelpMode::Octavo if sf == 0 => 0.625,
+                _ => 1.0,
+            };
+
+        if weight != 1.0 {
+            let mut lsp = self.quant_lsp;
+            lsp.blend(&self.prev_lsp, weight);
+            lsp.get_lpc()
+        } else if matches!(mode, QcelpMode::Full | QcelpMode::Half | QcelpMode::Quarter) {
+            self.quant_lsp.get_lpc()
+        } else if mode == QcelpMode::Silence && sf == 0 {
+            self.prev_lsp.get_lpc()
+        } else {
+            self.last_lpc
+        }
+    }
+    fn postfilter(&mut self, buf: &mut [f32]) {
+        let mut lpc_z = self.last_lpc;
+        let mut lpc_p = self.last_lpc;
+        for (dst, &scale) in lpc_z.iter_mut().zip(POW_0_625.iter()) {
+            *dst *= scale;
+        }
+        for (dst, &scale) in lpc_p.iter_mut().zip(POW_0_775.iter()) {
+            *dst *= scale;
+        }
+
+        let mut zero_out = [0.0; BLOCK_SIZE];
+        for (i, dst) in zero_out.iter_mut().enumerate() {
+            *dst = self.formant_mem[i + ORDER];
+            for (j, &coef) in lpc_z.iter().enumerate() {
+                *dst += coef * self.formant_mem[i + ORDER - j - 1];
+            }
+        }
+
+        let mut pole_out = [0.0; BLOCK_SIZE + ORDER];
+        pole_out[..ORDER].copy_from_slice(&self.postfilt_hist);
+        for (i, &src) in zero_out.iter().enumerate() {
+            pole_out[i + ORDER] = src;
+            for (j, &coef) in lpc_p.iter().enumerate() {
+                pole_out[i + ORDER] -= coef * pole_out[i + ORDER - 1 - j];
+            }
+        }
+        self.postfilt_hist.copy_from_slice(&pole_out[BLOCK_SIZE..]);
+
+        let tilt_hist = pole_out[pole_out.len() - 1];
+        let tilt = 0.3;
+        for i in (1..BLOCK_SIZE).rev() {
+            pole_out[ORDER + i] -= tilt * pole_out[ORDER + i - 1];
+        }
+        pole_out[ORDER] -= tilt * self.postfilt_tilt;
+        self.postfilt_tilt = tilt_hist;
+
+        const AGC_ALPHA: f32 = 0.9375;
+        let in_energy = pole_out[ORDER..].iter().fold(0.0f32, |acc, &a| acc + a * a);
+        let f_energy = self.formant_mem[ORDER..].iter().fold(0.0f32, |acc, &a| acc + a * a);
+        let mut agc = self.postfilt_agc;
+
+        let gain_factor = (if in_energy > 0.0 { (f_energy / in_energy).sqrt() } else { 1.0 }) * (1.0 - AGC_ALPHA);
+        for (dst, &src) in buf.iter_mut().zip(pole_out[ORDER..].iter()) {
+            agc = agc * AGC_ALPHA + gain_factor;
+            *dst = src * agc;
+        }
+        self.postfilt_agc = agc;
+    }
+}
+
+impl NADecoder for QcelpDecoder {
+    fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> {
+        if let NACodecTypeInfo::Audio(ainfo) = info.get_properties() {
+            self.ainfo = NAAudioInfo::new(ainfo.sample_rate, 1, SND_F32P_FORMAT, BLOCK_SIZE);
+            Ok(())
+        } else {
+            Err(DecoderError::InvalidData)
+        }
+    }
+    fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult<NAFrameRef> {
+        let info = pkt.get_stream().get_info();
+        if let NACodecTypeInfo::Audio(_) = info.get_properties() {
+            let pktbuf = pkt.get_buffer();
+
+            let abuf = alloc_audio_buffer(self.ainfo, BLOCK_SIZE, self.chmap.clone())?;
+            let mut adata = abuf.get_abuf_f32().unwrap();
+            let dst = adata.get_data_mut().unwrap();
+
+            let mode = get_mode(&pktbuf)?;
+            let mut frame = QcelpFrame::default();
+            frame.read(&pktbuf, mode)?;
+            self.decode_gain(mode, &mut frame)?;
+            self.compute_cb_vector(mode, &frame, dst);
+            self.decode_lsp(mode, &frame)?;
+            self.apply_pitch_filters(mode, &mut frame, dst);
+
+            let mut fpos = 10;
+            for (sf, data) in dst.chunks_exact_mut(BLOCK_SIZE / 4).enumerate() {
+                let lpc = self.interpolate_lpc(mode, sf);
+                self.last_lpc = lpc;
+                for (i, &src) in data.iter().enumerate() {
+                    self.formant_mem[fpos + i] = src;
+                    for (j, &coef) in lpc.iter().enumerate() {
+                        self.formant_mem[fpos + i] -= coef * self.formant_mem[fpos + i - 1 - j];
+                    }
+                }
+                fpos += BLOCK_SIZE / 4;
+            }
+
+            self.postfilter(dst);
+
+            let (head, tail) = self.formant_mem.split_at_mut(ORDER);
+            head.copy_from_slice(&tail[BLOCK_SIZE - ORDER..]);
+
+            self.prev_lsp = self.quant_lsp;
+
+            self.prev_mode = mode;
+            if mode == QcelpMode::Octavo {
+                self.oct_count += 1;
+            } else {
+                self.oct_count = 0;
+            }
+
+            let mut frm = NAFrame::new_from_pkt(pkt, info.replace_info(NACodecTypeInfo::Audio(self.ainfo)), abuf);
+            frm.set_duration(Some(BLOCK_SIZE as u64));
+            frm.set_keyframe(false);
+            Ok(frm.into_ref())
+        } else {
+            Err(DecoderError::InvalidData)
+        }
+    }
+    fn flush(&mut self) {
+    }
+}
+
+impl NAOptionHandler for QcelpDecoder {
+    fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] }
+    fn set_options(&mut self, _options: &[NAOption]) { }
+    fn query_option_value(&self, _name: &str) -> Option<NAValue> { None }
+}
+
+pub fn get_decoder() -> Box<dyn NADecoder + Send> {
+    Box::new(QcelpDecoder::new())
+}
+
+#[cfg(test)]
+mod test {
+    use nihav_core::codecs::RegisteredDecoders;
+    use nihav_core::demuxers::RegisteredDemuxers;
+    use nihav_codec_support::test::dec_video::*;
+    use crate::qt_register_all_decoders;
+    use nihav_commonfmt::generic_register_all_demuxers;
+    #[test]
+    fn test_qcelp() {
+        let mut dmx_reg = RegisteredDemuxers::new();
+        generic_register_all_demuxers(&mut dmx_reg);
+        let mut dec_reg = RegisteredDecoders::new();
+        qt_register_all_decoders(&mut dec_reg);
+
+        // sample: https://samples.mplayerhq.hu/A-codecs/qclp/oldepisode.mov
+        test_decoding("mov", "qualcomm-purevoice", "assets/QT/oldepisode.mov", Some(32), &dmx_reg, &dec_reg,
+                      ExpectedTestResult::Decodes);
+    }
+}
+
+const QCELP_GAINS: [f32; 61] = [
+    1.000 / 8192.0,   1.125 / 8192.0,   1.250 / 8192.0,   1.375 / 8192.0,
+    1.625 / 8192.0,   1.750 / 8192.0,   2.000 / 8192.0,   2.250 / 8192.0,
+    2.500 / 8192.0,   2.875 / 8192.0,   3.125 / 8192.0,   3.500 / 8192.0,
+    4.000 / 8192.0,   4.500 / 8192.0,   5.000 / 8192.0,   5.625 / 8192.0,
+    6.250 / 8192.0,   7.125 / 8192.0,   8.000 / 8192.0,   8.875 / 8192.0,
+   10.000 / 8192.0,  11.250 / 8192.0,  12.625 / 8192.0,  14.125 / 8192.0,
+   15.875 / 8192.0,  17.750 / 8192.0,  20.000 / 8192.0,  22.375 / 8192.0,
+   25.125 / 8192.0,  28.125 / 8192.0,  31.625 / 8192.0,  35.500 / 8192.0,
+   39.750 / 8192.0,  44.625 / 8192.0,  50.125 / 8192.0,  56.250 / 8192.0,
+   63.125 / 8192.0,  70.750 / 8192.0,  79.375 / 8192.0,  89.125 / 8192.0,
+  100.000 / 8192.0, 112.250 / 8192.0, 125.875 / 8192.0, 141.250 / 8192.0,
+  158.500 / 8192.0, 177.875 / 8192.0, 199.500 / 8192.0, 223.875 / 8192.0,
+  251.250 / 8192.0, 281.875 / 8192.0, 316.250 / 8192.0, 354.875 / 8192.0,
+  398.125 / 8192.0, 446.625 / 8192.0, 501.125 / 8192.0, 562.375 / 8192.0,
+  631.000 / 8192.0, 708.000 / 8192.0, 794.375 / 8192.0, 891.250 / 8192.0,
+ 1000.000 / 8192.0
+];
+
+const QCELP_FULL_MODE_CB: [i16; 128] = [
+     10,  -65,  -59,   12,  110,   34, -134,  157,
+    104,  -84,  -34, -115,   23, -101,    3,   45,
+   -101,  -16,  -59,   28,  -45,  134,  -67,   22,
+     61,  -29,  226,  -26,  -55, -179,  157,  -51,
+   -220,  -93,  -37,   60,  118,   74,  -48,  -95,
+   -181,  111,   36,  -52, -215,   78, -112,   39,
+    -17,  -47, -223,   19,   12,  -98, -142,  130,
+     54, -127,   21,  -12,   39,  -48,   12,  128,
+      6, -167,   82, -102,  -79,   55,  -44,   48,
+    -20,  -53,    8,  -61,   11,  -70, -157, -168,
+     20,  -56,  -74,   78,   33,  -63, -173,   -2,
+    -75,  -53, -146,   77,   66,  -29,    9,  -75,
+     65,  119,  -43,   76,  233,   98,  125, -156,
+    -27,   78,   -9,  170,  176,  143, -148,   -7,
+     27, -136,    5,   27,   18,  139,  204,    7,
+   -184, -197,   52,   -3,   78, -189,    8,  -65
+];
+const QCELP_HALF_MODE_CB: [i16; 128] = [
+     0, -4,  0, -3,  0,  0,  0,  0,
+     0,  0,  0,  0,  0,  0,  0,  0,
+     0, -3, -2,  0,  0,  0,  0,  0,
+     0,  0,  0,  0,  0,  0,  0,  5,
+     0,  0,  0,  0,  0,  0,  4,  0,
+     0,  3,  2,  0,  3,  4,  0,  0,
+     0,  0,  0,  0,  0,  0,  0,  0,
+     0,  0,  0,  0,  0,  3,  0,  0,
+    -3,  3,  0,  0, -2,  0,  3,  0,
+     0,  0,  0,  0,  0,  0, -5,  0,
+     0,  0,  0,  3,  0,  0,  0,  3,
+     0,  0,  0,  0,  0,  0,  0,  4,
+     0,  0,  0,  0,  0,  0,  0,  0,
+     0,  3,  6, -3, -4,  0, -3, -3,
+     3, -3,  0,  0,  0,  0,  0,  0,
+     0,  0,  0,  0,  0,  0,  0,  0
+];
+
+const RND_FIR_COEFFS: [f32; 10] = [
+    -0.1344519,  0.01735384, -0.06905826, 0.02434368,
+    -0.08210701, 0.03041388, -0.09251384, 0.03501983,
+    -0.09918777, 0.03749518
+];
+const RND_FIR_COEF10: f32 = 0.8985137;
+
+const LSP_CB0: [[i16; 2]; 64] = [
+    [  327,  118 ], [  919,  111 ], [  427,  440 ], [ 1327,  185 ],
+    [  469,   50 ], [ 1272,   91 ], [  892,   59 ], [ 1771,  193 ],
+    [  222,  158 ], [ 1100,  127 ], [  827,   55 ], [  978,  791 ],
+    [  665,   47 ], [  700, 1401 ], [  670,  859 ], [ 1913, 1048 ],
+    [  471,  215 ], [ 1046,  125 ], [  645,  298 ], [ 1599,  160 ],
+    [  593,   39 ], [ 1187,  462 ], [  749,  341 ], [ 1520,  511 ],
+    [  290,  792 ], [  909,  362 ], [  753,   81 ], [ 1111, 1058 ],
+    [  519,  253 ], [  828,  839 ], [  685,  541 ], [ 1421, 1258 ],
+    [  386,  130 ], [  962,  119 ], [  542,  387 ], [ 1431,  185 ],
+    [  526,   51 ], [ 1175,  260 ], [  831,  167 ], [ 1728,  510 ],
+    [  273,  437 ], [ 1172,  113 ], [  771,  144 ], [ 1122,  751 ],
+    [  619,  119 ], [  492, 1276 ], [  658,  695 ], [ 1882,  615 ],
+    [  415,  200 ], [ 1018,   88 ], [  681,  339 ], [ 1436,  325 ],
+    [  555,  122 ], [ 1042,  485 ], [  826,  345 ], [ 1374,  743 ],
+    [  383, 1018 ], [ 1005,  358 ], [  704,   86 ], [ 1301,  586 ],
+    [  597,  241 ], [  832,  621 ], [  555,  573 ], [ 1504,  839 ]
+];
+const LSP_CB1: [[i16; 2]; 128] = [
+    [  255,  293 ], [  904,  219 ], [  151, 1211 ], [ 1447,  498 ],
+    [  470,  253 ], [ 1559,  177 ], [ 1547,  994 ], [ 2394,  242 ],
+    [   91,  813 ], [  857,  590 ], [  934, 1326 ], [ 1889,  282 ],
+    [  813,  472 ], [ 1057, 1494 ], [  450, 3315 ], [ 2163, 1895 ],
+    [  538,  532 ], [ 1399,  218 ], [  146, 1552 ], [ 1755,  626 ],
+    [  822,  202 ], [ 1299,  663 ], [  706, 1732 ], [ 2656,  401 ],
+    [  418,  745 ], [  762, 1038 ], [  583, 1748 ], [ 1746, 1285 ],
+    [  527, 1169 ], [ 1314,  830 ], [  556, 2116 ], [ 1073, 2321 ],
+    [  297,  570 ], [  981,  403 ], [  468, 1103 ], [ 1740,  243 ],
+    [  725,  179 ], [ 1255,  474 ], [ 1374, 1362 ], [ 1922,  912 ],
+    [  285,  947 ], [  930,  700 ], [  593, 1372 ], [ 1909,  576 ],
+    [  588,  916 ], [ 1110, 1116 ], [  224, 2719 ], [ 1633, 2220 ],
+    [  402,  520 ], [ 1061,  448 ], [  402, 1352 ], [ 1499,  775 ],
+    [  664,  589 ], [ 1081,  727 ], [  801, 2206 ], [ 2165, 1157 ],
+    [  566,  802 ], [  911, 1116 ], [  306, 1703 ], [ 1792,  836 ],
+    [  655,  999 ], [ 1061, 1038 ], [  298, 2089 ], [ 1110, 1753 ],
+    [  361,  311 ], [  970,  239 ], [  265, 1231 ], [ 1495,  573 ],
+    [  566,  262 ], [ 1569,  293 ], [ 1341, 1144 ], [ 2271,  544 ],
+    [  214,  877 ], [  847,  719 ], [  794, 1384 ], [ 2067,  274 ],
+    [  703,  688 ], [ 1099, 1306 ], [  391, 2947 ], [ 2024, 1670 ],
+    [  471,  525 ], [ 1245,  290 ], [  264, 1557 ], [ 1568,  807 ],
+    [  718,  399 ], [ 1193,  685 ], [  883, 1594 ], [ 2729,  764 ],
+    [  500,  754 ], [  809, 1108 ], [  541, 1648 ], [ 1523, 1385 ],
+    [  614, 1196 ], [ 1209,  847 ], [  345, 2242 ], [ 1442, 1747 ],
+    [  199,  560 ], [ 1092,  194 ], [  349, 1253 ], [ 1653,  507 ],
+    [  625,  354 ], [ 1376,  431 ], [ 1187, 1465 ], [ 2164,  872 ],
+    [  360,  974 ], [ 1008,  698 ], [  704, 1346 ], [ 2114,  452 ],
+    [  720,  816 ], [ 1240, 1089 ], [  439, 2475 ], [ 1498, 2040 ],
+    [  336,  718 ], [ 1213,  187 ], [  451, 1450 ], [ 1368,  885 ],
+    [  592,  578 ], [ 1131,  531 ], [  861, 1855 ], [ 1764, 1500 ],
+    [  444,  970 ], [  935,  903 ], [  424, 1687 ], [ 1633, 1102 ],
+    [  793,  897 ], [ 1060,  897 ], [  185, 2011 ], [ 1205, 1855 ]
+];
+const LSP_CB2: [[i16; 2]; 128] = [
+    [  225,  283 ], [ 1296,  355 ], [  543,  343 ], [ 2073,  274 ],
+    [  204, 1099 ], [ 1562,  523 ], [ 1388,  161 ], [ 2784,  274 ],
+    [  112,  849 ], [ 1870,  175 ], [ 1189,  160 ], [ 1490, 1088 ],
+    [  969, 1115 ], [  659, 3322 ], [ 1158, 1073 ], [ 3183, 1363 ],
+    [  517,  223 ], [ 1740,  223 ], [  704,  387 ], [ 2637,  234 ],
+    [  692, 1005 ], [ 1287, 1610 ], [  952,  532 ], [ 2393,  646 ],
+    [  490,  552 ], [ 1619,  657 ], [  845,  670 ], [ 1784, 2280 ],
+    [  191, 1775 ], [  272, 2868 ], [  942,  952 ], [ 2628, 1479 ],
+    [  278,  579 ], [ 1565,  218 ], [  814,  180 ], [ 2379,  187 ],
+    [  276, 1444 ], [ 1199, 1223 ], [ 1200,  349 ], [ 3009,  307 ],
+    [  312,  844 ], [ 1898,  306 ], [  863,  470 ], [ 1685, 1241 ],
+    [  513, 1727 ], [  711, 2233 ], [ 1085,  864 ], [ 3398,  527 ],
+    [  414,  440 ], [ 1356,  612 ], [  964,  147 ], [ 2173,  738 ],
+    [  465, 1292 ], [  877, 1749 ], [ 1104,  689 ], [ 2105, 1311 ],
+    [  580,  864 ], [ 1895,  752 ], [  652,  609 ], [ 1485, 1699 ],
+    [  514, 1400 ], [  386, 2131 ], [  933,  798 ], [ 2473,  986 ],
+    [  334,  360 ], [ 1375,  398 ], [  621,  276 ], [ 2183,  280 ],
+    [  311, 1114 ], [ 1382,  807 ], [ 1284,  175 ], [ 2605,  636 ],
+    [  230,  816 ], [ 1739,  408 ], [ 1074,  176 ], [ 1619, 1120 ],
+    [  784, 1371 ], [  448, 3050 ], [ 1189,  880 ], [ 3039, 1165 ],
+    [  424,  241 ], [ 1672,  186 ], [  815,  333 ], [ 2432,  324 ],
+    [  584, 1029 ], [ 1137, 1546 ], [ 1015,  585 ], [ 2198,  995 ],
+    [  574,  581 ], [ 1746,  647 ], [  733,  740 ], [ 1938, 1737 ],
+    [  347, 1710 ], [  373, 2429 ], [  787, 1061 ], [ 2439, 1438 ],
+    [  185,  536 ], [ 1489,  178 ], [  703,  216 ], [ 2178,  487 ],
+    [  154, 1421 ], [ 1414,  994 ], [ 1103,  352 ], [ 3072,  473 ],
+    [  408,  819 ], [ 2055,  168 ], [  998,  354 ], [ 1917, 1140 ],
+    [  665, 1799 ], [  993, 2213 ], [ 1234,  631 ], [ 3003,  762 ],
+    [  373,  620 ], [ 1518,  425 ], [  913,  300 ], [ 1966,  836 ],
+    [  402, 1185 ], [  948, 1385 ], [ 1121,  555 ], [ 1802, 1509 ],
+    [  474,  886 ], [ 1888,  610 ], [  739,  585 ], [ 1231, 2379 ],
+    [  661, 1335 ], [  205, 2211 ], [  823,  822 ], [ 2480, 1179 ]
+];
+const LSP_CB3: [[i16; 2]; 64] = [
+    [  348,  311 ], [  812, 1145 ], [  552,  461 ], [ 1826,  263 ],
+    [  601,  675 ], [ 1730,  172 ], [ 1523,  193 ], [ 2449,  277 ],
+    [  334,  668 ], [  805, 1441 ], [ 1319,  207 ], [ 1684,  910 ],
+    [  582, 1318 ], [ 1403, 1098 ], [  979,  832 ], [ 2700, 1359 ],
+    [  624,  228 ], [ 1292,  979 ], [  800,  195 ], [ 2226,  285 ],
+    [  730,  862 ], [ 1537,  601 ], [ 1115,  509 ], [ 2720,  354 ],
+    [  218, 1167 ], [ 1212, 1538 ], [ 1074,  247 ], [ 1674, 1710 ],
+    [  322, 2142 ], [ 1263,  777 ], [  981,  556 ], [ 2119, 1710 ],
+    [  193,  596 ], [ 1035,  957 ], [  694,  397 ], [ 1997,  253 ],
+    [  743,  603 ], [ 1584,  321 ], [ 1346,  346 ], [ 2221,  708 ],
+    [  451,  732 ], [ 1040, 1415 ], [ 1184,  230 ], [ 1853,  919 ],
+    [  310, 1661 ], [ 1625,  706 ], [  856,  843 ], [ 2902,  702 ],
+    [  467,  348 ], [ 1108, 1048 ], [  859,  306 ], [ 1964,  463 ],
+    [  560, 1013 ], [ 1425,  533 ], [ 1142,  634 ], [ 2391,  879 ],
+    [  397, 1084 ], [ 1345, 1700 ], [  976,  248 ], [ 1887, 1189 ],
+    [  644, 2087 ], [ 1262,  603 ], [  877,  550 ], [ 2203, 1307 ]
+];
+const LSP_CB4: [[i16; 2]; 64] = [
+    [  360,  222 ], [  820, 1097 ], [  601,  319 ], [ 1656,  198 ],
+    [  604,  513 ], [ 1552,  141 ], [ 1391,  155 ], [ 2474,  261 ],
+    [  269,  785 ], [ 1463,  646 ], [ 1123,  191 ], [ 2015,  223 ],
+    [  785,  844 ], [ 1202, 1011 ], [  980,  807 ], [ 3014,  793 ],
+    [  570,  180 ], [ 1135, 1382 ], [  778,  256 ], [ 1901,  179 ],
+    [  807,  622 ], [ 1461,  458 ], [ 1231,  178 ], [ 2028,  821 ],
+    [  387,  927 ], [ 1496, 1004 ], [  888,  392 ], [ 2246,  341 ],
+    [  295, 1462 ], [ 1156,  694 ], [ 1022,  473 ], [ 2226, 1364 ],
+    [  210,  478 ], [ 1029, 1020 ], [  722,  181 ], [ 1730,  251 ],
+    [  730,  488 ], [ 1465,  293 ], [ 1303,  326 ], [ 2595,  387 ],
+    [  458,  584 ], [ 1569,  742 ], [ 1029,  173 ], [ 1910,  495 ],
+    [  605, 1159 ], [ 1268,  719 ], [  973,  646 ], [ 2872,  428 ],
+    [  443,  334 ], [  835, 1465 ], [  912,  138 ], [ 1716,  442 ],
+    [  620,  778 ], [ 1316,  450 ], [ 1186,  335 ], [ 1446, 1665 ],
+    [  486, 1050 ], [ 1675, 1019 ], [  880,  278 ], [ 2214,  202 ],
+    [  539, 1564 ], [ 1142,  533 ], [  984,  391 ], [ 2130, 1089 ]
+];
+const LSP_CBS: [&[[i16; 2]]; 5] = [&LSP_CB0, &LSP_CB1, &LSP_CB2, &LSP_CB3, &LSP_CB4];
+
+const POW_0_775: [f32; ORDER] = [
+    0.775000, 0.600625, 0.465484, 0.360750, 0.279582,
+    0.216676, 0.167924, 0.130141, 0.100859, 0.078166
+];
+const POW_0_625: [f32; ORDER] = [
+    0.625000, 0.390625, 0.244141, 0.152588, 0.095367,
+    0.059605, 0.037253, 0.023283, 0.014552, 0.009095
+];