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