spectrograms/lib.rs
1#![warn(clippy::all)]
2#![warn(clippy::pedantic)]
3#![warn(clippy::nursery)]
4#![allow(unused_unsafe)]
5#![allow(clippy::cast_possible_truncation)]
6#![allow(clippy::cast_precision_loss)]
7#![allow(clippy::cast_sign_loss)] // False positives with NonZeroUsize conversions
8#![allow(clippy::cast_possible_wrap)] // False positives with NonZeroUsize conversions
9#![allow(clippy::module_name_repetitions)]
10#![allow(clippy::too_many_lines)]
11#![allow(clippy::collapsible_if)]
12#![allow(clippy::if_same_then_else)]
13#![allow(clippy::unnecessary_cast)]
14#![allow(clippy::tuple_array_conversions)] // False positives with ndarray indexing
15#![allow(clippy::identity_op)]
16#![allow(clippy::needless_borrows_for_generic_args)]
17#![allow(clippy::needless_pass_by_value)] // False positives with PyO3
18#![allow(clippy::trivially_copy_pass_by_ref)] // False positives with PyO3 (likes of __repr__ and any pymethod requires &self)
19#![allow(clippy::unsafe_derive_deserialize)]
20#![allow(clippy::multiple_unsafe_ops_per_block)]
21#![allow(clippy::doc_markdown)]
22#![warn(clippy::missing_errors_doc)]
23#![warn(clippy::iter_cloned_collect)]
24#![warn(clippy::panic_in_result_fn)]
25#![warn(clippy::undocumented_unsafe_blocks)]
26
27//! # Spectrograms - FFT-Based Computations
28//!
29//! High-performance FFT-based computations for audio and image processing.
30//!
31//! # Overview
32//!
33//! This library provides:
34//! - **1D FFTs**: For time-series and audio signals
35//! - **2D FFTs**: For images and spatial data
36//! - **Spectrograms**: Time-frequency representations (STFT, Mel, ERB, CQT)
37//! - **Image operations**: Convolution, filtering, edge detection
38//! - **Two backends**: `RealFFT` (pure Rust) or FFTW (fastest)
39//! - **Plan-based API**: Reusable plans for batch processing
40//! - **Precision-generic**: Works in `f32` or `f64` (defaults to `f64`) via the [`Sample`] trait
41//!
42//! # Domain Organization
43//!
44//! The library is organized by application domain:
45//!
46//! - [`audio`] - Audio processing (spectrograms, MFCC, chroma, pitch analysis)
47//! - [`image`] - Image processing (convolution, filtering, frequency analysis)
48//! - [`mod@fft`] - Core FFT operations (1D and 2D transforms)
49//!
50//! All functionality is also exported at the crate root for convenience.
51//!
52//! # Audio Processing
53//!
54//! Compute various types of spectrograms:
55//! - Linear-frequency spectrograms
56//! - Mel-frequency spectrograms
57//! - ERB spectrograms
58//! - Logarithmic-frequency spectrograms
59//! - CQT (Constant-Q Transform)
60//!
61//! With multiple amplitude scales:
62//! - Power (`|X|²`)
63//! - Magnitude (`|X|`)
64//! - Decibels (`10·log₁₀(power)`)
65//!
66//! # Image Processing
67//!
68//! Frequency-domain operations for images:
69//! - 2D FFT and inverse FFT
70//! - Convolution via FFT (faster for large kernels)
71//! - Spatial filtering (low-pass, high-pass, band-pass)
72//! - Edge detection
73//! - Sharpening and blurring
74//!
75//! # Features
76//!
77//! - **Two FFT backends**: `RealFFT` (default, pure Rust) or FFTW (fastest performance)
78//! - **Plan-based computation**: Reuse FFT plans for efficient batch processing
79//! - **Comprehensive window functions**: Hanning, Hamming, Blackman, Kaiser, Gaussian, etc.
80//! - **Type-safe API**: Compile-time guarantees for spectrogram types
81//!
82//! # Quick Start
83//!
84//! ## Audio: Compute a Mel Spectrogram
85//!
86//! ```
87//! use spectrograms::*;
88//! use std::f64::consts::PI;
89//! use non_empty_slice::NonEmptyVec;
90//!
91//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
92//! // Generate a sine wave at 440 Hz
93//! let sample_rate = 16000.0;
94//! let samples_vec: Vec<f64> = (0..16000)
95//! .map(|i| (2.0 * PI * 440.0 * i as f64 / sample_rate).sin())
96//! .collect();
97//! let samples = NonEmptyVec::new(samples_vec).unwrap();
98//!
99//! // Set up parameters
100//! let stft = StftParams::new(nzu!(512), nzu!(256), WindowType::Hanning, true)?;
101//! let params = SpectrogramParams::new(stft, sample_rate)?;
102//! let mel = MelParams::new(nzu!(80), 0.0, 8000.0)?;
103//!
104//! // Compute Mel spectrogram
105//! let spec = MelPowerSpectrogram::compute(samples.as_ref(), ¶ms, &mel, None)?;
106//! println!("Computed {} bins x {} frames", spec.n_bins(), spec.n_frames());
107//! # Ok(())
108//! # }
109//! ```
110//!
111//! ## Image: Apply Gaussian Blur via FFT
112//!
113//! ```
114//! use spectrograms::image_ops::*;
115//! use spectrograms::nzu;
116//! use ndarray::Array2;
117//!
118//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
119//! // Create a 256x256 image
120//! let image = Array2::<f64>::from_shape_fn((256, 256), |(i, j)| {
121//! ((i as f64 - 128.0).powi(2) + (j as f64 - 128.0).powi(2)).sqrt()
122//! });
123//!
124//! // Apply Gaussian blur
125//! let kernel = gaussian_kernel_2d(nzu!(9), 2.0)?;
126//! let blurred = convolve_fft(&image.view(), &kernel.view())?;
127//! # Ok(())
128//! # }
129//! ```
130//!
131//! ## General: 2D FFT
132//!
133//! ```
134//! use spectrograms::fft2d::*;
135//! use ndarray::Array2;
136//!
137//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
138//! let data = Array2::<f64>::zeros((128, 128));
139//! let spectrum = fft2d(&data.view())?;
140//! let power = power_spectrum_2d(&data.view())?;
141//! # Ok(())
142//! # }
143//! ```
144//!
145//! # Feature Flags
146//!
147//! The library requires exactly one FFT backend:
148//!
149//! - `realfft` (default): Pure-Rust FFT implementation, no system dependencies
150//! - `fftw`: Uses FFTW C library for fastest performance (requires system install)
151//!
152//! # Numeric Precision (`f32` / `f64`)
153//!
154//! All core computations are generic over the floating-point scalar type via the
155//! sealed [`Sample`] trait (implemented for `f32` and `f64`). The scalar type
156//! **defaults to `f64`**, so existing code is unchanged; pass `f32` inputs (or
157//! annotate the type) to compute in single precision — useful for memory-bound
158//! workloads and ML pipelines that train in `f32`:
159//!
160//! ```
161//! use spectrograms::*;
162//! use non_empty_slice::NonEmptyVec;
163//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
164//! let sig = NonEmptyVec::new(vec![0.0_f32; 1024]).unwrap();
165//! // `T = f32` is inferred from the input type.
166//! let spec = stft(&sig, nzu!(256), nzu!(128), WindowType::Hanning, true)?;
167//! # Ok(()) }
168//! ```
169//!
170//! Coverage spans STFT, Mel/ERB/CQT, MFCC, chroma, MDCT, convolution,
171//! minimum-phase, binaural, and 2D FFT / image operations. The `f64`-input
172//! convenience constructors ([`chromagram`], [`mfcc`], [`gaussian_kernel_2d`])
173//! return `f64`; reach single precision via their input-generic variants
174//! ([`chromagram_from_spectrogram`], [`mfcc_from_log_mel`]) or the primitives.
175//!
176//! # Examples
177//!
178//! ## Mel Spectrogram
179//!
180//! ```
181//! use spectrograms::*;
182//! use non_empty_slice::non_empty_vec;
183//!
184//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
185//! let samples = non_empty_vec![0.0; nzu!(16000)];
186//!
187//! let stft = StftParams::new(nzu!(512), nzu!(256), WindowType::Hanning, true)?;
188//! let params = SpectrogramParams::new(stft, 16000.0)?;
189//! let mel = MelParams::new(nzu!(80), 0.0, 8000.0)?;
190//! let db = LogParams::new(-80.0)?;
191//!
192//! let spec = MelDbSpectrogram::compute(samples.as_ref(), ¶ms, &mel, Some(&db))?;
193//! # Ok(())
194//! # }
195//! ```
196//!
197//! ## MDCT (Modified Discrete Cosine Transform)
198//!
199//! ```
200//! use spectrograms::*;
201//! use non_empty_slice::NonEmptyVec;
202//!
203//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
204//! let samples: Vec<f64> = (0..4096).map(|i| (i as f64 * 0.01).sin()).collect();
205//! let samples = NonEmptyVec::new(samples).unwrap();
206//!
207//! // Sine window gives perfect reconstruction at 50% hop
208//! let params = MdctParams::sine_window(nzu!(512))?;
209//!
210//! let coefficients = mdct(samples.as_non_empty_slice(), ¶ms)?;
211//! let reconstructed = imdct(&coefficients, ¶ms, Some(samples.len().get()))?;
212//! # Ok(())
213//! # }
214//! ```
215//!
216//! ## Efficient Batch Processing
217//!
218//! ```
219//! use spectrograms::*;
220//! use non_empty_slice::non_empty_vec;
221//!
222//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
223//! let signals = vec![non_empty_vec![0.0; nzu!(16000)], non_empty_vec![0.0; nzu!(16000)]];
224//!
225//! let stft = StftParams::new(nzu!(512), nzu!(256), WindowType::Hanning, true)?;
226//! let params = SpectrogramParams::new(stft, 16000.0)?;
227//!
228//! // Create plan once, reuse for all signals
229//! let planner = SpectrogramPlanner::new();
230//! let mut plan = planner.linear_plan::<Power, _>(¶ms, None)?;
231//!
232//! for signal in &signals {
233//! let spec = plan.compute(&signal)?;
234//! // Process spec...
235//! }
236//! # Ok(())
237//! # }
238//! ```
239
240pub mod binaural;
241mod chroma;
242mod convolution;
243mod cqt;
244mod erb;
245mod error;
246pub mod fft2d;
247mod fft_backend;
248pub mod image_ops;
249mod mdct;
250mod mfcc;
251mod min_phase;
252mod sample;
253pub mod source;
254mod spectrogram;
255mod window;
256
257#[cfg(feature = "python")]
258pub mod python;
259
260// ============================================================================
261// Domain-Specific Module Organization
262// ============================================================================
263
264/// Audio processing utilities (spectrograms, MFCC, chroma, etc.)
265///
266/// This module contains all audio-related functionality:
267/// - Spectrogram computation (Linear, Mel, ERB, CQT)
268/// - MFCC (Mel-Frequency Cepstral Coefficients)
269/// - Chromagram (pitch class profiles)
270/// - Window functions
271///
272/// # Examples
273///
274/// ```
275/// use spectrograms::{nzu, audio::*};
276/// use non_empty_slice::non_empty_vec;
277///
278/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
279/// let samples = non_empty_vec![0.0; nzu!(16000)];
280/// let stft = StftParams::new(nzu!(512), nzu!(256), WindowType::Hanning, true)?;
281/// let params = SpectrogramParams::new(stft, 16000.0)?;
282/// let spec = LinearPowerSpectrogram::compute(&samples, ¶ms, None)?;
283/// # Ok(())
284/// # }
285/// ```
286pub mod audio {
287 pub use crate::chroma::*;
288 pub use crate::cqt::*;
289 pub use crate::erb::*;
290 pub use crate::mfcc::*;
291 pub use crate::spectrogram::*;
292 pub use crate::window::*;
293}
294
295/// Image processing utilities (convolution, filtering, etc.)
296///
297/// This module contains image processing operations using 2D FFTs:
298/// - Convolution and correlation
299/// - Spatial filtering (low-pass, high-pass, band-pass)
300/// - Edge detection
301/// - Sharpening and blurring
302///
303/// # Examples
304///
305/// ```
306/// use spectrograms::image::*;
307/// use spectrograms::nzu;
308/// use ndarray::Array2;
309///
310/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
311/// let image = Array2::<f64>::zeros((128, 128));
312/// let kernel = gaussian_kernel_2d(nzu!(5), 1.0)?;
313/// let blurred = convolve_fft(&image.view(), &kernel.view())?;
314/// # Ok(())
315/// # }
316/// ```
317pub mod image {
318 pub use crate::image_ops::*;
319}
320
321/// Core FFT operations (1D and 2D)
322///
323/// This module provides direct access to FFT functions:
324/// - 1D FFT: `fft()`, `rfft()`, `irfft()`
325/// - 2D FFT: `fft2d()`, `ifft2d()`
326/// - STFT: `stft()`, `istft()`
327/// - Power/magnitude spectra
328///
329/// # Examples
330///
331/// ```
332/// use spectrograms::{nzu, fft::*};
333/// use ndarray::Array2;
334/// use non_empty_slice::non_empty_vec;
335///
336/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
337/// // 1D FFT
338/// let signal = non_empty_vec![0.0; nzu!(1024)];
339/// let spectrum = rfft(&signal, nzu!(1024))?;
340///
341/// // 2D FFT
342/// let image = Array2::<f64>::zeros((128, 128));
343/// let spectrum_2d = fft2d(&image.view())?;
344/// # Ok(())
345/// # }
346/// ```
347pub mod fft {
348 pub use crate::convolution::{OverlapSaveConvolver, fft_convolve, fft_deconvolve};
349 pub use crate::fft2d::*;
350 pub use crate::min_phase::{minimum_phase, minimum_phase_with};
351 pub use crate::spectrogram::{
352 fft, irfft, istft, magnitude_spectrum, power_spectrum, rfft, stft,
353 };
354}
355
356// Re-export everything at top level for backward compatibility
357pub use chroma::{
358 ChromaNorm, ChromaParams, Chromagram, N_CHROMA, chromagram, chromagram_from_spectrogram,
359};
360pub use convolution::{OverlapSaveConvolver, fft_convolve, fft_deconvolve};
361pub use cqt::{CqtParams, CqtResult, cqt};
362pub use erb::{
363 ErbParams, ErbSpacing, GammatoneParams, gammatone_center_frequencies, gammatone_iir_spectrogram,
364};
365pub use error::{SpectrogramError, SpectrogramResult};
366#[cfg(feature = "realfft")]
367pub use fft_backend::realfft_backend::RealFftC2cPlan;
368pub use fft_backend::{C2cPlan, C2rPlan, C2rPlanner, R2cPlan, R2cPlanner, r2c_output_size};
369pub use fft2d::*;
370pub use image_ops::*;
371pub use mdct::{MdctParams, imdct, mdct};
372pub use mfcc::{Mfcc, MfccParams, mfcc, mfcc_from_log_mel};
373pub use min_phase::{minimum_phase, minimum_phase_with};
374pub use sample::Sample;
375// The complex type used by the FFT plan traits, so downstream crates can name
376// it without depending on num-complex directly.
377pub use num_complex::Complex;
378pub use source::{ChromaSource, CqtSource, GammatoneSource, MfccSource, SpectrogramSource};
379pub use spectrogram::*;
380pub use window::{
381 WindowType, blackman_window, gaussian_window, hamming_window, hanning_window, kaiser_window,
382 rectangular_window,
383};
384#[macro_export]
385macro_rules! nzu {
386 ($rate:expr) => {{
387 const RATE: usize = $rate;
388 const { assert!(RATE > 0, "non zero usize must be greater than 0") };
389 // SAFETY: We just asserted RATE > 0 at compile time
390 unsafe { ::core::num::NonZeroUsize::new_unchecked(RATE) }
391 }};
392}
393
394#[cfg(all(feature = "fftw", feature = "realfft"))]
395compile_error!(
396 "Features 'fftw' and 'realfft' are mutually exclusive. Please enable only one of them."
397);
398
399#[cfg(not(any(feature = "fftw", feature = "realfft")))]
400compile_error!("At least one FFT backend feature must be enabled: 'fftw' or 'realfft'.");
401
402#[cfg(feature = "realfft")]
403pub use fft_backend::realfft_backend::*;
404
405#[cfg(feature = "fftw")]
406pub use fft_backend::fftw_backend::*;
407
408/// Python module definition for `PyO3`.
409///
410/// This module is only available when the `python` feature is enabled.
411#[cfg(feature = "python")]
412use pyo3::prelude::*;
413
414#[cfg(feature = "python")]
415#[pymodule]
416fn _spectrograms(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
417 python::register_module(py, m)?;
418 m.add("__version__", env!("CARGO_PKG_VERSION"))?;
419 Ok(())
420}
421
422pub(crate) fn min_max_single_pass<A: AsRef<[f64]>>(data: A) -> (f64, f64) {
423 let mut min_val = f64::INFINITY;
424 let mut max_val = f64::NEG_INFINITY;
425 for &val in data.as_ref() {
426 if val < min_val {
427 min_val = val;
428 }
429 if val > max_val {
430 max_val = val;
431 }
432 }
433 (min_val, max_val)
434}