use std::num::NonZeroUsize;
use ndarray::Array2;
use non_empty_slice::NonEmptySlice;
use crate::chroma::{ChromaParams, N_CHROMA, chromagram};
use crate::cqt::{CqtParams, cqt};
use crate::erb::{ErbParams, gammatone_center_frequencies, gammatone_iir_spectrogram};
use crate::error::{SpectrogramError, SpectrogramResult};
use crate::mfcc::{MfccParams, mfcc};
use crate::sample::Sample;
use crate::spectrogram::{AmpScaleSpec, SpectrogramPlan, StftParams};
pub trait SpectrogramSource<T: Sample> {
fn compute_matrix(&mut self, samples: &[T]) -> SpectrogramResult<Array2<T>>;
fn n_bands(&self) -> usize;
fn center_frequencies(&self) -> Vec<f64>;
fn sample_rate(&self) -> f64;
fn hop_seconds(&self) -> f64;
}
impl<F, A, T> SpectrogramSource<T> for SpectrogramPlan<F, A, T>
where
F: Copy + Clone + 'static,
A: AmpScaleSpec + 'static,
T: Sample,
{
fn compute_matrix(&mut self, samples: &[T]) -> SpectrogramResult<Array2<T>> {
let samples = NonEmptySlice::new(samples)
.ok_or_else(|| SpectrogramError::invalid_input("samples must be non-empty"))?;
Ok(self.compute(samples)?.into_data())
}
fn n_bands(&self) -> usize {
self.freq_axis().frequencies().len().get()
}
fn center_frequencies(&self) -> Vec<f64> {
self.freq_axis().frequencies().as_slice().to_vec()
}
fn sample_rate(&self) -> f64 {
self.params().sample_rate_hz()
}
fn hop_seconds(&self) -> f64 {
self.params().frame_period_seconds()
}
}
#[derive(Clone, Debug)]
pub struct GammatoneSource {
sample_rate: f64,
frame_size: NonZeroUsize,
hop_size: NonZeroUsize,
params: ErbParams,
}
impl GammatoneSource {
#[must_use]
pub const fn new(
sample_rate: f64,
frame_size: NonZeroUsize,
hop_size: NonZeroUsize,
params: ErbParams,
) -> Self {
Self {
sample_rate,
frame_size,
hop_size,
params,
}
}
#[must_use]
pub const fn params(&self) -> &ErbParams {
&self.params
}
}
impl<T: Sample> SpectrogramSource<T> for GammatoneSource {
fn compute_matrix(&mut self, samples: &[T]) -> SpectrogramResult<Array2<T>> {
let (matrix, _center_freqs) = gammatone_iir_spectrogram(
samples,
self.sample_rate,
self.frame_size,
self.hop_size,
&self.params,
)?;
Ok(matrix)
}
fn n_bands(&self) -> usize {
self.params.n_filters().get()
}
fn center_frequencies(&self) -> Vec<f64> {
gammatone_center_frequencies(&self.params)
}
fn sample_rate(&self) -> f64 {
self.sample_rate
}
fn hop_seconds(&self) -> f64 {
self.hop_size.get() as f64 / self.sample_rate
}
}
#[derive(Clone, Debug)]
pub struct CqtSource {
sample_rate: f64,
hop_size: NonZeroUsize,
params: CqtParams,
}
impl CqtSource {
#[must_use]
pub fn new(sample_rate: f64, hop_size: NonZeroUsize, params: CqtParams) -> Self {
Self {
sample_rate,
hop_size,
params,
}
}
#[must_use]
pub const fn params(&self) -> &CqtParams {
&self.params
}
}
impl<T: Sample> SpectrogramSource<T> for CqtSource {
fn compute_matrix(&mut self, samples: &[T]) -> SpectrogramResult<Array2<T>> {
let samples = NonEmptySlice::new(samples)
.ok_or_else(|| SpectrogramError::invalid_input("samples must be non-empty"))?;
Ok(cqt(samples, self.sample_rate, &self.params, self.hop_size)?.to_magnitude())
}
fn n_bands(&self) -> usize {
let nyquist = self.sample_rate / 2.0;
(0..self.params.num_bins().get())
.filter(|&i| self.params.bin_frequency(i) < nyquist)
.count()
}
fn center_frequencies(&self) -> Vec<f64> {
let nyquist = self.sample_rate / 2.0;
(0..self.params.num_bins().get())
.map(|i| self.params.bin_frequency(i))
.filter(|&f| f < nyquist)
.collect()
}
fn sample_rate(&self) -> f64 {
self.sample_rate
}
fn hop_seconds(&self) -> f64 {
self.hop_size.get() as f64 / self.sample_rate
}
}
#[derive(Clone, Debug)]
pub struct ChromaSource {
sample_rate: f64,
stft_params: StftParams,
params: ChromaParams,
}
impl ChromaSource {
#[must_use]
pub fn new(sample_rate: f64, stft_params: StftParams, params: ChromaParams) -> Self {
Self {
sample_rate,
stft_params,
params,
}
}
#[must_use]
pub const fn params(&self) -> &ChromaParams {
&self.params
}
}
impl<T: Sample> SpectrogramSource<T> for ChromaSource {
fn compute_matrix(&mut self, samples: &[T]) -> SpectrogramResult<Array2<T>> {
let samples = NonEmptySlice::new(samples)
.ok_or_else(|| SpectrogramError::invalid_input("samples must be non-empty"))?;
Ok(chromagram(samples, &self.stft_params, self.sample_rate, &self.params)?.data)
}
fn n_bands(&self) -> usize {
N_CHROMA
}
fn center_frequencies(&self) -> Vec<f64> {
let f_min = self.params.f_min();
(0..N_CHROMA)
.map(|i| f_min * (i as f64 / N_CHROMA as f64).exp2())
.collect()
}
fn sample_rate(&self) -> f64 {
self.sample_rate
}
fn hop_seconds(&self) -> f64 {
self.stft_params.hop_size().get() as f64 / self.sample_rate
}
}
#[derive(Clone, Debug)]
pub struct MfccSource {
sample_rate: f64,
stft_params: StftParams,
n_mels: NonZeroUsize,
params: MfccParams,
}
impl MfccSource {
#[must_use]
pub fn new(
sample_rate: f64,
stft_params: StftParams,
n_mels: NonZeroUsize,
params: MfccParams,
) -> Self {
Self {
sample_rate,
stft_params,
n_mels,
params,
}
}
#[must_use]
pub const fn params(&self) -> &MfccParams {
&self.params
}
}
impl<T: Sample> SpectrogramSource<T> for MfccSource {
fn compute_matrix(&mut self, samples: &[T]) -> SpectrogramResult<Array2<T>> {
let samples = NonEmptySlice::new(samples)
.ok_or_else(|| SpectrogramError::invalid_input("samples must be non-empty"))?;
Ok(mfcc(
samples,
&self.stft_params,
self.sample_rate,
self.n_mels,
&self.params,
)?
.data)
}
fn n_bands(&self) -> usize {
self.params.n_mfcc().get()
}
fn center_frequencies(&self) -> Vec<f64> {
(0..self.params.n_mfcc().get()).map(|i| i as f64).collect()
}
fn sample_rate(&self) -> f64 {
self.sample_rate
}
fn hop_seconds(&self) -> f64 {
self.stft_params.hop_size().get() as f64 / self.sample_rate
}
}