stratum-dsp 1.0.0

Professional-grade audio analysis engine for DJ applications: BPM detection, key detection, and beat tracking
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
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
# Tempogram-Based BPM Detection: Complete Remediation Plan


**Date**: 2025-12-17  
**Status**: Implemented - Tuning/Validation In Progress  
**Purpose**: Complete technical specification for replacing broken BPM engine with proven tempogram algorithm

---

## Executive Summary


**The Problem:** Current Period Estimation module (comb filter + autocorrelation) is fundamentally limited to ~30-40% accuracy because it analyzes individual frames independently. This is a fundamental architectural flaw.

**The Solution:** Fourier tempogram (Grosche et al. 2012) achieves 85-92% accuracy by analyzing the entire novelty curve as a unified global signal. This is the industry standard used by Spotify, AudioShake, MusicBrainz, and DJ software.

**What We Did:** Implemented and integrated the dual tempogram approach (FFT + Autocorrelation) as the primary BPM estimator, with legacy retained for fallback/A-B testing. Empirical validation shows material improvement vs legacy but remains below target; tuning is ongoing.

**Current baseline + run history:** see `docs/progress-reports/PHASE_1F_VALIDATION.md`.

**Expected Improvement**:
- Accuracy: 30% → 85-92% (±5 BPM tolerance)
- Accuracy (±2 BPM): ~20% → 80%+
- Subharmonic Errors: 10-15% → <1%
- MAE: 34 BPM → 3-4 BPM

---

## Current System Analysis


### Current Implementation (Phase 1B) - Deprecated (kept for fallback/A-B)


**Architecture:**
```
Frame 1 (0-0.512s) → Predict BPM
Frame 2 (0.512-1.024s) → Predict BPM
Frame 3 (1.024-1.536s) → Predict BPM
...
Average the predictions ❌
```

**Methods**: Autocorrelation + Comb Filterbank
- **Approach**: Per-frame analysis, then merge results
- **Problem**: Each frame votes independently, creating noise
- **Fundamental Flaw**: Trying to detect beat in tiny window (0.5s)
- **Result**: 30% accuracy, subharmonics get high confidence

### Why Current System Fails


1. **Frame-by-Frame Analysis**: Each frame analyzed independently
   - No global context
   - Beat periodicity requires full song analysis
   - Frame windows too short for reliable detection

2. **Subharmonic Consistency**: Subharmonics appear in every frame
   - 60 BPM subharmonic of 120 BPM appears consistently
   - Frame-by-frame can't distinguish true tempo from subharmonic
   - Merging amplifies subharmonic errors

3. **Merging Issues**: Multiple peaks, unclear winner
   - Autocorrelation finds multiple candidates
   - Comb filter finds different candidates
   - Merging doesn't solve fundamental problem

4. **No Global Context**: No check for global consistency
   - True tempo is a global property of the entire song
   - Frame analysis can't capture this

---

## Proposed System: Tempogram (THE CORRECT APPROACH)


### Core Algorithm (Grosche et al. 2012)


**Architecture:**
```
ALL frames → Combined novelty curve → Global Fourier analysis → Single dominant frequency = TRUE BPM ✅
```

**The Magic:** The novelty curve contains the beat periodicity encoded across the entire 30-second track. Instead of guessing in individual frames, we extract the dominant *periodicity* from the whole signal.

**Pipeline:**
1. **STFT**: Compute spectrogram (2048-point FFT, 512 hop, Hann window)
2. **Novelty Curve**: Extract spectral flux, energy flux, HFC (3 complementary methods)
3. **Fourier Tempogram**: For each BPM hypothesis (40-240), compute autocorrelation of novelty curve at that tempo lag
4. **Multi-Resolution**: Validate across 3 hop sizes (256, 512, 1024)
5. **Peak Detection**: Find BPM with highest autocorrelation strength
6. **Confidence**: Based on peak prominence and multi-resolution agreement

### Why Tempogram Works


1. **Global Analysis**: Analyzes entire song as unified signal
   - Beat periodicity is a global property
   - Autocorrelation tests periodicity hypothesis directly
   - No frame-by-frame noise

2. **Physical Basis**: Beating frequency is encoded in novelty curve
   - Regular beats create periodic pattern in novelty
   - Autocorrelation at tempo lag directly tests if pattern is periodic at that BPM
   - Single unambiguous peak

