timestretch 0.1.0

Pure Rust audio time stretching library optimized for EDM
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
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
# Progress

## Completed — Phase 1: Foundation (agent-3)
- [x] Cargo.toml with `rustfft` dependency, `#![forbid(unsafe_code)]`
- [x] Core types: `AudioBuffer`, `Sample`, `Channels`, `StretchParams`, `EdmPreset`
- [x] Error types: `StretchError` enum with descriptive variants
- [x] Window functions: Hann, Blackman-Harris, Kaiser-Bessel (with Bessel I0 series expansion)
- [x] WAV reader: 16-bit PCM, 24-bit PCM, 32-bit float IEEE
- [x] WAV writer: 16-bit PCM and 32-bit float
- [x] Linear and cubic (Hermite) resampling

## Completed — Phase 2: Algorithms (agent-3)
- [x] Phase vocoder with identity phase locking and sub-bass phase locking
- [x] WSOLA with normalized cross-correlation matching
- [x] Transient detector: spectral flux with adaptive threshold, HF-weighted bins (2-8kHz)
- [x] Beat detection with BPM estimation and grid quantization

## Completed — Phase 3: EDM Optimization (agent-3)
- [x] Hybrid stretcher: transient-aware switching between WSOLA and PV
- [x] Crossfade engine: raised-cosine crossfade between algorithm segments
- [x] Frequency band splitter (sub/low/mid/high)
- [x] All 5 EDM presets: DjBeatmatch, HouseLoop, Halftime, Ambient, VocalChop

## Completed — Phase 4: Streaming & API (agent-3)
- [x] `StreamProcessor` with chunk-based processing
- [x] Real-time stretch ratio changes (smooth interpolation)
- [x] Latency reporting API
- [x] Public API: `stretch()`, `stretch_buffer()`, builder pattern for params
- [x] CLI binary (behind `cli` feature flag)

## Completed — Phase 5: Testing (agent-3)
- [x] 65 unit tests across all modules (64 original + 1 new WSOLA extreme compression)
- [x] 11 identity tests (mono, stereo, 48kHz, all presets, frequency, SNR, sub-bass, near-unity, DC, clicks)
- [x] 5 quality tests (RMS preservation, length proportionality, clipping, DJ quality, sub-bass)
- [x] 6 EDM preset integration tests
- [x] 6 streaming tests (basic, stereo, ratio change, reset, small chunks, latency)
- [x] 6 benchmark tests
- [x] 1 doc-test
- [x] Total: 100 tests, all passing
- [x] Zero clippy warnings

## Agent-4 Contributions
- [x] Implemented full test suite: 53 integration tests across identity, quality, EDM presets, streaming, and benchmarks
- [x] Applied clippy fixes to src/ files (frequency.rs, transient.rs, phase_vocoder.rs, wsola.rs, lib.rs)
- [x] 95 unit tests across all modules
- [x] 148 total tests, all passing
- [x] `#![forbid(unsafe_code)]` enforced

