audio_samples 2.0.0

A typed audio processing library for Rust that treats audio as a first-class, invariant-preserving object rather than an unstructured numeric buffer.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
# AudioSamples Architecture

This document provides detailed information on how the audio_samples crate is architected from its building blocks up to the highest-level API.
The architecture follows core principles of **type safety**, **zero-allocation efficiency**, **trait-based composition**, and **modular feature design**.

## Core Design Principles

### 1. Type Safety Through Strong Typing

All audio data is strongly typed with the sample format (`i16`, `I24`, `i32`, `f32`, `f64`), ensuring mathematical operations are performed with appropriate precision and range.
The type system prevents common audio processing errors like mixing incompatible sample formats.

### 2. Zero-Allocation Efficiency

The library leverages `ndarray`'s view system to enable zero-allocation access patterns wherever possible.
Operations prefer in-place modifications and views over copying data.
The library adds several wrappers, ``MonoRepr/MultiRepr`` and ``MonoData/MultiData``, around `ndarray` arrays to facilitate owned and borrowed data automatically.
The end user should never have to deal with these wrappers.
The top layer of the wrappers is the ``AudioSamples<'_, T: AudioSample>`` struct.
This is the entry point for a user and the impl blocks for ``AudioSamples`` connect to the right backend wrapper.

### 3. Trait-Based Composition

Functionality is organized into focused, composable traits rather than monolithic implementations.
Each trait handles a specific aspect of audio processing with clear separation of concerns.
Traits also allow for easy feature-gating.

### 4. Metadata Integration

Audio samples are always paired with essential metadata (sample rate) to prevent common audio processing errors and enable automatic format conversions.

### 5. Feature-Gated Modularity

The library uses cargo features extensively to keep dependencies minimal, allowing users to enable only the functionality they need.

### 6. Dual-Variant Operations

Every transforming operation is exposed as a matched pair. The canonical, unsuffixed name **borrows** and returns a new owned copy:

```rust,ignore
fn op(&self, ..) -> AudioSampleResult<Self>;        // returns a NEW modified value
fn op_in_place(&mut self, ..) -> AudioSampleResult<()>; // mutates the receiver
```

The `*_in_place` variant is the primitive; the borrowing variant clones the receiver and delegates to it. Because the borrowing form does not consume `self`, results compose into pipelines (`audio.normalize(cfg)?.scale(0.5).remove_dc_offset()?`) while leaving the original binding usable. Operations that cannot fail (e.g. `scale`) return `Self` directly rather than `AudioSampleResult<Self>`, so no `?` is needed.

## Building Blocks

### AudioSample/StandardSample (./traits.rs)

The `AudioSample` and `StandardSample` traits are the foundation of the entire type system.
It defines the interface for all supported audio sample formats:

**Supported Types:**

- `i16`: 16-bit signed integer samples (most common for audio files)
- `I24`: 24-bit signed integer samples (professional audio) -- **Always use the re-export of ``I24`` from the crate.**
- `i32`: 32-bit signed integer samples (high precision)
- `f32`: 32-bit floating-point samples (normalized -1.0 to 1.0)
- `f64`: 64-bit floating-point samples (highest precision)

**Key Requirements:**

- Standard arithmetic operations (`Add`, `Sub`, `Mul`, `Div`)
- Memory safety guarantees (`NoUninit` for safe byte serialization)
- Numeric operations (`Num`, `Zero`, `One`, `Signed`)
- Optional serde support via the `MaybeSerdeAudioSample` bound (adds `Serialize`/`Deserialize` only when the `serde` feature is enabled)
- Constants for range information (`MAX`, `MIN`, `BITS`)

The `StandardSample` trait extends the `AudioSample` trait with conversion functionality provided via the `ConvertTo` and `ConvertFrom` traits.

**API Contracts:**

- All sample types must provide consistent arithmetic behavior
- Byte serialization must be safe and deterministic
- Range constants must accurately represent the format's dynamic range

### ConvertTo/ConvertFrom (./traits.rs)

The `ConvertTo<T>` and `ConvertFrom<T>` trait provides audio-aware conversions between different sample formats with proper scaling:

**Conversion Behavior:**