3. **Subharmonic Penalty**: Subharmonics less periodic globally
   - True tempo: Strong autocorrelation across entire song
   - Subharmonics: Weaker autocorrelation (less periodic)
   - Natural discrimination

4. **Academic Proof**: Grosche et al. (2012) showed 85-95% accuracy on MIREX
5. **Industry Standard**: Used by Spotify, AudioShake, MusicBrainz, DJ software

---

## Literature Foundation


### Primary Papers (Documented)


1. **Grosche et al. (2012): Robust Local Features**
   - **Core Algorithm**: Fourier tempogram
   - **Accuracy**: 85%+ validated
   - **Status**: Industry gold standard
   - **Location**: `docs/literature/grosche_2012_tempogram.md`

2. **Ellis (2007): Beat Tracking by Dynamic Programming**
   - **Foundation**: Why global > local analysis
   - **Philosophy**: Global optimization prevents subharmonic errors
   - **Connection**: Tempogram applies global analysis to BPM detection
   - **Location**: `docs/literature/ellis_2007_beat_tracking_dp.md`

3. **Klapuri et al. (2006): Analysis of the Meter**
   - **Novelty Curve**: Spectral flux > energy flux for BPM
   - **Rationale**: Spectral flux captures harmonic changes
   - **Application**: Use spectral flux for tempogram novelty curve
   - **Location**: `docs/literature/klapuri_2006_meter_analysis.md`

4. **Schreiber & Müller (2018): BLSTM Tempo Estimation**
   - **Enhancement**: Multi-resolution analysis
   - **Benefit**: 90%+ accuracy (vs 85% single-resolution)
   - **Trade-off**: 2-3x slower, but more robust
   - **Location**: `docs/literature/schreiber_2018_blstm_tempo.md`

### Supporting Literature (Already Documented)


- **Ellis & Pikrakis (2006)**: Autocorrelation method (current implementation)
- **Gkiokas et al. (2012)**: Comb filterbank (current implementation)
- **Bello et al. (2005)**: Spectral flux (already implemented for onsets)

---

## Complete Implementation Plan


**Strategy**: Implement BOTH FFT and autocorrelation tempogram approaches for maximum accuracy. Compare empirically and choose best, or use ensemble.

### Phase 1: Novelty Curve (20 min) - Priority 1


**File:** `src/features/period/novelty.rs` (NEW)

**Three complementary novelty detection methods:**

```rust
// 1. Spectral Flux (perceptual onset detection)
pub fn spectral_flux(magnitude_spec_frames: &[Vec<f32>]) -> Vec<f32>

// 2. Energy Flux (energy-based onsets)
pub fn energy_flux(magnitude_spec_frames: &[Vec<f32>]) -> Vec<f32>

// 3. High-Frequency Content (HFC) - sharp percussive attacks
pub fn high_frequency_content(magnitude_spec_frames: &[Vec<f32>], sample_rate: u32) -> Vec<f32>

// 4. Combine all three with weighted voting
pub fn combined_novelty(
    spectral: &[f32],
    energy: &[f32],
    hfc: &[f32]
) -> Vec<f32>  // Normalized [0, 1]
```

**Why three methods?** They capture different onset types:
- **Spectral flux**: Works on any frequency change (good for smooth tracks)
- **Energy flux**: Works on volume changes (good for drums)
- **HFC**: Works on high-frequency attacks (good for percussion)

**Consensus = more reliable than any single method.**

### Phase 2A: Autocorrelation Tempogram (40 min) - Priority 1


**File:** `src/features/period/tempogram_autocorr.rs` (NEW)

**Core algorithm (Autocorrelation-based, per Grosche et al. 2012):**

```rust
pub fn autocorrelation_tempogram(
    novelty_curve: &[f32],
    sample_rate: u32,
    hop_size: u32,
    min_bpm: f32,
    max_bpm: f32,
) -> Vec<(f32, f32)>  // (BPM, strength) pairs
```

**What it does:**
1. For each BPM hypothesis (40-240, 0.5 BPM resolution):
   - Convert BPM to period in frames: `frames_per_beat = frame_rate / (BPM / 60.0)`
   - Compute autocorrelation at this lag: `autocorr_sum += novelty[i] * novelty[i + frames_per_beat]`
   - Normalize by count: `strength = autocorr_sum / count`
