use crate::ics_info::IcsInfo;
use crate::section_data::{INTENSITY_HCB, INTENSITY_HCB2};
#[cfg(test)]
use crate::swb_offset::{
long_window_offsets, short_window_offsets, LONG_WINDOW_LEN, SHORT_WINDOW_LEN,
};
use crate::{Error, Result};
pub fn is_intensity(right_cb: u8) -> i32 {
match right_cb {
INTENSITY_HCB => 1,
INTENSITY_HCB2 => -1,
_ => 0,
}
}
pub fn invert_intensity(per_band_mask: bool, ms_used: bool) -> i32 {
if per_band_mask {
1 - 2 * (ms_used as i32)
} else {
1
}
}
pub fn intensity_gain(is_pos: i32) -> f64 {
0.5f64.powf(0.25 * is_pos as f64)
}
#[derive(Debug)]
pub struct IntensityPairSpectra<'a> {
pub left: &'a [f64],
pub right: &'a mut [f64],
pub right_sfb_cb: &'a [Vec<u8>],
pub is_pos: &'a [Vec<i32>],
}
pub fn apply_intensity_stereo(
pair: &mut IntensityPairSpectra<'_>,
ms_mask_present: bool,
ms_used: &[Vec<bool>],
ics_info: &IcsInfo,
fs_index: u8,
) -> Result<()> {
let IntensityPairSpectra {
left,
right,
right_sfb_cb,
is_pos,
} = pair;
let window_len = ics_info.window_len()?;
let offsets = ics_info.swb_offsets(fs_index)?;
let num_swb = offsets.len() - 1;
let num_windows = ics_info.num_windows as usize;
let num_groups = ics_info.num_window_groups as usize;
let max_sfb = ics_info.max_sfb as usize;
let expected = num_windows * window_len;
if left.len() != expected || right.len() != expected {
return Err(Error::IntensityStereoInvalid);
}
if ics_info.window_group_length.len() != num_groups
|| ics_info
.window_group_length
.iter()
.map(|&w| w as usize)
.sum::<usize>()
!= num_windows
{
return Err(Error::IntensityStereoInvalid);
}
if max_sfb > num_swb {
return Err(Error::IntensityStereoInvalid);
}
if right_sfb_cb.len() != num_groups || is_pos.len() != num_groups {
return Err(Error::IntensityStereoInvalid);
}
for g in 0..num_groups {
if right_sfb_cb[g].len() < max_sfb || is_pos[g].len() < max_sfb {
return Err(Error::IntensityStereoInvalid);
}
}
if ms_mask_present {
if ms_used.len() != num_groups {
return Err(Error::IntensityStereoInvalid);
}
for row in ms_used {
if row.len() < max_sfb {
return Err(Error::IntensityStereoInvalid);
}
}
}
let mut window_base = 0usize;
for g in 0..num_groups {
let wgl = ics_info.window_group_length[g] as usize;
for sfb in 0..max_sfb {
let sign = is_intensity(right_sfb_cb[g][sfb]);
if sign == 0 {
continue;
}
let used = ms_mask_present && ms_used[g][sfb];
let inv = invert_intensity(ms_mask_present, used);
let scale = sign as f64 * inv as f64 * intensity_gain(is_pos[g][sfb]);
let start = offsets[sfb] as usize;
let end = offsets[sfb + 1] as usize;
for b in 0..wgl {
let base = (window_base + b) * window_len;
for i in start..end {
right[base + i] = scale * left[base + i];
}
}
}
window_base += wgl;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ics_info::{WindowSequence, WindowShape};
use crate::section_data::{NOISE_HCB, ZERO_HCB};
const FS_44100: u8 = 4;
const SPECTRUM_CB: u8 = 2;
fn long_ics_info(max_sfb: u8) -> IcsInfo {
IcsInfo {
family: crate::swb_offset::FrameFamily::Lc1024,
ics_reserved_bit: false,
window_sequence: WindowSequence::OnlyLong,
window_shape: WindowShape::Sine,
max_sfb,
scale_factor_grouping: None,
predictor_data_present: false,
predictor_data: None,
ltp_data_present: false,
ltp_data: None,
ltp_data_present_pair: None,
ltp_data_pair: None,
num_windows: 1,
num_window_groups: 1,
window_group_length: vec![1],
num_swb: crate::ics_info::NUM_SWB_LONG_WINDOW[FS_44100 as usize],
}
}
fn short_ics_info(max_sfb: u8, window_group_length: Vec<u8>) -> IcsInfo {
let num_window_groups = window_group_length.len() as u8;
IcsInfo {
family: crate::swb_offset::FrameFamily::Lc1024,
ics_reserved_bit: false,
window_sequence: WindowSequence::EightShort,
window_shape: WindowShape::Sine,
max_sfb,
scale_factor_grouping: Some(0),
predictor_data_present: false,
predictor_data: None,
ltp_data_present: false,
ltp_data: None,
ltp_data_present_pair: None,
ltp_data_pair: None,
num_windows: 8,
num_window_groups,
window_group_length,
num_swb: crate::ics_info::NUM_SWB_SHORT_WINDOW[FS_44100 as usize],
}
}
fn plain_cb(num_groups: usize, max_sfb: usize) -> Vec<Vec<u8>> {
vec![vec![SPECTRUM_CB; max_sfb]; num_groups]
}
fn zero_pos(num_groups: usize, max_sfb: usize) -> Vec<Vec<i32>> {
vec![vec![0i32; max_sfb]; num_groups]
}
#[allow(clippy::too_many_arguments)]
fn run(
left: &[f64],
right: &mut [f64],
ms_mask_present: bool,
ms_used: &[Vec<bool>],
right_sfb_cb: &[Vec<u8>],
is_pos: &[Vec<i32>],
ics_info: &IcsInfo,
fs_index: u8,
) -> Result<()> {
let mut pair = IntensityPairSpectra {
left,
right,
right_sfb_cb,
is_pos,
};
apply_intensity_stereo(&mut pair, ms_mask_present, ms_used, ics_info, fs_index)
}
#[test]
fn is_intensity_sign() {
assert_eq!(is_intensity(INTENSITY_HCB), 1);
assert_eq!(is_intensity(INTENSITY_HCB2), -1);
assert_eq!(is_intensity(SPECTRUM_CB), 0);
assert_eq!(is_intensity(NOISE_HCB), 0);
assert_eq!(is_intensity(ZERO_HCB), 0);
}
#[test]
fn invert_intensity_branches() {
assert_eq!(invert_intensity(false, false), 1);
assert_eq!(invert_intensity(false, true), 1);
assert_eq!(invert_intensity(true, false), 1);
assert_eq!(invert_intensity(true, true), -1);
}
#[test]
fn intensity_gain_quarter_ladder() {
assert!((intensity_gain(0) - 1.0).abs() < 1e-12);
assert!((intensity_gain(4) - 0.5).abs() < 1e-12);
assert!((intensity_gain(8) - 0.25).abs() < 1e-12);
assert!((intensity_gain(-4) - 2.0).abs() < 1e-12);
}
#[test]
fn in_phase_pos_zero_copies_left() {
let info = long_ics_info(3);
let max_sfb = 3;
let off = long_window_offsets(FS_44100).unwrap();
let n = LONG_WINDOW_LEN as usize;
let mut left = vec![0.0f64; n];
let mut right = vec![0.0f64; n];
for (k, (l, r)) in left.iter_mut().zip(right.iter_mut()).enumerate() {
if k >= off[0] as usize && k < off[3] as usize {
*l = (k as f64) * 0.5 - 3.0;
*r = 999.0; }
}
let mut cb = plain_cb(1, max_sfb);
cb[0][1] = INTENSITY_HCB; let pos = zero_pos(1, max_sfb);
run(&left, &mut right, false, &[], &cb, &pos, &info, FS_44100).unwrap();
for (r, l) in right
.iter()
.zip(left.iter())
.take(off[2] as usize)
.skip(off[1] as usize)
{
assert!((r - l).abs() < 1e-12);
}
for &r in right.iter().take(off[1] as usize).skip(off[0] as usize) {
assert_eq!(r, 999.0);
}
for &r in right.iter().take(off[3] as usize).skip(off[2] as usize) {
assert_eq!(r, 999.0);
}
}
#[test]
fn out_of_phase_negates() {
let info = long_ics_info(2);
let off = long_window_offsets(FS_44100).unwrap();
let n = LONG_WINDOW_LEN as usize;
let mut left = vec![0.0f64; n];
let mut right = vec![0.0f64; n];
for l in left.iter_mut().take(off[1] as usize).skip(off[0] as usize) {
*l = 7.0;
}
let mut cb = plain_cb(1, 2);
cb[0][0] = INTENSITY_HCB2;
let pos = zero_pos(1, 2);
run(&left, &mut right, false, &[], &cb, &pos, &info, FS_44100).unwrap();
for &r in right.iter().take(off[1] as usize).skip(off[0] as usize) {
assert!((r + 7.0).abs() < 1e-12);
}
}
#[test]
fn position_scales_gain() {
let info = long_ics_info(1);
let off = long_window_offsets(FS_44100).unwrap();
let n = LONG_WINDOW_LEN as usize;
let mut left = vec![0.0f64; n];
let mut right = vec![0.0f64; n];
for l in left.iter_mut().take(off[1] as usize).skip(off[0] as usize) {
*l = 16.0;
}
let mut cb = plain_cb(1, 1);
cb[0][0] = INTENSITY_HCB;
let mut pos = zero_pos(1, 1);
pos[0][0] = 4;
run(&left, &mut right, false, &[], &cb, &pos, &info, FS_44100).unwrap();
for &r in right.iter().take(off[1] as usize).skip(off[0] as usize) {
assert!((r - 8.0).abs() < 1e-12);
}
}
#[test]
fn ms_used_inverts_phase_under_mask() {
let info = long_ics_info(1);
let off = long_window_offsets(FS_44100).unwrap();
let n = LONG_WINDOW_LEN as usize;
let mut left = vec![0.0f64; n];
let mut right = vec![0.0f64; n];
for l in left.iter_mut().take(off[1] as usize).skip(off[0] as usize) {
*l = 3.0;
}
let mut cb = plain_cb(1, 1);
cb[0][0] = INTENSITY_HCB;
let pos = zero_pos(1, 1);
let ms_used = vec![vec![true]];
run(
&left, &mut right, true, &ms_used, &cb, &pos, &info, FS_44100,
)
.unwrap();
for &r in right.iter().take(off[1] as usize).skip(off[0] as usize) {
assert!((r + 3.0).abs() < 1e-12);
}
}
#[test]
fn ms_used_ignored_without_mask() {
let info = long_ics_info(1);
let off = long_window_offsets(FS_44100).unwrap();
let n = LONG_WINDOW_LEN as usize;
let mut left = vec![0.0f64; n];
let mut right = vec![0.0f64; n];
for l in left.iter_mut().take(off[1] as usize).skip(off[0] as usize) {
*l = 3.0;
}
let mut cb = plain_cb(1, 1);
cb[0][0] = INTENSITY_HCB;
let pos = zero_pos(1, 1);
run(&left, &mut right, false, &[], &cb, &pos, &info, FS_44100).unwrap();
for &r in right.iter().take(off[1] as usize).skip(off[0] as usize) {
assert!((r - 3.0).abs() < 1e-12);
}
}
#[test]
fn non_intensity_bands_untouched() {
let info = long_ics_info(4);
let n = LONG_WINDOW_LEN as usize;
let left = vec![1.0f64; n];
let mut right = vec![42.0f64; n];
let cb = plain_cb(1, 4); let pos = zero_pos(1, 4);
run(&left, &mut right, false, &[], &cb, &pos, &info, FS_44100).unwrap();
assert!(right.iter().all(|&v| v == 42.0));
}
#[test]
fn short_window_grouping() {
let wgl = vec![4u8, 4u8];
let info = short_ics_info(2, wgl.clone());
let off = short_window_offsets(FS_44100).unwrap();
let wlen = SHORT_WINDOW_LEN as usize;
let n = 8 * wlen;
let mut left = vec![0.0f64; n];
let mut right = vec![0.0f64; n];
for w in 0..8 {
for k in (off[0] as usize)..(off[1] as usize) {
left[w * wlen + k] = (w as f64) + 1.0;
}
}
let mut cb = plain_cb(2, 2);
cb[1][0] = INTENSITY_HCB; let pos = zero_pos(2, 2);
run(&left, &mut right, false, &[], &cb, &pos, &info, FS_44100).unwrap();
for w in 0..4 {
for k in (off[0] as usize)..(off[1] as usize) {
assert_eq!(right[w * wlen + k], 0.0);
}
}
for w in 4..8 {
for k in (off[0] as usize)..(off[1] as usize) {
assert!((right[w * wlen + k] - left[w * wlen + k]).abs() < 1e-12);
}
}
}
#[test]
fn rejects_length_mismatch() {
let info = long_ics_info(1);
let left = vec![0.0f64; 10];
let mut right = vec![0.0f64; LONG_WINDOW_LEN as usize];
let cb = plain_cb(1, 1);
let pos = zero_pos(1, 1);
let e = run(&left, &mut right, false, &[], &cb, &pos, &info, FS_44100);
assert_eq!(e, Err(Error::IntensityStereoInvalid));
}
#[test]
fn rejects_short_is_pos() {
let info = long_ics_info(3);
let n = LONG_WINDOW_LEN as usize;
let left = vec![0.0f64; n];
let mut right = vec![0.0f64; n];
let cb = plain_cb(1, 3);
let pos = zero_pos(1, 2); let e = run(&left, &mut right, false, &[], &cb, &pos, &info, FS_44100);
assert_eq!(e, Err(Error::IntensityStereoInvalid));
}
#[test]
fn rejects_missing_ms_used_under_mask() {
let info = long_ics_info(1);
let n = LONG_WINDOW_LEN as usize;
let left = vec![0.0f64; n];
let mut right = vec![0.0f64; n];
let cb = plain_cb(1, 1);
let pos = zero_pos(1, 1);
let e = run(&left, &mut right, true, &[], &cb, &pos, &info, FS_44100);
assert_eq!(e, Err(Error::IntensityStereoInvalid));
}
#[test]
fn rejects_max_sfb_over_num_swb() {
let mut info = long_ics_info(1);
info.max_sfb = 99; let n = LONG_WINDOW_LEN as usize;
let left = vec![0.0f64; n];
let mut right = vec![0.0f64; n];
let cb = vec![vec![SPECTRUM_CB; 99]];
let pos = vec![vec![0i32; 99]];
let e = run(&left, &mut right, false, &[], &cb, &pos, &info, FS_44100);
assert_eq!(e, Err(Error::IntensityStereoInvalid));
}
#[test]
fn no_intensity_band_leaves_right_untouched() {
let mut info = long_ics_info(0);
info.max_sfb = 0;
let n = LONG_WINDOW_LEN as usize;
let left = vec![1.0f64; n];
let mut right = vec![5.0f64; n];
let cb: Vec<Vec<u8>> = vec![vec![]];
let pos: Vec<Vec<i32>> = vec![vec![]];
run(&left, &mut right, false, &[], &cb, &pos, &info, FS_44100).unwrap();
assert!(right.iter().all(|&v| v == 5.0));
}
}