- Integer to integer: bit-shift scaling to preserve the full dynamic range
- Integer to/from float: normalized scaling (-1.0 to 1.0 for floats)
- Float to float: direct cast with a precision change
- I24: custom handling for the 24-bit type

**Design Patterns:**

- Returns `T`, designed not to fail.
- Uses macro-generated implementations for consistency and to cut down on manual code.
- Maintains mathematical precision across format boundaries
- Handles edge cases like range overflows gracefully
- The two traits provide functionality like `Into` and `From` in the standard library.

**API Contracts:**

- Conversions must preserve audio dynamic range proportionally
- Round-trip conversions should minimize precision loss
- Error cases must be clearly documented and handled

### CastInto, CastFrom and Castable (./traits.rs)

The casting trait family provides raw numeric conversions without audio-specific scaling:

**Purpose:**

- Raw numeric casting for non-audio operations
- Direct type conversions without range normalization
- Performance-critical paths where scaling is not needed

**Design Patterns:**

Sometimes you just need to cast an int to a float and back again.
These traits **DO NOT** perform any audio-specific scaling/conversions.

- `CastFrom<S>`: Cast from source type to Self
- `CastInto<T>`: Cast self into target type
- `Castable`: Marker trait for types that can cast to all audio formats

**API Contracts:**

- Casting preserves numeric values without audio scaling
- Out-of-range values are clamped to target type's limits
- No error handling - assumes well-formed input.

If something like this fails then things are bad.

### AudioSamples<'_, T: StandardSample> (repr.rs)

The main data container that combines audio samples with essential metadata:

```rust,ignore
pub struct AudioSamples<'a, T: StandardSample> {
    data: AudioData<'a, T>,        // private
    sample_rate: SampleRate,       // private; guaranteed non-zero
}
```

**Sealed fields and accessors:** the fields are **private**. Callers reach the underlying data and metadata through accessors:

- `data(&self) -> &AudioData<'a, T>` and `data_mut(&mut self) -> &mut AudioData<'a, T>`
- `into_data(self) -> AudioData<'static, T>` (owned) and `into_data_borrowed(self) -> AudioData<'a, T>` (preserves the borrow)
- `sample_rate(&self) -> SampleRate` (plus the convenience `sample_rate_hz() -> f64`)

Channel **indices** are plain `usize`; channel **counts** use the dedicated `ChannelCount` type, keeping the two concepts from being confused at call sites.

**Key Features:**

- Generic over any `StandardSample` type
- Lifetime parameter `'a` enables zero-copy views
- Always includes a non-zero sample rate (`SampleRate`)
- Provides uniform interface for mono and multi-channel audio

**Memory Layout:**

- Mono audio: 1D arrays via `MonoData<'a, T>`
- Multi-channel audio: 2D arrays via `MultiData<'a, T>` with channels as rows
- Both support borrowed and owned data

**API Contracts:**

- Sample rate must be positive
- Lifetime safety ensured through Rust's borrow checker.
But at the user level of the API, unless they are really concerned with lifetime management and reuse in their program, lifetimes should not be a concern.
- Metadata consistency maintained across operations

### AudioSamples Iteration (iterators.rs)

Provides multiple iteration patterns for efficient audio processing:

**Iterator Types:**

- `frames()`: Iterate by frames (one sample from each channel)
- `channels()`: Iterate by complete channels
- `windows(size, hop)`: Windowed iteration with configurable overlap (owning `WindowIterator`)
- `windows_ref(size, hop)`: Zero-copy windowed iteration that borrows each window (`WindowRefIterator`) instead of allocating
- Support for different padding modes: `Zero`, `None`, `Skip`

**Design Patterns:**

- Zero-allocation views where possible
- Configurable windowing for FFT and analysis operations
- Type-safe iterator adaptors
- Memory-efficient streaming for large audio files

**API Contracts:**

- Iterator stability guaranteed for immutable operations
- Window boundaries handled consistently
- Padding modes clearly defined and documented

### AudioSamples Conversion (conversions.rs)

Implements the `AudioTypeConversion` trait for safe type transformations on ``AudioSamples``:

**In-Domain Conversions:**

