Skip to main content

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(), &params, &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(), &params, &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>(&params, None)?;
190//!
191//! for signal in &signals {
192//!     let spec = plan.compute(&signal)?;
193//!     // Process spec...
194//! }
195//! # Ok(())
196//! # }
197//! ```
198
199mod chroma;
200mod cqt;
201mod erb;
202mod error;
203pub mod fft2d;
204mod fft_backend;
205pub mod image_ops;
206mod mfcc;
207mod spectrogram;
208mod window;
209
210#[cfg(feature = "python")]
211mod python;
212
213// ============================================================================
214// Domain-Specific Module Organization
215// ============================================================================
216
217/// Audio processing utilities (spectrograms, MFCC, chroma, etc.)
218///
219/// This module contains all audio-related functionality:
220/// - Spectrogram computation (Linear, Mel, ERB, CQT)
221/// - MFCC (Mel-Frequency Cepstral Coefficients)
222/// - Chromagram (pitch class profiles)
223/// - Window functions
224///
225/// # Examples
226///
227/// ```
228/// use spectrograms::{nzu, audio::*};
229/// use non_empty_slice::non_empty_vec;
230///
231/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
232/// let samples = non_empty_vec![0.0; nzu!(16000)];
233/// let stft = StftParams::new(nzu!(512), nzu!(256), WindowType::Hanning, true)?;
234/// let params = SpectrogramParams::new(stft, 16000.0)?;
235/// let spec = LinearPowerSpectrogram::compute(&samples, &params, None)?;
236/// # Ok(())
237/// # }
238/// ```
239pub mod audio {
240    pub use crate::chroma::*;
241    pub use crate::cqt::*;
242    pub use crate::erb::*;
243    pub use crate::mfcc::*;
244    pub use crate::spectrogram::*;
245    pub use crate::window::*;
246}
247
248/// Image processing utilities (convolution, filtering, etc.)
249///
250/// This module contains image processing operations using 2D FFTs:
251/// - Convolution and correlation
252/// - Spatial filtering (low-pass, high-pass, band-pass)
253/// - Edge detection
254/// - Sharpening and blurring
255///
256/// # Examples
257///
258/// ```
259/// use spectrograms::image::*;
260/// use spectrograms::nzu;
261/// use ndarray::Array2;
262///
263/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
264/// let image = Array2::<f64>::zeros((128, 128));
265/// let kernel = gaussian_kernel_2d(nzu!(5), 1.0)?;
266/// let blurred = convolve_fft(&image.view(), &kernel.view())?;
267/// # Ok(())
268/// # }
269/// ```
270pub mod image {
271    pub use crate::image_ops::*;
272}
273
274/// Core FFT operations (1D and 2D)
275///
276/// This module provides direct access to FFT functions:
277/// - 1D FFT: `fft()`, `rfft()`, `irfft()`
278/// - 2D FFT: `fft2d()`, `ifft2d()`
279/// - STFT: `stft()`, `istft()`
280/// - Power/magnitude spectra
281///
282/// # Examples
283///
284/// ```
285/// use spectrograms::{nzu, fft::*};
286/// use ndarray::Array2;
287/// use non_empty_slice::non_empty_vec;
288///
289/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
290/// // 1D FFT
291/// let signal = non_empty_vec![0.0; nzu!(1024)];
292/// let spectrum = rfft(&signal, nzu!(1024))?;
293///
294/// // 2D FFT
295/// let image = Array2::<f64>::zeros((128, 128));
296/// let spectrum_2d = fft2d(&image.view())?;
297/// # Ok(())
298/// # }
299/// ```
300pub mod fft {
301    pub use crate::fft2d::*;
302    pub use crate::spectrogram::{
303        fft, irfft, istft, magnitude_spectrum, power_spectrum, rfft, stft,
304    };
305}
306
307// Re-export everything at top level for backward compatibility
308pub use chroma::{
309    ChromaNorm, ChromaParams, Chromagram, N_CHROMA, chromagram, chromagram_from_spectrogram,
310};
311pub use cqt::{CqtParams, CqtResult, cqt};
312pub use erb::{ErbParams, GammatoneParams};
313pub use error::{SpectrogramError, SpectrogramResult};
314pub use fft_backend::{C2rPlan, C2rPlanner, R2cPlan, R2cPlanner, r2c_output_size};
315pub use fft2d::*;
316pub use image_ops::*;
317pub use mfcc::{Mfcc, MfccParams, mfcc, mfcc_from_log_mel};
318pub use spectrogram::*;
319pub use window::{
320    WindowType, blackman_window, gaussian_window, hamming_window, hanning_window, kaiser_window,
321    rectangular_window,
322};
323#[macro_export]
324macro_rules! nzu {
325    ($rate:expr) => {{
326        const RATE: usize = $rate;
327        const { assert!(RATE > 0, "non zero usize must be greater than 0") };
328        // SAFETY: We just asserted RATE > 0 at compile time
329        unsafe { ::core::num::NonZeroUsize::new_unchecked(RATE) }
330    }};
331}
332
333#[cfg(all(feature = "fftw", feature = "realfft"))]
334compile_error!(
335    "Features 'fftw' and 'realfft' are mutually exclusive. Please enable only one of them."
336);
337
338#[cfg(not(any(feature = "fftw", feature = "realfft")))]
339compile_error!("At least one FFT backend feature must be enabled: 'fftw' or 'realfft'.");
340
341#[cfg(feature = "realfft")]
342pub use fft_backend::realfft_backend::*;
343
344#[cfg(feature = "fftw")]
345pub use fft_backend::fftw_backend::*;
346
347/// Python module definition for `PyO3`.
348///
349/// This module is only available when the `python` feature is enabled.
350#[cfg(feature = "python")]
351use pyo3::prelude::*;
352
353#[cfg(feature = "python")]
354#[pymodule]
355fn _spectrograms(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
356    python::register_module(py, m)?;
357    m.add("__version__", env!("CARGO_PKG_VERSION"))?;
358    Ok(())
359}
360
361pub(crate) fn min_max_single_pass<A: AsRef<[f64]>>(data: A) -> (f64, f64) {
362    let mut min_val = f64::INFINITY;
363    let mut max_val = f64::NEG_INFINITY;
364    for &val in data.as_ref() {
365        if val < min_val {
366            min_val = val;
367        }
368        if val > max_val {
369            max_val = val;
370        }
371    }
372    (min_val, max_val)
373}