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::exhaustive_enums)]
23#![warn(clippy::exhaustive_structs)]
24#![warn(clippy::missing_inline_in_public_items)]
25#![warn(clippy::missing_errors_doc)]
26#![warn(clippy::iter_cloned_collect)]
27#![warn(clippy::panic_in_result_fn)]
28#![warn(clippy::undocumented_unsafe_blocks)]
29
30//! # Spectrograms - FFT-Based Computations
31//!
32//! High-performance FFT-based computations for audio and image processing.
33//!
34//! # Overview
35//!
36//! This library provides:
37//! - **1D FFTs**: For time-series and audio signals
38//! - **2D FFTs**: For images and spatial data
39//! - **Spectrograms**: Time-frequency representations (STFT, Mel, ERB, CQT)
40//! - **Image operations**: Convolution, filtering, edge detection
41//! - **Two backends**: `RealFFT` (pure Rust) or FFTW (fastest)
42//! - **Plan-based API**: Reusable plans for batch processing
43//!
44//! # Domain Organization
45//!
46//! The library is organized by application domain:
47//!
48//! - [`audio`] - Audio processing (spectrograms, MFCC, chroma, pitch analysis)
49//! - [`image`] - Image processing (convolution, filtering, frequency analysis)
50//! - [`fft`] - Core FFT operations (1D and 2D transforms)
51//!
52//! All functionality is also exported at the crate root for convenience.
53//!
54//! # Audio Processing
55//!
56//! Compute various types of spectrograms:
57//! - Linear-frequency spectrograms
58//! - Mel-frequency spectrograms
59//! - ERB spectrograms
60//! - Logarithmic-frequency spectrograms
61//! - CQT (Constant-Q Transform)
62//!
63//! With multiple amplitude scales:
64//! - Power (`|X|²`)
65//! - Magnitude (`|X|`)
66//! - Decibels (`10·log₁₀(power)`)
67//!
68//! # Image Processing
69//!
70//! Frequency-domain operations for images:
71//! - 2D FFT and inverse FFT
72//! - Convolution via FFT (faster for large kernels)
73//! - Spatial filtering (low-pass, high-pass, band-pass)
74//! - Edge detection
75//! - Sharpening and blurring
76//!
77//! # Features
78//!
79//! - **Two FFT backends**: `RealFFT` (default, pure Rust) or FFTW (fastest performance)
80//! - **Plan-based computation**: Reuse FFT plans for efficient batch processing
81//! - **Comprehensive window functions**: Hanning, Hamming, Blackman, Kaiser, Gaussian, etc.
82//! - **Type-safe API**: Compile-time guarantees for spectrogram types
83//! - **Zero-copy design**: Efficient memory usage with minimal allocations
84//!
85//! # Quick Start
86//!
87//! ## Audio: Compute a Mel Spectrogram
88//!
89//! ```
90//! use spectrograms::*;
91//! use std::f64::consts::PI;
92//! use non_empty_slice::NonEmptyVec;
93//!
94//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
95//! // Generate a sine wave at 440 Hz
96//! let sample_rate = 16000.0;
97//! let samples_vec: Vec<f64> = (0..16000)
98//! .map(|i| (2.0 * PI * 440.0 * i as f64 / sample_rate).sin())
99//! .collect();
100//! let samples = NonEmptyVec::new(samples_vec).unwrap();
101//!
102//! // Set up parameters
103//! let stft = StftParams::new(nzu!(512), nzu!(256), WindowType::Hanning, true)?;
104//! let params = SpectrogramParams::new(stft, sample_rate)?;
105//! let mel = MelParams::new(nzu!(80), 0.0, 8000.0)?;
106//!
107//! // Compute Mel spectrogram
108//! let spec = MelPowerSpectrogram::compute(samples.as_ref(), ¶ms, &mel, None)?;
109//! println!("Computed {} bins x {} frames", spec.n_bins(), spec.n_frames());
110//! # Ok(())
111//! # }
112//! ```
113//!
114//! ## Image: Apply Gaussian Blur via FFT
115//!
116//! ```
117//! use spectrograms::image_ops::*;
118//! use spectrograms::nzu;
119//! use ndarray::Array2;
120//!
121//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
122//! // Create a 256x256 image
123//! let image = Array2::<f64>::from_shape_fn((256, 256), |(i, j)| {
124//! ((i as f64 - 128.0).powi(2) + (j as f64 - 128.0).powi(2)).sqrt()
125//! });
126//!
127//! // Apply Gaussian blur
128//! let kernel = gaussian_kernel_2d(nzu!(9), 2.0)?;
129//! let blurred = convolve_fft(&image.view(), &kernel.view())?;
130//! # Ok(())
131//! # }
132//! ```
133//!
134//! ## General: 2D FFT
135//!
136//! ```
137//! use spectrograms::fft2d::*;
138//! use ndarray::Array2;
139//!
140//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
141//! let data = Array2::<f64>::zeros((128, 128));
142//! let spectrum = fft2d(&data.view())?;
143//! let power = power_spectrum_2d(&data.view())?;
144//! # Ok(())
145//! # }
146//! ```
147//!
148//! # Feature Flags
149//!
150//! The library requires exactly one FFT backend:
151//!
152//! - `realfft` (default): Pure-Rust FFT implementation, no system dependencies
153//! - `fftw`: Uses FFTW C library for fastest performance (requires system install)
154//!
155//! # Examples
156//!
157//! ## Mel Spectrogram
158//!
159//! ```
160//! use spectrograms::*;
161//! use non_empty_slice::non_empty_vec;
162//!
163//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
164//! let samples = non_empty_vec![0.0; nzu!(16000)];
165//!
166//! let stft = StftParams::new(nzu!(512), nzu!(256), WindowType::Hanning, true)?;
167//! let params = SpectrogramParams::new(stft, 16000.0)?;
168//! let mel = MelParams::new(nzu!(80), 0.0, 8000.0)?;
169//! let db = LogParams::new(-80.0)?;
170//!
171//! let spec = MelDbSpectrogram::compute(samples.as_ref(), ¶ms, &mel, Some(&db))?;
172//! # Ok(())
173//! # }
174//! ```
175//!
176//! ## Efficient Batch Processing
177//!
178//! ```
179//! use spectrograms::*;
180//! use non_empty_slice::non_empty_vec;
181//!
182//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
183//! let signals = vec![non_empty_vec![0.0; nzu!(16000)], non_empty_vec![0.0; nzu!(16000)]];
184//!
185//! let stft = StftParams::new(nzu!(512), nzu!(256), WindowType::Hanning, true)?;
186//! let params = SpectrogramParams::new(stft, 16000.0)?;
187//!
188//! // Create plan once, reuse for all signals
189//! let planner = SpectrogramPlanner::new();
190//! let mut plan = planner.linear_plan::<Power>(¶ms, None)?;
191//!
192//! for signal in &signals {
193//! let spec = plan.compute(&signal)?;
194//! // Process spec...
195//! }
196//! # Ok(())
197//! # }
198//! ```
199
200mod chroma;
201mod cqt;
202mod erb;
203mod error;
204pub mod fft2d;
205mod fft_backend;
206pub mod image_ops;
207mod mfcc;
208mod spectrogram;
209mod window;
210
211#[cfg(feature = "python")]
212mod python;
213
214// ============================================================================
215// Domain-Specific Module Organization
216// ============================================================================
217
218/// Audio processing utilities (spectrograms, MFCC, chroma, etc.)
219///
220/// This module contains all audio-related functionality:
221/// - Spectrogram computation (Linear, Mel, ERB, CQT)
222/// - MFCC (Mel-Frequency Cepstral Coefficients)
223/// - Chromagram (pitch class profiles)
224/// - Window functions
225///
226/// # Examples
227///
228/// ```
229/// use spectrograms::{nzu, audio::*};
230/// use non_empty_slice::non_empty_vec;
231///
232/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
233/// let samples = non_empty_vec![0.0; nzu!(16000)];
234/// let stft = StftParams::new(nzu!(512), nzu!(256), WindowType::Hanning, true)?;
235/// let params = SpectrogramParams::new(stft, 16000.0)?;
236/// let spec = LinearPowerSpectrogram::compute(&samples, ¶ms, None)?;
237/// # Ok(())
238/// # }
239/// ```
240pub mod audio {
241 pub use crate::chroma::*;
242 pub use crate::cqt::*;
243 pub use crate::erb::*;
244 pub use crate::mfcc::*;
245 pub use crate::spectrogram::*;
246 pub use crate::window::*;
247}
248
249/// Image processing utilities (convolution, filtering, etc.)
250///
251/// This module contains image processing operations using 2D FFTs:
252/// - Convolution and correlation
253/// - Spatial filtering (low-pass, high-pass, band-pass)
254/// - Edge detection
255/// - Sharpening and blurring
256///
257/// # Examples
258///
259/// ```
260/// use spectrograms::image::*;
261/// use spectrograms::nzu;
262/// use ndarray::Array2;
263///
264/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
265/// let image = Array2::<f64>::zeros((128, 128));
266/// let kernel = gaussian_kernel_2d(nzu!(5), 1.0)?;
267/// let blurred = convolve_fft(&image.view(), &kernel.view())?;
268/// # Ok(())
269/// # }
270/// ```
271pub mod image {
272 pub use crate::image_ops::*;
273}
274
275/// Core FFT operations (1D and 2D)
276///
277/// This module provides direct access to FFT functions:
278/// - 1D FFT: `fft()`, `rfft()`, `irfft()`
279/// - 2D FFT: `fft2d()`, `ifft2d()`
280/// - STFT: `stft()`, `istft()`
281/// - Power/magnitude spectra
282///
283/// # Examples
284///
285/// ```
286/// use spectrograms::{nzu, fft::*};
287/// use ndarray::Array2;
288/// use non_empty_slice::non_empty_vec;
289///
290/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
291/// // 1D FFT
292/// let signal = non_empty_vec![0.0; nzu!(1024)];
293/// let spectrum = rfft(&signal, nzu!(1024))?;
294///
295/// // 2D FFT
296/// let image = Array2::<f64>::zeros((128, 128));
297/// let spectrum_2d = fft2d(&image.view())?;
298/// # Ok(())
299/// # }
300/// ```
301pub mod fft {
302 pub use crate::fft2d::*;
303 pub use crate::spectrogram::{
304 fft, irfft, istft, magnitude_spectrum, power_spectrum, rfft, stft,
305 };
306}
307
308// Re-export everything at top level for backward compatibility
309pub use chroma::{
310 ChromaNorm, ChromaParams, Chromagram, N_CHROMA, chromagram, chromagram_from_spectrogram,
311};
312pub use cqt::{CqtParams, CqtResult, cqt};
313pub use erb::{ErbParams, GammatoneParams};
314pub use error::{SpectrogramError, SpectrogramResult};
315pub use fft_backend::{C2rPlan, C2rPlanner, R2cPlan, R2cPlanner, r2c_output_size};
316pub use fft2d::*;
317pub use image_ops::*;
318pub use mfcc::{Mfcc, MfccParams, mfcc, mfcc_from_log_mel};
319pub use spectrogram::*;
320pub use window::{
321 WindowType, blackman_window, gaussian_window, hamming_window, hanning_window, kaiser_window,
322 rectangular_window,
323};
324#[macro_export]
325macro_rules! nzu {
326 ($rate:expr) => {{
327 const RATE: usize = $rate;
328 const { assert!(RATE > 0, "non zero usize must be greater than 0") };
329 // SAFETY: We just asserted RATE > 0 at compile time
330 unsafe { ::core::num::NonZeroUsize::new_unchecked(RATE) }
331 }};
332}
333
334#[cfg(all(feature = "fftw", feature = "realfft"))]
335compile_error!(
336 "Features 'fftw' and 'realfft' are mutually exclusive. Please enable only one of them."
337);
338
339#[cfg(not(any(feature = "fftw", feature = "realfft")))]
340compile_error!("At least one FFT backend feature must be enabled: 'fftw' or 'realfft'.");
341
342#[cfg(feature = "realfft")]
343pub use fft_backend::realfft_backend::*;
344
345#[cfg(feature = "fftw")]
346pub use fft_backend::fftw_backend::*;
347
348/// Python module definition for `PyO3`.
349///
350/// This module is only available when the `python` feature is enabled.
351#[cfg(feature = "python")]
352use pyo3::prelude::*;
353
354#[cfg(feature = "python")]
355#[pymodule]
356fn _spectrograms(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
357 python::register_module(py, m)?;
358 m.add("__version__", env!("CARGO_PKG_VERSION"))?;
359 Ok(())
360}
361
362pub(crate) fn min_max_single_pass<A: AsRef<[f64]>>(data: A) -> (f64, f64) {
363 let mut min_val = f64::INFINITY;
364 let mut max_val = f64::NEG_INFINITY;
365 for &val in data.as_ref() {
366 if val < min_val {
367 min_val = val;
368 }
369 if val > max_val {
370 max_val = val;
371 }
372 }
373 (min_val, max_val)
374}