use super::decoder_tables::{DGRAY, START_POS_2I40_9};
use super::{L_SUBFR, SHARPMAX};
use crate::fixed_point::arith::{add, extract_l, mult, negate, sub};
use crate::fixed_point::arith32::l_mult;
use crate::fixed_point::shift::{l_shr, shl, shr};
use crate::fixed_point::types::{DspContext, Word16};
pub type Codevector = [Word16; L_SUBFR];
const POSITIVE_PULSE: Word16 = Word16(8191);
const NEGATIVE_PULSE: Word16 = Word16(-8192);
const PULSE_MR102: Word16 = Word16(8191);
const PULSE_MR102_NEG: Word16 = Word16(-8191);
const PULSE_MR122: Word16 = Word16(4096);
const PULSE_MR122_NEG: Word16 = Word16(-4096);
const STEP_MR102: i16 = 4;
const TRACKS_MR102: usize = 4;
const STEP: i16 = 5;
const TRACKS_MR122: usize = 5;
const RECIP_25: Word16 = Word16(1311);
const RECIP_5: Word16 = Word16(6554);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FixedCodebook {
TwoPulses9Bit {
subframe: u8,
signs: u16,
positions: u16,
},
TwoPulses11Bit {
signs: u16,
positions: u16,
},
ThreePulses14Bit {
signs: u16,
positions: u16,
},
FourPulses17Bit {
signs: u16,
positions: u16,
},
EightPulses31Bit([u16; 7]),
TenPulses35Bit([u16; 10]),
}
impl FixedCodebook {
#[must_use]
pub fn decode(self, ctx: &mut DspContext) -> Codevector {
match self {
Self::TwoPulses9Bit {
subframe,
signs,
positions,
} => decode_two_pulses_9bit(ctx, subframe, signs, positions),
Self::TwoPulses11Bit { signs, positions } => {
decode_two_pulses_11bit(ctx, signs, positions)
}
Self::ThreePulses14Bit { signs, positions } => {
decode_three_pulses_14bit(ctx, signs, positions)
}
Self::FourPulses17Bit { signs, positions } => {
decode_four_pulses_17bit(ctx, signs, positions)
}
Self::EightPulses31Bit(params) => decode_eight_pulses_31bit(ctx, ¶ms),
Self::TenPulses35Bit(params) => decode_ten_pulses_35bit(ctx, ¶ms),
}
}
}
#[inline]
fn field(value: u16, bits: u32) -> Word16 {
debug_assert!(
bits <= 13,
"the widest AMR-NB parameter field is thirteen bits"
);
let masked = value & ((1u16 << bits) - 1);
Word16(i16::try_from(masked).unwrap_or(0))
}
#[inline]
fn sample(position: Word16) -> usize {
usize::from(position.0.max(0).unsigned_abs()).min(L_SUBFR - 1)
}
#[inline]
fn times_five(ctx: &mut DspContext, i: Word16) -> Word16 {
let quadruple = shl(ctx, i, 2);
add(ctx, i, quadruple)
}
#[inline]
fn exact_product(ctx: &mut DspContext, i: Word16, k: i16) -> Word16 {
let doubled = l_mult(ctx, i, Word16(k));
extract_l(l_shr(ctx, doubled, 1))
}
fn place_pulses(ctx: &mut DspContext, signs: Word16, positions: &[Word16]) -> Codevector {
let mut code = [Word16(0); L_SUBFR];
let mut signs = signs;
for &position in positions {
let positive = (signs.0 & 1) != 0;
signs = shr(ctx, signs, 1);
code[sample(position)] = if positive {
POSITIVE_PULSE
} else {
NEGATIVE_PULSE
};
}
code
}
#[must_use]
pub fn decode_two_pulses_9bit(
ctx: &mut DspContext,
subframe: u8,
signs: u16,
positions: u16,
) -> Codevector {
let index = field(positions, 7);
let pair = shr(ctx, Word16(index.0 & 64), 6);
let subframe = Word16(i16::from(subframe & 3));
let pair_base = shl(ctx, pair, 3);
let subframe_base = shl(ctx, subframe, 1);
let base = add(ctx, pair_base, subframe_base);
let slot = Word16(index.0 & 7);
let scaled = times_five(ctx, slot);
let first = add(ctx, scaled, start_position(base));
let index = shr(ctx, index, 3);
let slot = Word16(index.0 & 7);
let next = add(ctx, base, Word16(1));
let scaled = times_five(ctx, slot);
let second = add(ctx, scaled, start_position(next));
place_pulses(ctx, field(signs, 2), &[first, second])
}
#[inline]
fn start_position(subscript: Word16) -> Word16 {
let at = usize::from(subscript.0.max(0).unsigned_abs());
Word16(START_POS_2I40_9[at.min(START_POS_2I40_9.len() - 1)])
}
#[must_use]
pub fn decode_two_pulses_11bit(ctx: &mut DspContext, signs: u16, positions: u16) -> Codevector {
let index = field(positions, 9);
let track = Word16(index.0 & 1);
let index = shr(ctx, index, 1);
let slot = Word16(index.0 & 7);
let scaled = times_five(ctx, slot);
let offset = add(ctx, scaled, Word16(1));
let doubled = shl(ctx, track, 1);
let first = add(ctx, offset, doubled);
let index = shr(ctx, index, 3);
let track = Word16(index.0 & 3);
let index = shr(ctx, index, 2);
let slot = Word16(index.0 & 7);
let selects_last = sub(ctx, track, Word16(3)).0 == 0;
let scaled = times_five(ctx, slot);
let second = if selects_last {
add(ctx, scaled, Word16(4))
} else {
add(ctx, scaled, track)
};
place_pulses(ctx, field(signs, 2), &[first, second])
}
#[must_use]
pub fn decode_three_pulses_14bit(ctx: &mut DspContext, signs: u16, positions: u16) -> Codevector {
let index = field(positions, 11);
let slot = Word16(index.0 & 7);
let first = times_five(ctx, slot);
let index = shr(ctx, index, 3);
let track = Word16(index.0 & 1);
let index = shr(ctx, index, 1);
let slot = Word16(index.0 & 7);
let scaled = times_five(ctx, slot);
let offset = add(ctx, scaled, Word16(1));
let doubled = shl(ctx, track, 1);
let second = add(ctx, offset, doubled);
let index = shr(ctx, index, 3);
let track = Word16(index.0 & 1);
let index = shr(ctx, index, 1);
let slot = Word16(index.0 & 7);
let scaled = times_five(ctx, slot);
let offset = add(ctx, scaled, Word16(2));
let doubled = shl(ctx, track, 1);
let third = add(ctx, offset, doubled);
place_pulses(ctx, field(signs, 3), &[first, second, third])
}
#[must_use]
pub fn decode_four_pulses_17bit(ctx: &mut DspContext, signs: u16, positions: u16) -> Codevector {
let index = field(positions, 13);
let slot = ungray(index);
let first = times_five(ctx, slot);
let index = shr(ctx, index, 3);
let slot = ungray(index);
let scaled = times_five(ctx, slot);
let second = add(ctx, scaled, Word16(1));
let index = shr(ctx, index, 3);
let slot = ungray(index);
let scaled = times_five(ctx, slot);
let third = add(ctx, scaled, Word16(2));
let index = shr(ctx, index, 3);
let track = Word16(index.0 & 1);
let index = shr(ctx, index, 1);
let slot = ungray(index);
let scaled = times_five(ctx, slot);
let offset = add(ctx, scaled, Word16(3));
let fourth = add(ctx, offset, track);
place_pulses(ctx, field(signs, 4), &[first, second, third, fourth])
}
#[inline]
fn ungray(index: Word16) -> Word16 {
Word16(DGRAY[usize::from((index.0 & 7).unsigned_abs())])
}
#[must_use]
pub fn decode_eight_pulses_31bit(ctx: &mut DspContext, params: &[u16; 7]) -> Codevector {
let mut code = [Word16(0); L_SUBFR];
let (signs, slots) = decompress_code(ctx, params);
for track in 0..TRACKS_MR102 {
let offset = Word16(i16::try_from(track).unwrap_or(0));
let scaled = exact_product(ctx, slots[track], STEP_MR102);
let first = add(ctx, scaled, offset);
let mut sign = if signs[track] {
PULSE_MR102
} else {
PULSE_MR102_NEG
};
code[sample(first)] = sign;
let scaled = exact_product(ctx, slots[track + 4], STEP_MR102);
let second = add(ctx, scaled, offset);
if sub(ctx, second, first).0 < 0 {
sign = negate(ctx, sign);
}
let at = sample(second);
code[at] = add(ctx, code[at], sign);
}
code
}
fn decompress_code(ctx: &mut DspContext, params: &[u16; 7]) -> ([bool; 4], [Word16; 8]) {
let signs = [
field(params[0], 1).0 == 0,
field(params[1], 1).0 == 0,
field(params[2], 1).0 == 0,
field(params[3], 1).0 == 0,
];
let mut slots = [Word16(0); 8];
let word = field(params[4], 10);
let msbs = shr(ctx, word, 3);
let (a, b, c) = decompress_triple(ctx, msbs, Word16(word.0 & 7));
slots[0] = a;
slots[4] = b;
slots[1] = c;
let word = field(params[5], 10);
let msbs = shr(ctx, word, 3);
let (a, b, c) = decompress_triple(ctx, msbs, Word16(word.0 & 7));
slots[2] = a;
slots[6] = b;
slots[5] = c;
let word = field(params[6], 7);
let msbs = shr(ctx, word, 2);
let (a, b) = decompress_pair(ctx, msbs, Word16(word.0 & 3));
slots[3] = a;
slots[7] = b;
(signs, slots)
}
fn decompress_triple(ctx: &mut DspContext, msbs: Word16, lsbs: Word16) -> (Word16, Word16, Word16) {
let msbs = if sub(ctx, msbs, Word16(124)).0 > 0 {
Word16(124)
} else {
msbs
};
let plane = mult(ctx, msbs, RECIP_25);
let scaled = exact_product(ctx, plane, 25);
let remainder = sub(ctx, msbs, scaled);
let row = mult(ctx, remainder, RECIP_5);
let scaled = exact_product(ctx, row, 5);
let column = sub(ctx, remainder, scaled);
let low = shl(ctx, column, 1);
let quarters = shr(ctx, lsbs, 2);
let scaled = shl(ctx, quarters, 2);
let corner = sub(ctx, lsbs, scaled);
let a = add(ctx, low, Word16(corner.0 & 1));
let middle = shl(ctx, row, 1);
let carry = shr(ctx, corner, 1);
let b = add(ctx, middle, carry);
let plane = mult(ctx, msbs, RECIP_25);
let high = shl(ctx, plane, 1);
let c = add(ctx, high, quarters);
(a, b, c)
}
fn decompress_pair(ctx: &mut DspContext, msbs: Word16, lsbs: Word16) -> (Word16, Word16) {
let scaled = exact_product(ctx, msbs, 25);
let rounded = add(ctx, scaled, Word16(12));
let cell = shr(ctx, rounded, 5);
let row = mult(ctx, cell, RECIP_5);
let reversed = (row.0 & 1) == 1;
let scaled = exact_product(ctx, row, 5);
let mut column = sub(ctx, cell, scaled);
if reversed {
column = sub(ctx, Word16(4), column);
}
let doubled = shl(ctx, column, 1);
let a = add(ctx, doubled, Word16(lsbs.0 & 1));
let doubled = shl(ctx, row, 1);
let carry = shr(ctx, lsbs, 1);
let b = add(ctx, doubled, carry);
(a, b)
}
#[must_use]
pub fn decode_ten_pulses_35bit(ctx: &mut DspContext, params: &[u16; 10]) -> Codevector {
let mut code = [Word16(0); L_SUBFR];
for track in 0..TRACKS_MR122 {
let offset = Word16(i16::try_from(track).unwrap_or(0));
let packed = field(params[track], 4);
let slot = ungray(packed);
let scaled = exact_product(ctx, slot, STEP);
let first = add(ctx, scaled, offset);
let negative = (shr(ctx, packed, 3).0 & 1) != 0;
let mut sign = if negative {
PULSE_MR122_NEG
} else {
PULSE_MR122
};
code[sample(first)] = sign;
let slot = ungray(field(params[track + 5], 3));
let scaled = exact_product(ctx, slot, STEP);
let second = add(ctx, scaled, offset);
if sub(ctx, second, first).0 < 0 {
sign = negate(ctx, sign);
}
let at = sample(second);
code[at] = add(ctx, code[at], sign);
}
code
}
pub fn sharpen(ctx: &mut DspContext, code: &mut Codevector, lag: i16, factor: Word16) {
let lag = usize::from(lag.max(0).unsigned_abs());
for i in lag..L_SUBFR {
let echo = mult(ctx, code[i - lag], factor);
code[i] = add(ctx, code[i], echo);
}
}
#[must_use]
pub fn sharpening_factor(ctx: &mut DspContext, source: Word16) -> Word16 {
shl(ctx, source, 1)
}
#[must_use]
pub fn sharpening_state(gain_pitch: Word16) -> Word16 {
Word16(gain_pitch.0.min(SHARPMAX))
}
#[cfg(test)]
mod tests {
use super::super::vectors::rows;
use super::*;
fn ctx() -> DspContext {
DspContext::default()
}
fn cases(section: &str) -> Vec<(Vec<i32>, Vec<Word16>)> {
let rows = rows(section);
let mut out = Vec::new();
let mut pending: Option<Vec<i32>> = None;
for row in rows {
match row.label {
"case" => {
assert!(pending.is_none(), "{section}: two case lines in a row");
pending = Some(row.ints());
}
"nz" => {
let case = pending.take().expect("a codevector without a case line");
out.push((case, row.pulses(L_SUBFR)));
}
other => panic!("{section}: unexpected row {other:?}"),
}
}
assert!(
pending.is_none(),
"{section}: trailing case without a vector"
);
out
}
fn as_u16(v: i32) -> u16 {
u16::try_from(v).expect("fixture parameters are non-negative and fit a field")
}
#[test]
fn nine_bit_codebook_is_bit_exact() {
let mut c = ctx();
let cases = cases("cb2i40_9");
assert_eq!(cases.len(), 656, "cb2i40_9 case count");
for (case, want) in &cases {
let [sub_nr, signs, positions] = case[..] else {
panic!("cb2i40_9 case is `subNr sign index`, got {case:?}")
};
let got = decode_two_pulses_9bit(
&mut c,
u8::try_from(sub_nr).expect("subframe 0..=3"),
as_u16(signs),
as_u16(positions),
);
assert_eq!(
&got[..],
&want[..],
"subframe {sub_nr} sign {signs} index {positions}"
);
}
}
#[test]
fn eleven_bit_codebook_is_bit_exact() {
let mut c = ctx();
let cases = cases("cb2i40_11");
assert_eq!(cases.len(), 602, "cb2i40_11 case count");
for (case, want) in &cases {
let [signs, positions] = case[..] else {
panic!("cb2i40_11 case is `sign index`, got {case:?}")
};
let got = decode_two_pulses_11bit(&mut c, as_u16(signs), as_u16(positions));
assert_eq!(&got[..], &want[..], "sign {signs} index {positions}");
}
}
#[test]
fn fourteen_bit_codebook_is_bit_exact() {
let mut c = ctx();
let cases = cases("cb3i40_14");
assert_eq!(cases.len(), 1297, "cb3i40_14 case count");
for (case, want) in &cases {
let [signs, positions] = case[..] else {
panic!("cb3i40_14 case is `sign index`, got {case:?}")
};
let got = decode_three_pulses_14bit(&mut c, as_u16(signs), as_u16(positions));
assert_eq!(&got[..], &want[..], "sign {signs} index {positions}");
}
}
#[test]
fn seventeen_bit_codebook_is_bit_exact() {
let mut c = ctx();
let cases = cases("cb4i40_17");
assert_eq!(cases.len(), 1756, "cb4i40_17 case count");
for (case, want) in &cases {
let [signs, positions] = case[..] else {
panic!("cb4i40_17 case is `sign index`, got {case:?}")
};
let got = decode_four_pulses_17bit(&mut c, as_u16(signs), as_u16(positions));
assert_eq!(&got[..], &want[..], "sign {signs} index {positions}");
}
}
#[test]
fn thirty_one_bit_codebook_is_bit_exact() {
let mut c = ctx();
let cases = cases("cb8i40_31");
assert_eq!(cases.len(), 250, "cb8i40_31 case count");
for (case, want) in &cases {
assert_eq!(case.len(), 7, "cb8i40_31 case is seven parameters");
let mut params = [0u16; 7];
for (slot, value) in params.iter_mut().zip(case) {
*slot = as_u16(*value);
}
let got = decode_eight_pulses_31bit(&mut c, ¶ms);
assert_eq!(&got[..], &want[..], "params {params:?}");
}
}
#[test]
fn thirty_five_bit_codebook_is_bit_exact() {
let mut c = ctx();
let cases = cases("cb10i40_35");
assert_eq!(cases.len(), 250, "cb10i40_35 case count");
for (case, want) in &cases {
assert_eq!(case.len(), 10, "cb10i40_35 case is ten parameters");
let mut params = [0u16; 10];
for (slot, value) in params.iter_mut().zip(case) {
*slot = as_u16(*value);
}
let got = decode_ten_pulses_35bit(&mut c, ¶ms);
assert_eq!(&got[..], &want[..], "params {params:?}");
}
}
#[test]
fn the_dispatch_enum_agrees_with_the_six_functions() {
let mut c = ctx();
let mut compared = 0;
for (case, want) in cases("cb2i40_9") {
let variant = FixedCodebook::TwoPulses9Bit {
subframe: u8::try_from(case[0]).expect("subframe 0..=3"),
signs: as_u16(case[1]),
positions: as_u16(case[2]),
};
assert_eq!(&variant.decode(&mut c)[..], &want[..]);
compared += 1;
}
for (case, want) in cases("cb2i40_11") {
let variant = FixedCodebook::TwoPulses11Bit {
signs: as_u16(case[0]),
positions: as_u16(case[1]),
};
assert_eq!(&variant.decode(&mut c)[..], &want[..]);
compared += 1;
}
for (case, want) in cases("cb3i40_14") {
let variant = FixedCodebook::ThreePulses14Bit {
signs: as_u16(case[0]),
positions: as_u16(case[1]),
};
assert_eq!(&variant.decode(&mut c)[..], &want[..]);
compared += 1;
}
for (case, want) in cases("cb4i40_17") {
let variant = FixedCodebook::FourPulses17Bit {
signs: as_u16(case[0]),
positions: as_u16(case[1]),
};
assert_eq!(&variant.decode(&mut c)[..], &want[..]);
compared += 1;
}
for (case, want) in cases("cb8i40_31") {
let mut params = [0u16; 7];
for (slot, value) in params.iter_mut().zip(&case) {
*slot = as_u16(*value);
}
assert_eq!(
&FixedCodebook::EightPulses31Bit(params).decode(&mut c)[..],
&want[..]
);
compared += 1;
}
for (case, want) in cases("cb10i40_35") {
let mut params = [0u16; 10];
for (slot, value) in params.iter_mut().zip(&case) {
*slot = as_u16(*value);
}
assert_eq!(
&FixedCodebook::TenPulses35Bit(params).decode(&mut c)[..],
&want[..]
);
compared += 1;
}
assert_eq!(compared, 656 + 602 + 1297 + 1756 + 250 + 250);
}
#[test]
fn the_gray_map_and_its_inverse_are_inverses() {
use super::super::decoder_tables::GRAY;
for (code, &decoded) in DGRAY.iter().enumerate() {
let back = GRAY[usize::try_from(decoded).expect("slot 0..=7")];
assert_eq!(
usize::try_from(back).expect("slot 0..=7"),
code,
"gray[dgray[{code}]] must be {code}"
);
}
let mut seen = [false; 8];
for &slot in &DGRAY {
let slot = usize::try_from(slot).expect("slot 0..=7");
assert!(
slot < 8 && !seen[slot],
"dgray must permute the eight slots"
);
seen[slot] = true;
}
}
#[test]
fn the_nine_bit_track_pairs_never_collide() {
assert_eq!(START_POS_2I40_9.len(), 16);
for entry in START_POS_2I40_9 {
assert!(
(0..5).contains(&entry),
"track offsets are 0..=4, got {entry}"
);
}
for pair in 0..2 {
for subframe in 0..4 {
let base = pair * 8 + subframe * 2;
assert_ne!(
START_POS_2I40_9[base],
START_POS_2I40_9[base + 1],
"pair {pair} subframe {subframe} puts both pulses on one track"
);
}
}
}
fn total_magnitude(code: &Codevector) -> i32 {
code.iter().map(|s| i32::from(s.0).abs()).sum()
}
#[test]
fn the_wide_codebooks_conserve_pulse_magnitude() {
let mut c = ctx();
let mut compared = 0;
for (case, want) in cases("cb8i40_31") {
assert_eq!(total_magnitude(&want.clone().try_into().unwrap()), 8 * 8191);
let mut params = [0u16; 7];
for (slot, value) in params.iter_mut().zip(&case) {
*slot = as_u16(*value);
}
assert_eq!(
total_magnitude(&decode_eight_pulses_31bit(&mut c, ¶ms)),
8 * 8191
);
compared += 1;
}
for (case, want) in cases("cb10i40_35") {
assert_eq!(
total_magnitude(&want.clone().try_into().unwrap()),
10 * 4096
);
let mut params = [0u16; 10];
for (slot, value) in params.iter_mut().zip(&case) {
*slot = as_u16(*value);
}
assert_eq!(
total_magnitude(&decode_ten_pulses_35bit(&mut c, ¶ms)),
10 * 4096
);
compared += 1;
}
assert_eq!(compared, 500);
}
#[test]
fn the_narrow_codebooks_place_one_pulse_per_track() {
let mut c = ctx();
let residues = |code: &Codevector| -> Vec<usize> {
let mut r: Vec<usize> = (0..L_SUBFR)
.filter(|&i| code[i].0 != 0)
.map(|i| i % 5)
.collect();
r.sort_unstable();
r
};
for positions in 0u16..2048 {
let tracks = residues(&decode_three_pulses_14bit(&mut c, 0, positions));
assert_eq!(tracks.len(), 3, "index {positions} lost a pulse");
assert!(
tracks.contains(&0),
"index {positions}: no pulse on track 0"
);
assert_eq!(
tracks.iter().filter(|t| [1, 3].contains(t)).count(),
1,
"index {positions}: the second pulse left tracks 1 and 3"
);
assert_eq!(
tracks.iter().filter(|t| [2, 4].contains(t)).count(),
1,
"index {positions}: the third pulse left tracks 2 and 4"
);
}
for positions in 0u16..8192 {
let tracks = residues(&decode_four_pulses_17bit(&mut c, 0, positions));
assert_eq!(tracks.len(), 4, "index {positions} lost a pulse");
for track in [0, 1, 2] {
assert!(
tracks.contains(&track),
"index {positions}: no pulse on track {track}"
);
}
assert_eq!(
tracks.iter().filter(|t| [3, 4].contains(t)).count(),
1,
"index {positions}: the fourth pulse left tracks 3 and 4"
);
}
}
#[test]
fn the_eleven_bit_codebook_overwrites_a_collision_instead_of_doubling_it() {
let mut c = ctx();
let mut collisions = 0;
for positions in 0u16..512 {
let code = decode_two_pulses_11bit(&mut c, 0b11, positions);
let nonzero: Vec<Word16> = code.into_iter().filter(|s| s.0 != 0).collect();
assert!(!nonzero.is_empty() && nonzero.len() <= 2);
for sample in &nonzero {
assert_eq!(
sample.0, POSITIVE_PULSE.0,
"index {positions} doubled a pulse"
);
}
if nonzero.len() == 1 {
collisions += 1;
}
}
assert!(
collisions > 0,
"the collision case must be reachable at all"
);
}
#[test]
fn the_ten_two_position_decode_stays_within_its_tracks() {
let mut c = ctx();
let mut checked = 0;
for word in 0u16..1024 {
for (params, indices) in [
([0, 0, 0, 0, word, 0, 0], [0usize, 4, 1]),
([0, 0, 0, 0, 0, word, 0], [2, 6, 5]),
] {
let (_, slots) = decompress_code(&mut c, ¶ms);
for i in indices {
assert!(
(0..10).contains(&slots[i].0),
"word {word} gave slot {} for pulse {i}",
slots[i].0
);
}
checked += 1;
}
}
for word in 0u16..128 {
let (_, slots) = decompress_code(&mut c, &[0, 0, 0, 0, 0, 0, word]);
for i in [3usize, 7] {
assert!(
(0..10).contains(&slots[i].0),
"word {word} gave slot {} for pulse {i}",
slots[i].0
);
}
checked += 1;
}
assert_eq!(checked, 1024 * 2 + 128);
}
#[test]
fn parameters_wider_than_their_fields_decode_without_panicking() {
let mut c = ctx();
let wide = [u16::MAX; 7];
let masked = [1u16, 1, 1, 1, 0x3FF, 0x3FF, 0x7F];
assert_eq!(
decode_eight_pulses_31bit(&mut c, &wide),
decode_eight_pulses_31bit(&mut c, &masked)
);
assert_eq!(
total_magnitude(&decode_eight_pulses_31bit(&mut c, &wide)),
8 * 8191
);
let wide = [u16::MAX; 10];
let masked = [0xFu16, 0xF, 0xF, 0xF, 0xF, 7, 7, 7, 7, 7];
assert_eq!(
decode_ten_pulses_35bit(&mut c, &wide),
decode_ten_pulses_35bit(&mut c, &masked)
);
assert_eq!(
total_magnitude(&decode_ten_pulses_35bit(&mut c, &wide)),
10 * 4096
);
assert_eq!(
decode_two_pulses_9bit(&mut c, 3, u16::MAX, u16::MAX),
decode_two_pulses_9bit(&mut c, 3, 0b11, 0x7F)
);
for subframe in 0u8..=255 {
let code = decode_two_pulses_9bit(&mut c, subframe, 0b11, 0x7F);
assert_eq!(code.iter().filter(|s| s.0 != 0).count(), 2);
}
assert_eq!(
decode_two_pulses_11bit(&mut c, u16::MAX, u16::MAX),
decode_two_pulses_11bit(&mut c, 0b11, 0x1FF)
);
assert_eq!(
decode_three_pulses_14bit(&mut c, u16::MAX, u16::MAX),
decode_three_pulses_14bit(&mut c, 0b111, 0x7FF)
);
assert_eq!(
decode_four_pulses_17bit(&mut c, u16::MAX, u16::MAX),
decode_four_pulses_17bit(&mut c, 0b1111, 0x1FFF)
);
}
#[test]
fn masking_never_changes_a_fixture_case() {
for (case, _) in cases("cb8i40_31") {
for (value, bits) in case.iter().zip([1u32, 1, 1, 1, 10, 10, 7]) {
assert!(*value < (1 << bits), "{value} does not fit {bits} bits");
}
}
for (case, _) in cases("cb10i40_35") {
for (value, bits) in case.iter().zip([4u32, 4, 4, 4, 4, 3, 3, 3, 3, 3]) {
assert!(*value < (1 << bits), "{value} does not fit {bits} bits");
}
}
}
#[test]
fn sharpening_floors_toward_negative_infinity() {
let mut c = ctx();
let factor = sharpening_factor(&mut c, Word16(SHARPMAX));
assert_eq!(factor.0, 26034);
let mut code = [Word16(0); L_SUBFR];
code[0] = NEGATIVE_PULSE;
code[1] = Word16(-8191);
code[2] = POSITIVE_PULSE;
sharpen(&mut c, &mut code, 19, factor);
assert_eq!(code[19].0, -6509, "mult(-8192, 26034) floors to -6509");
assert_eq!(code[20].0, -6508, "mult(-8191, 26034) floors to -6508");
assert_eq!(code[21].0, 6507, "mult(8191, 26034) floors the other way");
}
#[test]
fn sharpening_feeds_back_on_itself_within_one_pass() {
let mut c = ctx();
let factor = sharpening_factor(&mut c, Word16(SHARPMAX));
let mut code = [Word16(0); L_SUBFR];
code[0] = NEGATIVE_PULSE;
sharpen(&mut c, &mut code, 19, factor);
assert_eq!(code[19].0, -6509);
assert_eq!(code[38].0, -5172);
assert_eq!(code[39].0, 0, "sample 39 has no ancestor two lags back");
}
#[test]
fn sharpening_saturates_asymmetrically_rather_than_wrapping() {
let mut c = ctx();
let factor = sharpening_factor(&mut c, Word16(SHARPMAX));
let mut code = [Word16(0); L_SUBFR];
code[0] = Word16(-16382);
code[19] = Word16(-16382);
code[38] = Word16(-16382);
sharpen(&mut c, &mut code, 19, factor);
assert_eq!(code[19].0, -29398);
assert_eq!(
code[38].0,
i16::MIN,
"-39739 saturates to -32768, not -32767"
);
}
#[test]
fn a_lag_of_a_whole_subframe_leaves_the_codevector_alone() {
let mut c = ctx();
let factor = sharpening_factor(&mut c, Word16(SHARPMAX));
let original = decode_four_pulses_17bit(&mut c, 0b1010, 1234);
for lag in [40i16, 41, 143] {
let mut code = original;
sharpen(&mut c, &mut code, lag, factor);
assert_eq!(code, original, "lag {lag} must not reach into the subframe");
}
}
#[test]
fn the_sharpening_factor_saturates_only_where_the_reference_says() {
let mut c = ctx();
assert_eq!(sharpening_factor(&mut c, Word16(SHARPMAX)).0, 26034);
assert_eq!(sharpening_factor(&mut c, Word16(16383)).0, 32766);
assert_eq!(sharpening_factor(&mut c, Word16(16384)).0, i16::MAX);
assert_eq!(sharpening_factor(&mut c, Word16(19660)).0, i16::MAX);
assert_eq!(sharpening_state(Word16(19660)).0, SHARPMAX);
assert_eq!(sharpening_state(Word16(1000)).0, 1000);
assert_eq!(sharpening_state(Word16(SHARPMAX)).0, SHARPMAX);
}
}