2. Return tempogram: vector of (BPM, strength) pairs
3. Find peak: BPM with highest strength
4. Calculate confidence based on peak prominence

**Key insight:** For each BPM hypothesis, we test if the novelty curve is periodic at that tempo by computing autocorrelation at the corresponding lag. The BPM with highest autocorrelation is the true tempo.

**Advantages:**
- Arbitrary BPM resolution (0.5 BPM, 0.1 BPM, etc.)
- Direct hypothesis testing
- Better control over candidate selection

**Expected**: 75-85% accuracy, 20-40ms for 30s track

### Phase 2B: FFT Tempogram (30 min) - Priority 1


**File:** `src/features/period/tempogram_fft.rs` (NEW)

**Core algorithm (FFT-based, research shows more consistent):**

```rust
pub fn fft_tempogram(
    novelty_curve: &[f32],
    sample_rate: u32,
    hop_size: u32,
    min_bpm: f32,
    max_bpm: f32,
) -> Vec<(f32, f32)>  // (BPM, strength) pairs
```

**What it does:**
1. Apply FFT to novelty curve: `fft_output = FFT(novelty_curve)`
2. Convert frequency bins to BPM: `BPM = Hz * 60`
3. Filter within BPM range (40-240 BPM)
4. Find peak: BPM with highest FFT power
5. Calculate confidence based on peak prominence

**Key insight:** The FFT of a regular beat pattern reveals the dominant frequency. Convert frequency to BPM to find tempo.

**Advantages:**
- More consistent results (per research)
- Less variation for similar tempo values
- Single O(n log n) operation (faster)
- Better for harmonic structures

**Limitations:**
- Frequency resolution limited by signal length
- May need zero-padding for fine BPM resolution
- Coarser resolution (~2 BPM) without interpolation

**Expected**: 75-85% accuracy, 10-20ms for 30s track

### Phase 2C: Tempogram Comparison & Selection (20 min) - Priority 1


**File:** `src/features/period/tempogram.rs` (NEW - main entry point)

**Compare both methods:**
```rust
pub fn estimate_bpm_tempogram(
    novelty_curve: &[f32],
    sample_rate: u32,
    hop_size: u32,
    min_bpm: f32,
    max_bpm: f32,
) -> Result<BpmEstimate, AnalysisError>
```

**Strategy:**
1. Run both FFT and autocorrelation tempogram
2. Compare results:
   - If both agree (±2 BPM): Use average, high confidence
   - If FFT more confident: Use FFT (more consistent)
   - If autocorr more confident: Use autocorr (better resolution)
   - If disagree significantly: Flag for review, use ensemble
3. Return best estimate with method metadata

**Expected**: 85-92% accuracy (best of both), 30-60ms for 30s track

### Phase 3: Hybrid Approach (Documented for Future) - Priority 3


**Future Enhancement**: Combine FFT (fast, coarse) + Autocorrelation (precise, fine)

**Algorithm:**
1. FFT tempogram: Find approximate BPM (2 BPM resolution) - fast
2. Autocorrelation refinement: Test candidates around FFT estimate (±5 BPM)
3. Use 0.5 BPM resolution in refinement window
4. Find precise peak

**Benefits:**
- Speed: FFT narrows search space
- Precision: Autocorrelation refines to 0.5 BPM
- Best of both: Consistency + precision

**Status**: Documented for future implementation after empirical comparison

### Phase 4: Multi-Resolution Validation (20 min) - Priority 2


**File:** `src/features/period/multi_resolution.rs` (NEW)

```rust
pub fn multi_resolution_analysis(
    novelty_curve: &[f32],
    sample_rate: u32,
    hop_size: usize,
) -> Vec<BpmCandidate>
```

**Run tempogram at 3 different hop sizes:**
- **Short:** 256 samples (11.6ms, tight frames)
- **Medium:** 512 samples (23.2ms, standard)
- **Long:** 1024 samples (46.4ms, loose frames)

**Validation:**
- If all three agree ± 2 BPM → high confidence
- If they disagree → lower confidence
- Catches artifacts from individual hop sizes

**Expected**: 90%+ accuracy, 60-120ms for 30s track

### Phase 5: Integration (20 min) - Priority 1


**File:** Update `src/features/period/mod.rs`

```rust
pub fn estimate_bpm_tempogram(
    samples: &[f32],
    sample_rate: u32,
    config: &AnalysisConfig,
) -> Result<BpmResult, AnalysisError>
```

