use alloc::vec;
use alloc::vec::Vec;
use num_complex::Complex;
#[cfg(not(feature = "std"))]
use num_traits::Float;
use super::super::params::{NMAX, NSPS, NTONES};
use super::types::{AudioSample, NFFT_SPEC, NSTEP, SAMPLE_RATE_HZ, TONE_SPACING_HZ};
#[cfg(not(feature = "fixed-point"))]
use crate::core::fft::default_planner;
#[cfg(not(feature = "fixed-point"))]
pub type SpecCell = f32;
#[cfg(feature = "fixed-point")]
pub type SpecCell = u16;
#[cfg(feature = "fixed-point")]
const FP_SPEC_SHIFT: u32 = 12;
pub struct Spectrogram {
pub n_freq: usize,
pub n_time: usize,
pub data: Vec<SpecCell>,
}
impl Spectrogram {
pub fn from_parts(n_freq: usize, n_time: usize, data: Vec<SpecCell>) -> Self {
assert_eq!(
data.len(),
n_freq * n_time,
"Spectrogram::from_parts: data length must be n_freq * n_time"
);
Self {
n_freq,
n_time,
data,
}
}
}
pub(super) type CoarseAcc = f32;
impl Spectrogram {
#[inline]
pub(super) fn power_acc(&self, freq_bin: usize, time_idx: usize) -> CoarseAcc {
debug_assert!(freq_bin < self.n_freq);
debug_assert!(time_idx < self.n_time);
#[allow(clippy::unnecessary_cast)]
let v = self.data[time_idx * self.n_freq + freq_bin] as CoarseAcc;
v
}
}
#[cfg(not(feature = "fixed-point"))]
pub fn compute_spectrogram<S: AudioSample>(audio: &[S], max_freq_hz: f32) -> Spectrogram {
let df = SAMPLE_RATE_HZ / NFFT_SPEC as f32;
let band_top_hz = max_freq_hz + (NTONES as f32) * TONE_SPACING_HZ;
let n_freq_full = NFFT_SPEC / 2;
let n_freq = ((band_top_hz / df).ceil() as usize + 1).min(n_freq_full);
let n_time = NMAX / NSTEP - 3;
let scale = 1.0f32 / 300.0;
let mut planner = default_planner();
let fft = planner.plan_forward(NFFT_SPEC);
let mut data = vec![0.0f32; n_freq * n_time];
let mut buf = vec![Complex::new(0.0f32, 0.0); NFFT_SPEC];
for j in 0..n_time {
let ia = j * NSTEP;
for (k, c) in buf.iter_mut().enumerate() {
*c = if k < NSPS {
let sample = if ia + k < audio.len() {
audio[ia + k].to_f32() * scale
} else {
0.0
};
Complex::new(sample, 0.0)
} else {
Complex::new(0.0, 0.0)
};
}
fft.process(&mut buf);
let row_base = j * n_freq;
for i in 0..n_freq {
data[row_base + i] = buf[i].norm_sqr();
}
}
Spectrogram {
n_freq,
n_time,
data,
}
}
#[cfg(feature = "fixed-point")]
pub fn compute_spectrogram<S: AudioSample>(audio: &[S], max_freq_hz: f32) -> Spectrogram {
use crate::core::fft::default_planner_16;
let df = SAMPLE_RATE_HZ / NFFT_SPEC as f32;
let band_top_hz = max_freq_hz + (NTONES as f32) * TONE_SPACING_HZ;
let n_freq_full = NFFT_SPEC / 2;
let n_freq = ((band_top_hz / df).ceil() as usize + 1).min(n_freq_full);
let n_time = NMAX / NSTEP - 3;
let target_peak: i32 = (NFFT_SPEC * 2) as i32;
let mut peak_abs: i32 = 1;
let n_scan = audio.len().min(NMAX);
for k in 0..n_scan {
let v = audio[k].to_i16() as i32;
let a = v.unsigned_abs() as i32;
if a > peak_abs {
peak_abs = a;
}
}
let mut shift: u32 = 0;
while peak_abs << shift < target_peak && shift < 8 {
shift += 1;
}
let mut planner = default_planner_16();
let fft = planner.plan_forward(NFFT_SPEC);
let mut data: Vec<u16> = vec![0u16; n_freq * n_time];
let mut buf: Vec<Complex<i16>> = vec![Complex::new(0i16, 0i16); NFFT_SPEC];
let n_pairs = n_time / 2;
let n_odd = n_time & 1;
let mut hann = [0i16; NSPS];
for n in 0..NSPS {
let phase = 2.0 * core::f32::consts::PI * (n as f32) / (NSPS as f32);
let w = 0.5 - 0.5 * phase.cos();
hann[n] = (w * 32767.0) as i16;
}
let pack = |buf: &mut [Complex<i16>], ia_a: usize, ia_b: Option<usize>| {
for (k, c) in buf.iter_mut().enumerate() {
let re = if k < NSPS && ia_a + k < audio.len() {
let raw = audio[ia_a + k].to_i16() as i32;
let scaled = (raw << shift).clamp(i16::MIN as i32, i16::MAX as i32);
((scaled * hann[k] as i32) >> 15) as i16
} else {
0
};
let im = match ia_b {
Some(ia_b) if k < NSPS && ia_b + k < audio.len() => {
let raw = audio[ia_b + k].to_i16() as i32;
let scaled = (raw << shift).clamp(i16::MIN as i32, i16::MAX as i32);
((scaled * hann[k] as i32) >> 15) as i16
}
_ => 0,
};
*c = Complex::new(re, im);
}
};
for jj in 0..n_pairs {
let j_a = 2 * jj;
let j_b = j_a + 1;
pack(&mut buf, j_a * NSTEP, Some(j_b * NSTEP));
fft.process(&mut buf);
let row_a = j_a * n_freq;
let row_b = j_b * n_freq;
for k in 0..n_freq {
let kn = if k == 0 { 0 } else { NFFT_SPEC - k };
let yk_re = buf[k].re as i32;
let yk_im = buf[k].im as i32;
let yn_re = buf[kn].re as i32;
let yn_im = buf[kn].im as i32;
let a_re = (yk_re + yn_re) >> 1;
let a_im = (yk_im - yn_im) >> 1;
let b_re = (yk_im + yn_im) >> 1;
let b_im = (yn_re - yk_re) >> 1;
let mag2_a = ((a_re * a_re + a_im * a_im) as u32) >> FP_SPEC_SHIFT;
let mag2_b = ((b_re * b_re + b_im * b_im) as u32) >> FP_SPEC_SHIFT;
data[row_a + k] = mag2_a as u16;
data[row_b + k] = mag2_b as u16;
}
}
if n_odd != 0 {
let j = 2 * n_pairs;
pack(&mut buf, j * NSTEP, None);
fft.process(&mut buf);
let row_base = j * n_freq;
for i in 0..n_freq {
let re = buf[i].re as i32;
let im = buf[i].im as i32;
let mag2 = ((re * re + im * im) as u32) >> FP_SPEC_SHIFT;
data[row_base + i] = mag2 as u16;
}
}
Spectrogram {
n_freq,
n_time,
data,
}
}