1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! Perceptual Noise Substitution — ISO/IEC 14496-3 §4.6.13.
use super::error::{Error, Result};
use super::ics::IcsInfo;
use super::section::{SectionData, is_noise};
use super::sf::ScaleFactors;
/// LCG suggested by §4.6.13.3 ("one multiply-accumulate per random value").
///
/// ISO leaves the generator non-normative. Seed and int-as-float mapping
/// match FFmpeg libavcodec (`ac->random_state = 0x1f2e3d4c`).
#[derive(Clone, Copy, Debug)]
pub struct Lcg {
state: u32,
}
impl Lcg {
/// lavc `ff_aac_decode_init` seed.
#[must_use]
pub fn new() -> Self {
Self { state: 0x1f2e_3d4c }
}
fn next_i32(&mut self) -> i32 {
self.state = self
.state
.wrapping_mul(1_664_525)
.wrapping_add(1_013_904_223);
self.state as i32
}
}
impl Default for Lcg {
fn default() -> Self {
Self::new()
}
}
/// Pair-channel PNS correlation inputs (§4.6.13.3).
pub struct PairPns<'a> {
/// CPE M/S mask (true when the band is correlated).
pub ms: Option<&'a super::stereo::MsInfo>,
/// The other channel's `sfb_cb`.
pub other_cb: Option<&'a [Vec<u8>]>,
/// Shared random vector for a correlated band (pre-normalise).
pub shared: &'a mut Vec<f32>,
pub have_shared: &'a mut bool,
}
/// Fill NOISE_HCB bands. When the pair is noise on both sides and `ms_used`,
/// reuse `shared` so the pair is correlated (§4.6.13.3).
pub fn apply(
spec: &mut [f32],
ics: &IcsInfo,
sections: &SectionData,
sf: &ScaleFactors,
fs_index: u8,
rng: &mut Lcg,
mut pair: Option<&mut PairPns<'_>>,
) -> Result<()> {
let win_len = ics.window_len();
let offsets = ics.swb_offsets(fs_index)?;
let mut wbase = 0usize;
for g in 0..ics.num_window_groups as usize {
let glen = ics.window_group_length[g] as usize;
let cbs = sections.sfb_cb.get(g).ok_or(Error::SpectrumInvalid)?;
for sfb in 0..ics.max_sfb as usize {
let cb = *cbs.get(sfb).unwrap_or(&0);
if !is_noise(cb) {
continue;
}
let start = *offsets.get(sfb).ok_or(Error::SpectrumInvalid)? as usize;
let end = *offsets.get(sfb + 1).ok_or(Error::SpectrumInvalid)? as usize;
let width = end.saturating_sub(start);
if width == 0 {
continue;
}
let nrg = *sf.noise_nrg.get(g).and_then(|v| v.get(sfb)).unwrap_or(&0);
// ISO §4.6.13 energy. lavc stores `-2^(sfo/4)` because its IMDCT
// polarity is inverted; ours is the ISO sum, so the scale is positive
// and PCM matches lavc (PNS-only corr was −1 with the extra minus).
let target = (0.25 * nrg as f32).exp2();
let correlated = pair.as_ref().is_some_and(|p| {
p.ms.map(|ms| ms.used(g, sfb)).unwrap_or(false)
&& p.other_cb
.and_then(|o| o.get(g).and_then(|v| v.get(sfb)).copied())
.map(is_noise)
.unwrap_or(false)
});
for b in 0..glen {
let w = wbase + b;
let base = w * win_len + start;
let dst = &mut spec[base..base + width];
if correlated {
if let Some(p) = pair.as_mut() {
if *p.have_shared && p.shared.len() == width {
dst.copy_from_slice(p.shared);
} else {
for x in dst.iter_mut() {
*x = rng.next_i32() as f32;
}
p.shared.clear();
p.shared.extend_from_slice(dst);
*p.have_shared = true;
}
} else {
for x in dst.iter_mut() {
*x = rng.next_i32() as f32;
}
}
} else {
for x in dst.iter_mut() {
*x = rng.next_i32() as f32;
}
}
let mut energy = 0.0f32;
for &x in dst.iter() {
energy += x * x;
}
let scale = if energy > 0.0 {
target / energy.sqrt()
} else {
0.0
};
for x in dst.iter_mut() {
*x *= scale;
}
}
if !correlated && let Some(p) = pair.as_mut() {
*p.have_shared = false;
}
}
wbase += glen;
}
Ok(())
}