use crate::fixed_point::random::random;
use crate::fixed_point::types::Word16;
pub const NB_STAGES: &str = include_str!("../testdata/nb_stages.txt");
#[derive(Debug, Clone, Copy)]
pub struct Row {
pub label: &'static str,
pub tokens: &'static str,
}
impl Row {
pub fn parts(&self) -> impl Iterator<Item = &'static str> {
self.tokens.split_whitespace()
}
#[must_use]
pub fn ints(&self) -> Vec<i32> {
self.parts()
.map(|v| {
v.parse()
.unwrap_or_else(|_| panic!("{}: {v:?} is not an integer", self.label))
})
.collect()
}
#[must_use]
pub fn i16s(&self) -> Vec<i16> {
self.ints()
.into_iter()
.map(|v| i16::try_from(v).expect("stage vector value fits in i16"))
.collect()
}
#[must_use]
pub fn words(&self) -> Vec<Word16> {
self.i16s().into_iter().map(Word16).collect()
}
#[must_use]
pub fn tag(&self, n: usize) -> &'static str {
self.parts()
.nth(n)
.unwrap_or_else(|| panic!("{}: no token {n}", self.label))
}
#[must_use]
pub fn pulses(&self, len: usize) -> Vec<Word16> {
let v = self.ints();
assert_eq!(self.label, "nz", "not a sparse codevector row");
let count = usize::try_from(v[0]).expect("non-negative pulse count");
assert_eq!(
v.len(),
1 + 2 * count,
"sparse row length disagrees with its count"
);
let mut out = vec![Word16(0); len];
for pair in v[1..].chunks_exact(2) {
let pos = usize::try_from(pair[0]).expect("non-negative position");
out[pos] = Word16(i16::try_from(pair[1]).expect("pulse fits in i16"));
}
out
}
}
#[must_use]
pub fn rows(section: &str) -> Vec<Row> {
let mut out = Vec::new();
let mut inside = false;
for line in NB_STAGES.lines() {
if line.starts_with(char::is_whitespace) {
if inside {
let trimmed = line.trim_start();
let (label, tokens) = trimmed
.split_once(char::is_whitespace)
.unwrap_or((trimmed, ""));
out.push(Row {
label,
tokens: tokens.trim(),
});
}
} else if inside {
break;
} else {
inside = line.trim_end() == section;
}
}
assert!(
!out.is_empty(),
"nb_stages.txt has no section {section:?} — regenerate it with \
tools/build-amrnb-reference.sh"
);
out
}
#[must_use]
pub fn noise(seed: i16, count: usize, shift: u32) -> Vec<Word16> {
let mut s = seed;
(0..count)
.map(|_| Word16(random(&mut s) >> shift))
.collect()
}
pub fn next_noise(seed: &mut i16) -> i16 {
random(seed)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_section_the_oracle_writes_is_readable() {
for section in [
"lag3",
"lag6",
"predlt3",
"predlt6",
"cb2i40_9",
"cb2i40_11",
"cb3i40_14",
"cb4i40_17",
"cb8i40_31",
"cb10i40_35",
"gains",
"conceal",
"synfilt",
"agc",
"agc2",
"weightai",
"residu",
"preemph",
"postfilter",
"postproc",
"phdisp",
"plsf5",
"intlsf",
"lspavg",
"cbgainav",
"bgnscd",
"exctrl",
] {
let r = rows(section);
assert!(!r.is_empty(), "{section} is empty");
}
}
#[test]
fn sections_do_not_bleed_into_each_other() {
let lag3 = rows("lag3");
assert!(lag3.iter().all(|r| r.label == "case" || r.label == "out"));
let lag6 = rows("lag6");
assert!(lag6.len() < lag3.len(), "lag6 is the smaller sweep");
}
#[test]
fn the_noise_generator_matches_the_oracles() {
let v = noise(1374, 4, 3);
assert_eq!(v.len(), 4);
let mut s = 1374i16;
for w in v {
let want = ((i32::from(s).wrapping_mul(31821) + 13849) & 0xFFFF) as i16;
s = want;
assert_eq!(w.0, want >> 3);
}
}
}