- `to_format<O>()`: Borrows original, returns new type
- `to_type<O>()`: Consumes original, returns new type
- Convenience methods: `as_f32()`, `as_i16()`, `as_i24()`, etc.
- Uses `ConvertTo` and `ConvertFrom` traits for audio-aware scaling

**Out-of-Domain Conversions:**

- `cast_as<O>()`: Borrows original, raw numeric casting
- `cast_to<O>()`: Consumes original, raw numeric casting
- Uses `CastFrom` trait for direct numeric conversion

**API Contracts:**

- Clear distinction between audio-aware and raw conversions
- Lifetime management ensures memory safety
- Type bounds enforce conversion compatibility

### NdResult (lib.rs)

Some operations (for example envelope followers) produce an array whose dimensionality mirrors the input: a mono input yields a 1-D array, a multi-channel input yields a 2-D `channels x samples` array. `NdResult<T>` is the tagged return type that expresses this 1-D / 2-D by layout distinction in a single value: it is either `Mono(Array1<T>)` or `MultiChannel(Array2<T>)`. Callers pattern-match, or use the `into_array1()` / `into_array2()` helpers when the expected shape is known. Both variants are guaranteed non-empty.

### Utilities (./utils)

Provides supporting functionality organized by purpose:

- `generation.rs`: Signal generation. Beyond the classic oscillators (`sine_wave`, `cosine_wave`, `square_wave`, `triangle_wave`, `sawtooth_wave`, `chirp`, `impulse`, `silence`, `compound_tone`, `am_signal`), this now includes exponential/log chirps (`exponential_chirp`), band-limited oscillators (`square_wave_bandlimited`, `sawtooth_wave_bandlimited`, `triangle_wave_bandlimited`), FM signals (`fm_signal`), and the noise sources `white_noise`/`pink_noise`/`brown_noise` (under `random-generation`).
- `detection.rs`: Feature detection algorithms
- `comparison.rs`: Audio comparison and similarity metrics - `correlation`, `mse`, `snr`, `psnr`, `segmental_snr`, `log_spectral_distance`, plus per-channel variants of the first three (`correlation_per_channel`, `mse_per_channel`, `snr_per_channel`).
- `audio_math.rs`: Audio-domain mathematical utilities and canonical unit conversions.
In future, this may become a more general ``algorithms`` module.

**Design Patterns:**

- Pure functions where possible
- Consistent error handling patterns
- Performance-optimized implementations
- Feature-gated advanced functionality

### Errors (./error.rs)

Hierarchical error handling. `AudioSampleError` is the `#[non_exhaustive]` root, and each variant wraps a specialised sub-enum that covers one failure domain:

```rust,ignore
pub enum AudioSampleError {
    Conversion(ConversionError),   // type conversion and casting failures
    Parameter(ParameterError),     // invalid parameters and configuration values
    Layout(LayoutError),           // memory layout / array-structure issues
    Processing(ProcessingError),   // DSP algorithm and arithmetic failures
    Feature(FeatureError),         // missing/misconfigured optional cargo features
}
```

Grouping by domain lets callers match a broad category in a single arm while still drilling into the specific cause when needed.

**Error Handling Strategy:**

- Built on `thiserror`; every error also implements [`miette::Diagnostic`].
- Each variant carries a stable, namespaced **code** (`audio_samples::<domain>::<variant>`) for programmatic matching and an actionable **help** hint.
- Text-parsing failures (note names, enum `FromStr`) carry a `SourceSpan` pointing a caret at the offending character.
- The coloured caret-underline rendering is gated behind the `fancy` feature, so library consumers pay nothing for graphical dependencies unless they opt in.
- `AudioSampleResult<T>` (= `Result<T, AudioSampleError>`) is the consistent return alias for fallible operations.

## Trait Extensions

Available:

- AudioStatistics (descriptive stats + spectral feature suite)
- AudioVoiceActivityDetection
- AudioProcessing
- AudioTransforms
- AudioPitchAnalysis (`track_pitch`, `estimate_key`)
- AudioBeatTracking (`estimate_tempo`)
- AudioOnsetDetection
- AudioIirFiltering (filter design, `filtfilt`, streaming)
- AudioParametricEq
- AudioDynamicRange
- AudioEditing
- AudioChannelOps
- AudioEnvelopes
- AudioPerceptualAnalysis (psychoacoustic; defined in `codecs::perceptual`)
- AudioPlotting
- AudioDecomposition
- AudioTypeConversion

