use std::sync::OnceLock;
use crate::frame::{BlockType, GranuleSideInfo, GRANULE_LINES};
use crate::header::{FrameHeader, MpegVersion};
use crate::tables;
use super::scalefactors::ScaleFactors;
pub(crate) fn pow43_table() -> &'static [f64] {
static T: OnceLock<Vec<f64>> = OnceLock::new();
T.get_or_init(|| (0..8207).map(|i| f64::from(i).powf(4.0 / 3.0)).collect())
}
const Q_MIN: i32 = -400;
const Q_MAX: i32 = 64;
pub(crate) fn pow2_quarter_table() -> &'static [f64] {
static T: OnceLock<Vec<f64>> = OnceLock::new();
T.get_or_init(|| {
(Q_MIN..=Q_MAX)
.map(|q| 2f64.powf(0.25 * f64::from(q)))
.collect()
})
}
#[inline]
pub(crate) fn band_scale(pow2q: &[f64], gain: i32, scalefac_scale: bool, sf: i32) -> f64 {
let q = gain - if scalefac_scale { 4 * sf } else { 2 * sf };
match pow2q.get((q - Q_MIN) as usize) {
Some(&s) => s,
None => 2f64.powf(0.25 * f64::from(q)),
}
}
#[inline]
pub(crate) fn dequant(c: i32, pow: &[f64], scale: f64) -> f32 {
(f64::from(c.signum()) * pow[c.unsigned_abs() as usize] * scale) as f32
}
pub fn apply(
header: &FrameHeader,
gi: &GranuleSideInfo,
sf: &ScaleFactors,
coeffs: &[i32; GRANULE_LINES],
nz: usize,
out: &mut [f32; GRANULE_LINES],
) {
out.fill(0.0);
debug_assert!(
coeffs[nz.min(GRANULE_LINES)..].iter().all(|&c| c == 0),
"rzero invariant broken: non-zero coefficient at or above nz={nz}"
);
let pow = pow43_table(); let gain = i32::from(gi.global_gain) - 210;
let pow2q = pow2_quarter_table(); let is_short = gi.window_switching && gi.block_type == BlockType::Short;
if is_short {
let first_short = if gi.mixed_block {
let long_end = if header.version == MpegVersion::V1 {
8
} else {
6
};
let off = tables::sfb_long_offsets(header.sample_rate);
for sfb in 0..long_end {
let start = off[sfb] as usize;
if start >= nz {
break;
}
let end = (off[sfb + 1] as usize).min(nz);
let pre = i32::from(if gi.preflag { tables::PRETAB[sfb] } else { 0 });
let scale = band_scale(
pow2q,
gain,
gi.scalefac_scale,
i32::from(sf.long[sfb]) + pre,
);
for i in start..end {
out[i] = dequant(coeffs[i], pow, scale);
}
}
3
} else {
0
};
let off = tables::sfb_short_offsets(header.sample_rate);
for sfb in first_short..13 {
let start = off[sfb] as usize;
let width = (off[sfb + 1] - off[sfb]) as usize;
if start * 3 >= nz {
break; }
for window in 0..3 {
let gain_w = gain - 8 * i32::from(gi.subblock_gain[window]);
let scale = band_scale(
pow2q,
gain_w,
gi.scalefac_scale,
i32::from(sf.short[window][sfb]),
);
for f in 0..width {
let src = start * 3 + window * width + f;
if src >= nz {
break; }
let dst = start * 3 + window + f * 3;
if src < GRANULE_LINES && dst < GRANULE_LINES {
out[dst] = dequant(coeffs[src], pow, scale);
}
}
}
}
} else {
let off = tables::sfb_long_offsets(header.sample_rate);
for sfb in 0..22 {
let start = off[sfb] as usize;
if start >= nz {
break; }
let end = (off[sfb + 1] as usize).min(GRANULE_LINES).min(nz);
let pre = i32::from(if gi.preflag { tables::PRETAB[sfb] } else { 0 });
let scale = band_scale(
pow2q,
gain,
gi.scalefac_scale,
i32::from(sf.long[sfb]) + pre,
);
for i in start..end {
out[i] = dequant(coeffs[i], pow, scale);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frame::ChannelMode;
use crate::header::MpegVersion;
fn hdr() -> FrameHeader {
FrameHeader {
version: MpegVersion::V1,
crc_protected: false,
bitrate_kbps: 128,
sample_rate: 44100,
padding: false,
channel_mode: ChannelMode::Mono,
copyright: false,
original: true,
emphasis: 0,
}
}
#[test]
fn long_block_power_law_and_sign() {
let gi = GranuleSideInfo {
global_gain: 210,
..Default::default()
};
let sf = ScaleFactors::default();
let mut coeffs = [0i32; GRANULE_LINES];
coeffs[0] = 3;
coeffs[1] = -2;
coeffs[2] = 1;
let mut out = [0f32; GRANULE_LINES];
apply(&hdr(), &gi, &sf, &coeffs, 3, &mut out);
assert!((out[0] - 3f32.powf(4.0 / 3.0)).abs() < 1e-4);
assert!((out[1] + 2f32.powf(4.0 / 3.0)).abs() < 1e-4);
assert!((out[2] - 1.0).abs() < 1e-4);
}
#[test]
fn scalefactor_halves_the_step() {
let gi = GranuleSideInfo {
global_gain: 210,
..Default::default()
};
let mut sf = ScaleFactors::default();
sf.long[0] = 2;
let mut coeffs = [0i32; GRANULE_LINES];
coeffs[0] = 1;
let mut out = [0f32; GRANULE_LINES];
apply(&hdr(), &gi, &sf, &coeffs, 1, &mut out);
assert!((out[0] - 0.5).abs() < 1e-4, "1^(4/3)·2^-1 = 0.5");
}
#[test]
#[cfg_attr(miri, ignore = "exhaustive: millions of powf calls")]
fn band_scale_matches_powf_exactly() {
let pow2q = pow2_quarter_table();
for global_gain in 0..=255i32 {
for sub in 0..=7i32 {
let gain = global_gain - 210 - 8 * sub;
for sf in 0..=255i32 {
for pre in 0..=3i32 {
for scalefac_scale in [false, true] {
let sf_mult = if scalefac_scale { 1.0f64 } else { 0.5 };
let exp =
0.25 * f64::from(gain) - sf_mult * (f64::from(sf) + f64::from(pre));
let want = 2f64.powf(exp);
let got = band_scale(pow2q, gain, scalefac_scale, sf + pre);
assert_eq!(
got.to_bits(),
want.to_bits(),
"gain {gain} sf {sf} pre {pre}"
);
}
}
}
}
}
}
}