audio_samples/
lib.rs

1#![cfg_attr(feature = "simd", feature(portable_simd))]
2// Correctness and logic
3#![warn(clippy::unit_cmp)] // Detects comparing unit types
4#![warn(clippy::match_same_arms)]
5// Duplicate match arms
6// #![warn(clippy::unreachable)] // Detects unreachable code
7
8// Performance-focused
9#![warn(clippy::inefficient_to_string)] // `format!("{}", x)` vs `x.to_string()`
10#![warn(clippy::map_clone)] // Cloning inside `map()` unnecessarily
11#![warn(clippy::unnecessary_to_owned)] // Detects redundant `.to_owned()` or `.clone()`
12#![warn(clippy::large_stack_arrays)] // Helps avoid stack overflows
13#![warn(clippy::box_collection)] // Warns on boxed `Vec`, `String`, etc.
14#![warn(clippy::vec_box)] // Avoids using `Vec<Box<T>>` when unnecessary
15#![warn(clippy::needless_collect)] // Avoids `.collect().iter()` chains
16
17// Style and idiomatic Rust
18#![warn(clippy::redundant_clone)] // Detects unnecessary `.clone()`
19#![warn(clippy::identity_op)] // e.g., `x + 0`, `x * 1`
20#![warn(clippy::needless_return)] // Avoids `return` at the end of functions
21#![warn(clippy::let_unit_value)] // Avoids binding `()` to variables
22#![warn(clippy::manual_map)] // Use `.map()` instead of manual `match`
23#![warn(clippy::unwrap_used)] // Avoids using `unwrap()`
24
25// Maintainability
26#![warn(clippy::missing_panics_doc)] // Docs for functions that might panic
27#![warn(clippy::missing_safety_doc)] // Docs for `unsafe` functions
28#![warn(clippy::missing_const_for_fn)] // Suggests making eligible functions `const`
29#![allow(clippy::too_many_arguments)]
30// Allow functions with many parameters (very few and far between)
31#![deny(missing_docs)] // Documentation is a must for release
32
33//! # AudioSamples
34//!
35//! A high-performance audio processing library for Rust that provides type-safe sample format conversions, statistical analysis, and various audio processing operations.
36//!
37//! ## Overview
38//!
39//! <!-- This section is reserved for the project's purpose and motivation. -->
40//! <!-- The author will fill this in later. -->
41//!
42//! ## Installation
43//!
44//! Add this to your `Cargo.toml`:
45//!
46//! ```toml
47//! [dependencies]
48//! audio_samples = "0.10.0"
49//! ```
50//!
51//! or more easily with:
52//! ```bash
53//! cargo add audio_samples
54//! ```
55//!
56//! For specific features, enable only what you need:
57//!
58//! ```toml
59//! [dependencies]
60//! audio_samples = { version = "*", features = ["fft", "plotting"] }
61//! ```
62//!
63//! Or enable everything:
64//!
65//! ```toml
66//! [dependencies]
67//! audio_samples = { version = "*", features = ["full"] }
68//! ```
69//!
70//! ## Features
71//!
72//! The library uses a modular feature system to keep dependencies minimal:
73//!
74//! - `statistics`: statistics utilities (peak, RMS, variance, etc.)
75//! - `processing`: processing operations (normalize, scale, clip, etc.)
76//! - `editing`: time-domain editing operations (trim, pad, reverse, etc.)
77//! - `channels`: channel operations (mono/stereo conversion)
78//! - `fft`: FFT-backed transforms (adds FFT dependencies)
79//! - `plotting`: plotting utilities (adds signal plotting dependencies)
80//! - `resampling`: resampling utilities (using `rubato` crate)
81//! - `serialization`: serialization utilities (using `serde` crate)
82//!
83//! See `Cargo.toml` for the complete feature list and feature groups.
84//!
85//!
86//! ## Error Handling
87//!
88//! The library uses a hierarchical error system designed for precise error handling:
89//!
90//! ```rust
91//! use audio_samples::{AudioSampleError, AudioSampleResult, ParameterError};
92//!
93//! let audio_result: AudioSampleResult<()> = Err(AudioSampleError::Parameter(
94//!     ParameterError::invalid_value("window_size", "must be > 0"),
95//! ));
96//!
97//! match audio_result {
98//!     Ok(()) => {}
99//!     Err(AudioSampleError::Conversion(err)) => eprintln!("Conversion failed: {err}"),
100//!     Err(AudioSampleError::Parameter(err)) => eprintln!("Invalid parameter: {err}"),
101//!     Err(other_err) => eprintln!("Other error: {other_err}"),
102//! }
103//! ```
104//!
105//! ## Full Examples
106//!
107//! Examples live in the repository (see `examples/`) and in the crate-level docs on
108//! <https://docs.rs/audio_samples>.
109//!
110//! ## Quick Start
111//!
112//! ### Creating Audio Data
113//!
114//! ```rust
115//! use audio_samples::AudioSamples;
116//! use ndarray::array;
117//!
118//! // Create mono audio
119//! let data = array![0.1f32, 0.5, -0.3, 0.8, -0.2];
120//! let audio = AudioSamples::new_mono(data, 44100);
121//!
122//! // Create stereo audio
123//! let stereo_data = array![
124//!     [0.1f32, 0.5, -0.3],  // Left channel
125//!     [0.8f32, -0.2, 0.4]   // Right channel
126//! ];
127//! let stereo_audio = AudioSamples::new_multi_channel(stereo_data, 44100);
128//! ```
129//!
130//! ### Basic Statistics
131//!
132//! ```rust
133//! use audio_samples::{AudioStatistics, sine_wave};
134//! use std::time::Duration;
135//!
136//! // frequency, sample rate, duration, amplitude
137//! // <f32, f32> indicates the sine wave will be
138//! // generated using single float precision and
139//! // the resulting samples will also be f32
140//! let audio = sine_wave::<f32, f32>(440.0, Duration::from_secs_f32(1.0), 44100, 1.0);
141//! // Simple statistics
142//! let peak = audio.peak();
143//! let min = audio.min_sample();
144//! let max = audio.max_sample();
145//! let mean = audio.mean();
146//!
147//! // More complex statistics (return Result)
148//! let rms = audio.rms().unwrap();
149//! let variance = audio.variance().unwrap();
150//! let zero_crossings = audio.zero_crossings();
151//! ```
152//!
153//! ### Processing Operations
154//!
155//! ```rust
156//! use audio_samples::{AudioProcessing, NormalizationMethod};
157//! use ndarray::array;
158//!
159//! let data = array![0.1f32, 0.5, -0.3, 0.8, -0.2];
160//! let mut audio = audio_samples::AudioSamples::new_mono(data, 44100);
161//!
162//! // Basic processing (in-place)
163//! audio
164//!     .normalize(-1.0, 1.0, NormalizationMethod::Peak)
165//!     .unwrap();
166//! audio.scale(0.5); // Reduce volume by half
167//! audio.remove_dc_offset();
168//! ```
169//!
170//! ### Type Conversions
171//!
172//! ```rust
173//! use audio_samples::AudioSamples;
174//! use ndarray::array;
175//!
176//! // Convert between sample types
177//! let audio_f32 = AudioSamples::new_mono(array![1.0f32, 2.0, 3.0], 44100);
178//! let audio_i16 = audio_f32.as_type::<i16>().unwrap();
179//! let audio_f64 = audio_f32.as_type::<f64>().unwrap();
180//! ```
181//!
182//! ### Iterating Over Audio Data
183//!
184//! ```rust
185//! use audio_samples::AudioSampleIterators;
186//! use ndarray::array;
187//!
188//! let audio = audio_samples::AudioSamples::new_mono(array![1.0f32, 2.0, 3.0, 4.0], 44100);
189//!
190//! // Iterate by frames (one sample from each channel)
191//! for frame in audio.frames() {
192//!     println!("Frame: {:?}", frame);
193//! }
194//!
195//! // Iterate by channels
196//! for channel in audio.channels() {
197//!     println!("Channel: {:?}", channel);
198//! }
199//!
200//! // Windowed iteration for analysis
201//! for window in audio.windows(1024, 512) {
202//!     // Process 1024-sample windows with 50% overlap
203//!     let window_rms = window.rms().unwrap();
204//!     println!("Window RMS: {:.3}", window_rms);
205//! }
206//! ```
207//!
208//! ## Builder Pattern for Complex Processing
209//!
210//! For more complex operations, use the fluent builder API:
211//!
212//! ```rust
213//! use audio_samples::{AudioSamples, NormalizationMethod};
214//! use ndarray::array;
215//!
216//! let data = array![0.1f32, 0.5, -0.3, 0.8, -0.2];
217//! let mut audio = AudioSamples::new_mono(data, 44100);
218//!
219//! // Chain multiple operations
220//! audio.processing()
221//!     .normalize(-1.0, 1.0, NormalizationMethod::Peak)
222//!     .scale(0.8)
223//!     .remove_dc_offset()
224//!     .apply()
225//!     .unwrap();
226//! ```
227//!
228//! ## Core Type System
229//!
230//! ### Supported Sample Types
231//!
232//! - `i16` - 16-bit signed integer
233//! - `I24` - 24-bit signed integer (from `i24` crate)
234//! - `i32` - 32-bit signed integer
235//! - `f32` - 32-bit floating point
236//! - `f64` - 64-bit floating point
237//!
238//! ### Type System Traits
239//!
240//! The library provides a rich trait system for working with different audio sample types:
241//!
242//! #### `AudioSample` Trait
243//!
244//! Core trait that all audio sample types implement. Provides common operations and constraints needed for audio processing.
245//!
246//! #### Conversion Traits
247//!
248//! - **`ConvertTo<T>`** - Type-safe conversions between audio sample types with proper scaling
249//!   - `i16 -> f32`: Normalized to -1.0 to 1.0 range
250//!   - `f32 -> i16`: Scaled and clamped to integer range
251//!   - Handles bit depth differences automatically
252//!
253//! - **`CastFrom<T>` / `CastInto<T>`** - Direct type casting without audio-specific scaling
254//!   - For computational operations where you need the raw numeric value
255//!   - Example: `i16` sample `1334` casts to `f32` as `1334.0` (not normalized)
256//!   - Use when you need to work with samples as regular numbers, not audio values
257//!
258//! ## Utility Functions
259//!
260//! The `utils` module provides convenient functions for common audio tasks:
261//!
262//! ### Signal Generation (`utils::generation`)
263//!
264//! - **Test Signals**: `generate_sine()`, `generate_white_noise()`, `generate_chirp()`
265//! - **Complex Signals**: `generate_multi_tone()` for multiple frequencies
266//! - **Calibration**: Known reference signals for testing
267//!
268//!
269
270//! ```rust
271//! use audio_samples::{comparison, sine_wave};
272//! use std::time::Duration;
273//!
274//! let a = sine_wave::<f32, f32>(440.0, Duration::from_secs(1), 44100, 1.0);
275//! let b = sine_wave::<f32, f32>(440.0, Duration::from_secs(1), 44100, 1.0);
276//! let corr: f32 = comparison::correlation::<f32, f32>(&a, &b).unwrap();
277//! assert!(corr > 0.99);
278//! ```
279//! ## Documentation
280//!
281//! Full API documentation is available at [docs.rs/audio_samples](https://docs.rs/audio_samples).
282//!
283//! ## License
284//!
285//! MIT License
286//!
287//! ## Contributing
288//!
289//! Contributions are welcome! Please feel free to submit a Pull Request.
290
291mod error;
292
293#[cfg(any(
294    feature = "core-ops",
295    feature = "statistics",
296    feature = "processing",
297    feature = "editing",
298    feature = "channels"
299))]
300#[cfg(any(
301    feature = "core-ops",
302    feature = "statistics",
303    feature = "processing",
304    feature = "editing",
305    feature = "channels"
306))]
307pub mod operations;
308
309pub mod conversions;
310pub mod iterators;
311mod repr;
312#[cfg(feature = "resampling")]
313pub mod resampling;
314pub mod simd_conversions;
315/// Core traits for audio processing.
316pub mod traits;
317pub mod utils;
318
319use std::fmt::Debug;
320
321pub use crate::error::{
322    AudioSampleError, AudioSampleResult, ConversionError, FeatureError, LayoutError,
323    ParameterError, ProcessingError,
324};
325pub use crate::iterators::{
326    AudioSampleIterators, ChannelIterator, ChannelIteratorMut, FrameIterator, FrameIteratorMut,
327    FrameMut, PaddingMode, WindowIterator, WindowIteratorMut, WindowMut,
328};
329#[cfg(feature = "statistics")]
330pub use crate::operations::AudioStatistics;
331
332#[cfg(feature = "processing")]
333pub use crate::operations::{AudioProcessing, NormalizationMethod};
334
335#[cfg(feature = "editing")]
336pub use crate::operations::AudioEditing;
337
338#[cfg(feature = "channels")]
339pub use crate::operations::AudioChannelOps;
340
341#[cfg(feature = "spectral-analysis")]
342pub use crate::operations::AudioTransforms;
343
344#[cfg(feature = "plotting")]
345pub use crate::operations::AudioPlottingUtils;
346#[cfg(feature = "fixed-size-audio")]
347pub use crate::repr::FixedSizeAudioSamples;
348pub use crate::repr::{AudioBytes, AudioData, AudioSamples, SampleType, StereoAudioSamples};
349pub use crate::traits::{
350    AudioSample, AudioTypeConversion, CastFrom, CastInto, Castable, ConvertTo,
351};
352pub use crate::utils::{
353    audio_math::{
354        amplitude_to_db, db_to_amplitude, fft_frequencies, frames_to_time, hz_to_mel, hz_to_midi,
355        mel_scale, mel_to_hz, midi_to_hz, midi_to_note, note_to_midi, power_to_db, time_to_frames,
356    },
357    comparison, detection,
358    generation::{
359        ToneComponent, chirp, compound_tone, cosine_wave, impulse, multichannel_compound_tone,
360        sawtooth_wave, silence, sine_wave, square_wave, stereo_chirp, stereo_silence,
361        stereo_sine_wave, triangle_wave,
362    },
363    samples_to_seconds, seconds_to_samples,
364};
365
366// Re-export noise generation functions with feature gating
367#[cfg(feature = "random-generation")]
368pub use crate::utils::generation::{brown_noise, pink_noise, white_noise};
369
370pub use i24::{I24, PackedStruct}; // Re-export I24 type that has the AudioSample implementation
371
372// Re-export NonZero types used in the API
373pub use core::num::{NonZeroU32, NonZeroUsize};
374
375use num_traits::{Float, FloatConst, NumCast};
376#[cfg(feature = "resampling")]
377pub use resampling::{resample, resample_by_ratio};
378#[cfg(feature = "resampling")]
379use rubato::Sample;
380
381/// Array of supported audio sample data types as string identifiers
382pub const SUPPORTED_DTYPES: [&str; 5] = ["i16", "I24", "i32", "f32", "f64"];
383/// Left channel index.
384pub const LEFT: usize = 0;
385/// Right channel index.
386pub const RIGHT: usize = 1;
387
388#[cfg(not(feature = "resampling"))]
389/// Marker trait for real floating-point types (f32, f64)
390pub trait RealFloat: Float + FloatConst + NumCast + AudioSample {}
391
392#[cfg(feature = "resampling")]
393/// Marker trait for real floating-point types (f32, f64)
394pub trait RealFloat: Float + FloatConst + NumCast + AudioSample + Sample {}
395
396impl RealFloat for f32 {}
397impl RealFloat for f64 {}
398
399/// Describes how multi-channel audio data is organized in memory
400#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
401pub enum ChannelLayout {
402    /// Samples from different channels are stored sequentially (LRLRLR...)
403    /// This is the most common format for audio files and streaming
404    #[default]
405    Interleaved,
406    /// Samples from each channel are stored in separate contiguous blocks (LLL...RRR...)
407    /// This format is often preferred for digital signal processing
408    NonInterleaved,
409}
410
411impl ChannelLayout {
412    /// Returns true if the layout is interleaved
413    pub const fn is_interleaved(&self) -> bool {
414        matches!(self, ChannelLayout::Interleaved)
415    }
416
417    /// Returns true if the layout is non-interleaved
418    pub const fn is_non_interleaved(&self) -> bool {
419        matches!(self, ChannelLayout::NonInterleaved)
420    }
421}
422
423/// Casts a numeric value into the target floating-point type `F`.
424///
425/// This function provides a *transparent* conversion mechanism for numeric
426/// values (`T`) into a chosen target type (`F`), typically `f32` or `f64`.
427///
428/// Internally it uses `num_traits::NumCast::from` and will **panic** if the
429/// cast is not representable by the target type (e.g. out-of-range values,
430/// or non-finite floats when converting to an integer type).
431///
432/// The main purpose is to **abstract over floating-point precision**
433/// in generic code where the target type `F: RealFloat` may vary.
434/// This enables you to write a single numeric implementation that
435/// automatically adapts to either `f32` or `f64` precision without
436/// explicit `as` conversions.
437///
438/// # Arguments
439/// * `value` - The numeric value to convert to the target floating-point type
440///
441/// # Returns
442/// The input value converted to the target floating-point type `F`.
443///
444/// # Behaviour
445/// - Uses `NumCast::from(value)`.
446/// - Panics if the conversion fails.
447///
448/// In practice, if `F` and `T` are the same type (e.g. `f32 → f32`),
449/// this operation is a **compile-time no-op** with no runtime overhead.
450///
451/// # Examples
452/// ```
453/// use audio_samples::to_precision;
454///
455/// let value_i32 = 42i32;
456/// let value_f32: f32 = to_precision(value_i32);
457/// assert_eq!(value_f32, 42.0);
458///
459/// let value_f64: f64 = to_precision(value_i32);
460/// assert_eq!(value_f64, 42.0);
461/// ```
462///
463/// # Panics
464/// Panics if the numeric conversion fails.
465#[inline(always)]
466pub fn to_precision<F, T>(value: T) -> F
467where
468    F: RealFloat + NumCast,
469    T: NumCast,
470{
471    NumCast::from(value).expect("safe_cast: valid numeric conversion")
472}