Each transforming trait method follows the dual-variant convention (`op` / `op_in_place`) described in the Core Design Principles.

### The `operations` Module (./operations)

The operations module organizes audio processing functionality into focused, composable traits.
Each trait addresses a specific domain of audio processing:

#### Core Organization

- **`traits.rs`**: Trait definitions and type bounds
- **Implementation files**: Named after traits (e.g., `AudioChannelOps` -> `channels.rs`)
- **Supporting types**: Enums and structs in `types.rs`
- **Feature gating**: Advanced functionality behind cargo features

#### `AudioStatistics` (statistics.rs)

**Purpose**: Statistical analysis operations for audio data.

**Why use it?**: Essential for audio analysis, level monitoring, and processing decisions.

**How to use it?**:

```rust,ignore
let peak = audio.peak();                  // Returns T directly
let rms: f64 = audio.rms();               // Returns f64 directly (infallible)
let crossings = audio.zero_crossings();   // Signal analysis

// Spectral features fold multi-channel input via a ChannelReduction policy.
let centroid = audio.spectral_centroid(ChannelReduction::Average)?;
let flatness = audio.spectral_flatness(ChannelReduction::First)?;
```

**Key Operations**:

- `peak()`, `min_sample()`, `max_sample()`: Level measurements
- `mean()`, `variance()`, `std_dev()`: Statistical measures
- `rms()`: Perceptually relevant loudness measure
- `zero_crossings()`, `zero_crossing_rate()`: Periodicity analysis
- `autocorrelation()`, `cross_correlation()`: Correlation analysis (requires `transforms`)
- Spectral feature suite (requires `transforms`): `spectral_centroid()`, `spectral_rolloff()`, `spectral_bandwidth()`, `spectral_flatness()`, `spectral_contrast()`, `spectral_slope()`, `spectral_crest()` - each takes a `ChannelReduction` argument

**ChannelReduction policy**: spectral analysis on multi-channel audio must decide how to collapse channels to a single value. `ChannelReduction` is the parameter that chooses: `Error` (default - refuse multi-channel input), `First`, `Average`, or `Channel(usize)`.

**API Contracts**:

- Simple statistics (`peak`, `rms`, `mean`, `variance`, `zero_crossings`, ...) return values **directly** - no `Result` - leveraging the non-empty invariant of `AudioSamples`.
- Generic over float types for numerical operations
- Consistent behavior across mono and multi-channel audio

#### `AudioProcessing` (processing.rs)

**Purpose**: Core signal processing operations following the dual-variant convention.

**Why use it?**: Fundamental audio modifications like normalization, scaling, DC removal.

**How to use it?**:

```rust,ignore
// Borrowing variants return a NEW value and compose into a pipeline. `scale` is
// infallible (returns Self, no `?`); the others are fallible.
let processed = audio
    .normalize(NormalizationConfig::peak(1.0))?
    .scale(0.8)
    .remove_dc_offset()?;

// In-place variants mutate the receiver instead of allocating a copy.
let mut buf = audio.clone();
buf.normalize_in_place(NormalizationConfig::peak(1.0))?;
buf.scale_in_place(0.8);
```

**Key Operations**:

- `normalize()`: normalization strategies, configured via `NormalizationConfig` (e.g. `NormalizationConfig::peak(target)`)
- `scale()`: amplitude scaling (infallible)
- `clip()`: hard limiting to prevent overflows
- `remove_dc_offset()`: DC bias removal
- `apply_window()`, `apply_filter()`, `mu_compress()`/`mu_expand()`, `low_pass_filter()`/`high_pass_filter()`/`band_pass_filter()`

(Fades live on `AudioEditing`, not here.)

**Convention**: each operation above has both the borrowing form shown here and an `*_in_place` counterpart, as described under *Dual-Variant Operations*. Multi-parameter behaviour is captured in dedicated config structs rather than long positional argument lists.

#### `AudioTransforms` (transforms.rs)

