use crate::SUBFRAME as SPAN;
use crate::codebook_search::{Search, takes_lead};
use crate::fixed::{acc, hi, low, mul, sat, shift, trunc32};
pub fn response(impulse: &[i16], lag: i16, fraction: i16, gain: i16) -> [i16; SPAN] {
let mut out = [0i16; SPAN];
for (o, &v) in out.iter_mut().zip(impulse.iter()) {
*o = hi(shift(acc((v as i64) << 16), 1));
}
if acc(SPAN as i64 - (lag as i64)) > 0 {
crate::pitch::extend(&mut out, lag, fraction, gain);
}
out
}
pub fn lag_products(x: &[i16], y: &[i16]) -> [i16; SPAN] {
let mut out = [0i16; SPAN];
for (lag, o) in out.iter_mut().enumerate() {
let mut total = 0i64;
for k in lag..SPAN {
total = acc(total + mul(x[k], y[k - lag]));
}
*o = hi(shift(total, -1));
}
out
}
const SHAPES: usize = 128;
const PULSES: usize = 5;
pub const SHORTLIST: usize = 8;
const VECTOR_SAMPLES: usize = SHORTLIST * SPAN;
fn shape_score(table: &[i16], products: &[i16], entry: usize) -> i16 {
let mut total = 0i16;
for p in 0..PULSES {
let pulse = table[entry * PULSES + p];
let term = products[pulse.unsigned_abs() as usize - 1] as i64;
let sum = (total as i64) + if pulse > 0 { term } else { -term };
total = hi(acc(sum << 16));
}
total
}
struct ShapeShortlist {
score: [i16; SHORTLIST],
number: [i16; SHORTLIST],
}
impl ShapeShortlist {
fn new() -> Self {
Self {
score: [0; SHORTLIST],
number: [127; SHORTLIST],
}
}
fn consider(&mut self, entry: usize, total: i16) {
if total <= self.score[SHORTLIST - 1] {
return;
}
let mut at = 0usize;
for j in (0..SHORTLIST - 1).rev() {
if total < self.score[j] {
at = j + 1;
break;
}
self.score[j + 1] = self.score[j];
self.number[j + 1] = self.number[j];
}
self.score[at] = total;
self.number[at] = (SHAPES - 1 - entry) as i16;
}
fn numbers(self) -> [i16; SHORTLIST] {
let mut out = [0i16; SHORTLIST];
for k in 0..SHORTLIST {
out[SHORTLIST - 1 - k] = low(acc((SHAPES as i64 - 1) - (self.number[k] as i64)));
}
out
}
}
pub fn shapes(table: &[i16], products: &[i16]) -> [i16; SHORTLIST] {
let mut shortlist = ShapeShortlist::new();
for entry in 0..SHAPES {
shortlist.consider(entry, shape_score(table, products, entry));
}
shortlist.numbers()
}
pub fn quarter(response: &mut [i16; SPAN]) {
for v in response.iter_mut() {
*v = hi(shift(acc((*v as i64) << 16), -2));
}
}
fn shape_vectors_into(response: &[i16; SPAN], numbers: &[i16], table: &[i16], out: &mut [i16]) {
assert_eq!(out.len(), VECTOR_SAMPLES);
out.fill(0);
for (slot, &number) in numbers.iter().enumerate() {
let base = slot * SPAN;
for p in 0..PULSES {
let pulse = table[(number as usize) * PULSES + p] as i64;
let at = pulse.unsigned_abs() as usize - 1;
for k in 0..SPAN - at {
let carried = (out[base + at + k] as i64) << 16;
let term = (response[k] as i64) << 16;
out[base + at + k] = hi(acc(if pulse > 0 {
carried + term
} else {
carried - term
}));
}
}
}
}
pub fn shape_vectors(response: &[i16; SPAN], numbers: &[i16], table: &[i16]) -> Vec<i16> {
let mut out = vec![0i16; VECTOR_SAMPLES];
shape_vectors_into(response, numbers, table, &mut out);
out
}
pub fn orthogonalise(vectors: &mut [i16], contribution: &[i16], gain: i16, gain_shift: i16) {
if acc(gain as i64) <= 0 {
return;
}
for slot in 0..SHORTLIST {
let base = slot * SPAN;
let mut total = 0i64;
for k in 0..SPAN {
total = acc(total + mul(vectors[base + k], contribution[k]));
}
let share = crate::fixed::mul32x16(trunc32(sat(total)), gain);
let scale = hi(shift(shift(share, gain_shift as i32), -2));
for k in (0..SPAN).rev() {
let term = shift(acc(mul(scale, contribution[k])), 2);
vectors[base + k] = hi(acc(((vectors[base + k] as i64) << 16) - term));
}
}
}
const CANDIDATES: usize = 2 * SHORTLIST;
const COMBINED_SAMPLES: usize = CANDIDATES * SPAN;
fn shape_correlations(target: &[i16], vectors: &[i16]) -> ([i64; CANDIDATES], i16) {
let mut correlation = [0i64; CANDIDATES];
let mut largest = 0i64;
for slot in 0..CANDIDATES {
let mut total = 0i64;
for k in 0..SPAN {
total = acc(total + mul(target[k], vectors[slot * SPAN + k]));
}
correlation[slot] = trunc32(total);
largest = largest.max(acc(total).abs());
}
let shift_up = if sat(largest) == 0 {
14
} else {
crate::fixed::exp(largest).min(15) - 1
};
for value in &mut correlation {
*value = trunc32(shift(*value, shift_up));
}
(correlation, shift_up as i16)
}
fn shape_energies(vectors: &[i16]) -> ([i64; CANDIDATES], i16) {
let mut energy = [0i64; CANDIDATES];
let mut largest = 0i64;
for slot in 0..CANDIDATES {
let mut total = 0i64;
for k in 0..SPAN {
let value = vectors[slot * SPAN + k];
total = acc(total + mul(value, value));
}
let scaled = shift(total, -5);
energy[slot] = trunc32(scaled);
largest = largest.max(scaled);
}
let shift_up = if sat(largest) == 0 {
13
} else {
crate::fixed::exp(largest).min(15) - 2
};
for value in &mut energy {
*value = trunc32(shift(*value, shift_up));
}
(energy, shift_up as i16)
}
pub fn measure_shapes(target: &[i16], vectors: &[i16]) -> (Vec<i64>, i16, Vec<i64>, i16) {
let (correlation, correlation_shift) = shape_correlations(target, vectors);
let (energy, energy_shift) = shape_energies(vectors);
(
correlation.to_vec(),
correlation_shift,
energy.to_vec(),
energy_shift,
)
}
fn shape_pair_score(
first: &[i16],
second: &[i16],
correlation: &[i64],
energy: &[i64],
up: i16,
i: usize,
j: usize,
) -> Option<(i64, i64)> {
let sum = acc(correlation[i] + correlation[SHORTLIST + j]);
if acc(sum) <= 0 {
return None;
}
let mut cross = 0i64;
for k in 0..SPAN {
cross = acc(cross + mul(first[i * SPAN + k], second[j * SPAN + k]));
}
let total = acc(shift(cross, up as i32) + shift(acc(energy[i] + energy[SHORTLIST + j]), 5));
Some((sum, hi(shift(total, -5)) as i64))
}
fn best_pair_indices(
first: &[i16],
second: &[i16],
correlation: &[i64],
energy: &[i64],
up: i16,
) -> ((i64, i64), Option<(usize, usize)>) {
let mut best = (32767i64, 1i64);
let mut chosen: Option<(usize, usize)> = None;
for i in 0..SHORTLIST {
for j in 0..SHORTLIST {
let Some((sum, energy)) =
shape_pair_score(first, second, correlation, energy, up, i, j)
else {
continue;
};
if takes_lead(&mut best, sum, energy) {
chosen = Some((i, j));
}
}
}
(best, chosen)
}
pub fn best_pair(
first: &[i16],
second: &[i16],
correlation: &[i64],
energy: &[i64],
up: i16,
numbers: (&[i16], &[i16]),
) -> ((i16, i16), (i16, i16)) {
let (best, chosen) = best_pair_indices(first, second, correlation, energy, up);
let score = (low(best.0), low(best.1));
match chosen {
Some((i, j)) => ((numbers.0[i], numbers.1[j]), score),
None => ((0, 0), score),
}
}
struct ShapeCandidates {
vectors: [i16; COMBINED_SAMPLES],
first_numbers: [i16; SHORTLIST],
second_numbers: [i16; SHORTLIST],
}
fn shape_candidates(
input: &Search,
residual: &[i16; SPAN],
contribution: &[i16; SPAN],
inverse: i16,
left: i16,
) -> ShapeCandidates {
let mut response = response(input.impulse, input.lag, input.fraction, input.extension.1);
let products = lag_products(residual, &response);
let first_numbers = shapes(&FIRST_SHAPES, &products);
let second_numbers = shapes(&SECOND_SHAPES, &products);
quarter(&mut response);
let mut vectors = [0i16; COMBINED_SAMPLES];
let (first, second) = vectors.split_at_mut(VECTOR_SAMPLES);
shape_vectors_into(&response, &first_numbers, &FIRST_SHAPES, first);
shape_vectors_into(&response, &second_numbers, &SECOND_SHAPES, second);
orthogonalise(first, contribution, inverse, left);
orthogonalise(second, contribution, inverse, left);
ShapeCandidates {
vectors,
first_numbers,
second_numbers,
}
}
pub fn shape_pair(
input: &Search,
scaled: &[i16; SPAN],
residual: &[i16; SPAN],
contribution: &[i16; SPAN],
inverse: i16,
left: i16,
numbers: &mut [i16; 2 * SHORTLIST],
) -> ((i16, i16), (i16, i16), i16, i16) {
let candidates = shape_candidates(input, residual, contribution, inverse, left);
let (first, second) = candidates.vectors.split_at(VECTOR_SAMPLES);
let (correlation, up) = shape_correlations(scaled, &candidates.vectors);
let (energy, over) = shape_energies(&candidates.vectors);
numbers[..SHORTLIST].copy_from_slice(&candidates.first_numbers);
numbers[SHORTLIST..].copy_from_slice(&candidates.second_numbers);
let (chosen, score) = best_pair(
first,
second,
&correlation,
&energy,
over + 1,
(&candidates.first_numbers, &candidates.second_numbers),
);
(chosen, score, up, over)
}
use crate::tables::{FCB_ALT_A as FIRST_SHAPES, FCB_ALT_B as SECOND_SHAPES};