## Agent-2 Contributions
- [x] Fixed clippy warnings in library code (frequency.rs, resample.rs, hybrid.rs)
- [x] Fixed clippy warnings in tests (streaming.rs, edm_presets.rs)
- [x] Fixed clippy warnings in examples (dj_beatmatch.rs, realtime_stream.rs, sample_halftime.rs)
- [x] Fixed example files to match current API (removed `.expect()` from non-Result returns)
- [x] Fixed WSOLA compression ratio accuracy (fractional output position tracking)
- [x] Implemented FFT-accelerated cross-correlation for WSOLA (auto-selects FFT vs direct based on search range)
- [x] Added comprehensive identity tests (merged with agent-3's tests: frequency, SNR, multi-freq, transients, stereo, sub-bass, DC offset)
- [x] Added 15 edge case tests (boundary inputs, extreme ratios, silence, impulse, DC offset, all presets with compression)

## Agent-5 Refactoring
- [x] Added builder methods to `StretchParams`: `with_sub_bass_cutoff()`, `with_wsola_segment_size()`, `with_wsola_search_range()`
- [x] Extracted `reconstruct_spectrum()` and `normalize_output()` from `PhaseVocoder::process()` (was 104 lines)
- [x] Extracted `stretch_segment()` and `stretch_with_wsola()` from `HybridStretcher::process()` (was 88 lines)
- [x] Added getter methods to `PhaseVocoder` (`fft_size()`, `hop_analysis()`, `hop_synthesis()`)
- [x] Added getter methods to `Wsola` (`segment_size()`, `search_range()`, `stretch_ratio()`)
- [x] Removed unused `ProcessingError` variant from `StretchError`
- [x] Extracted `write_wav_header()` helper to eliminate WAV writer duplication (wav.rs)
- [x] Extracted `create_vocoders()` helper to eliminate 3x duplicated PhaseVocoder init (processor.rs)
- [x] Simplified redundant guard in `segment_audio()` (hybrid.rs)
- [x] All 66 unit tests passing, zero clippy warnings

### Second pass (agent-5)
- [x] Split `read_wav()` into `validate_riff_header()`, `parse_wav_chunks()`, `convert_samples()`
- [x] Extracted `io_error()` helper to deduplicate 6 identical WAV I/O error formatting calls
- [x] Deduplicated `write_wav_file_16bit`/`write_wav_file_float` via shared `write_wav_file()`
- [x] Extracted `compute_spectral_flux()` from `detect_transients()` (was 64 lines, now 24)
- [x] Added `StretchParams::output_length()` helper, used in hybrid stretcher (eliminates 2 duplicated calculations)
- [x] Added doc comments for `with_preset()` and `with_sample_rate()` documenting override behavior
- [x] Removed unused `sub_bass_bin` field and `#[allow(dead_code)]` from `PhaseVocoder`
- [x] All 68 unit tests passing, zero clippy warnings

## Completed — Performance Optimization (agent-3)
- [x] Comprehensive benchmarks: PV mono/stereo, EDM presets, FFT sizes, streaming, signal scaling
- [x] PhaseVocoder: reuse FFT/magnitude/phase buffers, pre-compute phase advance, efficient wrap_phase
- [x] StreamProcessor: persistent per-channel PhaseVocoder instances, reusable deinterleave buffers
- [x] HybridStretcher: reuse single PV instance across tonal segments, fast path for single segment
- [x] Transient detector: reusable sort buffer in adaptive_threshold
- [x] Result: **5-7x speedup** across all processing paths
  - PV mono: 161ms → 28ms (176x realtime vs 31x before)
  - PV stereo: 321ms → 54ms (93x realtime vs 16x before)
  - Streaming: 436ms → 59ms (169x realtime vs 23x before)
- [x] Fixed WSOLA compression accuracy for ratio < 0.5 (trim to target length)
- [x] Added extreme compression tests (ratios 0.25-0.5)

## Completed — Comprehensive Identity Tests (agent-3)
- [x] 11 identity tests (was 4): frequency preservation, SNR, sub-bass coherence, near-unity ratios, DC offset, click timing
- [x] Total: 100 tests, all passing
- [x] Zero clippy warnings

## Streaming Fix & Equivalence Tests (agent-3)
- [x] Fixed StreamProcessor overlap handling: drain processed input correctly instead of re-processing overlap, which caused output to be ~2x expected length
- [x] Added 8 new streaming tests (14 total, was 6):
  - Streaming vs batch output length comparison (multiple ratios: 0.75, 1.0, 1.25, 1.5, 2.0)
  - Streaming vs batch RMS energy preservation
  - Streaming vs batch frequency content preservation (440Hz DFT check)
  - Chunk size consistency (512, 4096, 16384 produce similar RMS)
  - Stereo streaming vs batch equivalence (channel separation verified)
  - Streaming with EDM preset (DjBeatmatch)
  - Flush produces remaining output
  - Reset-then-reprocess consistency
- [x] Added 4 streaming edge case tests (18 total):
  - Compression ratio correctness (0.5, 0.75)
  - Empty/double flush safety
  - Single-sample chunks
  - Large FFT size (8192)
- [x] Total: 131 tests, all passing
- [x] Zero clippy warnings

## Agent-1 Contributions
- [x] Fixed WSOLA compression ratio accuracy: added early loop termination for compression and removed `overlap_size` padding from output trimming
- [x] Added `Clone`, `PartialEq`, `Eq` derives to `StretchError` for better ergonomics
- [x] Merged and expanded edge case tests (combined with agent-2's tests):
  - Extreme compression (0.25x) and stretch (4.0x, 10.0x)
  - Silence input, DC offset input, impulse input
  - Very short input (50 and 100 samples), single sample input
  - Parameter boundary validation (min/max ratios, invalid ratios)
  - WSOLA compression accuracy across multiple ratios
  - NaN/Inf output detection across all ratios
  - Stereo channel independence and mono/stereo consistency
  - Frequency edge cases (20 Hz sub-bass, 15 kHz near-Nyquist)
  - All EDM presets with compression
  - Alternating silence/tone patterns
  - FFT size variations (256, 8192)
- [x] Total: 137 tests, all passing

## Agent-2 Phase Locking & Streaming Fixes
- [x] Implemented sub-bass phase locking in PhaseVocoder (bins below cutoff get rigid phase propagation instead of standard deviation tracking)
- [x] Sub-bass bin index computed from cutoff frequency and sample rate at construction time
- [x] Identity phase locking now only applies to bins above sub-bass range
- [x] Added `set_stretch_ratio()` to PhaseVocoder for in-place ratio updates without phase reset
- [x] Fixed StreamProcessor to use `set_stretch_ratio()` instead of recreating vocoders on ratio change (prevents clicks)
- [x] Added 4 new unit tests: sub-bass bin calculation, low-freq preservation, high-freq independence, click-free ratio changes
- [x] Fixed identity phase locking bug: was a no-op (`phases[bin] = phases[peak] + phases[bin] - phases[peak]`); now correctly uses analysis phase relationships per Laroche & Dolson (1999)
- [x] Added `pitch_shift()` public API: time-stretch + cubic resample for pitch shifting without duration change
- [x] Added 4 pitch_shift tests: length preservation, empty input, invalid factor, stereo
- [x] Total: 166 tests, all passing
- [x] Zero clippy warnings on library code

## Documentation (agent-3)
- [x] Comprehensive README.md with feature overview, quick start, streaming example, EDM presets table, algorithm explanation, parameter reference, performance data
- [x] Applied cargo fmt across entire codebase (21 files)

## Enhanced Identity Tests (agent-3)
- [x] 9 new identity tests (21 total, was 12):
  - Waveform cross-correlation (must be > 0.9)
  - Max per-sample error bound (< 0.5)
  - Silence preservation (RMS and peak both < 1e-6)
  - Peak amplitude preservation (ratio within [0.5, 1.5])
  - No spectral coloring (low/high energy ratio preserved)
  - Streaming vs batch identity equivalence
  - Stereo with one silent channel (no channel leakage)
  - Click timing preservation (transient positions maintained)
  - Per-segment energy distribution (no energy redistribution)
- [x] Total: 153 tests, all passing
- [x] Zero clippy warnings

## Documentation (agent-1)
- [x] Merged and improved README (combined agent-1 and agent-3 versions)
- [x] Added crate-level rustdoc with compilable examples (quick start + streaming)
- [x] Added rustdoc examples to `stretch()`, `stretch_buffer()`, `AudioBuffer`, `StretchParams`, `EdmPreset`, `StreamProcessor`
- [x] Added `# Errors` sections to public functions

### Third pass (agent-5)
- [x] Extracted `fft_cross_correlate()` and `find_best_candidate()` from `Wsola::find_best_position_fft()` (was 91 lines, now 47)
- [x] Extracted `analyze_frame()` and `advance_phases()` from `PhaseVocoder::process()` (was 83 lines, now 38); includes sub-bass rigid phase locking and analysis phase tracking
- [x] Extracted `process_channels()`, `deinterleave_channel()`, `drain_consumed_input()`, `interleave_output()` from `StreamProcessor::process()` (was 80 lines, now 28)
- [x] Replaced magic numbers with named constants: `ENERGY_EPSILON`, `FFT_CANDIDATE_THRESHOLD`, `FFT_OVERLAP_THRESHOLD` (wsola.rs), `MIN_WINDOW_SUM_RATIO`, `WINDOW_SUM_EPSILON` (phase_vocoder.rs), `RATIO_SNAP_THRESHOLD`, `RATIO_INTERPOLATION_ALPHA` (processor.rs)
- [x] All tests passing, zero clippy warnings

## CI/CD Pipeline (agent-1)
- [x] GitHub Actions workflow: `.github/workflows/ci.yml`
  - Test job: runs `cargo test --all-targets` on ubuntu, macOS, windows + MSRV (1.65)
  - Clippy job: `cargo clippy --all-targets -- -D warnings`
  - Format job: `cargo fmt --all --check`
  - Documentation job: `cargo doc --no-deps` with `-D warnings`
- [x] Added `rust-version = "1.65"` MSRV to Cargo.toml
- [x] Fixed MSRV incompatibility: replaced `is_multiple_of()` (1.87+) with `% 2 != 0` in wav.rs
- [x] Applied `cargo fmt` to fix formatting drift from recent refactors
- [x] All 165 tests passing, zero clippy warnings, docs build clean

### Fourth pass (agent-5)
- [x] Added `ms_to_samples()` helper and `WSOLA_SEGMENT_MS`/`WSOLA_SEARCH_MS_*` constants to `types.rs`, replacing 6 inline `(sample_rate * seconds)` calculations in preset configuration
- [x] Extracted 7 named constants in `hybrid.rs`: `CROSSFADE_SECS`, `TRANSIENT_REGION_SECS`, `MIN_SEGMENT_FOR_STRETCH`, `MIN_WSOLA_SEGMENT`, `MIN_WSOLA_SEARCH`, `TRANSIENT_MAX_FFT`, `TRANSIENT_MAX_HOP`
- [x] Simplified `stretch_segment()` conditional chain from nested if-else-if to single `use_phase_vocoder` boolean (eliminates duplicate WSOLA branch)
- [x] Extracted 3 named constants in `transient.rs`: `MEDIAN_WINDOW_FRAMES`, `MIN_ONSET_GAP_FRAMES`, `THRESHOLD_FLOOR`
- [x] Extracted 5 named constants in `beat.rs`: `BEAT_FFT_SIZE`, `BEAT_HOP_SIZE`, `BEAT_SENSITIVITY`, `MIN_EDM_BPM`, `MAX_EDM_BPM`
- [x] Extracted shared `deinterleave()` and `interleave()` helpers in `lib.rs`, deduplicating identical channel processing in `stretch()` and `pitch_shift()` (net -23 lines)
- [x] Extracted `process_buffer()` helper in `lib.rs`, deduplicating the params-override-and-wrap pattern across `stretch_buffer()`, `stretch_bpm_buffer()`, and `stretch_bpm_buffer_auto()` (net -7 lines)
- [x] Extracted `Band` enum and `classify_bin()` helper in `frequency.rs`, deduplicating the if-elif band classification pattern used 3x in `split_spectrum_into_bands()` and 1x in `compute_band_energy()` (net -12 lines)
- [x] Removed unused `snr_db()` function and `#[allow(dead_code)]` from `tests/quality.rs`
- [x] All tests passing, zero clippy warnings

## BPM-Aware Stretch API (agent-3)
- [x] `stretch_to_bpm(input, source_bpm, target_bpm, params)` — stretch audio between known BPM values
- [x] `stretch_to_bpm_auto(input, target_bpm, params)` — auto-detect source BPM via beat detection, then stretch
- [x] `stretch_bpm_buffer()` and `stretch_bpm_buffer_auto()` — AudioBuffer convenience wrappers
- [x] `bpm_ratio(source, target)` — utility to compute stretch ratio from BPM pair
- [x] `BpmDetectionFailed` error variant for invalid BPM values and failed auto-detection
- [x] 14 BPM integration tests: DJ beatmatch (126→128), slowdown (128→126), halftime (128→64), doubletime (128→256), stereo, all presets, buffer API, auto-detection with clicks, invalid BPMs, 48kHz
- [x] 12 BPM unit tests: ratio utility, speedup, slowdown, same BPM identity, empty input, silence auto-detect, buffer API
- [x] Total: 189 tests, all passing
- [x] Zero clippy warnings

## WAV Round-Trip Integration Tests (agent-1)
- [x] 11 new integration tests in `tests/wav_roundtrip.rs`:
  - 16-bit WAV encode/decode round-trip (mono)
  - 32-bit float WAV encode/decode round-trip (stereo)
  - Stretch through 16-bit WAV pipeline (mono, 1.5x)
  - Stretch through float WAV pipeline (stereo, 0.75x compression)
  - EDM kick pattern DJ beatmatch stretch (128→126 BPM)
  - Kick pattern halftime effect (2.0x)
  - 16-bit vs float stretch consistency
  - 48 kHz sample rate WAV stretch
  - All 5 EDM presets WAV round-trip
  - Double stretch pipeline (stretch → WAV → stretch back)
  - Stretched output preservation through WAV encode/decode
- [x] Applied cargo fmt to fix formatting drift from other agents' changes
- [x] Total: 177 tests (168 integration/unit + 9 doc-tests), all passing

## Spectral Quality Tests (agent-3)
- [x] 13 spectral quality tests covering:
  - Frequency preservation: 440Hz energy dominant after stretch at 5 ratios (0.75-2.0)
  - Multi-tone: two-tone signal preserves both frequencies, no broadband smearing
  - Sub-bass: 60Hz energy preserved after 1.5x stretch
  - Dominant frequency preservation: 3-tone signal maintains frequency hierarchy at 4 ratios
  - No new harmonics: pure sine doesn't introduce strong 2nd/3rd harmonics
  - Band energy distribution: two-tone signal preserves relative energy ordering
  - Transient attack: click at 0.5s appears at ~0.75s after 1.5x stretch
  - Click train spacing: regular clicks maintain proportional intervals
  - DJ transparency: small-ratio stretch preserves 3-tone spectral content
  - Extreme stretch: 4x stretch retains fundamental frequency
  - Compression: 0.5x preserves fundamental
  - Stereo spectral independence: L=440Hz R=880Hz stay in correct channels
  - Frequency sweep: no silent "holes" in chirp output
- [x] Total: 205+ tests, all passing
- [x] Zero clippy warnings

## Pitch Shift Integration Tests (agent-1)
- [x] 14 new integration tests in `tests/pitch_shift.rs`:
  - Length preservation across multiple pitch factors (mono and stereo)
  - Frequency shift verification: octave up (440→880Hz) and octave down (880→440Hz)
  - Identity test (pitch factor 1.0 preserves frequency content)
  - Small DJ-style adjustments (±2%)
  - Stereo channel independence
  - Extreme factors (4.0x up, 0.25x down)
  - No clipping across all factors
  - Silence preservation
  - All 5 EDM presets compatibility
  - 48kHz sample rate
  - NaN/Inf sweep across 10 pitch factors
- [x] Total: 206 tests (197 integration/unit + 9 doc-tests), all passing
- [x] Zero clippy warnings

## Agent-2: API Improvements & Input Validation
- [x] Added `StretchParams::from_tempo(source_bpm, target_bpm)` convenience constructor for DJ workflow
- [x] Added `StretchError::NonFiniteInput` variant for NaN/Inf rejection
- [x] Added NaN/Inf input validation to `stretch()`, `pitch_shift()`, `stretch_to_bpm_auto()`, and `StreamProcessor::process()`
- [x] Added Cargo.toml metadata: `keywords`, `categories`, `readme`
- [x] Added 7 new tests: `from_tempo` unit test, NaN/Inf rejection (stretch, pitch_shift, streaming), from_tempo integration
- [x] All 226 tests passing, zero clippy warnings

### Fifth pass (agent-5)
- [x] Extracted `convert_pcm_16bit()`, `convert_pcm_24bit()`, `convert_ieee_float_32bit()` from `convert_samples()` in wav.rs (was 53 lines, now 10-line dispatch)
- [x] Extracted PCM scaling constants in wav.rs: `PCM_16BIT_SCALE`, `PCM_16BIT_MAX_OUT`, `PCM_24BIT_SCALE`, `PCM_24BIT_SIGN_BIT`, `PCM_24BIT_MASK`, `WAV_MIN_HEADER_SIZE`, `WAV_FMT_MIN_SIZE`
- [x] Extracted frequency band weight constants in transient.rs: `WEIGHT_SUB_BASS`, `WEIGHT_BASS_MID`, `WEIGHT_MID`, `WEIGHT_HIGH_MID`, `WEIGHT_VERY_HIGH` and band boundary constants
- [x] Extracted Blackman-Harris window coefficients as module-level constants in window.rs: `BH_A0`..`BH_A3`
- [x] All tests passing, zero clippy warnings

## BPM Detection Public API (agent-3)
- [x] `detect_bpm(samples, sample_rate)` — convenience function returning BPM as f64
- [x] `detect_beat_grid(samples, sample_rate)` — returns full BeatGrid with beat positions
- [x] Re-exported `BeatGrid` from top-level `timestretch` module
- [x] 5 new unit tests: silence, empty, short input, beat grid, click train BPM detection
- [x] 2 new doctests for `detect_bpm()` and `detect_beat_grid()`
- [x] Total: 243 tests, all passing
- [x] Zero clippy warnings

## Test Signal Generator (agent-1)
- [x] `test_audio/generate.rs` example binary — generates 8 WAV test files:
  - `sine_440hz.wav` — 2s mono 440 Hz sine
  - `sine_60hz.wav` — 2s mono 60 Hz sub-bass
  - `sine_stereo.wav` — 2s stereo (L=440Hz, R=880Hz)
  - `click_train_128bpm.wav` — 4s click train at 128 BPM
  - `kick_pattern_128bpm.wav` — 4s 808-style EDM kick at 128 BPM
  - `white_noise.wav` — 2s deterministic pseudo-random noise
  - `sweep_20_20k.wav` — 4s logarithmic frequency sweep 20Hz–20kHz
  - `edm_mix.wav` — 4s layered kick + sub-bass + hi-hat pattern
- [x] Run with `cargo run --example generate_test_audio`
- [x] Generated WAV files gitignored (`test_audio/.gitignore`)

### Sixth pass (agent-5)
- [x] Extracted `validate_input()` helper in lib.rs — deduplicates empty-check + NaN/Inf validation from `stretch()`, `pitch_shift()`, and `stretch_to_bpm_auto()` (net -9 lines)
- [x] Extracted `validate_bpm()` helper in lib.rs — deduplicates BPM positivity checks from `stretch_to_bpm()` and `stretch_to_bpm_auto()` (net -8 lines)
- [x] Added `#[inline]` hints to hot-path helpers: `deinterleave()`, `interleave()`, `validate_input()`, `validate_bpm()` (lib.rs), `overlap_add()` (wsola.rs), `snap_to_grid()` (beat.rs)
- [x] Applied cargo fmt to fix formatting drift from other agents' code
- [x] All 238 tests passing (98 unit + 126 integration + 14 doc), zero clippy warnings

## API Wrappers (agent-3)
- [x] `pitch_shift_buffer(buffer, params, pitch_factor)` — AudioBuffer convenience wrapper for pitch shifting
- [x] `detect_bpm_buffer(buffer)` — BPM detection from AudioBuffer (handles stereo by extracting left channel)
- [x] 4 new unit tests: pitch_shift_buffer mono/stereo, detect_bpm_buffer silence/stereo
- [x] Total: 249+ tests, all passing

## Agent-2: Performance & Beat-Aware Segmentation
- [x] Pre-allocated output/window_sum buffers in PhaseVocoder — eliminates 2 Vec allocations per `process()` call; buffers grow as needed but never shrink
- [x] Integrated beat-aware segmentation into HybridStretcher (Task 14):
  - Beat grid positions merged with transient onsets for segment boundaries
  - Nearby positions (<512 samples apart) deduplicated to avoid micro-segments
  - Short inputs (<1s) skip beat detection to avoid unreliable results
  - Enabled by default for all EDM presets (`beat_aware` field on StretchParams)
  - Added `with_beat_aware()` builder method for manual control
- [x] Added 7 new unit tests: merge logic (empty, no overlap, dedup, bounds), beat-aware integration, short-input safety, flag defaults
- [x] Fixed clippy warning (needless_range_loop → abs_diff)
- [x] Total: 252+ tests, all passing
- [x] Zero clippy warnings

## Streaming DJ API & detect_beat_grid_buffer (agent-1)
- [x] `StreamProcessor::from_tempo(source_bpm, target_bpm, sample_rate, channels)` — convenience constructor for DJ workflow, auto-applies `DjBeatmatch` preset
- [x] `StreamProcessor::set_tempo(target_bpm)` — smoothly change target BPM during playback (requires `from_tempo`)
- [x] `StreamProcessor::source_bpm()` — returns source BPM if set via `from_tempo`
- [x] `StreamProcessor::params()` — read-only access to current parameters
- [x] `detect_beat_grid_buffer(buffer)` — AudioBuffer convenience wrapper for beat grid detection (handles stereo)
- [x] 8 new unit tests: from_tempo, from_tempo_stereo, set_tempo, set_tempo_no_source, set_tempo_invalid, from_tempo_produces_output, params_accessor, detect_beat_grid_buffer mono/stereo
- [x] 5 new streaming integration tests: from_tempo DJ workflow, from_tempo stereo, set_tempo mid-stream, set_tempo without source, from_tempo slowdown
- [x] Updated `realtime_stream` example to showcase `from_tempo()` + `set_tempo()` (3-part demo: BPM matching, live tempo fader, manual ratio)
- [x] Fixed rustdoc ambiguous link warning on `bpm_ratio` (`stretch``stretch()`)
- [x] `cargo doc --no-deps` now builds with zero warnings
- [x] Total: 270 tests (118 unit + 138 integration + 14 doc), all passing
- [x] Zero clippy warnings, cargo fmt clean

### Seventh pass (agent-5)
- [x] Moved `preset_description()` standalone function to `EdmPreset::description()` method (more idiomatic Rust)
- [x] Extracted `RATIO_MIN`/`RATIO_MAX`/`FFT_SIZE_MIN`/`SAMPLE_RATE_MIN`/`SAMPLE_RATE_MAX` constants in params.rs — eliminates magic numbers in `validate_params()` and `pitch_shift()`
- [x] Simplified `validate_params()` ratio validation from 3 separate if-checks to single `contains()` range check
- [x] Extracted `BESSEL_MAX_TERMS`/`BESSEL_CONVERGENCE` constants in window.rs
- [x] Extracted `extract_mono()` helper in lib.rs — deduplicates stereo-to-mono extraction in `detect_bpm_buffer()`, `detect_beat_grid_buffer()`, and `stretch_to_bpm_auto()`
- [x] Deduplicated `pitch_shift_buffer()` via `process_buffer()` helper (was 10 lines, now 1-line delegation)
- [x] All 263+ tests passing, zero clippy warnings, cargo fmt clean

## AudioBuffer API & WAV Enhancements (agent-3)
- [x] `AudioBuffer::is_empty()` — returns true if buffer has no samples
- [x] `AudioBuffer::is_mono()` / `is_stereo()` — channel layout predicates
- [x] `AudioBuffer::left()` / `right()` — extract L/R channel samples (mono returns all samples for both)
- [x] `AudioBuffer::mix_to_mono()` — average all channels to mono
- [x] `write_wav_24bit()` / `write_wav_file_24bit()` — 24-bit PCM WAV output (completes read/write symmetry)
- [x] Fixed rustdoc ambiguity warning (`stretch` is both function and module)
- [x] `stretch_wav_file(input, output, params)` — high-level WAV file stretch convenience
- [x] `pitch_shift_wav_file(input, output, params, factor)` — high-level WAV file pitch shift convenience
- [x] `AudioBuffer::to_stereo()` — mono-to-stereo conversion (duplicates signal to both channels)
- [x] `AudioBuffer::total_samples()` — total sample count (frames * channels)
- [x] Updated README with full API reference, pitch shifting and BPM examples
- [x] 21 new tests total, zero clippy warnings, cargo fmt clean, zero doc warnings

### Eighth pass (agent-5)
- [x] Extracted `init_wav_buffer()` helper in wav.rs — deduplicates header setup from `write_wav_16bit()`, `write_wav_24bit()`, `write_wav_float()` (net -26 lines)
- [x] Replaced verbose `iter_mut().for_each(|x| *x = 0.0)` with idiomatic `fill(0.0)` in PhaseVocoder::process() (4 occurrences)
- [x] Replaced `.cloned()` with `.copied()` for f32 iteration in normalize_output()
- [x] Removed dead guard `if mirror < self.fft_size` in PhaseVocoder::reconstruct_spectrum() (always true since mirror = fft_size - bin, bin >= 1)
- [x] Removed dead guard `if mirror < spectrum.len()` in frequency.rs split_spectrum_into_bands() (always true when spectrum.len() == fft_size)
- [x] All 126 unit tests passing, zero clippy warnings

### Ninth pass (agent-5)
- [x] Extracted `DEFAULT_SAMPLE_RATE`, `DEFAULT_FFT_SIZE`, `DEFAULT_HOP_SIZE`, `DEFAULT_TRANSIENT_SENSITIVITY`, `DEFAULT_SUB_BASS_CUTOFF` constants in types.rs — eliminates 5 magic numbers in `StretchParams::new()`
- [x] Extracted `PresetConfig` struct and `EdmPreset::config()` method — replaces 5-arm match with data-driven dispatch in `with_preset()` (net -20 lines)
- [x] Extracted `LATENCY_FFT_MULTIPLIER` constant in processor.rs — replaces 3 occurrences of magic `* 2`
- [x] Extracted `PEAKS_CAPACITY_DIVISOR` constant in phase_vocoder.rs — documents why peaks buffer is allocated at 1/4 of bins
- [x] Extracted `WAV_HEADER_WRITE_SIZE` and `WAV_RIFF_OVERHEAD` constants in wav.rs — replaces magic `44` and `36` in header writing
- [x] Used `WAV_FMT_MIN_SIZE` for fmt chunk size write (was inline `16u32`)
- [x] Moved `PCM_24BIT_MAX_OUT` to top of wav.rs with other PCM constants (better organization)
- [x] All 291 tests passing (133 unit + 144 integration + 14 doc), zero clippy warnings

## CLI Enhancements (agent-1)
- [x] `--from-bpm <f> --to-bpm <f>` for DJ BPM matching (auto-selects DjBeatmatch preset)
- [x] `--pitch <f>` for pitch shifting (preserves audio duration)
- [x] `--24bit` and `--float` output format flags (default remains 16-bit)
- [x] Named flags (`--ratio`, `--preset`) alongside backward-compatible positional syntax
- [x] Usage help with examples
- [x] Tested with ratio, BPM, pitch, 24-bit, and legacy modes

## Agent-2: Sub-Bass Band-Split Processing (Task 17)
- [x] Implemented `separate_sub_bass()` FFT-based band-splitting filter in hybrid.rs
  - Overlap-add with Hann window, 4096 FFT, 75% overlap
  - Correctly handles conjugate-symmetric negative frequency mirroring
- [x] Added `band_split` field to `StretchParams` (default false, enabled by all EDM presets)
- [x] Added `with_band_split()` builder method
- [x] Added `process_band_split()` to `HybridStretcher`:
  - Separates sub-bass below cutoff (default 120 Hz) via FFT filtering
  - Sub-bass processed exclusively through PV with rigid phase locking
  - Remainder processed through normal hybrid (WSOLA for transients, PV for tonal)
  - Results summed (longer output zero-padded to match)
  - Prevents WSOLA from smearing sub-bass during kick drum transients
- [x] 10 new unit tests: energy preservation, high-freq passthrough, reconstruction,
      band-split stretch output, split vs no-split length, compression, all presets,
      short input fallback, zero cutoff, flag defaults
- [x] Reduced hot-path allocations: avoid `to_vec()` in merge_onsets_and_beats,
      avoid `clone()` for transient onsets when beat-aware is disabled
- [x] 15 integration tests in tests/band_split.rs: EDM signal, sub-bass energy,
      high-freq passthrough, DJ beatmatch, halftime, compression, stereo, 48kHz,
      ambient extreme stretch, vocal chop, custom cutoff, pitch shift, builder toggling
- [x] Total: 355 tests, all passing
- [x] Zero clippy warnings

## AudioBuffer Utilities, Trait Impls & CLI Auto-BPM (agent-1)
- [x] `AudioBuffer::slice(start_frame, num_frames)` — extract sub-region by frame range
- [x] `AudioBuffer::concatenate(buffers)` — join multiple buffers (validates sample rate/channels match)
- [x] `AudioBuffer::normalize(target_peak)` — scale peak to target amplitude
- [x] `AudioBuffer::apply_gain(gain_db)` — apply dB gain (positive amplifies, negative attenuates)
- [x] `AudioBuffer::trim_silence(threshold)` — remove leading/trailing silence (works for mono and stereo)
- [x] `impl Display for AudioBuffer` — e.g. `AudioBuffer(44100 frames, 44100Hz, Mono, 1.000s)`
- [x] `impl Display for StretchParams` — e.g. `StretchParams(ratio=1.5000, 48000Hz, Stereo, preset=DjBeatmatch, fft=4096, hop=1024)`
- [x] `impl Display for EdmPreset` — variant name as string
- [x] `impl Default for StretchParams` — ratio 1.0, stereo, 44100 Hz
- [x] CLI `--auto-bpm` flag — auto-detect source BPM from input WAV, use with `--to-bpm`
- [x] Fixed clippy warnings in edm_presets.rs and identity.rs (from other agents' code)
- [x] Applied cargo fmt across all modified files
- [x] 24 new unit tests (slice, concatenate, normalize, apply_gain, trim_silence, Display, Default)
- [x] All 316+ tests passing, zero clippy warnings, zero doc warnings

### Tenth pass (agent-5)
- [x] Extracted `encode_wav_samples()` helper in wav.rs — deduplicates init+loop+return pattern from `write_wav_16bit()`, `write_wav_24bit()`, `write_wav_float()` (each caller now a 3-line closure delegation)
- [x] Extracted `assign` closure in frequency.rs `split_spectrum_into_bands()` — deduplicates the identical classify-and-assign pattern from positive and negative frequency loops
- [x] Fixed clippy warnings in test files: `needless_range_loop` in edm_presets.rs, `collapsible_if` in identity.rs
- [x] All 302+ tests passing, zero clippy warnings

## Ergonomic API Traits & CLI Verbose (agent-1)
- [x] `impl PartialEq for AudioBuffer` — compare data, sample rate, and channels
- [x] `impl AsRef<[f32]> for AudioBuffer` — direct access to underlying sample slice
- [x] `AudioBuffer::frames()``FrameIter` — iterate over frames (1 sample for mono, 2 for stereo)
- [x] `FrameIter` implements `Iterator + ExactSizeIterator`
- [x] `FrameIter` re-exported from `timestretch::FrameIter`
- [x] CLI `--verbose`/`-v` flag — shows full parameter dump, processing time, realtime factor
- [x] Applied cargo fmt to hybrid.rs (other agents' code)
- [x] 9 new unit tests (PartialEq, AsRef, frames iterator, exact size)
- [x] All 335+ tests passing, zero clippy warnings, zero doc warnings

## Audio Analysis, Fades & IntoIterator (agent-1)
- [x] `impl IntoIterator for &AudioBuffer` — enables `for frame in &buf` syntax
- [x] `AudioBuffer::peak()` — peak absolute amplitude
- [x] `AudioBuffer::rms()` — root mean square amplitude (f64 precision)
- [x] `AudioBuffer::fade_in(duration_frames)` — linear fade-in
- [x] `AudioBuffer::fade_out(duration_frames)` — linear fade-out
- [x] Both fades work correctly with mono and stereo, handle edge cases
- [x] Applied cargo fmt to frequency.rs (other agents' code)
- [x] 14 new unit tests (IntoIterator, peak, RMS, fade in/out, stereo fades)
- [x] All 190+ unit tests passing, zero clippy/doc warnings

## Comprehensive Error Path & Edge Case Tests (agent-4)
- [x] 41 WAV I/O error path tests in `tests/wav_error_paths.rs`:
  - RIFF/WAVE header validation (empty, truncated, missing magic, wrong identifiers)
  - Unsupported format rejection (8-bit PCM, 32-bit PCM, ADPCM, A-law, mu-law, IEEE float 16/24-bit)
  - Channel count validation (0 channels, 6 channels)
  - Truncated/empty data chunks, incomplete samples (16/24/32-bit)
  - WAV write/read round-trip for all formats (16-bit, 24-bit, float) with quantization accuracy checks
  - Clipping/boundary values, negative 24-bit values, 48kHz preservation
  - Unknown/odd-sized chunks between fmt and data
  - File I/O error paths (nonexistent read, invalid write directory)
  - StretchError display, Clone, Eq, From<io::Error>, std::error::Error
- [x] 49 algorithm edge case tests in `tests/algorithm_edge_cases.rs`:
  - Window functions: size 2, size 3, Kaiser beta=0 (rectangle), high beta, all window types finite
  - Resample: single sample, 2/3/4 sample cubic fallback, output length 0/1, extreme upsample
  - Frequency analysis: short/silence/FFT-size input, freq_to_bin edge cases, custom band config
  - Beat detection: very short, DC constant, white noise, empty grid snap, interval calculation
  - Parameter validation: exact boundary ratios (0.01, 100.0), hop=fft, large/min FFT, output_length calc
  - Multi-stage: stretch→compress round-trip, 5 successive small stretches, pathological inputs (step function, saturated, inverted-phase stereo)
  - Builder API: all methods combined, preset overrides, beat_aware toggle
- [x] 20 streaming edge case tests in `tests/streaming_edge_cases.rs`:
  - Rapid ratio changes, multiple tempo changes, set_tempo without from_tempo, invalid BPM
  - Empty chunks, single-sample repeated, very large chunk, stereo channel separation
  - Flush behavior: normal, double, without input
  - Reset-and-reprocess consistency, latency reporting, NaN/Inf rejection
  - All presets streaming, various compression ratios
- [x] Fixed 2 pre-existing clippy warnings in tests/edm_presets.rs and tests/identity.rs
- [x] Zero clippy warnings (`cargo clippy --all-targets -- -D warnings` clean)

### Eleventh pass (agent-5)
- [x] Named magic `0.1` window sum floor ratio in hybrid.rs as `WINDOW_SUM_FLOOR_RATIO` and `WINDOW_SUM_EPSILON` constants (matches phase_vocoder.rs naming)
- [x] Replaced indexed loops with idiomatic `zip` iterators in `apply_window()` and `apply_window_copy()` (window.rs)
- [x] Replaced manual while-loop deinterleave with `step_by` iterator in `StreamProcessor::deinterleave_channel()` (processor.rs)
- [x] Replaced indexed normalization loop with `zip` in `separate_sub_bass()` (hybrid.rs)
- [x] Used `.copied()` instead of `.cloned()` for f32 fold in hybrid.rs (idiomatic for Copy types)
- [x] Removed dead `ws > WINDOW_SUM_EPSILON` guard in phase_vocoder `normalize_output()` — ws is already clamped above epsilon by `max()` on previous line
- [x] All 190+ unit tests passing, zero clippy warnings

## Buffer Workflow Integration Tests (agent-1)
- [x] 18 integration tests in tests/buffer_workflows.rs
- [x] Tests cover: slice+stretch, concatenate+stretch, normalize+stretch, fade+stretch,
      trim_silence+stretch, peak/rms metrics, frames iterator, AsRef interop, PartialEq
- [x] Complex workflow tests: DJ crossfade, sample chop (slice→stretch→normalize→fade→concatenate)
- [x] All tests passing, zero clippy warnings

## Window Type Selection & RMS Normalization (agent-3)
- [x] Added `window_type` field to `StretchParams` with `with_window_type()` builder method
- [x] Re-exported `WindowType` from top-level crate (`pub use core::window::WindowType`)
- [x] Added `PhaseVocoder::with_window()` constructor accepting a `WindowType`
- [x] Propagated window type through `HybridStretcher` and `StreamProcessor`
- [x] Updated `PresetConfig` to include `window_type` — Ambient preset uses Blackman-Harris, others use Hann
- [x] Added `normalize` field to `StretchParams` with `with_normalize()` builder method
- [x] Implemented RMS normalization in `stretch()` and `pitch_shift()` — scales output to match input RMS
- [x] Added `compute_rms()` and `normalize_rms()` helper functions (uses f64 accumulation for precision)
- [x] 14 new tests: window type builder, default window, preset window selection, preset override, normalize flag, RMS preservation (stretch/compress/pitch shift), silence normalization, Blackman-Harris/Kaiser PV processing, different-windows-different-output
- [x] Applied cargo fmt across codebase (also fixed pre-existing format drift from other agents)
- [x] Zero clippy warnings on library code, cargo doc builds clean

## Streaming Window Type Tests (agent-3)
- [x] 6 new integration tests in `tests/streaming.rs` for window type coverage:
  - Blackman-Harris streaming produces valid output with energy
  - Kaiser streaming produces valid output with energy
  - All window types preserve 440 Hz frequency content through streaming
  - Blackman-Harris window persists through ratio changes without clicks
  - Ambient preset streaming correctly uses Blackman-Harris
  - Batch normalize + window type produces consistent RMS across window types

## CLI --window and --normalize flags (agent-3)
- [x] Added `--window <hann|blackman-harris|bh|kaiser[:beta]>` flag to CLI
- [x] Added `--normalize` / `-n` flag to CLI for RMS-matched output
- [x] Added `-w` short flag for `--window`
- [x] Updated help text with examples
- [x] Added verbose output for window type and normalize settings
- [x] 6 unit tests for window parsing (hann, blackman-harris, bh alias, kaiser default, kaiser:beta, kaiser fractional)

### Twelfth pass (agent-5)
- [x] Extracted `window_and_transform()`, `split_bands()`, `normalize_band_split()` from `separate_sub_bass()` in hybrid.rs (was 91 lines, now 48)
- [x] Replaced indexed band summation loop with `zip+chain+map` iterator in hybrid.rs `process_band_split()`
- [x] Idiomatic `chunks_exact(nc).map()` in `mix_to_mono()` and `flat_map(|&s| [s, s])` in `to_stereo()` (types.rs)
- [x] Replaced indexed `interleave()` loop with `flat_map` iterator, removed unused `num_channels` parameter (lib.rs)
- [x] Idiomatic `chunks_exact` iterators in WAV converters: `convert_pcm_16bit()`, `convert_pcm_24bit()`, `convert_ieee_float_32bit()` (wav.rs)
- [x] Idiomatic `range+map+collect` in `compute_bin_weights()` (transient.rs)
- [x] Removed dead `read_i16_le()` helper (wav.rs)
- [x] All 529 tests passing (205 unit + 302 integration + 22 doc), zero clippy warnings

## Agent-2: Hybrid Streaming Mode
- [x] Added `use_hybrid` field to `StreamProcessor` for full hybrid algorithm in streaming
  - When enabled, uses HybridStretcher (transient detection + WSOLA + PV + band splitting)
  - Deinterleaves, processes per-channel through HybridStretcher, reinterleaves
  - PV-only remains default for lowest latency
- [x] Added `set_hybrid_mode(bool)` and `is_hybrid_mode()` API methods
- [x] `process_hybrid_path()` internal method for hybrid processing pipeline
- [x] Hybrid mode persists across `reset()` calls (it's a mode setting, not state)
- [x] 6 new unit tests: mode toggle, output, stretch ratio, NaN rejection, stereo
- [x] 15 new integration tests in `tests/hybrid_streaming.rs`:
  - Basic mono/stereo/compression/48kHz hybrid streaming
  - Hybrid streaming vs batch output comparison (length, RMS)
  - EDM signal quality tests (kicks + hi-hats + sub-bass + pad)
  - DJ beatmatch with EDM signal
  - All 5 EDM presets in hybrid streaming mode
  - Hybrid vs PV-only mode comparison
  - Flush, reset, ratio change mid-stream
- [x] Total: 550+ tests, all passing
- [x] Zero clippy warnings

## Algorithm Internals Tests (agent-4)
- [x] 87 new unit tests covering previously untested internal functions
- [x] Phase vocoder (18 tests): identity_phase_lock (no peaks, single peak, multiple peaks, early returns), normalize_output (uniform/low/zero window sum), wrap_phase boundaries, set_stretch_ratio, sub_bass_bin clamping, conjugate symmetry, buffer reuse
- [x] WSOLA (18 tests): normalized_cross_correlation edge cases (zero energy, orthogonal, empty, mismatched), FFT cross-correlation (self-correlation, shifted signal), find_best_candidate (identical signals, zero-energy search), FFT vs direct threshold boundary, overlap_add (crossfade linearity, bounds clamping, truncation), invalid ratio error
- [x] Transient detection (13 tests): bin_weights all 5 bands verified + 48kHz, adaptive_threshold (empty, below threshold, single spike, sensitivity, min gap, separated spikes), spectral_flux (silence, tone onset, impulse)
- [x] Beat detection (14 tests): beat_interval_samples, snap_to_grid edge cases (empty, before/after/equidistant/exact), estimate_bpm octave normalization (halving, doubling, in-range, outlier), quantize_to_grid edge cases
- [x] Hybrid stretcher (24 tests): segment_audio (no onsets, single onset, onset at 0, near end, overlapping), crossfade (empty, single, zero, oversized, raised-cosine midpoint, three segments), merge dedup boundary (511 vs 512), sub_bass short input fallback, very short segment fallback
- [x] Total: 580 tests (278 unit + 302 integration), all passing
- [x] Zero clippy warnings

### Thirteenth pass (agent-5)
- [x] Replaced 3 indexed loops in `fft_cross_correlate()` with idiomatic `map+chain+take` for zero-padded FFT buffers and `zip+map` for spectral multiply (wsola.rs)
- [x] Replaced indexed loop in `normalized_cross_correlation()` with `zip+fold` accumulator pattern (wsola.rs)
- [x] Replaced indexed prefix-sum loop in `find_best_candidate()` with idiomatic `push` accumulator (wsola.rs)
- [x] Replaced indexed conditional loop in `window_and_transform()` with `zip+chain` iterator (hybrid.rs)
- [x] All 284 unit tests passing, zero clippy warnings

## Coverage Gap Tests (agent-4)
- [x] 93 new integration tests in `tests/coverage_gaps.rs` covering previously untested code paths:
  - lib.rs helpers (13 tests): deinterleave/interleave round-trip, subnormal float acceptance, BPM validation (NaN/Inf), extract_mono from stereo, RMS near-zero normalization, bpm_ratio edge cases, stretch_to_bpm_auto empty input, process_buffer sample_rate/channel override
  - resample.rs edge cases (2 tests): near-unity pitch factors (0.999, 1.001), boundary factors (0.01, 100.0, rejection below/above)
  - params.rs boundaries (12 tests): sample_rate min/max (8000/192000), below/above min/max rejection, hop_size==fft_size, hop_size==1, hop_size==0/exceeds_fft rejection, fft_size 128/256/non-power-of-2 validation, ratio exact boundaries
  - window.rs edge cases (8 tests): size 2/3, Kaiser beta=0 (rectangular), beta=50 (narrow), all windows finite for 15 sizes, apply_window_copy, mismatched lengths, empty window
  - AudioBuffer edge cases (26 tests): empty buffer operations (all methods), single-frame mono/stereo, slice (entire/zero/past-end), concatenate (empty/single/mismatch panics), normalize (zero/at-target/silent), apply_gain (0dB/±6dB), trim_silence (all-silent/none/stereo), fade (longer-than-buffer/zero-duration), frames iterator (stereo/empty), IntoIterator, PartialEq (data/rate/channels), AsRef, Display (mono/stereo), Default, from_channels
  - StreamProcessor edge cases (11 tests): empty chunks, flush (none/twice), reset, stereo even output, hybrid mode persistence, hybrid mid-stream switch, latency scaling, rapid ratio changes, from_tempo round-trip
  - Preset configs (7 tests): window type per preset, override, band_split, beat_aware, descriptions, Display
  - Window type stretch (2 tests): Kaiser stretch, different-windows comparison
  - Normalize edge cases (3 tests): DC offset, stereo RMS, pitch shift stereo
  - Builder API (5 tests): full chain, from_tempo, output_length, Display, wsola params
- [x] Total: 700 tests (284 unit + 93 coverage_gaps + 323 other integration), all passing
- [x] Zero clippy warnings

## Agent-2: Hot-Path Allocation Elimination
- [x] Pre-allocated FFT buffers in Wsola struct (`fft_ref_buf`, `fft_search_buf`, `fft_corr_buf`):
  - Eliminates 3 `Vec<Complex<f32>>` heap allocations per `fft_cross_correlate()` call
  - Buffers grow as needed but never shrink (amortized zero-allocation)
  - `fft_cross_correlate()` now writes results to `self.fft_corr_buf` instead of returning a Vec
- [x] Pre-allocated `prefix_sq_buf` in Wsola struct:
  - Eliminates 1 `Vec<f64>` allocation per FFT-path search in `find_best_candidate()`
  - Prefix-sum energy buffer reused across all search iterations
- [x] Fixed `StreamProcessor::interleave_output()` to preserve scratch buffer allocation:
  - Was using `std::mem::take()` which gave away the buffer, losing the allocation each call
  - Now clones data from scratch buffer, keeping the allocation for reuse
- [x] Updated `find_best_candidate()` to accept pre-computed prefix sums (avoids recomputation)
- [x] Pre-allocated output buffer in WSOLA for `process()` reuse:
  - `output_buf` taken from self, grown-only, zeroed, returned to self after use
  - Eliminates 1 large `Vec<f32>` allocation per WSOLA `process()` call
  - Uses `std::mem::take` pattern for borrow checker compatibility
- [x] Zero-copy streaming API (`process_into` / `flush_into`):
  - `process_into(&mut self, input: &[f32], output: &mut Vec<f32>) -> Result<usize>`
  - `flush_into(&mut self, output: &mut Vec<f32>) -> Result<usize>`
  - Appends stretched audio directly into caller-provided buffer
  - Eliminates final `Vec<f32>` clone on every process() call
  - 7 new tests: equivalence with process(), stereo, NaN, count, append, flush, hybrid
- [x] Zero-copy batch API (`stretch_into`):
  - `stretch_into(input: &[f32], params: &StretchParams, output: &mut Vec<f32>) -> Result<usize>`
  - Appends stretched audio into caller-provided buffer, returns sample count
  - Handles normalization on appended region only
  - 7 new tests: equivalence with stretch(), empty, append, invalid ratio, stereo, normalize, NaN
- [x] Total: 900+ tests, all passing
- [x] Zero clippy warnings

## DJ Workflow API & Creative Effects (agent-3)
- [x] `AudioBuffer::reverse()` — frame-order reversal for creative DJ effects (reverse cymbals, tape-stop)
  - Preserves channel pairing in stereo (L/R stay together)
  - Double-reverse is identity
- [x] `AudioBuffer::channel_count()` — convenience getter (returns 1 or 2)
- [x] 12 new DJ workflow integration tests in `tests/dj_workflows.rs`:
  - Resample+stretch pipeline (48kHz→44.1kHz then stretch)
  - Stereo resample preserves channels
  - Resample round-trip frequency accuracy (44.1k→48k→44.1k)
  - Crossfade of two stretched tracks (DJ mixing)
  - Stereo crossfade workflow
  - Reverse+stretch creative effect
  - Full DJ pipeline (resample→stretch→crossfade)
  - Reverse cymbal build effect
  - Slice+stretch+concatenate sample chopping
  - channel_count preservation through operations
- [x] 6 new unit tests: reverse (mono, stereo, empty, double), channel_count (mono, stereo)
- [x] All 759 tests passing, zero clippy warnings

## New Feature Integration Tests (agent-4)
- [x] 73 new integration tests in `tests/new_features.rs` covering recently added features:
  - AudioBuffer::resample() (11 tests): 44.1↔48kHz duration preservation, stereo channel separation, resample+stretch pipeline, stretch+resample pipeline, RMS energy preservation, identity, double/half rate, round-trip, empty, extreme rates, very short buffers
  - AudioBuffer::crossfade_into() (9 tests): DJ transition with stretched tracks, stereo crossfade, zero-frames concatenation, full overlap clamping, midpoint equal-mix verification, DC energy conservation, 3-segment chain, mismatched sample rate/channels panics
  - WAV file convenience APIs (6 tests): stretch_to_bpm_wav_file basic/stereo/same-BPM/all-presets, stretch_wav_file, pitch_shift_wav_file, nonexistent input error
  - DJ streaming workflow (11 tests): from_tempo mono/stereo, set_tempo smooth transition, multiple tempo changes, hybrid mode streaming, hybrid mode switch mid-stream, reset preserves source BPM, params accessor, set_tempo without from_tempo, invalid tempo values
  - Conversions & traits (15 tests): From<AudioBuffer> for Vec<f32> mono/stereo/after-stretch/empty, with_stretch_ratio override/pipeline, Debug for AudioBuffer/StretchParams/StreamProcessor, Display presets, PartialEq after resample, AsRef, IntoIterator stereo, Default params, from_tempo ratio calculation/preset chain
  - Combined workflows (10 tests): full DJ transition, sample rate conversion+stretch, chop+stretch+crossfade, normalize+crossfade, streaming+resample, window types with BPM stretch, normalize flag with WAV, beat-aware with clicks, band-split+crossfade
  - Edge cases (11 tests): very short/extreme resample, single-frame/empty crossfade, identity BPM streaming, with_stretch_ratio override from_tempo, hybrid persists across reset, latency reporting, stereo frame alignment, asymmetric crossfade, output_length helper
- [x] 57 new integration tests in `tests/new_features_2.rs` covering additional new APIs:
  - AudioBuffer::split_at() (9 tests): middle/beginning/end/beyond-end split, stereo, data preservation, recombine equals original, split+stretch both halves, empty buffer
  - AudioBuffer::repeat() (9 tests): twice/once/zero/stereo/empty/large count, sample rate preservation, repeat+stretch workflow, RMS preservation
  - AudioBuffer::mix() (10 tests): two sines, inverse cancellation, mix with silence, different lengths zero-pad, stereo, mismatched rate/channels panics, mix+stretch, commutativity, self-doubling
  - AudioBuffer::into_data() (5 tests): mono/stereo/empty, after stretch, vs From<> conversion equivalence
  - AsMut<[Sample]> (5 tests): modify samples, manual gain, zero-out, stereo channel modification, as_mut+stretch workflow
  - StreamProcessor::process_into()/flush_into() (9 tests): basic, accumulates, matches process() output, flush basic/empty, stereo, NaN rejection, ratio change, pre-allocated buffer
  - Streaming-batch parity (4 tests): expansion/compression length matching, RMS matching, stereo parity
  - Combined workflows (6 tests): split+stretch+recombine, repeat+mix layering, as_mut+normalize+split, into_data for external processing, process_into+split+mix, repeat+crossfade DJ loop
- [x] Total: 878 tests (311 unit + 540 integration + 27 doc), all passing
- [x] Zero clippy warnings

## DJ Mix Example (agent-3)
- [x] New `examples/dj_mix.rs` — full DJ workflow example demonstrating:
  - Two tracks at different sample rates (48kHz, 44.1kHz) and tempos (126, 130 BPM)
  - Resample to common rate, stretch to target BPM (128) with DjBeatmatch preset
  - Reverse cymbal build-up effect using `reverse()` + `fade_in()`
  - Track splitting with `split_at()`, layering with `mix()`, crossfade transition
  - Demonstrates all new AudioBuffer APIs in a realistic scenario

## API Completions & Factory Methods (agent-1)
- [x] `stretch_to_bpm_auto_wav_file()` — auto-detect BPM from WAV, stretch to target, write output (completes WAV file API symmetry)
- [x] `StreamProcessor::target_stretch_ratio()` — getter for the target ratio (what the interpolation is converging toward)
- [x] `StreamProcessor::target_bpm()` — returns target BPM computed from source BPM and target ratio (requires `from_tempo`)
- [x] `AudioBuffer::silence(sample_rate, duration_secs)` — factory for silent mono buffers (useful for padding, gaps, tests)
- [x] `AudioBuffer::tone(freq_hz, sample_rate, duration_secs, amplitude)` — factory for mono sine tone test signals
- [x] `AudioBuffer::pan(pan)` — mono-to-stereo with constant-power panning (-1.0 hard left, 0.0 center, 1.0 hard right)
- [x] `AudioBuffer::with_gain_envelope(breakpoints)` — apply time-varying gain via linear-interpolated breakpoints (volume automation, ducking)
- [x] 24 new unit tests: silence (3), tone (4), pan (7), with_gain_envelope (7), StreamProcessor getters (4)
- [x] Applied cargo fmt to fix formatting drift from other agents' code
- [x] All 923 tests passing (354 unit + 535 integration + 34 doc), zero clippy warnings, zero doc warnings

## TODO
- [ ] SIMD-friendly inner loop layout

## Notes
- Phase vocoder window type is now configurable (Hann, Blackman-Harris, Kaiser) — Hann is default
- Phase vocoder window-sum normalization clamped to prevent amplification in low-overlap regions
- For very short segments, hybrid falls back to linear resampling
- WSOLA cross-correlation uses FFT for search ranges > 64 candidates, direct computation otherwise
- WSOLA output position tracked fractionally to prevent cumulative rounding drift at all stretch ratios
- wrap_phase uses floor-based modulo instead of while loops (more predictable for large phase values)
- Sub-bass bins (< 120 Hz by default) use rigid phase propagation to prevent phase cancellation — critical for EDM mono-bass compatibility
- StreamProcessor ratio changes are now phase-continuous (no vocoder recreation), enabling smooth DJ pitch fader behavior
- Sub-bass band splitting (when enabled via `band_split`) separates sub-bass before stretch processing, preventing WSOLA from smearing bass during kick transients
- Band splitting uses Hann-window overlap-add FFT filter (4096-point, 75% overlap) with correct conjugate-symmetric negative frequency handling