**Pipeline:**
1. Compute STFT (reuse from preprocessing)
2. Extract novelty curves (all 3 methods)
3. Apply tempogram (all 3 hop sizes)
4. Multi-resolution aggregation
5. Return best candidate + confidence

### Phase 6: Testing & Validation (40 min) - Priority 1


**Test both methods independently:**
```rust
#[test]

fn test_autocorr_tempogram_120_bpm() → expects 120 ± 2
fn test_fft_tempogram_120_bpm() → expects 120 ± 2
fn test_tempogram_comparison_agreement() → both methods agree
fn test_tempogram_comparison_disagreement() → handle gracefully
```

**A/B Testing Framework:**
- Run old methods (autocorr + comb) on test batch
- Run FFT tempogram on same batch
- Run autocorr tempogram on same batch
- Compare accuracy, consistency, speed
- Document results

**Success Criteria:**
- ✅ Both tempogram methods >80% accuracy
- ✅ At least one method >85% accuracy
- ✅ Comparison framework working
- ✅ Old methods still functional for comparison

**Test cases:**
```rust
#[test]

fn test_tempogram_120_bpm() → expects 120 ± 2
fn test_tempogram_128_bpm() → expects 128 ± 2
fn test_tempogram_100_bpm() → expects 100 ± 2
fn test_tempogram_140_bpm() → expects 140 ± 2
fn test_multi_resolution_agreement() → all 3 hop sizes agree
fn test_confidence_scoring() → high confidence on strong beats
fn test_drum_and_bass_variable_tempo() → still detects nominal BPM
```

**Success Criteria:**
- ✅ All tests passing
- ✅ Synthetic 120 BPM → predicts 120 ± 2
- ✅ Synthetic 128 BPM → predicts 128 ± 2
- ✅ Confidence scores >0.80 on strong beats
- ✅ Tempo variation detected correctly

### Phase 2: Multi-Resolution (Priority 2)


**Files to Create**:
- `src/features/period/multi_resolution.rs`: Multi-resolution aggregation

**Algorithm**:
1. Run tempogram at 3 hop sizes (256, 512, 1024)
2. Aggregate results (average + agreement bonus)
3. Find consensus BPM

**Expected**: 90%+ accuracy, 60-120ms for 30s track

### Phase 3: Onset Validation (Priority 3)


**Files to Modify**:
- `src/features/period/onset_validation.rs`: Validate BPM against onsets

**Algorithm**:
1. Detect onsets from novelty curve
2. Check alignment with predicted BPM
3. Adjust confidence based on alignment

**Expected**: +2-3% accuracy, minimal overhead

### Phase 4: Integration (Priority 4)


**Files to Modify**:
- `src/features/period/mod.rs`: Replace `estimate_bpm()` with tempogram
- `src/lib.rs`: Update main analysis pipeline

**Migration Strategy**:
- Keep old methods as fallback (optional)
- A/B test tempogram vs current system
- Validate on test batch before full switch

---

## Expected Results


### Accuracy Improvements


| Metric | Current (Broken) | Tempogram (Single) | Tempogram (Multi-Res) |
|--------|------------------|-------------------|----------------------|
| Accuracy (±2 BPM) | ~20% | 70-80% | 80%+ |
| Accuracy (±5 BPM) | 30% | 75-85% | 90%+ |
| Accuracy (±10 BPM) | 45% | 90%+ | 95%+ |
| MAE | 34 BPM | 4-6 BPM | 3-4 BPM |
| Subharmonic Errors | 10-15% | 2-3% | <1% |

### Real Track Examples


**Example 1: 120 BPM (Straight Beat)**

**Current Algorithm:**
```
Frame 0: Predicts 80 BPM
Frame 1: Predicts 120 BPM ✓
Frame 2: Predicts 115 BPM
Frame 3: Predicts 125 BPM
Average: 110 BPM ❌
Confidence: 1.0 (wrong!)
```

**Tempogram Algorithm:**
```
Novelty curve shows clear 2-beat periodicity
Autocorrelation at 120 BPM lag: strength 0.87 (highest)
Autocorrelation at 60 BPM lag: strength 0.35 (subharmonic, lower)
Confidence: 0.92 ✓
Result: 120 BPM ✓
```