**Purpose**: Frequency-domain analysis and transformations (requires `transforms`).

**Why use it?**: Spectral analysis, filtering, and frequency-domain processing. The heavy time-frequency machinery lives in the companion `spectrograms` crate.

**How to use it?**:

```rust,ignore
let spectrum = audio.fft(nzu!(8192))?;
let stft = audio.stft(&stft_params)?;
let mfcc = audio.mfcc(&stft_params, nzu!(40), &MfccParams::speech_standard())?;
let psd = audio.power_spectral_density(nzu!(1024), 0.5)?; // -> Psd
```

**Key Operations**:

- `fft()`: forward Fast Fourier Transform (per channel)
- `stft()`, `istft()`: Short-Time Fourier Transform and its inverse (parameterised by `StftParams`)
- `mfcc()`, `chromagram()`, `constant_q_transform()`: higher-level spectral representations
- `power_spectral_density()`: returns a structured `Psd { frequencies, density }` rather than a bare tuple (access via `frequencies()` / `density()` / `into_parts()`)
- `convolve()`, `deconvolve()`: FFT-based linear convolution and regularised deconvolution

**Structured returns**: where a pre-2.0 API returned a loose tuple, the modern API returns a named type - for example `power_spectral_density` yields `Psd`, and (in the pitch-analysis trait) `track_pitch` yields `PitchContour` and `estimate_key` yields `Key { tonic, mode, confidence }`.

**Performance Considerations**:

- Uses `rustfft` by default
- Real-valued FFT optimization for audio signals
- Memory-efficient windowing operations (`windows()` / zero-copy `windows_ref()`)

#### `AudioEditing` (editing.rs)

**Purpose**: Time-domain editing and manipulation operations.

**Why use it?**: Audio arrangement, timing modifications, and content editing.

**How to use it?**:

```rust,ignore
let kept     = audio.trim(1.0, 2.0)?;      // keep the region from 1.0s to 2.0s
let padded   = audio.pad(0.0, 0.5, 0.0)?;  // append 0.5s padded with value 0.0
let reversed = audio.reverse();            // infallible; returns a new value
// `concatenate` is an associated function over a non-empty slice of segments:
let joined = AudioSamples::concatenate(segments)?;
```

**Key Operations**:

- `trim()`, `trim_silence()`, `trim_all_silence()`: remove a region or leading/trailing silence
- `pad()`, `pad_to_duration()`, `pad_samples_right()`: add padding
- `reverse()`: reverse the timeline (infallible)
- `concatenate()` (associated fn), `stack()`, `mix()`: combine multiple clips
- `repeat()`: loop content
- `fade_in()`, `fade_out()`, `perturb()`, `split()`

**API Contracts**:

- Time-based operations accept seconds or samples
- Sample rate consistency enforced across operations
- Memory-efficient implementation using views where possible

#### `AudioChannelOps` (channels.rs)

**Purpose**: Channel manipulation and spatial audio operations.

**Why use it?**: Mono/stereo conversion, channel mixing, spatial processing.

**How to use it?**:

```rust
let mono = stereo_audio.to_mono(MonoConversionMethod::Average)?;
let stereo = mono_audio.to_stereo(StereoConversionMethod::Duplicate)?;
let extracted = multi_audio.extract_channel(0)?;
```

**Key Operations**:

- `to_mono()`: Multiple mono conversion strategies
- `to_stereo()`: Stereo expansion from mono
- `extract_channel()`: Extract a specific channel.
allocates a new ``AudioSamples`` for the extracted channel
- `borrow_channel()`: Borrows a specific channel.
Does not allocate a new ``AudioSamples`` for the extracted channel, it just borrows the data.
- `swap_channels()`: Channel reordering
- `duplicate_to_channels(N)` - Duplicates mono audio to N channels
- `pan(x)` - Applies pan control to stereo audio
- `balance(x)` - Adjusts balance between left and right channels
- `apply_to_channel(i, f)` - Apply a function to a specific channel
- `interleave_channels(c)` - Interleave multiple channels into one audio sample
- `deinterleave_channels()` - Deinterleave audio into separate channel samples
**Conversion Methods**:

