# Audio Analysis with librosa — Key, Chords, Tempo Extraction

Working Python approach for extracting musical features when `songsee` (Go CLI) is unavailable.

## Prerequisites

```bash
pip install librosa
```

## Download Audio

```bash
yt-dlp -x --audio-format wav --no-part -o /tmp/song.wav "<youtube-url>"
```

## Analysis Script

```python
import librosa
import numpy as np

# Load first 60s at 22050 Hz (full tracks timeout on CQT)
y, sr = librosa.load("/tmp/song.wav", sr=22050, duration=60.0)

# Tempo — librosa returns numpy array, extract scalar with .item()
tempo, beats = librosa.beat.beat_track(y=y, sr=sr)
tempo_val = tempo.item() if hasattr(tempo, 'item') else float(tempo)
beat_times = librosa.frames_to_time(beats, sr=sr)

# Chroma (pitch class distribution)
chroma = librosa.feature.chroma_stft(y=y, sr=sr, n_chroma=12)
chroma_mean = chroma.mean(axis=1)
pitch_classes = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']

# Key estimation (Krumhansl-Schmuckler profiles)
major_profile = np.array([6.35, 2.23, 3.48, 2.33, 4.38, 4.09, 2.52, 5.19, 2.39, 3.66, 2.29, 2.88])
minor_profile = np.array([6.33, 2.68, 3.52, 5.38, 2.60, 3.53, 2.54, 4.75, 3.98, 2.69, 3.34, 3.17])

major_corrs, minor_corrs = [], []
for i in range(12):
    rotated = np.roll(chroma_mean, -i)
    major_corrs.append(np.corrcoef(rotated, major_profile)[0,1])
    minor_corrs.append(np.corrcoef(rotated, minor_profile)[0,1])

best_major = np.argmax(major_corrs)
best_minor = np.argmax(minor_corrs)

# Chroma per beat (chord progression)
beat_frames = librosa.time_to_frames(beat_times[:16], sr=sr)
for i in range(len(beat_frames)-1):
    seg = chroma[:, beat_frames[i]:beat_frames[i+1]]
    seg_mean = seg.mean(axis=1)
    top3 = [pitch_classes[j] for j in np.argsort(seg_mean)[-3:][::-1]]
    print(f"Beat {i:2d} ({beat_times[i]:5.1f}s): {top3}")
```

## Pitfalls

- **Timeout on full tracks**: 6+ minute WAV at 48kHz causes librosa CQT to hang. Always use `sr=22050, duration=60.0`.
- **Tempo is numpy array**: `librosa.beat.beat_track` returns `tempo` as ndarray. Use `.item()` not `float()`.
- **Turkish makam music**: Krumhansl-Schmuckler is Western-biased. For makam-based songs, expect mixed major/minor correlations (e.g. D major 0.707 and D minor 0.679 both high). Report both and note the modal ambiguity.
- **librosa not in sandbox**: `execute_code` runs in a sandbox without librosa. Write script to file and run via `terminal` with `python3 /path/to/script.py`.

## Output Interpretation

- **Key**: highest correlation score wins, but report top 2-3 candidates
- **Chords**: top 3 chroma notes per beat segment suggest the chord (e.g. D+F+A = Dm, D+F#+A = D)
- **Tempo**: for slow-feeling songs, the detected BPM may be double the felt tempo (e.g. 199 BPM detected → ~100 BPM felt)