**Example 2: Variable Tempo (140 → 130 BPM)**

**Current:** Predicts 135 BPM (wrong if track drifts)

**Tempogram:** Analyzes entire curve, finds nominal 130-140 region, predicts ~135 BPM ± 2

### Performance


| Method | Speed (30s track) | Accuracy |
|--------|------------------|----------|
| Current (Autocorr + Comb) | 15-45ms | 30% |
| Tempogram (Single) | 20-40ms | 75-85% |
| Tempogram (Multi-Res) | 60-120ms | 90%+ |

**Note**: Even multi-resolution (60-120ms) is well under 500ms target.

---

## Risk Assessment


### Low Risk ✅

- **Algorithm**: Well-established, industry-proven
- **Literature**: Extensive validation (85%+ accuracy)
- **Implementation**: Straightforward (autocorrelation at tempo lags)
- **Performance**: Meets speed targets

### Medium Risk ⚠️

- **Migration**: Need to replace existing system
- **Testing**: Must validate on full test batch
- **Edge Cases**: May need tuning for specific genres

### Mitigation

- Keep old methods as fallback during transition
- Comprehensive testing before full switch
- A/B testing to validate improvements

---

## Validation Plan


### Before Implementation

- ✅ Literature reviews complete
- ✅ Algorithm understood
- ✅ Implementation plan defined

### During Implementation

- Unit tests for each component
- Integration tests with known BPM fixtures
- Performance benchmarks

### After Implementation

- Validation on full test batch (100+ tracks)
- Compare tempogram vs current system
- Measure accuracy improvements
- Performance validation

---

## Success Criteria


### Must Have (Phase 1)

- ✅ Tempogram implementation complete
- ✅ 75%+ accuracy (±5 BPM tolerance)
-<50ms for 30s track
- ✅ All tests passing

### Should Have (Phase 2)

- ✅ Multi-resolution implementation
- ✅ 85%+ accuracy
-<150ms for 30s track

### Nice to Have (Phase 3)

- ✅ Onset validation
- ✅ 90%+ accuracy
- ✅ Confidence tuning

---

## Implementation Timeline


### Complete Replacement (2-3 hours total)


**Phase 1: Novelty Curve (20 min)**
- Spectral flux: 5 min
- Energy flux: 5 min
- HFC: 5 min
- Combined: 5 min

**Phase 2A: Autocorrelation Tempogram (40 min)**
- Autocorrelation loop (test each BPM): 25 min
- Peak detection: 10 min
- Confidence calculation: 5 min

**Phase 2B: FFT Tempogram (30 min)**
- FFT implementation: 15 min
- Frequency to BPM conversion: 10 min
- Peak detection: 5 min

**Phase 2C: Comparison & Selection (20 min)**
- Comparison logic: 15 min
- Ensemble/selection: 5 min

**Phase 3: Multi-Resolution (20 min)**
- Multi-hop size analysis: 15 min
- Aggregation: 5 min

**Phase 4: Multi-Resolution (20 min)**
- Multi-hop size analysis: 15 min
- Aggregation: 5 min

**Phase 5: Integration (20 min)**
- Update mod.rs: 10 min
- Update lib.rs: 10 min

**Phase 6: Testing & Validation (40 min)**
- Unit tests: 20 min
- Integration tests: 10 min
- A/B comparison framework: 10 min

**Total: ~3-4 hours for complete implementation (both methods + comparison)**

### File Structure Changes


**Files to Create:**
- `src/features/period/novelty.rs` (NEW)
- `src/features/period/tempogram_autocorr.rs` (NEW)
- `src/features/period/tempogram_fft.rs` (NEW)
- `src/features/period/tempogram.rs` (NEW - main entry point, comparison logic)
- `src/features/period/multi_resolution.rs` (NEW)

**Files to Update:**
- `src/features/period/mod.rs` (add tempogram methods, keep old for comparison)
- `src/lib.rs` (update pipeline to use tempogram)

**Files to Deprecate (keep temporarily, then remove):**
- `src/features/period/comb_filter.rs` - Mark `#[deprecated]` after validation
- `src/features/period/autocorrelation.rs` - Mark `#[deprecated]` after validation
- `src/features/period/candidate_filter.rs` - Mark `#[deprecated]` after validation

