use std::cmp::Ordering;
use crate::common::{FFT_LENGTH_BY_2, FFT_LENGTH_BY_2_PLUS_1};
use crate::config::EchoCanceller3Config;
use crate::render_buffer::RenderBuffer;
const COUNTER_THRESHOLD: usize = 5;
fn identify_small_narrow_band_regions(
render_buffer: &RenderBuffer<'_>,
delay_partitions: Option<usize>,
narrow_band_counters: &mut [usize; FFT_LENGTH_BY_2 - 1],
) {
let Some(delay) = delay_partitions else {
narrow_band_counters.fill(0);
return;
};
let mut channel_counters = [0usize; FFT_LENGTH_BY_2 - 1];
let x2 = render_buffer.spectrum(delay as i32);
for ch_spectrum in x2 {
for k in 1..FFT_LENGTH_BY_2 {
if ch_spectrum[k] > 3.0 * ch_spectrum[k - 1].max(ch_spectrum[k + 1]) {
channel_counters[k - 1] += 1;
}
}
}
for (nbc, &cc) in narrow_band_counters.iter_mut().zip(channel_counters.iter()) {
*nbc = if cc > 0 { *nbc + 1 } else { 0 };
}
}
fn identify_strong_narrow_band_component(
render_buffer: &RenderBuffer<'_>,
strong_peak_freeze_duration: usize,
narrow_peak_band: &mut Option<usize>,
narrow_peak_counter: &mut usize,
) {
if narrow_peak_band.is_some() {
*narrow_peak_counter += 1;
if *narrow_peak_counter > strong_peak_freeze_duration {
*narrow_peak_band = None;
}
}
let x_latest = render_buffer.get_block(0);
let mut max_peak_level = 0.0f32;
for channel in 0..x_latest.num_channels() {
let x2_latest = &render_buffer.spectrum(0)[channel];
let peak_bin = x2_latest
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(Ordering::Equal))
.map(|(i, _)| i)
.unwrap_or(0);
let mut non_peak_power = 0.0f32;
let start_low = peak_bin.saturating_sub(14);
let end_low = peak_bin.saturating_sub(4);
for &val in &x2_latest[start_low..end_low] {
non_peak_power = non_peak_power.max(val);
}
let start_high = (peak_bin + 5).min(FFT_LENGTH_BY_2_PLUS_1);
let end_high = (peak_bin + 15).min(FFT_LENGTH_BY_2_PLUS_1);
for &val in &x2_latest[start_high..end_high] {
non_peak_power = non_peak_power.max(val);
}
let band0 = x_latest.view(0, channel);
let mut max_abs = band0.iter().fold(0.0f32, |acc, &x| acc.max(x.abs()));
if x_latest.num_bands() > 1 {
let band1 = x_latest.view(1, channel);
let max_abs1 = band1.iter().fold(0.0f32, |acc, &x| acc.max(x.abs()));
max_abs = max_abs.max(max_abs1);
}
let peak_level = x2_latest[peak_bin];
if peak_bin > 0
&& max_abs > 100.0
&& peak_level > 100.0 * non_peak_power
&& peak_level > max_peak_level
{
max_peak_level = peak_level;
*narrow_peak_band = Some(peak_bin);
*narrow_peak_counter = 0;
}
}
}
#[derive(Debug)]
pub(crate) struct RenderSignalAnalyzer {
strong_peak_freeze_duration: usize,
narrow_band_counters: [usize; FFT_LENGTH_BY_2 - 1],
narrow_peak_band: Option<usize>,
narrow_peak_counter: usize,
}
impl Default for RenderSignalAnalyzer {
fn default() -> Self {
Self::new(&EchoCanceller3Config::default())
}
}
impl RenderSignalAnalyzer {
pub(crate) fn new(config: &EchoCanceller3Config) -> Self {
Self {
strong_peak_freeze_duration: config.filter.refined.length_blocks,
narrow_band_counters: [0; FFT_LENGTH_BY_2 - 1],
narrow_peak_band: None,
narrow_peak_counter: 0,
}
}
pub(crate) fn update(
&mut self,
render_buffer: &RenderBuffer<'_>,
delay_partitions: Option<usize>,
) {
identify_small_narrow_band_regions(
render_buffer,
delay_partitions,
&mut self.narrow_band_counters,
);
identify_strong_narrow_band_component(
render_buffer,
self.strong_peak_freeze_duration,
&mut self.narrow_peak_band,
&mut self.narrow_peak_counter,
);
}
pub(crate) fn poor_signal_excitation(&self) -> bool {
debug_assert!(self.narrow_band_counters.len() > 2);
self.narrow_band_counters.iter().any(|&a| a > 10)
}
pub(crate) fn mask_regions_around_narrow_bands(&self, v: &mut [f32; FFT_LENGTH_BY_2_PLUS_1]) {
if self.narrow_band_counters[0] > COUNTER_THRESHOLD {
v[1] = 0.0;
v[0] = 0.0;
}
#[allow(clippy::needless_range_loop, reason = "index used in arithmetic")]
for k in 2..FFT_LENGTH_BY_2 - 1 {
if self.narrow_band_counters[k - 1] > COUNTER_THRESHOLD {
v[k - 2] = 0.0;
v[k - 1] = 0.0;
v[k] = 0.0;
v[k + 1] = 0.0;
v[k + 2] = 0.0;
}
}
if self.narrow_band_counters[FFT_LENGTH_BY_2 - 2] > COUNTER_THRESHOLD {
v[FFT_LENGTH_BY_2] = 0.0;
v[FFT_LENGTH_BY_2 - 1] = 0.0;
}
}
pub(crate) fn narrow_peak_band(&self) -> Option<usize> {
self.narrow_peak_band
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::block_buffer::BlockBuffer;
use crate::fft_buffer::FftBuffer;
use crate::spectrum_buffer::SpectrumBuffer;
fn make_render_buffer(
size: usize,
num_channels: usize,
) -> (BlockBuffer, SpectrumBuffer, FftBuffer) {
(
BlockBuffer::new(size, 1, num_channels),
SpectrumBuffer::new(size, num_channels),
FftBuffer::new(size, num_channels),
)
}
#[test]
fn no_narrow_bands_initially() {
let config = EchoCanceller3Config::default();
let analyzer = RenderSignalAnalyzer::new(&config);
assert!(!analyzer.poor_signal_excitation());
assert!(analyzer.narrow_peak_band().is_none());
}
#[test]
fn mask_regions_with_no_narrow_bands() {
let config = EchoCanceller3Config::default();
let analyzer = RenderSignalAnalyzer::new(&config);
let mut v = [1.0f32; FFT_LENGTH_BY_2_PLUS_1];
analyzer.mask_regions_around_narrow_bands(&mut v);
for &val in &v {
assert_eq!(val, 1.0);
}
}
#[test]
fn update_without_delay_resets_counters() {
let config = EchoCanceller3Config::default();
let mut analyzer = RenderSignalAnalyzer::new(&config);
let size = 20;
let (bb, sb, fb) = make_render_buffer(size, 1);
let rb = RenderBuffer::new(&bb, &sb, &fb);
analyzer.update(&rb, None);
assert!(!analyzer.poor_signal_excitation());
}
}