use crate::ps_hybrid::HybridConfig;
use crate::ps_map::parameter_map;
use crate::sbr_qmf::Complex;
use crate::{Error, Result};
const DECAY_SLOPE: f64 = 0.05;
const A: [f64; 3] = [0.65143905753106, 0.56471812200776, 0.48954165955695];
const D: [usize; 3] = [3, 4, 5];
const Q_FRACT: [f64; 3] = [0.43, 0.75, 0.347];
const Q_PHI: f64 = 0.39;
const PEAK_DECAY: f64 = 0.76592833836465;
const A_SMOOTH: f64 = 0.25;
const GAMMA: f64 = 1.5;
const LONG_DELAY: usize = 14;
const F_CENTER_20: [f64; 10] = [
-3.0 / 8.0,
-1.0 / 8.0,
1.0 / 8.0,
3.0 / 8.0,
5.0 / 8.0,
7.0 / 8.0,
5.0 / 4.0,
7.0 / 4.0,
9.0 / 4.0,
11.0 / 4.0,
];
const F_CENTER_34: [f64; 32] = [
1.0 / 12.0,
3.0 / 12.0,
5.0 / 12.0,
7.0 / 12.0,
9.0 / 12.0,
11.0 / 12.0,
13.0 / 12.0,
15.0 / 12.0,
17.0 / 12.0,
-5.0 / 12.0,
-3.0 / 12.0,
-1.0 / 12.0,
17.0 / 8.0,
19.0 / 8.0,
5.0 / 8.0,
7.0 / 8.0,
9.0 / 8.0,
11.0 / 8.0,
13.0 / 8.0,
15.0 / 8.0,
9.0 / 4.0,
11.0 / 4.0,
13.0 / 4.0,
7.0 / 4.0,
17.0 / 4.0,
11.0 / 4.0,
13.0 / 4.0,
15.0 / 4.0,
17.0 / 4.0,
19.0 / 4.0,
21.0 / 4.0,
15.0 / 4.0,
];
#[derive(Debug, Clone, Copy)]
struct DecorrConsts {
nr_par_bands: usize,
nr_bands: usize,
decay_cutoff: usize,
nr_allpass_bands: usize,
short_delay_band: usize,
}
fn consts(config: HybridConfig) -> DecorrConsts {
match config {
HybridConfig::Bands1020 => DecorrConsts {
nr_par_bands: 20,
nr_bands: 71,
decay_cutoff: 10,
nr_allpass_bands: 30,
short_delay_band: 42,
},
HybridConfig::Bands34 => DecorrConsts {
nr_par_bands: 34,
nr_bands: 91,
decay_cutoff: 32,
nr_allpass_bands: 50,
short_delay_band: 62,
},
}
}
fn f_center(config: HybridConfig, k: usize) -> f64 {
match config {
HybridConfig::Bands1020 => {
if k < F_CENTER_20.len() {
F_CENTER_20[k]
} else {
k as f64 + 0.5 - 7.0
}
}
HybridConfig::Bands34 => {
if k < F_CENTER_34.len() {
F_CENTER_34[k]
} else {
k as f64 + 0.5 - 27.0
}
}
}
}
#[derive(Debug, Clone)]
struct AllpassState {
in2: [Complex; 2],
w: [Vec<Complex>; 3],
pos: [usize; 3],
}
impl AllpassState {
fn new() -> Self {
AllpassState {
in2: [Complex::default(); 2],
w: [
vec![Complex::default(); D[0]],
vec![Complex::default(); D[1]],
vec![Complex::default(); D[2]],
],
pos: [0; 3],
}
}
fn reset(&mut self) {
self.in2 = [Complex::default(); 2];
for (w, d) in self.w.iter_mut().zip(D) {
w.iter_mut().for_each(|c| *c = Complex::default());
debug_assert_eq!(w.len(), d);
}
self.pos = [0; 3];
}
}
#[derive(Debug, Clone)]
pub struct PsDecorr {
config: HybridConfig,
allpass: Vec<AllpassState>,
phi_fract: Vec<Complex>,
q_fract: Vec<[Complex; 3]>,
g_decay: Vec<f64>,
delay: Vec<Vec<Complex>>,
delay_pos: Vec<usize>,
peak_decay_nrg: Vec<f64>,
smooth_nrg: Vec<f64>,
smooth_peak_diff: Vec<f64>,
}
impl PsDecorr {
#[must_use]
pub fn new(config: HybridConfig) -> Self {
let c = consts(config);
let mut phi_fract = Vec::with_capacity(c.nr_allpass_bands);
let mut q_fract = Vec::with_capacity(c.nr_allpass_bands);
let mut g_decay = Vec::with_capacity(c.nr_allpass_bands);
for k in 0..c.nr_allpass_bands {
let f = f_center(config, k);
let arg = -core::f64::consts::PI * Q_PHI * f;
let (s, co) = arg.sin_cos();
phi_fract.push(Complex::new(co, s));
let mut qs = [Complex::default(); 3];
for (m, q) in qs.iter_mut().enumerate() {
let arg = -core::f64::consts::PI * Q_FRACT[m] * f;
let (s, co) = arg.sin_cos();
*q = Complex::new(co, s);
}
q_fract.push(qs);
let g = if k > c.decay_cutoff {
(1.0 - DECAY_SLOPE * (k as f64 - c.decay_cutoff as f64)).max(0.0)
} else {
1.0
};
g_decay.push(g);
}
let mut delay = Vec::with_capacity(c.nr_bands - c.nr_allpass_bands);
for k in c.nr_allpass_bands..c.nr_bands {
let d = if k < c.short_delay_band {
LONG_DELAY
} else {
1
};
delay.push(vec![Complex::default(); d]);
}
PsDecorr {
config,
allpass: vec![AllpassState::new(); c.nr_allpass_bands],
phi_fract,
q_fract,
g_decay,
delay_pos: vec![0; c.nr_bands - c.nr_allpass_bands],
delay,
peak_decay_nrg: vec![0.0; c.nr_par_bands],
smooth_nrg: vec![0.0; c.nr_par_bands],
smooth_peak_diff: vec![0.0; c.nr_par_bands],
}
}
pub fn reset_bands(&mut self, kmax: usize) {
let c = consts(self.config);
for k in kmax..c.nr_allpass_bands {
self.allpass[k].reset();
}
for k in kmax.max(c.nr_allpass_bands)..c.nr_bands {
let i = k - c.nr_allpass_bands;
self.delay[i]
.iter_mut()
.for_each(|v| *v = Complex::default());
self.delay_pos[i] = 0;
}
}
pub fn process(&mut self, s: &[Vec<Complex>]) -> Result<Vec<Vec<Complex>>> {
let c = consts(self.config);
let b_k = parameter_map(self.config);
if s.iter().any(|row| row.len() != c.nr_bands) {
return Err(Error::PsDataInvalid);
}
let mut out = vec![vec![Complex::default(); c.nr_bands]; s.len()];
for (n, row) in s.iter().enumerate() {
let mut p = vec![0.0f64; c.nr_par_bands];
for (k, v) in row.iter().enumerate() {
p[usize::from(b_k[k])] += v.norm_sqr();
}
let mut g_ratio = vec![1.0f64; c.nr_par_bands];
for i in 0..c.nr_par_bands {
let peak = if PEAK_DECAY * self.peak_decay_nrg[i] < p[i] {
p[i]
} else {
PEAK_DECAY * self.peak_decay_nrg[i]
};
self.peak_decay_nrg[i] = peak;
self.smooth_nrg[i] += A_SMOOTH * (p[i] - self.smooth_nrg[i]);
self.smooth_peak_diff[i] += A_SMOOTH * (peak - p[i] - self.smooth_peak_diff[i]);
if GAMMA * self.smooth_peak_diff[i] > self.smooth_nrg[i] {
g_ratio[i] = self.smooth_nrg[i] / (GAMMA * self.smooth_peak_diff[i]);
}
}
for k in 0..c.nr_allpass_bands {
let st = &mut self.allpass[k];
let delayed = st.in2[1];
st.in2[1] = st.in2[0];
st.in2[0] = row[k];
let mut u = self.phi_fract[k] * delayed;
let g = self.g_decay[k];
for m in 0..3 {
let coef = A[m] * g;
let q = self.q_fract[k][m];
let pos = st.pos[m];
let w_d = st.w[m][pos];
let w_n = u + q * w_d * coef;
u = q * w_d - w_n * coef;
st.w[m][pos] = w_n;
st.pos[m] = (pos + 1) % D[m];
}
out[n][k] = u * g_ratio[usize::from(b_k[k])];
}
for k in c.nr_allpass_bands..c.nr_bands {
let i = k - c.nr_allpass_bands;
let pos = self.delay_pos[i];
let v = self.delay[i][pos];
self.delay[i][pos] = row[k];
self.delay_pos[i] = (pos + 1) % self.delay[i].len();
out[n][k] = v * g_ratio[usize::from(b_k[k])];
}
}
Ok(out)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ps_hybrid::HybridConfig;
fn noise_slot(seed: u64, n: usize, nb: usize) -> Vec<Complex> {
(0..nb)
.map(|k| {
let mut h = seed
.wrapping_mul(6364136223846793005)
.wrapping_add((n * 128 + k) as u64);
h ^= h >> 33;
h = h.wrapping_mul(0xff51afd7ed558ccd);
h ^= h >> 33;
Complex::new(
(h & 0xFFFF) as f64 / 65535.0 - 0.5,
((h >> 16) & 0xFFFF) as f64 / 65535.0 - 0.5,
)
})
.collect()
}
#[test]
fn allpass_preserves_energy_on_stationary_noise() {
let config = HybridConfig::Bands1020;
let mut dec = PsDecorr::new(config);
let nb = config.nr_bands();
let mut in_e = vec![0.0f64; nb];
let mut out_e = vec![0.0f64; nb];
for f in 0..40 {
let s: Vec<Vec<Complex>> = (0..32).map(|n| noise_slot(3, f * 32 + n, nb)).collect();
let d = dec.process(&s).unwrap();
if f >= 8 {
for n in 0..32 {
for k in 0..nb {
in_e[k] += s[n][k].norm_sqr();
out_e[k] += d[n][k].norm_sqr();
}
}
}
}
for k in 0..nb {
let ratio = out_e[k] / in_e[k];
assert!(
(0.85..1.15).contains(&ratio),
"band {k}: energy ratio {ratio}"
);
}
}
#[test]
fn upper_bands_are_pure_delays() {
let config = HybridConfig::Bands1020;
let mut dec = PsDecorr::new(config);
let nb = config.nr_bands();
let mut frames: Vec<Vec<Vec<Complex>>> = Vec::new();
for f in 0..3 {
let s: Vec<Vec<Complex>> = (0..32)
.map(|n| {
let t = (f * 32 + n) as f64;
(0..nb)
.map(|k| {
let arg = 0.1 * t + k as f64;
let (si, co) = arg.sin_cos();
Complex::new(co, si)
})
.collect()
})
.collect();
frames.push(s);
}
let mut all_in: Vec<Vec<Complex>> = Vec::new();
let mut all_out: Vec<Vec<Complex>> = Vec::new();
for s in &frames {
let d = dec.process(s).unwrap();
all_in.extend_from_slice(s);
all_out.extend_from_slice(&d);
}
for (k, delay) in [(35usize, 14usize), (50, 1)] {
for n in 40..96 {
let d = all_out[n][k] - all_in[n - delay][k];
assert!(
d.norm_sqr() < 1e-20,
"band {k} slot {n}: not a {delay}-delay"
);
}
}
}
#[test]
fn transient_tail_is_ducked() {
let config = HybridConfig::Bands1020;
let nb = config.nr_bands();
let loud: Vec<Vec<Complex>> = (0..32).map(|_| vec![Complex::new(1.0, 0.0); nb]).collect();
let quiet: Vec<Vec<Complex>> = (0..32).map(|_| vec![Complex::default(); nb]).collect();
let mut dec = PsDecorr::new(config);
dec.process(&loud).unwrap();
let d = dec.process(&quiet).unwrap();
let first = d[0][35].norm_sqr();
let later = d[5][35].norm_sqr();
assert!((first - 1.0).abs() < 1e-12, "slot 0 should be unducked");
assert!(later < 0.64, "slot 5 should be ducked: {later}");
let even_later = d[10][35].norm_sqr();
assert!(even_later < later);
}
#[test]
fn partial_reset_clears_upper_state() {
let config = HybridConfig::Bands1020;
let nb = config.nr_bands();
let mut dec = PsDecorr::new(config);
let s: Vec<Vec<Complex>> = (0..32).map(|n| noise_slot(9, n, nb)).collect();
dec.process(&s).unwrap();
dec.reset_bands(40);
let zeros: Vec<Vec<Complex>> = (0..32).map(|_| vec![Complex::default(); nb]).collect();
let d = dec.process(&zeros).unwrap();
for (n, row) in d.iter().enumerate().take(14) {
for (k, v) in row.iter().enumerate().skip(40) {
assert_eq!(*v, Complex::default(), "slot {n} band {k}");
}
}
let rings = (0..8).any(|n| d[n][3].norm_sqr() > 0.0);
assert!(rings, "low-band state should survive a partial reset");
}
}