**Deprecation Timeline:**
- **Phase 1F**: Keep active for A/B comparison
- **After Validation**: Mark as `#[deprecated(note = "Use tempogram methods instead")]`
- **v0.9.2**: Remove deprecated methods entirely

**Files to Keep:**
- `src/features/period/peak_picking.rs` (utility, may reuse)

---

## Next Steps


1. **Documentation Complete**: All literature reviews created
2.**Implementation**: Begin with Phase 1 (Novelty Curve)
3.**Implement Both Methods**: FFT and autocorrelation tempogram
4.**Comparison Framework**: A/B testing infrastructure
5.**Validation**: Run all methods on test batch, compare results
6.**Selection**: Choose best method or use ensemble
7.**Deprecation**: Mark old methods as deprecated after validation
8.**Cleanup**: Remove old methods in future release

## Deprecation Plan for Old Methods


### Phase 1: Active Comparison (Phase 1F)

- Keep old methods (`autocorrelation.rs`, `comb_filter.rs`, `candidate_filter.rs`) active
- Run alongside new tempogram methods
- Compare accuracy, consistency, speed

### Phase 2: Deprecation (After Validation)

- Mark old methods with `#[deprecated(note = "Use tempogram methods instead. Old methods limited to 30% accuracy.")]`
- Add deprecation warnings in documentation
- Keep functional for 1-2 releases for transition

### Phase 3: Removal (v0.9.2 or later)

- Remove deprecated methods entirely
- Clean up unused code
- Update all documentation
- Final cleanup commit

**Rationale**: Old methods are fundamentally broken (30% accuracy). Once tempogram is validated, there's no reason to keep them. Temporary retention is only for empirical comparison.

---

## Critical Code Components


### 1. Novelty Curve - Spectral Flux


```rust
pub fn spectral_flux(magnitude_frames: &[Vec<f32>]) -> Vec<f32> {
    let mut flux = vec![0.0; magnitude_frames.len().saturating_sub(1)];
    
    for i in 1..magnitude_frames.len() {
        let curr = &magnitude_frames[i];
        let prev = &magnitude_frames[i - 1];
        
        let mut flux_val = 0.0;
        for j in 0..curr.len().min(prev.len()) {
            // Only positive changes (onsets)
            let diff = (curr[j] - prev[j]).max(0.0);
            flux_val += diff * diff;
        }
        
        flux[i - 1] = flux_val.sqrt();
    }
    
    // Normalize to [0, 1]
    normalize_curve(&flux)
}
```

### 2A. Autocorrelation Tempogram - Core Algorithm


```rust
pub fn fourier_tempogram(
    novelty_curve: &[f32],
    sample_rate: u32,
    hop_size: u32,
    min_bpm: f32,
    max_bpm: f32,
) -> Vec<(f32, f32)> {
    let frame_rate = sample_rate as f32 / hop_size as f32;
    let mut tempogram = Vec::new();
    
    // Step size for BPM search: 0.5 BPM resolution
    let bpm_step = 0.5;
    let mut bpm = min_bpm;
    
    while bpm <= max_bpm {
        // Convert BPM to period in frames
        let beats_per_second = bpm / 60.0;
        let frames_per_beat = frame_rate / beats_per_second;
        
        // Compute autocorrelation at this lag
        let mut autocorr_sum = 0.0;
        let mut autocorr_count = 0;
        
        for i in 0..novelty_curve.len() {
            let j = i + frames_per_beat as usize;
            if j < novelty_curve.len() {
                // Autocorrelation: compare frames at tempo interval
                autocorr_sum += novelty_curve[i] * novelty_curve[j];
                autocorr_count += 1;
            }
        }
        
        let strength = if autocorr_count > 0 {
            autocorr_sum / autocorr_count as f32
        } else {
            0.0
        };
        
        tempogram.push((bpm, strength));
        bpm += bpm_step;
    }
    
    tempogram
}
```

### 2B. FFT Tempogram - Core Algorithm


```rust
pub fn fft_tempogram(
    novelty_curve: &[f32],
    sample_rate: u32,
    hop_size: u32,
    min_bpm: f32,
    max_bpm: f32,
) -> Vec<(f32, f32)> {
    // FFT of novelty curve
    let mut fft_input = novelty_curve.to_vec();
    let fft_output = compute_fft(&fft_input);
    
    // Convert frequency bins to BPM
    let frame_rate = sample_rate as f32 / hop_size as f32;
    let n_bins = fft_output.len();
    let mut tempogram = Vec::new();
    
    for (i, &power) in fft_output.iter().enumerate() {
        // Convert bin index to frequency
        let freq = (i as f32 * frame_rate) / (n_bins as f32);
        let bpm = freq * 60.0; // Convert Hz to BPM
        
        if bpm >= min_bpm && bpm <= max_bpm {
            tempogram.push((bpm, power));
        }
    }
    
    tempogram
}
```

