Skip to content

feat(chords): Krumhansl-Schmuckler key detection#566

Open
seonghobae wants to merge 1 commit into
developfrom
feat/key-detection
Open

feat(chords): Krumhansl-Schmuckler key detection#566
seonghobae wants to merge 1 commit into
developfrom
feat/key-detection

Conversation

@seonghobae

Copy link
Copy Markdown
Collaborator

What

Adds KeyDetector (services/analysis-engine/src/bandscope_analysis/chords/key_detector.py), a musical key estimator for the analysis engine.

Why

Key detection is a core building block for chord/harmony analysis. This implements the standard Krumhansl-Schmuckler algorithm on top of the librosa chromagram already used elsewhere in the chords package.

How

  • Compute a CQT chromagram (librosa.feature.chroma_cqt, tuning=0.0 for deterministic results), average over time into a 12-bin pitch-class profile, and normalize it.
  • Pearson-correlate (computed with numpy — no scipy) against the 24 rotated Krumhansl-Kessler major/minor key profiles. Best correlation names the key.
  • Return {"key", "tonic", "mode", "confidence"}. Confidence blends the clamped best correlation with the gap to the runner-up, bounded to [0, 1].
  • Safe failure: empty audio, empty chromagram, or all-zero chroma return {"key": "", "tonic": "", "mode": "", "confidence": 0.0}; no exception escapes detect().
  • No new dependencies (only librosa/numpy). Class docstring includes a Security Notes block: untrusted in-memory arrays only, no file/network/shell, bounded by input size, safe failure.

Tests

tests/test_key_detector.py synthesizes known-key audio:

  • C-major scale -> C major
  • A-minor scale (tonic-emphasized) -> A minor
  • Empty audio -> empty result, confidence 0.0
  • chroma_cqt exception, empty chromagram, all-zero chromagram, and Pearson zero-variance/perfect-correlation branches.

New module has 100% line coverage. ruff check, docstring D100-D107, ruff format --check, mypy --strict, and bandit all pass.

🤖 Generated with Claude Code

Add a KeyDetector that estimates the musical key of a mono audio array
using the standard Krumhansl-Schmuckler method: a CQT chromagram averaged
into a 12-bin pitch-class profile is Pearson-correlated (computed with
numpy, no scipy) against the 24 rotated Krumhansl-Kessler major/minor key
profiles. Confidence blends the clamped best correlation with the gap to
the runner-up, bounded to [0, 1].

Degenerate input (empty audio, empty or all-zero chromagram) returns an
empty result and no exception escapes detect(). chroma_cqt is called with
tuning=0.0 to keep detection deterministic and avoid an unstable native
pitch-track path on pure synthetic tones.

Tests synthesize known-key audio: a C-major scale detects "C major", an
A-minor scale detects "A minor", and empty audio returns the empty result.
New module has 100% line coverage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RjGVapDZ3k7V7zKYk16P4C
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant