balakrishna567 commited on
Commit
3e92cd5
·
1 Parent(s): 770a891

feat: chromagram extractor — chroma CQT mean, L1-normalized

Browse files
app/services/chromagram.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import librosa
3
+
4
+
5
+ def extract_chroma_mean(y: np.ndarray, sr: int, hop_length: int = 512) -> np.ndarray:
6
+ """Extract mean chroma CQT vector (12,) for an audio chunk, L1-normalized."""
7
+ chroma = librosa.feature.chroma_cqt(y=y, sr=sr, hop_length=hop_length)
8
+ mean = chroma.mean(axis=1)
9
+ total = mean.sum()
10
+ return mean / total if total > 0 else mean
tests/test_chromagram.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from app.services.chromagram import extract_chroma_mean
3
+
4
+ def sine(freq, duration=2.0, sr=22050):
5
+ t = np.linspace(0, duration, int(sr * duration))
6
+ return np.sin(2 * np.pi * freq * t).astype(np.float32)
7
+
8
+ def test_output_shape():
9
+ assert extract_chroma_mean(sine(440.0), sr=22050).shape == (12,)
10
+
11
+ def test_output_is_normalized():
12
+ chroma = extract_chroma_mean(sine(440.0), sr=22050)
13
+ assert abs(chroma.sum() - 1.0) < 0.05
14
+
15
+ def test_output_non_negative():
16
+ assert (extract_chroma_mean(sine(440.0), sr=22050) >= 0).all()
17
+
18
+ def test_a440_peak_at_index_9():
19
+ # A = pitch class 9 (C=0 … A=9)
20
+ chroma = extract_chroma_mean(sine(440.0, duration=4.0), sr=22050)
21
+ assert np.argmax(chroma) == 9