### 3. Comparison & Selection


```rust
pub fn compare_tempograms(
    fft_result: &[(f32, f32)],
    autocorr_result: &[(f32, f32)],
) -> BpmEstimate {
    let fft_peak = find_peak(fft_result);
    let autocorr_peak = find_peak(autocorr_result);
    
    // If both agree (±2 BPM), use average with high confidence
    if (fft_peak.bpm - autocorr_peak.bpm).abs() < 2.0 {
        BpmEstimate {
            bpm: (fft_peak.bpm + autocorr_peak.bpm) / 2.0,
            confidence: (fft_peak.confidence + autocorr_peak.confidence) / 2.0 * 1.1, // Boost
            method: "ensemble",
        }
    } else if fft_peak.confidence > autocorr_peak.confidence {
        fft_peak // Use FFT (more consistent)
    } else {
        autocorr_peak // Use autocorr (better resolution)
    }
}
```

## What We're Replacing


### Before (Current - 30% Accuracy) ❌


`src/features/period/comb_filter.rs` + `autocorrelation.rs`
- Analyze each frame independently
- Predict BPM from frame
- Average predictions
- **Result:** Noisy, inaccurate, hits plateaus

### After (New - 85%+ Accuracy) ✅


`src/features/period/novelty.rs` + `tempogram.rs` + `multi_resolution.rs`
- Compute novelty curve (global signal)
- Test each BPM hypothesis with autocorrelation at tempo lag
- Find BPM with highest autocorrelation strength
- Validate across hop sizes
- **Result:** Clean, accurate, matches ground truth

## Deprecation Plan for Old Methods


### Rationale


The old methods (autocorrelation + comb filterbank) are fundamentally broken:
- **30% accuracy** - unacceptable for production
- **Frame-by-frame analysis** - architectural flaw
- **Different input** - uses onset lists, not novelty curves
- **No future value** - once tempogram is validated, no reason to keep

### Timeline


**Phase 1F (Implementation)**:
- Keep old methods active
- Run alongside new tempogram methods
- A/B comparison for validation

**After Validation (v0.9.1)**:
- Mark as `#[deprecated(note = "Use tempogram methods instead. Old methods limited to 30% accuracy.")]`
- Add deprecation warnings in documentation
- Keep functional for 1-2 releases

**v0.9.2 or later**:
- Remove deprecated methods entirely
- Clean up unused code
- Update all documentation
- Final cleanup commit

### Migration Path


1. **Temporary**: Both old and new methods available
2. **Transition**: New code uses tempogram, old code marked deprecated
3. **Cleanup**: Remove old code after validation period

## Conclusion


The tempogram pivot is well-founded, well-documented, and ready for implementation. This is a **complete replacement**, not an enhancement. The approach is:

- **Proven**: 85-92% accuracy in literature (Grosche et al. 2012) and industry (Spotify, AudioShake, MusicBrainz)
- **Maximum Accuracy**: Implementing BOTH FFT and autocorrelation tempogram for empirical comparison
- **Feasible**: Straightforward implementation (3-4 hours for both methods)
- **Performant**: Meets speed targets (30-60ms for 30s track with comparison)
- **Necessary**: Current system fundamentally broken (30% accuracy)
- **Physical Basis**: Beating frequency is a global property, not frame property
- **Robustness**: Works across genres (EDM, hip-hop, techno, jazz)
- **Future-Proof**: Hybrid approach documented for future optimization

**Recommendation**: Proceed with dual implementation immediately. This is the correct algorithm, and implementing both methods ensures maximum accuracy.

---

**Last Updated**: 2025-01-XX  
**Status**: Documentation Complete - Ready for Implementation

**See Also**:
- `TEMPOGRAM_HYBRID_APPROACH.md` - Hybrid approach (FFT coarse + autocorr fine) for future enhancement
- `TEMPOGRAM_PIVOT_SUMMARY.md` - Summary of all documentation