use crate::analysis::BINS;
pub use crate::fixed::DB_PER_OCTAVE;
use crate::fixed::{acc, exp, hi, low, mul32x16, sat, shift, square32};
use crate::tables::{BAND_EDGES, BAND_INV_WIDTH_HALVED, LOG2_FRACTION};
use std::ops::RangeInclusive;
pub const BANDS: usize = 20;
pub(crate) fn band_bins(band: usize) -> RangeInclusive<usize> {
BAND_EDGES[2 * band] as usize..=BAND_EDGES[2 * band + 1] as usize
}
const LEVEL_FLOOR: i16 = 2560;
const LOG_OFFSET: i16 = 4;
pub fn log2(value: i64) -> i64 {
if value <= 0 {
return 0;
}
let e = exp(value);
let integer = 30 - e;
let normalised = shift(value, e);
let shifted = shift(normalised, -9);
let index = low(shift(shifted, -16)) as usize;
let frac = (shift(shifted, -1) & 32767) as i16;
let base = (LOG2_FRACTION[index] as i64) << 16;
let slope = acc(base - ((LOG2_FRACTION[index + 1] as i64) << 16));
let interpolated = shift(acc(base - (hi(slope) as i64) * (frac as i64) * 2), -16);
acc(((integer as i64) << 16) + shift(interpolated, 1))
}
fn decibel_level(value: i64, offset: i16) -> i64 {
let log = log2(value);
let level = acc(shift(log, -1) - ((offset as i64) << 16));
let db = sat(shift(mul32x16(level, DB_PER_OCTAVE), 12));
db.max((LEVEL_FLOOR as i64) << 16)
}
fn spectrum_level(spectrum: &[i16; BINS], band: usize) -> i64 {
let mut sum = 0i64;
for bin in band_bins(band) {
sum = acc(sum + ((spectrum[bin] as i64) << 16));
}
let mean = mul32x16(sat(sum), BAND_INV_WIDTH_HALVED[band]);
decibel_level(shift(mean, 1), LOG_OFFSET)
}
pub fn levels(spectrum: &[i16; BINS]) -> [i64; BANDS] {
std::array::from_fn(|band| spectrum_level(spectrum, band))
}
const TRACKED_OFFSET: i16 = 6;
const SUMMARY_WEIGHT: i16 = 1638;
const DIFF_MAX: i16 = 20480;
const DIFF_MIN: i16 = -5120;
pub fn tracked_level(energy: i64) -> i64 {
decibel_level(energy, TRACKED_OFFSET)
}
pub fn tracked_levels(energies: &[i64; BANDS]) -> [i64; BANDS] {
std::array::from_fn(|band| tracked_level(energies[band]))
}
pub fn summary(levels: &[i64; BANDS]) -> i64 {
let mut total = 0i64;
for &l in levels.iter() {
total = acc(total + mul32x16(l, SUMMARY_WEIGHT));
}
total
}
pub fn summary_difference(current: i64, tracked: i64) -> i64 {
let diff = acc(current - tracked);
diff.min((DIFF_MAX as i64) << 16)
.max((DIFF_MIN as i64) << 16)
}
const EXCESS_MAX: i16 = 20480;
pub fn excess(levels: &[i64; BANDS], tracked: &[i64; BANDS]) -> [i64; BANDS] {
let mut out = [0i64; BANDS];
for i in 0..BANDS {
let d = acc(levels[i] - tracked[i]);
out[i] = d.min((EXCESS_MAX as i64) << 16).max(0);
}
out
}
const STAT_BANDS: usize = 18;
const STAT_MEAN_WEIGHT: i16 = 8192;
const STAT_INV_BANDS: i16 = 1820;
pub fn snr_statistics(snr: &[i64; BANDS]) -> (i64, i64) {
let mut sum = 0i64;
let mut sum_sq = 0i64;
for &v in snr[1..1 + STAT_BANDS].iter() {
sum = acc(sum + mul32x16(v, STAT_MEAN_WEIGHT));
sum_sq = acc(sum_sq + square32(v));
}
let scaled = shift(sum_sq, -4);
let mean_sq = mul32x16(square32(sum), STAT_INV_BANDS);
let variance = shift(mul32x16(acc(scaled - mean_sq), STAT_INV_BANDS), 4);
(sum, variance)
}