mod common;
use anyhow::Result;
use common::{cleanup_test_dir, create_complex_test_wav, create_test_wav, setup_test_dir};
use spectrs::io::audio::read_audio_file_mono;
use spectrs::spectrogram::stft::{SpectrogramType, compute_spectrogram, par_compute_spectrogram};
#[test]
fn test_compute_spectrogram_basic() -> Result<()> {
let sr = 16000;
let duration = 1.0;
let num_samples = (duration * sr as f32) as usize;
let samples: Vec<f32> = (0..num_samples)
.map(|t| (t as f32 * 440.0 * 2.0 * std::f32::consts::PI / sr as f32).sin())
.collect();
let n_fft = 512;
let hop_length = 160;
let win_length = 400;
let spec = par_compute_spectrogram(
&samples,
n_fft,
hop_length,
win_length,
false,
SpectrogramType::Power,
);
let n_freq_bins = n_fft / 2 + 1;
let expected_n_frames = (num_samples - win_length) / hop_length + 1;
assert_eq!(spec.len(), n_freq_bins);
assert!(spec[0].len() >= expected_n_frames - 1);
for freq_bin in &spec {
for &value in freq_bin {
assert!(value >= 0.0);
}
}
Ok(())
}
#[test]
fn test_compute_spectrogram_magnitude() -> Result<()> {
let sr = 16000;
let duration = 0.5;
let num_samples = (duration * sr as f32) as usize;
let samples: Vec<f32> = (0..num_samples)
.map(|t| (t as f32 * 440.0 * 2.0 * std::f32::consts::PI / sr as f32).sin())
.collect();
let n_fft = 256;
let hop_length = 128;
let win_length = 256;
let spec = par_compute_spectrogram(
&samples,
n_fft,
hop_length,
win_length,
false,
SpectrogramType::Magnitude,
);
for freq_bin in &spec {
for &value in freq_bin {
assert!(value >= 0.0);
}
}
Ok(())
}
#[test]
fn test_compute_spectrogram_centered() -> Result<()> {
let sr = 16000;
let duration = 0.5;
let num_samples = (duration * sr as f32) as usize;
let samples: Vec<f32> = (0..num_samples)
.map(|t| (t as f32 * 440.0 * 2.0 * std::f32::consts::PI / sr as f32).sin())
.collect();
let n_fft = 512;
let hop_length = 160;
let win_length = 400;
let spec_centered = par_compute_spectrogram(
&samples,
n_fft,
hop_length,
win_length,
true,
SpectrogramType::Power,
);
let spec_not_centered = par_compute_spectrogram(
&samples,
n_fft,
hop_length,
win_length,
false,
SpectrogramType::Power,
);
assert!(spec_centered.len() > 0);
assert!(spec_not_centered.len() > 0);
assert!(spec_centered[0].len() > 0);
assert!(spec_not_centered[0].len() > 0);
Ok(())
}
#[test]
fn test_compute_spectrogram_different_fft_sizes() -> Result<()> {
let sr = 16000;
let duration = 0.5;
let num_samples = (duration * sr as f32) as usize;
let samples: Vec<f32> = (0..num_samples)
.map(|t| (t as f32 * 440.0 * 2.0 * std::f32::consts::PI / sr as f32).sin())
.collect();
let fft_sizes = vec![256, 512, 1024, 2048];
for n_fft in fft_sizes {
let hop_length = n_fft / 4;
let win_length = n_fft / 2;
let spec = par_compute_spectrogram(
&samples,
n_fft,
hop_length,
win_length,
false,
SpectrogramType::Power,
);
let expected_freq_bins = n_fft / 2 + 1;
assert_eq!(spec.len(), expected_freq_bins);
assert!(spec[0].len() > 0);
}
Ok(())
}
#[test]
fn test_compute_spectrogram_different_hop_lengths() -> Result<()> {
let sr = 16000;
let duration = 1.0;
let num_samples = (duration * sr as f32) as usize;
let samples: Vec<f32> = (0..num_samples)
.map(|t| (t as f32 * 440.0 * 2.0 * std::f32::consts::PI / sr as f32).sin())
.collect();
let n_fft = 512;
let win_length = 400;
let hop_lengths = vec![80, 160, 320];
for hop_length in hop_lengths {
let spec = par_compute_spectrogram(
&samples,
n_fft,
hop_length,
win_length,
false,
SpectrogramType::Power,
);
assert!(spec.len() > 0);
assert!(spec[0].len() > 0);
}
Ok(())
}
#[test]
fn test_compute_spectrogram_from_file() -> Result<()> {
let test_dir = setup_test_dir()?;
let audio_path = test_dir.join("test_spec.wav");
create_test_wav(&audio_path, 1.0, 16000, 1, 16)?;
let (samples, sr) = read_audio_file_mono(&audio_path)?;
assert_eq!(sr, 16000);
let n_fft = 512;
let hop_length = 160;
let win_length = 400;
let spec = par_compute_spectrogram(
&samples,
n_fft,
hop_length,
win_length,
false,
SpectrogramType::Power,
);
let n_freq_bins = n_fft / 2 + 1;
assert_eq!(spec.len(), n_freq_bins);
assert!(spec[0].len() > 0);
cleanup_test_dir(&test_dir)?;
Ok(())
}
#[test]
fn test_compute_spectrogram_complex_signal() -> Result<()> {
let test_dir = setup_test_dir()?;
let audio_path = test_dir.join("test_complex.wav");
create_complex_test_wav(&audio_path, 1.0, 16000, 1, 16)?;
let (samples, _sr) = read_audio_file_mono(&audio_path)?;
let n_fft = 1024;
let hop_length = 256;
let win_length = 512;
let spec = par_compute_spectrogram(
&samples,
n_fft,
hop_length,
win_length,
false,
SpectrogramType::Power,
);
let mut bins_with_energy = 0;
for freq_bin in &spec {
let max_value = freq_bin.iter().copied().fold(f32::NEG_INFINITY, f32::max);
if max_value > 1.0 {
bins_with_energy += 1;
}
}
assert!(bins_with_energy > 5);
cleanup_test_dir(&test_dir)?;
Ok(())
}
#[test]
fn test_compute_spectrogram_power_vs_magnitude() -> Result<()> {
let sr = 16000;
let duration = 0.5;
let num_samples = (duration * sr as f32) as usize;
let samples: Vec<f32> = (0..num_samples)
.map(|t| (t as f32 * 440.0 * 2.0 * std::f32::consts::PI / sr as f32).sin())
.collect();
let n_fft = 512;
let hop_length = 160;
let win_length = 400;
let spec_power = par_compute_spectrogram(
&samples,
n_fft,
hop_length,
win_length,
false,
SpectrogramType::Power,
);
let spec_magnitude = par_compute_spectrogram(
&samples,
n_fft,
hop_length,
win_length,
false,
SpectrogramType::Magnitude,
);
assert_eq!(spec_power.len(), spec_magnitude.len());
assert_eq!(spec_power[0].len(), spec_magnitude[0].len());
for i in (0..spec_power.len()).step_by(50) {
for j in (0..spec_power[0].len()).step_by(10) {
if spec_magnitude[i][j] > 0.1 {
let ratio = spec_power[i][j] / (spec_magnitude[i][j] * spec_magnitude[i][j]);
assert!(
(ratio - 1.0).abs() < 0.01,
"Power should be magnitude squared"
);
}
}
}
Ok(())
}
#[test]
fn test_compute_spectrogram_short_audio() -> Result<()> {
let sr = 16000;
let duration = 0.1; let num_samples = (duration * sr as f32) as usize;
let samples: Vec<f32> = (0..num_samples)
.map(|t| (t as f32 * 440.0 * 2.0 * std::f32::consts::PI / sr as f32).sin())
.collect();
let n_fft = 512;
let hop_length = 160;
let win_length = 400;
let spec = par_compute_spectrogram(
&samples,
n_fft,
hop_length,
win_length,
false,
SpectrogramType::Power,
);
assert!(spec.len() > 0);
assert!(spec[0].len() > 0);
Ok(())
}
#[test]
fn test_compute_spectrogram_stereo_to_mono() -> Result<()> {
let test_dir = setup_test_dir()?;
let audio_path = test_dir.join("test_stereo.wav");
create_test_wav(&audio_path, 1.0, 16000, 2, 16)?;
let (samples, sr) = read_audio_file_mono(&audio_path)?;
assert_eq!(sr, 16000);
let n_fft = 512;
let hop_length = 160;
let win_length = 400;
let spec = par_compute_spectrogram(
&samples,
n_fft,
hop_length,
win_length,
false,
SpectrogramType::Power,
);
assert!(spec.len() > 0);
assert!(spec[0].len() > 0);
cleanup_test_dir(&test_dir)?;
Ok(())
}
#[test]
fn test_compute_vs_par_compute_same_results() -> Result<()> {
let sr = 16000;
let duration = 1.0;
let num_samples = (duration * sr as f32) as usize;
let samples: Vec<f32> = (0..num_samples)
.map(|t| (t as f32 * 440.0 * 2.0 * std::f32::consts::PI / sr as f32).sin())
.collect();
let n_fft = 512;
let hop_length = 160;
let win_length = 400;
let spec_single = compute_spectrogram(
&samples,
n_fft,
hop_length,
win_length,
false,
SpectrogramType::Power,
);
let spec_parallel = par_compute_spectrogram(
&samples,
n_fft,
hop_length,
win_length,
false,
SpectrogramType::Power,
);
assert_eq!(spec_single.len(), spec_parallel.len());
assert_eq!(spec_single[0].len(), spec_parallel[0].len());
for (i, (row_single, row_parallel)) in spec_single.iter().zip(spec_parallel.iter()).enumerate()
{
for (j, (&val_single, &val_parallel)) in
row_single.iter().zip(row_parallel.iter()).enumerate()
{
assert!(
(val_single - val_parallel).abs() < 1e-5,
"Mismatch at [{}, {}]: {} vs {}",
i,
j,
val_single,
val_parallel
);
}
}
Ok(())
}
#[test]
fn test_compute_spectrogram_single_threaded() -> Result<()> {
let sr = 16000;
let duration = 0.5;
let num_samples = (duration * sr as f32) as usize;
let samples: Vec<f32> = (0..num_samples)
.map(|t| (t as f32 * 440.0 * 2.0 * std::f32::consts::PI / sr as f32).sin())
.collect();
let n_fft = 256;
let hop_length = 128;
let win_length = 256;
let spec = compute_spectrogram(
&samples,
n_fft,
hop_length,
win_length,
false,
SpectrogramType::Power,
);
let n_freq_bins = n_fft / 2 + 1;
assert_eq!(spec.len(), n_freq_bins);
assert!(spec[0].len() > 0);
for freq_bin in &spec {
for &value in freq_bin {
assert!(value >= 0.0);
}
}
Ok(())
}