use crate::ics_info::IcsInfo;
use crate::section_data::NOISE_HCB;
#[cfg(test)]
use crate::swb_offset::{long_window_offsets, LONG_WINDOW_LEN};
use crate::{Error, Result};
pub fn is_noise(cb: u8) -> bool {
cb == NOISE_HCB
}
pub fn noise_target_norm(noise_nrg: i32) -> f64 {
2.0f64.powf(0.25 * noise_nrg as f64)
}
pub fn gen_rand_vector(out: &mut [f64], state: &mut u32) {
for v in out.iter_mut() {
*state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
*v = (*state as i32) as f64 / 2_147_483_648.0;
}
}
fn normalise_band(band: &mut [f64], noise_nrg: i32) {
let nrg: f64 = band.iter().map(|&x| x * x).sum();
if nrg <= 0.0 {
return;
}
let sqrt_nrg = nrg.sqrt();
let scale = noise_target_norm(noise_nrg) / sqrt_nrg;
for x in band.iter_mut() {
*x *= scale;
}
}
#[derive(Debug)]
pub struct PnsChannel<'a> {
pub spec: &'a mut [f64],
pub sfb_cb: &'a [Vec<u8>],
pub noise_nrg: &'a [Vec<i32>],
}
fn channel_geometry<'a>(
spec_len: usize,
sfb_cb: &[Vec<u8>],
noise_nrg: &[Vec<i32>],
ics_info: &IcsInfo,
fs_index: u8,
) -> Result<(usize, &'a [u16])> {
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;
if spec_len != num_windows * window_len {
return Err(Error::PnsInvalid);
}
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::PnsInvalid);
}
if max_sfb > num_swb {
return Err(Error::PnsInvalid);
}
if sfb_cb.len() != num_groups || noise_nrg.len() != num_groups {
return Err(Error::PnsInvalid);
}
for g in 0..num_groups {
if sfb_cb[g].len() < max_sfb || noise_nrg[g].len() < max_sfb {
return Err(Error::PnsInvalid);
}
}
Ok((window_len, offsets))
}
pub fn apply_pns<F>(
chan: &mut PnsChannel<'_>,
ics_info: &IcsInfo,
fs_index: u8,
mut rng: F,
) -> Result<()>
where
F: FnMut(&mut [f64]),
{
let PnsChannel {
spec,
sfb_cb,
noise_nrg,
} = chan;
let (window_len, offsets) =
channel_geometry(spec.len(), sfb_cb, noise_nrg, ics_info, fs_index)?;
let num_groups = ics_info.num_window_groups as usize;
let max_sfb = ics_info.max_sfb as usize;
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 {
if !is_noise(sfb_cb[g][sfb]) {
continue;
}
let start = offsets[sfb] as usize;
let end = offsets[sfb + 1] as usize;
let nrg = noise_nrg[g][sfb];
for b in 0..wgl {
let base = (window_base + b) * window_len;
let band = &mut spec[base + start..base + end];
rng(band);
normalise_band(band, nrg);
}
}
window_base += wgl;
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn apply_pns_pair<F>(
left: &mut PnsChannel<'_>,
right: &mut PnsChannel<'_>,
ms_mask_present: bool,
all_shared: bool,
ms_used: &[Vec<bool>],
ics_info: &IcsInfo,
fs_index: u8,
mut rng: F,
) -> Result<()>
where
F: FnMut(&mut [f64]),
{
let (window_len, offsets) = channel_geometry(
left.spec.len(),
left.sfb_cb,
left.noise_nrg,
ics_info,
fs_index,
)?;
let (rwindow_len, _) = channel_geometry(
right.spec.len(),
right.sfb_cb,
right.noise_nrg,
ics_info,
fs_index,
)?;
if rwindow_len != window_len {
return Err(Error::PnsInvalid);
}
let num_groups = ics_info.num_window_groups as usize;
let max_sfb = ics_info.max_sfb as usize;
if ms_mask_present && !all_shared {
if ms_used.len() != num_groups {
return Err(Error::PnsInvalid);
}
for row in ms_used {
if row.len() < max_sfb {
return Err(Error::PnsInvalid);
}
}
}
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 l_noise = is_noise(left.sfb_cb[g][sfb]);
let r_noise = is_noise(right.sfb_cb[g][sfb]);
if !l_noise && !r_noise {
continue;
}
let start = offsets[sfb] as usize;
let end = offsets[sfb + 1] as usize;
let size = end - start;
let band_ms_used = ms_used
.get(g)
.and_then(|row| row.get(sfb))
.copied()
.unwrap_or(false);
let shared = l_noise && r_noise && (all_shared || (ms_mask_present && band_ms_used));
for b in 0..wgl {
let base = (window_base + b) * window_len;
if shared {
let mut vec = vec![0.0f64; size];
rng(&mut vec);
let lband = &mut left.spec[base + start..base + end];
lband.copy_from_slice(&vec);
normalise_band(lband, left.noise_nrg[g][sfb]);
let rband = &mut right.spec[base + start..base + end];
rband.copy_from_slice(&vec);
normalise_band(rband, right.noise_nrg[g][sfb]);
} else {
if l_noise {
let lband = &mut left.spec[base + start..base + end];
rng(lband);
normalise_band(lband, left.noise_nrg[g][sfb]);
}
if r_noise {
let rband = &mut right.spec[base + start..base + end];
rng(rband);
normalise_band(rband, right.noise_nrg[g][sfb]);
}
}
}
}
window_base += wgl;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ics_info::{WindowSequence, WindowShape};
use crate::section_data::{INTENSITY_HCB, ZERO_HCB};
const FS_48000: u8 = 3;
fn long_ics(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_48000 as usize],
}
}
fn norm(s: &[f64]) -> f64 {
s.iter().map(|&x| x * x).sum::<f64>().sqrt()
}
#[test]
fn noise_target_norm_is_quarter_step_ladder() {
assert!((noise_target_norm(0) - 1.0).abs() < 1e-12);
assert!((noise_target_norm(4) - 2.0).abs() < 1e-12);
assert!((noise_target_norm(-4) - 0.5).abs() < 1e-12);
}
#[test]
fn gen_rand_vector_is_signed_and_nonzero_energy() {
let mut state = 1u32;
let mut v = vec![0.0f64; 16];
gen_rand_vector(&mut v, &mut state);
assert!(v.iter().all(|&x| (-1.0..1.0).contains(&x)));
assert!(v.iter().map(|&x| x * x).sum::<f64>() > 0.0);
assert!(v.iter().any(|&x| x < 0.0));
assert!(v.iter().any(|&x| x > 0.0));
}
#[test]
fn synthesised_band_has_exact_target_norm() {
let fs = 3u8;
let ics = long_ics(1);
let offsets = long_window_offsets(fs).unwrap();
let mut spec = vec![0.0f64; LONG_WINDOW_LEN as usize];
let sfb_cb = vec![vec![NOISE_HCB]];
let noise_nrg = vec![vec![8i32]]; let mut state = 12345u32;
let mut chan = PnsChannel {
spec: &mut spec,
sfb_cb: &sfb_cb,
noise_nrg: &noise_nrg,
};
apply_pns(&mut chan, &ics, fs, |out| gen_rand_vector(out, &mut state)).unwrap();
let start = offsets[0] as usize;
let end = offsets[1] as usize;
let got = norm(&spec[start..end]);
assert!((got - 4.0).abs() < 1e-9, "band L2 norm {got} != target 4.0");
assert!(spec[end..].iter().all(|&x| x == 0.0));
}
#[test]
fn non_noise_bands_left_untouched() {
let fs = 3u8;
let ics = long_ics(2);
let offsets = long_window_offsets(fs).unwrap();
let mut spec = vec![0.0f64; LONG_WINDOW_LEN as usize];
let b1 = offsets[1] as usize;
spec[b1] = 7.5;
let sfb_cb = vec![vec![NOISE_HCB, ZERO_HCB]];
let noise_nrg = vec![vec![0i32, 0i32]];
let mut state = 1u32;
let mut chan = PnsChannel {
spec: &mut spec,
sfb_cb: &sfb_cb,
noise_nrg: &noise_nrg,
};
apply_pns(&mut chan, &ics, fs, |out| gen_rand_vector(out, &mut state)).unwrap();
assert_eq!(spec[b1], 7.5, "non-noise band must be untouched");
assert!(norm(&spec[offsets[0] as usize..b1]) > 0.0);
}
#[test]
fn shape_mismatch_is_rejected() {
let fs = 3u8;
let ics = long_ics(1);
let mut spec = vec![0.0f64; 10]; let sfb_cb = vec![vec![NOISE_HCB]];
let noise_nrg = vec![vec![0i32]];
let mut chan = PnsChannel {
spec: &mut spec,
sfb_cb: &sfb_cb,
noise_nrg: &noise_nrg,
};
let r = apply_pns(&mut chan, &ics, fs, |_| {});
assert!(matches!(r, Err(Error::PnsInvalid)));
}
#[test]
fn pair_shared_vector_correlates_noise() {
let fs = 3u8;
let ics = long_ics(1);
let offsets = long_window_offsets(fs).unwrap();
let mut lspec = vec![0.0f64; LONG_WINDOW_LEN as usize];
let mut rspec = vec![0.0f64; LONG_WINDOW_LEN as usize];
let sfb_cb = vec![vec![NOISE_HCB]];
let noise_nrg = vec![vec![4i32]];
let ms_used = vec![vec![true]];
let mut state = 999u32;
{
let mut l = PnsChannel {
spec: &mut lspec,
sfb_cb: &sfb_cb,
noise_nrg: &noise_nrg,
};
let mut r = PnsChannel {
spec: &mut rspec,
sfb_cb: &sfb_cb,
noise_nrg: &noise_nrg,
};
apply_pns_pair(&mut l, &mut r, true, false, &ms_used, &ics, fs, |out| {
gen_rand_vector(out, &mut state)
})
.unwrap();
}
let start = offsets[0] as usize;
let end = offsets[1] as usize;
for i in start..end {
assert!(
(lspec[i] - rspec[i]).abs() < 1e-12,
"shared-vector bands must be identical at {i}"
);
}
assert!((norm(&lspec[start..end]) - noise_target_norm(4)).abs() < 1e-9);
}
#[test]
fn pair_independent_when_mask_absent() {
let fs = 3u8;
let ics = long_ics(1);
let offsets = long_window_offsets(fs).unwrap();
let mut lspec = vec![0.0f64; LONG_WINDOW_LEN as usize];
let mut rspec = vec![0.0f64; LONG_WINDOW_LEN as usize];
let sfb_cb = vec![vec![NOISE_HCB]];
let noise_nrg = vec![vec![4i32]];
let mut state = 7u32;
{
let mut l = PnsChannel {
spec: &mut lspec,
sfb_cb: &sfb_cb,
noise_nrg: &noise_nrg,
};
let mut r = PnsChannel {
spec: &mut rspec,
sfb_cb: &sfb_cb,
noise_nrg: &noise_nrg,
};
apply_pns_pair(&mut l, &mut r, false, false, &[], &ics, fs, |out| {
gen_rand_vector(out, &mut state)
})
.unwrap();
}
let start = offsets[0] as usize;
let end = offsets[1] as usize;
let identical = (start..end).all(|i| lspec[i] == rspec[i]);
assert!(!identical, "independent draws must not be bit-identical");
assert!((norm(&lspec[start..end]) - noise_target_norm(4)).abs() < 1e-9);
assert!((norm(&rspec[start..end]) - noise_target_norm(4)).abs() < 1e-9);
}
#[test]
fn pair_one_channel_noise_ignores_ms_used() {
let fs = 3u8;
let ics = long_ics(1);
let offsets = long_window_offsets(fs).unwrap();
let mut lspec = vec![0.0f64; LONG_WINDOW_LEN as usize];
let mut rspec = vec![0.0f64; LONG_WINDOW_LEN as usize];
let lcb = vec![vec![ZERO_HCB]];
let rcb = vec![vec![NOISE_HCB]];
let lnrg = vec![vec![0i32]];
let rnrg = vec![vec![4i32]];
let ms_used = vec![vec![true]];
let mut state = 42u32;
{
let mut l = PnsChannel {
spec: &mut lspec,
sfb_cb: &lcb,
noise_nrg: &lnrg,
};
let mut r = PnsChannel {
spec: &mut rspec,
sfb_cb: &rcb,
noise_nrg: &rnrg,
};
apply_pns_pair(&mut l, &mut r, true, false, &ms_used, &ics, fs, |out| {
gen_rand_vector(out, &mut state)
})
.unwrap();
}
let start = offsets[0] as usize;
let end = offsets[1] as usize;
assert!(
lspec[start..end].iter().all(|&x| x == 0.0),
"non-noise left band must stay silent"
);
assert!((norm(&rspec[start..end]) - noise_target_norm(4)).abs() < 1e-9);
}
#[test]
fn is_noise_predicate() {
assert!(is_noise(NOISE_HCB));
assert!(!is_noise(ZERO_HCB));
assert!(!is_noise(INTENSITY_HCB));
assert!(!is_noise(1));
}
}