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 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;
(raw << shift).clamp(i16::MIN as i32, i16::MAX as i32) 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;
(raw << shift).clamp(i16::MIN as i32, i16::MAX as i32) 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;
if n_freq > 0 {
let r0 = buf[0].re as i32;
let i0 = buf[0].im as i32;
data[row_a] = ((r0 * r0) as u32 >> FP_SPEC_SHIFT).min(u16::MAX as u32) as u16;
data[row_b] = ((i0 * i0) as u32 >> FP_SPEC_SHIFT).min(u16::MAX as u32) as u16;
}
let mut k = 1usize;
while k + 4 <= n_freq {
let yk0 = buf[k];
let yk1 = buf[k + 1];
let yk2 = buf[k + 2];
let yk3 = buf[k + 3];
let yn0 = buf[NFFT_SPEC - k];
let yn1 = buf[NFFT_SPEC - k - 1];
let yn2 = buf[NFFT_SPEC - k - 2];
let yn3 = buf[NFFT_SPEC - k - 3];
let (a0r, a0i) = (
(yk0.re as i32 + yn0.re as i32) >> 1,
(yk0.im as i32 - yn0.im as i32) >> 1,
);
let (b0r, b0i) = (
(yk0.im as i32 + yn0.im as i32) >> 1,
(yn0.re as i32 - yk0.re as i32) >> 1,
);
let (a1r, a1i) = (
(yk1.re as i32 + yn1.re as i32) >> 1,
(yk1.im as i32 - yn1.im as i32) >> 1,
);
let (b1r, b1i) = (
(yk1.im as i32 + yn1.im as i32) >> 1,
(yn1.re as i32 - yk1.re as i32) >> 1,
);
let (a2r, a2i) = (
(yk2.re as i32 + yn2.re as i32) >> 1,
(yk2.im as i32 - yn2.im as i32) >> 1,
);
let (b2r, b2i) = (
(yk2.im as i32 + yn2.im as i32) >> 1,
(yn2.re as i32 - yk2.re as i32) >> 1,
);
let (a3r, a3i) = (
(yk3.re as i32 + yn3.re as i32) >> 1,
(yk3.im as i32 - yn3.im as i32) >> 1,
);
let (b3r, b3i) = (
(yk3.im as i32 + yn3.im as i32) >> 1,
(yn3.re as i32 - yk3.re as i32) >> 1,
);
data[row_a + k] = (((a0r * a0r) as u32 + (a0i * a0i) as u32) >> FP_SPEC_SHIFT)
.min(u16::MAX as u32) as u16;
data[row_b + k] = (((b0r * b0r) as u32 + (b0i * b0i) as u32) >> FP_SPEC_SHIFT)
.min(u16::MAX as u32) as u16;
data[row_a + k + 1] = (((a1r * a1r) as u32 + (a1i * a1i) as u32) >> FP_SPEC_SHIFT)
.min(u16::MAX as u32) as u16;
data[row_b + k + 1] = (((b1r * b1r) as u32 + (b1i * b1i) as u32) >> FP_SPEC_SHIFT)
.min(u16::MAX as u32) as u16;
data[row_a + k + 2] = (((a2r * a2r) as u32 + (a2i * a2i) as u32) >> FP_SPEC_SHIFT)
.min(u16::MAX as u32) as u16;
data[row_b + k + 2] = (((b2r * b2r) as u32 + (b2i * b2i) as u32) >> FP_SPEC_SHIFT)
.min(u16::MAX as u32) as u16;
data[row_a + k + 3] = (((a3r * a3r) as u32 + (a3i * a3i) as u32) >> FP_SPEC_SHIFT)
.min(u16::MAX as u32) as u16;
data[row_b + k + 3] = (((b3r * b3r) as u32 + (b3i * b3i) as u32) >> FP_SPEC_SHIFT)
.min(u16::MAX as u32) as u16;
k += 4;
}
while k < n_freq {
let yn = buf[NFFT_SPEC - k];
let yk = buf[k];
let (ar, ai) = (
(yk.re as i32 + yn.re as i32) >> 1,
(yk.im as i32 - yn.im as i32) >> 1,
);
let (br, bi) = (
(yk.im as i32 + yn.im as i32) >> 1,
(yn.re as i32 - yk.re as i32) >> 1,
);
data[row_a + k] = (((ar * ar) as u32 + (ai * ai) as u32) >> FP_SPEC_SHIFT)
.min(u16::MAX as u32) as u16;
data[row_b + k] = (((br * br) as u32 + (bi * bi) as u32) >> FP_SPEC_SHIFT)
.min(u16::MAX as u32) as u16;
k += 1;
}
#[cfg(feature = "std")]
if jj == 0 {
for ki in [64usize, 823usize] {
if ki < n_freq {
eprintln!(
"[host spec] pair0 k={ki}: mag2_a={} mag2_b={}",
data[row_a + ki],
data[row_b + ki]
);
}
}
}
}
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) as u32 + (im * im) as u32) >> FP_SPEC_SHIFT).min(u16::MAX as u32);
data[row_base + i] = mag2 as u16;
}
}
Spectrogram {
n_freq,
n_time,
data,
}
}