File size: 2,135 Bytes
3b1b3cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import numpy as np

from audio_analysis import analyze_waveform


def _click_track(bpm: float, duration_sec: float, sample_rate: int) -> np.ndarray:
    total = int(duration_sec * sample_rate)
    y = np.zeros(total, dtype=np.float32)

    beat_interval = max(1, int(round(sample_rate * 60.0 / bpm)))
    pulse_len = max(8, int(sample_rate * 0.03))
    pulse = np.hanning(pulse_len).astype(np.float32)

    for i in range(0, total - pulse_len, beat_interval):
        y[i : i + pulse_len] += pulse

    return y


def _tone_mix(freqs: list[float], duration_sec: float, sample_rate: int) -> np.ndarray:
    n = int(duration_sec * sample_rate)
    t = np.arange(n, dtype=np.float32) / sample_rate
    y = np.zeros(n, dtype=np.float32)

    for f in freqs:
        y += 0.22 * np.sin(2 * np.pi * f * t)
        y += 0.09 * np.sin(2 * np.pi * (2.0 * f) * t)

    return y


def test_analyze_waveform_detects_bpm_and_key_for_musical_signal():
    sample_rate = 22050
    duration = 24.0

    rhythmic = _click_track(bpm=120.0, duration_sec=duration, sample_rate=sample_rate)
    harmonic = _tone_mix(freqs=[261.63, 329.63, 392.0], duration_sec=duration, sample_rate=sample_rate)

    y = rhythmic + harmonic
    y /= np.max(np.abs(y)) + 1e-9

    analysis = analyze_waveform(y, sample_rate=sample_rate)

    assert analysis["bpm"] is not None
    assert abs(float(analysis["bpm"]) - 120.0) <= 3.0
    assert analysis["bpm_confidence"] is not None
    assert float(analysis["bpm_confidence"]) >= 0.22

    assert analysis["musical_key"] in {"C", "G"}
    assert analysis["key_scale"] == "major"
    assert analysis["key_confidence"] is not None
    assert float(analysis["key_confidence"]) >= 0.22


def test_analyze_waveform_returns_unknown_for_short_audio():
    sample_rate = 22050
    short = _tone_mix(freqs=[440.0], duration_sec=1.5, sample_rate=sample_rate)
    analysis = analyze_waveform(short, sample_rate=sample_rate)

    assert analysis["bpm"] is None
    assert analysis["bpm_confidence"] is None
    assert analysis["musical_key"] is None
    assert analysis["key_scale"] is None
    assert analysis["key_confidence"] is None