use crate::ics_info::IcsInfo;
use crate::section_data::{INTENSITY_HCB, INTENSITY_HCB2, NOISE_HCB};
#[cfg(test)]
use crate::swb_offset::{long_window_offsets, short_window_offsets, SHORT_WINDOW_LEN};
use crate::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MsMaskPresent {
AllZeros,
Mask,
AllOnes,
}
impl MsMaskPresent {
pub fn from_bits(bits: u8) -> Result<Self> {
match bits {
0 => Ok(MsMaskPresent::AllZeros),
1 => Ok(MsMaskPresent::Mask),
2 => Ok(MsMaskPresent::AllOnes),
_ => Err(Error::MsStereoInvalid),
}
}
pub fn to_bits(self) -> u8 {
match self {
MsMaskPresent::AllZeros => 0,
MsMaskPresent::Mask => 1,
MsMaskPresent::AllOnes => 2,
}
}
pub fn is_active(self) -> bool {
!matches!(self, MsMaskPresent::AllZeros)
}
}
fn is_intensity_cb(cb: u8) -> bool {
cb == INTENSITY_HCB || cb == INTENSITY_HCB2
}
fn is_noise_cb(cb: u8) -> bool {
cb == NOISE_HCB
}
#[derive(Debug)]
pub struct ChannelPairSpectra<'a> {
pub left: &'a mut [f64],
pub right: &'a mut [f64],
pub left_sfb_cb: &'a [Vec<u8>],
pub right_sfb_cb: &'a [Vec<u8>],
}
pub fn apply_ms_stereo(
pair: &mut ChannelPairSpectra<'_>,
ms_mask_present: MsMaskPresent,
ms_used: &[Vec<bool>],
ics_info: &IcsInfo,
fs_index: u8,
) -> Result<()> {
let ChannelPairSpectra {
left,
right,
left_sfb_cb,
right_sfb_cb,
} = 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::MsStereoInvalid);
}
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::MsStereoInvalid);
}
if max_sfb > num_swb {
return Err(Error::MsStereoInvalid);
}
if left_sfb_cb.len() != num_groups || right_sfb_cb.len() != num_groups {
return Err(Error::MsStereoInvalid);
}
for cb in left_sfb_cb.iter().chain(right_sfb_cb.iter()) {
if cb.len() < max_sfb {
return Err(Error::MsStereoInvalid);
}
}
if ms_mask_present == MsMaskPresent::Mask {
if ms_used.len() != num_groups {
return Err(Error::MsStereoInvalid);
}
for row in ms_used {
if row.len() < max_sfb {
return Err(Error::MsStereoInvalid);
}
}
}
if !ms_mask_present.is_active() {
return Ok(());
}
let all_ones = ms_mask_present == MsMaskPresent::AllOnes;
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 band_on = all_ones || ms_used[g][sfb];
if !band_on {
continue;
}
if is_intensity_cb(right_sfb_cb[g][sfb])
|| is_noise_cb(left_sfb_cb[g][sfb])
|| is_noise_cb(right_sfb_cb[g][sfb])
{
continue;
}
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 {
let l = left[base + i];
let r = right[base + i];
left[base + i] = l + r;
right[base + i] = l - r;
}
}
}
window_base += wgl;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ics_info::{WindowSequence, WindowShape};
use crate::section_data::ZERO_HCB;
const FS_44100: u8 = 4;
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![2u8; max_sfb]; num_groups]
}
#[allow(clippy::too_many_arguments)]
fn run(
left: &mut [f64],
right: &mut [f64],
ms_mask_present: MsMaskPresent,
ms_used: &[Vec<bool>],
left_sfb_cb: &[Vec<u8>],
right_sfb_cb: &[Vec<u8>],
ics_info: &IcsInfo,
fs_index: u8,
) -> Result<()> {
let mut pair = ChannelPairSpectra {
left,
right,
left_sfb_cb,
right_sfb_cb,
};
apply_ms_stereo(&mut pair, ms_mask_present, ms_used, ics_info, fs_index)
}
#[test]
fn from_bits_roundtrip() {
for (bits, m) in [
(0u8, MsMaskPresent::AllZeros),
(1, MsMaskPresent::Mask),
(2, MsMaskPresent::AllOnes),
] {
assert_eq!(MsMaskPresent::from_bits(bits).unwrap(), m);
assert_eq!(m.to_bits(), bits);
}
assert!(matches!(
MsMaskPresent::from_bits(3),
Err(Error::MsStereoInvalid)
));
}
#[test]
fn all_zeros_is_identity() {
let ics = long_ics_info(4);
let mut l = vec![1.0f64; 1024];
let mut r = vec![2.0f64; 1024];
let cb = plain_cb(1, 4);
run(
&mut l,
&mut r,
MsMaskPresent::AllZeros,
&[],
&cb,
&cb,
&ics,
FS_44100,
)
.unwrap();
assert!(l.iter().all(|&x| x == 1.0));
assert!(r.iter().all(|&x| x == 2.0));
}
#[test]
fn all_ones_dematrixes_every_band() {
let ics = long_ics_info(2);
let offsets = long_window_offsets(FS_44100).unwrap();
assert_eq!(offsets[0], 0);
let mut l = vec![0.0f64; 1024];
let mut r = vec![0.0f64; 1024];
let band_end = offsets[2] as usize;
for x in l.iter_mut().take(band_end) {
*x = 3.0;
}
for x in r.iter_mut().take(band_end) {
*x = 1.0;
}
let cb = plain_cb(1, 2);
run(
&mut l,
&mut r,
MsMaskPresent::AllOnes,
&[],
&cb,
&cb,
&ics,
FS_44100,
)
.unwrap();
for i in 0..band_end {
assert_eq!(l[i], 4.0, "l'[{i}]");
assert_eq!(r[i], 2.0, "r'[{i}]");
}
assert_eq!(l[band_end], 0.0);
assert_eq!(r[band_end], 0.0);
}
#[test]
fn mask_gates_per_band() {
let ics = long_ics_info(2);
let offsets = long_window_offsets(FS_44100).unwrap();
let b0 = offsets[1] as usize; let b1 = offsets[2] as usize; let mut l = vec![0.0f64; 1024];
let mut r = vec![0.0f64; 1024];
for x in l.iter_mut().take(b1) {
*x = 5.0;
}
for x in r.iter_mut().take(b1) {
*x = 1.0;
}
let cb = plain_cb(1, 2);
let ms_used = vec![vec![true, false]];
run(
&mut l,
&mut r,
MsMaskPresent::Mask,
&ms_used,
&cb,
&cb,
&ics,
FS_44100,
)
.unwrap();
for i in 0..b0 {
assert_eq!(l[i], 6.0);
assert_eq!(r[i], 4.0);
}
for i in b0..b1 {
assert_eq!(l[i], 5.0);
assert_eq!(r[i], 1.0);
}
}
#[test]
fn intensity_band_excluded() {
let ics = long_ics_info(1);
let offsets = long_window_offsets(FS_44100).unwrap();
let b0 = offsets[1] as usize;
let mut l = vec![3.0f64; 1024];
let mut r = vec![1.0f64; 1024];
let left_cb = plain_cb(1, 1);
let mut right_cb = plain_cb(1, 1);
right_cb[0][0] = INTENSITY_HCB;
run(
&mut l,
&mut r,
MsMaskPresent::AllOnes,
&[],
&left_cb,
&right_cb,
&ics,
FS_44100,
)
.unwrap();
for i in 0..b0 {
assert_eq!(l[i], 3.0);
assert_eq!(r[i], 1.0);
}
}
#[test]
fn noise_band_excluded_from_either_channel() {
let ics = long_ics_info(1);
let offsets = long_window_offsets(FS_44100).unwrap();
let b0 = offsets[1] as usize;
let mut left_cb = plain_cb(1, 1);
left_cb[0][0] = NOISE_HCB;
let right_cb = plain_cb(1, 1);
let mut l = vec![3.0f64; 1024];
let mut r = vec![1.0f64; 1024];
run(
&mut l,
&mut r,
MsMaskPresent::AllOnes,
&[],
&left_cb,
&right_cb,
&ics,
FS_44100,
)
.unwrap();
for i in 0..b0 {
assert_eq!(l[i], 3.0);
assert_eq!(r[i], 1.0);
}
}
#[test]
fn short_window_grouping_applies_per_window() {
let ics = short_ics_info(2, vec![3, 5]);
let offsets = short_window_offsets(FS_44100).unwrap();
let win = SHORT_WINDOW_LEN as usize;
let bandlen = offsets[1] as usize; let mut l = vec![0.0f64; 8 * win];
let mut r = vec![0.0f64; 8 * win];
for w in 0..8 {
for i in 0..bandlen {
l[w * win + i] = 2.0;
r[w * win + i] = 1.0;
}
}
let cb = plain_cb(2, 2);
let ms_used = vec![vec![true, false], vec![false, false]];
run(
&mut l,
&mut r,
MsMaskPresent::Mask,
&ms_used,
&cb,
&cb,
&ics,
FS_44100,
)
.unwrap();
for w in 0..3 {
for i in 0..bandlen {
assert_eq!(l[w * win + i], 3.0, "win {w} band0");
assert_eq!(r[w * win + i], 1.0);
}
}
for w in 3..8 {
for i in 0..bandlen {
assert_eq!(l[w * win + i], 2.0, "win {w} band0");
assert_eq!(r[w * win + i], 1.0);
}
}
}
#[test]
fn shape_mismatch_rejected() {
let ics = long_ics_info(2);
let cb = plain_cb(1, 2);
let mut l = vec![0.0f64; 512]; let mut r = vec![0.0f64; 1024];
assert!(matches!(
run(
&mut l,
&mut r,
MsMaskPresent::AllOnes,
&[],
&cb,
&cb,
&ics,
FS_44100,
),
Err(Error::MsStereoInvalid)
));
}
#[test]
fn mask_mode_requires_full_ms_used() {
let ics = long_ics_info(3);
let cb = plain_cb(1, 3);
let mut l = vec![0.0f64; 1024];
let mut r = vec![0.0f64; 1024];
let ms_used = vec![vec![true, false]];
assert!(matches!(
run(
&mut l,
&mut r,
MsMaskPresent::Mask,
&ms_used,
&cb,
&cb,
&ics,
FS_44100,
),
Err(Error::MsStereoInvalid)
));
}
#[test]
fn dematrix_is_exactly_invertible_for_integers() {
let ics = long_ics_info(1);
let offsets = long_window_offsets(FS_44100).unwrap();
let b0 = offsets[1] as usize;
let cb = plain_cb(1, 1);
let mut l = vec![0.0f64; 1024];
let mut r = vec![0.0f64; 1024];
for i in 0..b0 {
l[i] = (i as f64) * 0.5 - 7.0; r[i] = 3.0 - (i as f64) * 0.25; }
let m: Vec<f64> = l[..b0].to_vec();
let s: Vec<f64> = r[..b0].to_vec();
run(
&mut l,
&mut r,
MsMaskPresent::AllOnes,
&[],
&cb,
&cb,
&ics,
FS_44100,
)
.unwrap();
for i in 0..b0 {
assert_eq!(l[i], m[i] + s[i]);
assert_eq!(r[i], m[i] - s[i]);
}
}
#[test]
fn zero_hcb_bands_still_dematrix() {
let ics = long_ics_info(1);
let offsets = long_window_offsets(FS_44100).unwrap();
let b0 = offsets[1] as usize;
let mut left_cb = plain_cb(1, 1);
left_cb[0][0] = ZERO_HCB;
let mut right_cb = plain_cb(1, 1);
right_cb[0][0] = ZERO_HCB;
let mut l = vec![0.0f64; 1024];
let mut r = vec![0.0f64; 1024];
run(
&mut l,
&mut r,
MsMaskPresent::AllOnes,
&[],
&left_cb,
&right_cb,
&ics,
FS_44100,
)
.unwrap();
for i in 0..b0 {
assert_eq!(l[i], 0.0);
assert_eq!(r[i], 0.0);
}
}
}