Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 63 additions & 7 deletions services/analysis-engine/tests/test_pitch_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
from bandscope_analysis.ranges.pitch_tracker import PitchTracker


def patched_pyin_audio() -> np.ndarray:
"""Return deterministic audio for tests that patch pYIN output."""
return np.zeros(22050, dtype=np.float32)


def test_pitch_tracker_empty_audio() -> None:
"""Test pitch tracking with empty audio array."""
tracker = PitchTracker()
Expand Down Expand Up @@ -76,9 +81,12 @@ def test_pitch_tracker_sweep() -> None:
def test_pitch_tracker_pyin_exception():
"""Test fallback when pyin raises ParameterError."""
tracker = PitchTracker()
y = np.random.randn(22050)
y = patched_pyin_audio()

with patch("librosa.pyin", side_effect=librosa.util.exceptions.ParameterError("Pyin Error")):
with patch(
"bandscope_analysis.ranges.pitch_tracker.librosa.pyin",
side_effect=librosa.util.exceptions.ParameterError("Pyin Error"),
):
result = tracker.track(y, sr=22050)
assert result["lowest_note"] is None
assert result["highest_note"] is None
Expand All @@ -101,9 +109,12 @@ def test_pitch_tracker_few_frames():
def test_pitch_tracker_none_f0():
"""Test when pyin returns None for pitch array."""
tracker = PitchTracker()
y = np.random.randn(22050)
y = patched_pyin_audio()

with patch("librosa.pyin", return_value=(None, np.array([False]), np.array([0.0]))):
with patch(
"bandscope_analysis.ranges.pitch_tracker.librosa.pyin",
return_value=(None, np.array([False]), np.array([0.0])),
):
result = tracker.track(y, sr=22050)
assert result["lowest_note"] is None

Expand All @@ -117,7 +128,10 @@ def test_pitch_tracker_medium_confidence() -> None:
voiced_flag = np.array([True, True, True, True, False, False, False, False, False, False])
voiced_probs = np.full(10, 0.4)

with patch("librosa.pyin", return_value=(f0, voiced_flag, voiced_probs)):
with patch(
"bandscope_analysis.ranges.pitch_tracker.librosa.pyin",
return_value=(f0, voiced_flag, voiced_probs),
):
result = tracker.track(y, sr=sr)

assert result["lowest_note"] == "A4"
Expand All @@ -134,7 +148,10 @@ def test_pitch_tracker_all_nan_voicing_probs_returns_low() -> None:
voiced_flag = np.full(10, True)
voiced_probs = np.full(10, np.nan)

with patch("librosa.pyin", return_value=(f0, voiced_flag, voiced_probs)):
with patch(
"bandscope_analysis.ranges.pitch_tracker.librosa.pyin",
return_value=(f0, voiced_flag, voiced_probs),
):
result = tracker.track(y, sr=sr)

assert result["lowest_note"] is None
Expand All @@ -150,7 +167,10 @@ def test_pitch_tracker_none_voicing_probs_returns_low() -> None:
f0 = np.full(10, 440.0)
voiced_flag = np.full(10, True)

with patch("librosa.pyin", return_value=(f0, voiced_flag, None)):
with patch(
"bandscope_analysis.ranges.pitch_tracker.librosa.pyin",
return_value=(f0, voiced_flag, None),
):
result = tracker.track(y, sr=sr)

assert result["lowest_note"] is None
Expand Down Expand Up @@ -207,3 +227,39 @@ def test_pitch_tracker_confidence_returns_low() -> None:

result = tracker._compute_confidence(voiced_probs, voiced_flag, y)
assert result == "low"


def test_pitch_tracker_nan_f0_returns_low() -> None:
"""Test that NaN-only voiced pitch values fail closed."""
tracker = PitchTracker()
y = patched_pyin_audio()

with patch(
"bandscope_analysis.ranges.pitch_tracker.librosa.pyin",
return_value=(
np.array([np.nan, np.nan]),
np.array([True, True]),
np.array([1.0, 1.0]),
),
):
Comment thread
seonghobae marked this conversation as resolved.
result = tracker.track(y, sr=22050)

assert result["lowest_note"] is None
assert result["highest_note"] is None
assert result["confidence"] == "low"


def test_pitch_tracker_low_average_voicing_probability_returns_low() -> None:
"""Test that very low average voicing probability suppresses note output."""
tracker = PitchTracker()
y = patched_pyin_audio()

with patch(
"bandscope_analysis.ranges.pitch_tracker.librosa.pyin",
return_value=(np.array([440.0]), np.array([True]), np.array([0.1])),
):
Comment thread
seonghobae marked this conversation as resolved.
result = tracker.track(y, sr=22050)

assert result["lowest_note"] is None
assert result["highest_note"] is None
assert result["confidence"] == "low"