diff --git a/services/analysis-engine/tests/test_chord_recognizer.py b/services/analysis-engine/tests/test_chord_recognizer.py index 20a6dcf7..6a1e99a3 100644 --- a/services/analysis-engine/tests/test_chord_recognizer.py +++ b/services/analysis-engine/tests/test_chord_recognizer.py @@ -1,5 +1,6 @@ """Tests for the chord recognizer module.""" +import typing from unittest.mock import patch import numpy as np @@ -86,7 +87,7 @@ def test_chord_recognizer_rms_padding() -> None: y = np.random.randn(SAMPLE_RATE * DURATION_SECONDS) # Mock RMS to return something shorter than chromagram - def mock_rms(*args, **kwargs): + def mock_rms(*args: typing.Any, **kwargs: typing.Any) -> np.ndarray: return np.array([[0.1, 0.1]]) with patch("librosa.feature.rms", side_effect=mock_rms): @@ -111,7 +112,7 @@ def test_chord_recognizer_rms_longer() -> None: y = np.random.randn(SAMPLE_RATE * DURATION_SECONDS) # Mock RMS to return something longer than chromagram - def mock_rms(*args, **kwargs): + def mock_rms(*args: typing.Any, **kwargs: typing.Any) -> np.ndarray: # Return a very long array return np.array([np.ones(1000)]) @@ -300,7 +301,7 @@ def test_chord_recognizer_compute_confidence_downgrade_path() -> None: original_compute = recognizer._compute_confidence - def mock_confidence(similarity, best_state): + def mock_confidence(similarity: np.ndarray, best_state: int) -> str: try: return next(confidence_values) except StopIteration: @@ -318,3 +319,57 @@ def mock_confidence(similarity, best_state): # Since first frame was high but subsequent were low, # the segment confidence should be low (conservative) assert non_n[0]["confidence"] == "low" + + +def test_recognize_orchestration() -> None: + """Test that recognize properly orchestrates the helper methods.""" + from unittest.mock import patch + + recognizer = ChordRecognizer() + y = np.array([1.0, 2.0, 3.0]) + sr = 22050 + + expected_chords = [{"start_time": 0.0, "end_time": 1.0, "chord": "C", "confidence": "high"}] + + with ( + patch.object(recognizer, "_separate_harmonic", return_value=y) as mock_separate, + patch.object( + recognizer, "_extract_chromagram", return_value=np.ones((12, 10)) + ) as mock_extract, + patch.object(recognizer, "_calculate_rms", return_value=np.ones(10)) as mock_rms, + patch.object( + recognizer, + "_match_templates", + return_value=(np.ones((24, 10)), np.zeros(10, dtype=int)), + ) as mock_match, + patch.object( + recognizer, "_create_chord_segments", return_value=expected_chords + ) as mock_create, + ): + result = recognizer.recognize(y, sr=sr) + + mock_separate.assert_called_once_with(y) + mock_extract.assert_called_once_with(y, sr) + mock_rms.assert_called_once() + mock_match.assert_called_once() + mock_create.assert_called_once() + + assert result == expected_chords + + +def test_create_chord_segments_empty() -> None: + """Test _create_chord_segments when no chord segments are created.""" + from unittest.mock import patch + + recognizer = ChordRecognizer() + + chromagram = np.zeros((12, 0)) + similarity = np.zeros((24, 0)) + rms = np.zeros(0) + + with ( + patch.object(recognizer, "_build_observation_probs", return_value=np.zeros((25, 0))), + patch.object(recognizer, "_viterbi_decode", return_value=np.array([], dtype=np.intp)), + ): + result = recognizer._create_chord_segments(chromagram, similarity, rms, sr=22050) + assert result == []