- **Mono** (`MonoConversionMethod`): `Average`, `Left`, `Right`, `Weighted(weights)`, `Center`
- **Stereo** (`StereoConversionMethod`): `Duplicate`, `Pan(value)`, `Left`, `Right`

#### `AudioIirFiltering` (iir_filtering.rs)

**Purpose**: Infinite Impulse Response filter design and application.

**Why use it?**: Real-time filtering, tone shaping, frequency response control.

**How to use it?**:

```rust,ignore
// Convenience constructors per family + response, in borrowing or in-place form.
let filtered = audio.butterworth_lowpass(nzu!(4), 1_000.0)?;
audio.butterworth_lowpass_in_place(nzu!(4), 1_000.0)?;

// Full control via an IirFilterDesign struct.
let design = IirFilterDesign::chebyshev_i(FilterResponse::HighPass, nzu!(6), 2_000.0, 1.0);
let shaped = audio.apply_iir_filter(&design)?;

// Zero-phase (forward-backward) filtering: no group delay.
let flat_phase = audio.filtfilt(&design)?;

// Design once, stream many blocks (real-time / chunked input).
let mut sos = SosFilter::from_design(&design, 44_100.0)?;
sos.process_block(&mut block);
```

**Filter families** (`IirFilterType`): `Butterworth` (default), `ChebyshevI`, `ChebyshevII`, `Elliptic` (Cauer), and `Bessel`.

**Responses** (`FilterResponse`): `LowPass`, `HighPass`, `BandPass`, `BandStop`.

**Key capabilities**:

- `IirFilterDesign`: a single struct describing family + response + order + cut-off(s) + ripple/attenuation, built with constructors such as `butterworth_lowpass`, `chebyshev_i`, `chebyshev_ii`, `bessel`, `elliptic`.
- `apply_iir_filter()` / `apply_iir_filter_in_place()`: apply a design.
- `filtfilt()` / `filtfilt_in_place()`: zero-phase forward-backward filtering (SciPy `sosfiltfilt`-style), doubling the magnitude response while cancelling group delay.
- `SosFilter::from_design(..)` + `process_block(&mut [f64])`: a design-once, second-order-sections filter that carries state across calls for streaming use.
- `frequency_response()`: magnitude/phase of the configured filter at probe frequencies.

#### `AudioParametricEq` (parametric_eq.rs)

**Purpose**: Multi-band parametric equalization.

**Why use it?**: Tone shaping, frequency response correction, creative EQ.

**How to use it?**:

```rust,ignore
let mut eq = ParametricEq::new();
eq.add_band(EqBand::peak(1000.0, 3.0, 2.0));  // +3 dB at 1 kHz, Q = 2.0
eq.add_band(EqBand::peak(5000.0, -6.0, 1.0)); // -6 dB at 5 kHz, Q = 1.0

let equalized = audio.apply_parametric_eq(&eq)?;          // borrowing variant
// audio.apply_parametric_eq_in_place(&eq)?;              // in-place variant

// Or the fixed three-band shelf/peak EQ via a config struct:
let config = ThreeBandEqConfig::new(200.0, 2.0, 1000.0, -1.0, 1.0, 5000.0, 3.0);
let shaped = audio.apply_three_band_eq(&config)?;
```

**Key Features**:

- Multiple simultaneous frequency bands (`ParametricEq` / `EqBand`)
- A convenience `ThreeBandEqConfig` for the common low/mid/high case
- Independent gain, frequency, and Q controls
- Both borrowing and `*_in_place` application variants
- Efficient cascaded biquad implementation

#### `AudioDynamicRange` (dynamic_range.rs)

**Purpose**: Dynamic range processing (compression, limiting, expansion).

**Why use it?**: Level control, dynamics processing, mastering operations.

**How to use it?**: each processor is driven by a dedicated config struct.

```rust,ignore
// CompressorConfig::new() yields a sensible default; presets and field setters
// tailor it further.
let compressed = audio.apply_compressor(&CompressorConfig::vocal())?;

// GateConfig / ExpanderConfig expose with_params(threshold, ratio, attack, release)
// and presets such as noise_gate().
let gated = audio.apply_gate(&GateConfig::noise_gate())?;

let limited = audio.apply_limiter(&LimiterConfig::default())?;
```

