pub const SINE_TABLE: [(f32, f32); 4] = [(1.0, 0.0), (0.0, 1.0), (-1.0, 0.0), (0.0, -1.0)];
#[derive(Debug, Clone, Default)]
pub struct QmfSine {
pub qmf_sine: Vec<Vec<(f32, f32)>>,
pub last_indices: Vec<Vec<u32>>,
}
#[derive(Debug, Clone, Default)]
pub struct ToneGenState {
pub prev: Option<Vec<Vec<u32>>>,
}
impl ToneGenState {
pub fn new() -> Self {
Self { prev: None }
}
pub fn reset(&mut self) {
self.prev = None;
}
}
#[inline]
pub fn sine_idx(ts: u32, atsg_sig0_qmf_slots: u32, sine_idx_prev: Option<u32>) -> u32 {
let base = match sine_idx_prev {
Some(p) => (p + 1) % 4,
None => 1,
};
let offset = ts.saturating_sub(atsg_sig0_qmf_slots);
(base + offset) % 4
}
#[allow(clippy::too_many_arguments)]
pub fn generate_qmf_sine(
sine_lev_sb_adj: &[Vec<f32>],
atsg_sig: &[u32],
num_ts_in_ats: u32,
num_qmf_subbands: u32,
sbx: u32,
num_sb_aspx: u32,
state: &mut ToneGenState,
) -> QmfSine {
let num_atsg_sig = atsg_sig.len().saturating_sub(1);
if num_atsg_sig == 0 || num_sb_aspx == 0 {
return QmfSine::default();
}
let ts_start = atsg_sig[0].saturating_mul(num_ts_in_ats);
let ts_end = atsg_sig[num_atsg_sig].saturating_mul(num_ts_in_ats);
let ts_total = ts_end as usize;
let mut qmf_sine: Vec<Vec<(f32, f32)>> = (0..num_qmf_subbands)
.map(|_| vec![(0.0_f32, 0.0_f32); ts_total])
.collect();
let mut last_indices: Vec<Vec<u32>> = (0..num_qmf_subbands)
.map(|_| vec![0_u32; ts_total])
.collect();
let atsg_sig0_qmf = atsg_sig[0].saturating_mul(num_ts_in_ats);
let mut atsg: usize = 0;
for ts in ts_start..ts_end {
while atsg + 1 < num_atsg_sig && ts >= atsg_sig[atsg + 1].saturating_mul(num_ts_in_ats) {
atsg += 1;
}
for sb in 0..(num_sb_aspx as usize) {
let sb_abs = sb + sbx as usize;
if sb_abs >= qmf_sine.len() {
break;
}
let prev = state.prev.as_ref().and_then(|mat| {
mat.get(sb_abs)
.and_then(|row| row.get(ts as usize).copied())
});
let idx = sine_idx(ts, atsg_sig0_qmf, prev);
last_indices[sb_abs][ts as usize] = idx;
let (st_re, st_im) = SINE_TABLE[idx as usize];
let lev = sine_lev_sb_adj
.get(sb)
.and_then(|row| row.get(atsg))
.copied()
.unwrap_or(0.0);
let sign = if (sb_abs) & 1 == 0 { 1.0_f32 } else { -1.0_f32 };
qmf_sine[sb_abs][ts as usize] = (lev * st_re, lev * sign * st_im);
}
}
state.prev = Some(last_indices.clone());
QmfSine {
qmf_sine,
last_indices,
}
}
pub fn add_qmf_sine(
y: &mut [Vec<(f32, f32)>],
qmf_sine: &QmfSine,
atsg_sig: &[u32],
num_ts_in_ats: u32,
sbx: u32,
sbz: u32,
) {
let num_atsg_sig = atsg_sig.len().saturating_sub(1);
if num_atsg_sig == 0 {
return;
}
let ts_start = atsg_sig[0].saturating_mul(num_ts_in_ats) as usize;
let ts_end = atsg_sig[num_atsg_sig].saturating_mul(num_ts_in_ats) as usize;
for sb in (sbx as usize)..(sbz as usize) {
if sb >= y.len() || sb >= qmf_sine.qmf_sine.len() {
break;
}
let src = &qmf_sine.qmf_sine[sb];
let dst = &mut y[sb];
let hi = ts_end.min(dst.len()).min(src.len());
for ts in ts_start..hi {
dst[ts].0 += src[ts].0;
dst[ts].1 += src[ts].1;
}
}
}
pub fn level_matrix_from_flags(
tone_mask_sb: &[bool],
num_atsg_sig: u32,
level: f32,
) -> Vec<Vec<f32>> {
tone_mask_sb
.iter()
.map(|&on| {
let v = if on { level } else { 0.0 };
vec![v; num_atsg_sig as usize]
})
.collect()
}
pub fn hf_assemble(
y: &mut [Vec<(f32, f32)>],
qmf_noise: &crate::aspx_noise::QmfNoise,
qmf_sine: &QmfSine,
atsg_sig: &[u32],
num_ts_in_ats: u32,
sbx: u32,
sbz: u32,
) {
crate::aspx_noise::add_qmf_noise(y, qmf_noise, atsg_sig, num_ts_in_ats, sbx, sbz);
add_qmf_sine(y, qmf_sine, atsg_sig, num_ts_in_ats, sbx, sbz);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sine_table_matches_table_196() {
assert_eq!(SINE_TABLE[0], (1.0, 0.0));
assert_eq!(SINE_TABLE[1], (0.0, 1.0));
assert_eq!(SINE_TABLE[2], (-1.0, 0.0));
assert_eq!(SINE_TABLE[3], (0.0, -1.0));
}
#[test]
fn sine_idx_first_frame_starts_at_1() {
assert_eq!(sine_idx(0, 0, None), 1);
assert_eq!(sine_idx(1, 0, None), 2);
assert_eq!(sine_idx(2, 0, None), 3);
assert_eq!(sine_idx(3, 0, None), 0);
assert_eq!(sine_idx(4, 0, None), 1);
}
#[test]
fn sine_idx_continues_from_prev() {
assert_eq!(sine_idx(0, 0, Some(2)), 3);
assert_eq!(sine_idx(0, 0, Some(3)), 0);
assert_eq!(sine_idx(5, 0, Some(1)), 3);
}
#[test]
fn sine_idx_offset_by_atsg_start() {
assert_eq!(sine_idx(4, 4, None), 1);
assert_eq!(sine_idx(5, 4, None), 2);
assert_eq!(sine_idx(7, 4, None), 0);
}
#[test]
fn generator_places_tones_only_in_marked_subbands() {
let atsg_sig = vec![0_u32, 2];
let tone_mask_sb = vec![true, false, false, true];
let sine_lev = level_matrix_from_flags(&tone_mask_sb, 1, 1.0);
let mut state = ToneGenState::new();
let out = generate_qmf_sine(&sine_lev, &atsg_sig, 1, 64, 8, 4, &mut state);
for sb in 0..8 {
for ts in 0..2 {
assert_eq!(out.qmf_sine[sb][ts], (0.0, 0.0));
}
}
for sb in 9..=10 {
for ts in 0..2 {
assert_eq!(out.qmf_sine[sb][ts], (0.0, 0.0));
}
}
assert!((out.qmf_sine[8][0].0 - 0.0).abs() < 1e-6);
assert!((out.qmf_sine[8][0].1 - 1.0).abs() < 1e-6);
assert!((out.qmf_sine[8][1].0 - (-1.0)).abs() < 1e-6);
assert!((out.qmf_sine[8][1].1 - 0.0).abs() < 1e-6);
assert!((out.qmf_sine[11][0].0 - 0.0).abs() < 1e-6);
assert!((out.qmf_sine[11][0].1 - (-1.0)).abs() < 1e-6);
assert!((out.qmf_sine[11][1].0 - (-1.0)).abs() < 1e-6);
assert!((out.qmf_sine[11][1].1 - 0.0).abs() < 1e-6);
assert!(state.prev.is_some());
}
#[test]
fn generator_scales_by_level() {
let atsg_sig = vec![0_u32, 4];
let sine_lev = vec![vec![3.5_f32]; 2];
let mut state = ToneGenState::new();
let out = generate_qmf_sine(&sine_lev, &atsg_sig, 1, 64, 4, 2, &mut state);
assert!((out.qmf_sine[4][0].1 - 3.5).abs() < 1e-5);
assert!((out.qmf_sine[4][1].0 + 3.5).abs() < 1e-5);
assert!((out.qmf_sine[4][2].1 + 3.5).abs() < 1e-5);
assert!((out.qmf_sine[4][3].0 - 3.5).abs() < 1e-5);
}
#[test]
fn generator_picks_correct_sig_envelope() {
let atsg_sig = vec![0_u32, 2, 4];
let sine_lev = vec![vec![1.0_f32, 4.0_f32], vec![0.0_f32, 0.0_f32]];
let mut state = ToneGenState::new();
let out = generate_qmf_sine(&sine_lev, &atsg_sig, 1, 64, 4, 2, &mut state);
assert!((out.qmf_sine[4][0].1 - 1.0).abs() < 1e-5);
assert!((out.qmf_sine[4][2].1 + 4.0).abs() < 1e-5);
}
#[test]
fn add_qmf_sine_adds_to_existing_matrix() {
let atsg_sig = vec![0_u32, 2];
let sine_lev = vec![vec![1.0_f32]; 2];
let mut state = ToneGenState::new();
let out = generate_qmf_sine(&sine_lev, &atsg_sig, 1, 64, 4, 2, &mut state);
let mut y: Vec<Vec<(f32, f32)>> = (0..64).map(|_| vec![(0.1_f32, 0.2_f32); 2]).collect();
add_qmf_sine(&mut y, &out, &atsg_sig, 1, 4, 6);
for row in y.iter().take(4) {
assert_eq!(row[0], (0.1, 0.2));
}
for row in y.iter().take(64).skip(6) {
assert_eq!(row[0], (0.1, 0.2));
}
assert!((y[4][0].0 - 0.1).abs() < 1e-5);
assert!((y[4][0].1 - 1.2).abs() < 1e-5);
}
#[test]
fn level_matrix_from_flags_preserves_shape() {
let tone_mask_sb = vec![true, false, true, true];
let m = level_matrix_from_flags(&tone_mask_sb, 3, 2.5);
assert_eq!(m.len(), 4);
assert_eq!(m[0], vec![2.5_f32; 3]);
assert_eq!(m[1], vec![0.0_f32; 3]);
assert_eq!(m[2], vec![2.5_f32; 3]);
assert_eq!(m[3], vec![2.5_f32; 3]);
}
#[test]
fn hf_assemble_adds_both_noise_and_tone() {
use crate::aspx_noise::{generate_qmf_noise, NoiseGenState};
let atsg_sig = vec![0_u32, 2];
let atsg_noise = vec![0_u32, 2];
let noise_lev = vec![vec![1.0_f32]; 2];
let sine_lev = vec![vec![1.0_f32]; 2];
let mut ns = NoiseGenState::new();
let mut ts = ToneGenState::new();
let noise = generate_qmf_noise(&noise_lev, &atsg_sig, &atsg_noise, 1, 64, 4, 2, &mut ns);
let sine = generate_qmf_sine(&sine_lev, &atsg_sig, 1, 64, 4, 2, &mut ts);
let mut y: Vec<Vec<(f32, f32)>> = (0..64).map(|_| vec![(0.0_f32, 0.0_f32); 2]).collect();
hf_assemble(&mut y, &noise, &sine, &atsg_sig, 1, 4, 6);
let idx = crate::aspx_noise::noise_idx(4, 0, 0, 1, 2, None);
let (nre, nim) = crate::aspx_noise::ASPX_NOISE_TABLE[idx as usize];
assert!((y[4][0].0 - (nre + 0.0)).abs() < 1e-5);
assert!((y[4][0].1 - (nim + 1.0)).abs() < 1e-5);
for row in y.iter().take(4) {
for &cell in row.iter().take(2) {
assert_eq!(cell, (0.0, 0.0));
}
}
for row in y.iter().take(64).skip(6) {
for &cell in row.iter().take(2) {
assert_eq!(cell, (0.0, 0.0));
}
}
}
#[test]
fn tone_gen_state_resets() {
let mut state = ToneGenState::new();
let atsg_sig = vec![0_u32, 1];
let sine_lev = vec![vec![1.0_f32]; 2];
let _ = generate_qmf_sine(&sine_lev, &atsg_sig, 1, 64, 2, 2, &mut state);
assert!(state.prev.is_some());
state.reset();
assert!(state.prev.is_none());
}
#[test]
fn noise_plus_tone_inject_hf_energy_round_trip() {
use crate::aspx_noise::{generate_qmf_noise, NoiseGenState};
use crate::qmf::{QmfAnalysisBank, QmfSynthesisBank, NUM_QMF_SUBBANDS};
let fs = 48_000.0_f32;
let f0 = 1_000.0_f32;
let n_samples = NUM_QMF_SUBBANDS * 16;
let pcm_in: Vec<f32> = (0..n_samples)
.map(|i| (2.0 * std::f32::consts::PI * f0 * i as f32 / fs).sin() * 0.5)
.collect();
let mut ana = QmfAnalysisBank::new();
let slots = ana.process_block(&pcm_in);
let n_slots = slots.len();
let sbx = 8_u32;
let sbz = 32_u32;
let num_sb_aspx = sbz - sbx;
let mut q: Vec<Vec<(f32, f32)>> = (0..NUM_QMF_SUBBANDS)
.map(|_| vec![(0.0f32, 0.0f32); n_slots])
.collect();
for (ts, slot) in slots.iter().enumerate() {
for (sb, s) in slot.iter().enumerate() {
q[sb][ts] = *s;
}
}
for row in q.iter_mut().skip(sbx as usize) {
for s in row.iter_mut() {
*s = (0.0, 0.0);
}
}
let atsg_sig = vec![0_u32, n_slots as u32];
let atsg_noise = vec![0_u32, n_slots as u32];
let noise_lev: Vec<Vec<f32>> = (0..num_sb_aspx).map(|_| vec![0.1_f32]).collect();
let mut tone_mask = vec![false; num_sb_aspx as usize];
tone_mask[16 - sbx as usize] = true;
let sine_lev = level_matrix_from_flags(&tone_mask, 1, 0.3);
let mut ns = NoiseGenState::new();
let mut ts_state = ToneGenState::new();
let qnoise = generate_qmf_noise(
&noise_lev,
&atsg_sig,
&atsg_noise,
1,
NUM_QMF_SUBBANDS as u32,
sbx,
num_sb_aspx,
&mut ns,
);
let qsine = generate_qmf_sine(
&sine_lev,
&atsg_sig,
1,
NUM_QMF_SUBBANDS as u32,
sbx,
num_sb_aspx,
&mut ts_state,
);
hf_assemble(&mut q, &qnoise, &qsine, &atsg_sig, 1, sbx, sbz);
let mut hf_qmf_energy = 0.0_f64;
for row in q.iter().take(sbz as usize).skip(sbx as usize) {
for &(re, im) in row.iter() {
hf_qmf_energy += (re as f64).powi(2) + (im as f64).powi(2);
}
}
assert!(hf_qmf_energy > 0.1, "HF QMF energy = {hf_qmf_energy}");
let mut syn = QmfSynthesisBank::new();
let mut pcm_out = Vec::with_capacity(n_samples);
#[allow(clippy::needless_range_loop)] for ts in 0..n_slots {
let mut slot = [(0.0f32, 0.0f32); NUM_QMF_SUBBANDS];
for (sb, s) in slot.iter_mut().enumerate() {
*s = q[sb][ts];
}
let row = syn.process_slot(&slot);
pcm_out.extend_from_slice(&row);
}
let skip = 384_usize.min(pcm_out.len().saturating_sub(64));
let probe_f = 6_187.5_f32;
let dft_at = |pcm: &[f32], skip: usize| -> f64 {
let mut re = 0.0_f64;
let mut im = 0.0_f64;
for (i, &s) in pcm[skip..].iter().enumerate() {
let angle =
-2.0 * std::f64::consts::PI * (probe_f as f64) * (i as f64) / (fs as f64);
re += (s as f64) * angle.cos();
im += (s as f64) * angle.sin();
}
(re * re + im * im).sqrt() / ((pcm.len() - skip).max(1) as f64)
};
let mag_out = dft_at(&pcm_out, skip);
let mag_in = dft_at(&pcm_in, skip);
assert!(
mag_out > 2.0 * mag_in.max(1e-6),
"mag_out={mag_out} mag_in={mag_in}",
);
let rms_out = (pcm_out[skip..]
.iter()
.map(|&s| (s as f64).powi(2))
.sum::<f64>()
/ (pcm_out.len() - skip) as f64)
.sqrt();
assert!(rms_out > 0.01, "rms_out = {rms_out}");
}
}