**Configuration structs**: `CompressorConfig`, `LimiterConfig`, `GateConfig`, and `ExpanderConfig` replace long positional argument lists. Each exposes a `new()`/`default()` baseline plus either presets or a `with_params(..)` helper, and side-chain behaviour is described by `SideChainConfig`.

**Processor Types** (each with a borrowing and an `*_in_place` variant):

- `apply_compressor()`: reduce dynamic range above the threshold
- `apply_limiter()`: hard limiting to prevent peaks
- `apply_expander()`: increase dynamic range below the threshold
- `apply_gate()`: remove low-level noise
- `apply_compressor_sidechain()` / `apply_limiter_sidechain()`: side-chained variants

#### Audio Resampling (resampling.rs)

**Purpose**: High-quality sample rate conversion (requires `resampling`).

**Why use it?**: Sample rate conversion, format compatibility, anti-aliasing.

**How to use it?**:

```rust,ignore
let resampled = audio.resample(sample_rate!(48000), ResamplingQuality::High)?;
```

**Quality Levels**:

- `Fast`: Quick conversion with acceptable quality
- `Medium`: Balanced quality and performance
- `High`: High-quality anti-aliasing

### Plotting (./operations/plotting)

**Purpose**: audio visualisation (requires `plotting`). Plots render to interactive HTML via plotly; PNG/SVG export is available behind `static-plots`.

**How to use it**: the `AudioPlotting` trait adds plot constructors to `AudioSamples`:

- `plot_waveform()`, `plot_magnitude_spectrum()`, `plot_phase_spectrum()`, `plot_spectrogram()`, `plot_lissajous()`

Each returns a plot value (`WaveformPlot`, `MagnitudeSpectrumPlot`, `PhaseSpectrumPlot`, `SpectrogramPlot`, `LissajousPlot`) that renders to an HTML string.

**Module layout**:

- `waveform.rs`, `spectrum.rs`, `spectrograms.rs`, `phase_spectrum.rs`, `lissajous.rs`: the individual plot types and their parameter structs (`WaveformPlotParams`, `MagnitudeSpectrumParams`, `SpectrogramPlotParams`, `PhaseSpectrumParams`, `LissajousParams`).
- `composite.rs`: `CompositePlot` / `CompositeLayout` for arranging several plots together.
- `dsp_overlays.rs`: overlays (onset and beat markers, event lines) drawn on top of a base plot.
- `mod.rs`: shared styling types (`PlotParams`, `FontSizes`, `Layout`) and the `AudioPlotting` trait wiring.

**Styling**: each plot has a `*Params` struct (with builders such as `WaveformPlotParamsBuilder`) for its own appearance; `PlotParams`, `FontSizes`, and `Layout` cover the shared title, axis, and size options.

**API Contracts**:

- Time and frequency axes are derived from the sample rate and the transform parameters.
- Plots render to an in-memory HTML string; nothing is written to disk unless the caller asks for it.
- Plotting is feature-gated and costs nothing when disabled.

## API Design Contracts

### Error Handling Strategy

1. **Simple operations never fail**: Basic getters return values directly
2. **Complex operations return Results**: Type conversions, processing operations
3. **Rich error context**: Specific error types with detailed messages
4. **Graceful degradation**: Optional features disabled cleanly

### Memory Management

1. **Zero-allocation views**: Borrow AudioSamples when possible
2. **In-place operations**: Prefer modification over copying
3. **Owned data when needed**: Automatic conversion from borrowed to owned
4. **Memory safety**: Rust's ownership system prevents data races

### Type System Guarantees

1 .**Sample format safety**: Strong typing prevents format mix-ups
2. **Lifetime correctness**: Borrowed data cannot outlive its source
3. **Feature consistency**: Trait bounds enforce feature requirements
4. **Conversion validity**: Type-safe conversions with error handling

### Performance Characteristics

1. **Predictable allocation**: Clearly documented allocation behavior
2. **SIMD optimization**: Automatic vectorization where beneficial
3. **Cache efficiency**: ndarray layouts optimized for memory access
4. **Scalable algorithms**: Linear complexity for core operations