| """Tests for audio processor service.""" |
|
|
| import pytest |
| import numpy as np |
| from backend.services.audio_processor import ( |
| shift_and_stretch_single, |
| process_all_stems, |
| mix_stems, |
| ) |
| from backend.models.session import StemData |
|
|
|
|
| def test_pitch_shift_up_2(sample_stems): |
| """Pitch shifting up 2 semitones should not crash and should return audio.""" |
| stems, sr = sample_stems |
| for name, audio in stems.items(): |
| result = shift_and_stretch_single( |
| audio, sr, semitones=2, tempo_ratio=1.0, stem_type=name |
| ) |
| assert isinstance(result, np.ndarray) |
| assert len(result) > 0 |
| |
| assert abs(len(result) - len(audio)) / sr < 0.1 |
|
|
|
|
| def test_pitch_shift_down_4(sample_stems): |
| """Pitch shifting down 4 semitones should work.""" |
| stems, sr = sample_stems |
| audio = stems["guitar"] |
| result = shift_and_stretch_single( |
| audio, sr, semitones=-4, tempo_ratio=1.0, stem_type="guitar" |
| ) |
| assert len(result) > 0 |
|
|
|
|
| def test_time_stretch_faster(sample_stems): |
| """Speeding up by 20% should produce shorter audio.""" |
| stems, sr = sample_stems |
| audio = stems["bass"] |
| result = shift_and_stretch_single( |
| audio, sr, semitones=0, tempo_ratio=1.2, stem_type="bass" |
| ) |
| expected_length = len(audio) / 1.2 |
| assert abs(len(result) - expected_length) / sr < 0.2 |
|
|
|
|
| def test_time_stretch_slower(sample_stems): |
| """Slowing down by 20% should produce longer audio.""" |
| stems, sr = sample_stems |
| audio = stems["bass"] |
| result = shift_and_stretch_single( |
| audio, sr, semitones=0, tempo_ratio=0.8, stem_type="bass" |
| ) |
| expected_length = len(audio) / 0.8 |
| assert abs(len(result) - expected_length) / sr < 0.2 |
|
|
|
|
| def test_combined_shift_and_stretch(sample_stems): |
| """Combined pitch+tempo should work in single pass.""" |
| stems, sr = sample_stems |
| audio = stems["guitar"] |
| result = shift_and_stretch_single( |
| audio, sr, semitones=3, tempo_ratio=1.15, stem_type="guitar" |
| ) |
| assert len(result) > 0 |
|
|
|
|
| def test_no_change_passthrough(sample_stems): |
| """Zero semitones + 1.0 ratio should return unchanged audio.""" |
| stems, sr = sample_stems |
| audio = stems["bass"] |
| result = shift_and_stretch_single( |
| audio, sr, semitones=0, tempo_ratio=1.0, stem_type="bass" |
| ) |
| np.testing.assert_array_almost_equal(result, audio, decimal=5) |
|
|
|
|
| def test_process_all_stems_parallel(sample_stems): |
| """Processing all stems in parallel should return all stems.""" |
| stems_dict, sr = sample_stems |
| stem_data = { |
| name: StemData(name=name, audio=audio, sample_rate=sr) |
| for name, audio in stems_dict.items() |
| } |
| results = process_all_stems(stem_data, semitones=2, tempo_ratio=1.1) |
| assert set(results.keys()) == set(stems_dict.keys()) |
|
|
|
|
| def test_mix_stems(sample_stems): |
| """Mixing stems should produce audio without clipping.""" |
| stems, sr = sample_stems |
| mixed = mix_stems(stems, sr) |
| assert np.max(np.abs(mixed)) <= 1.0 |
| assert len(mixed) == max(len(a) for a in stems.values()) |
|
|
|
|
| def test_mix_stems_empty(): |
| """Mixing empty stems dict should return empty array.""" |
| mixed = mix_stems({}, sample_rate=48000) |
| assert len(mixed) == 0 |
|
|
|
|
| def test_process_all_stems_no_change(sample_stems): |
| """Processing with no changes should return copies.""" |
| stems_dict, sr = sample_stems |
| stem_data = { |
| name: StemData(name=name, audio=audio, sample_rate=sr) |
| for name, audio in stems_dict.items() |
| } |
| results = process_all_stems(stem_data, semitones=0, tempo_ratio=1.0) |
|
|
| for name in stems_dict: |
| np.testing.assert_array_almost_equal( |
| results[name].audio, stem_data[name].audio, decimal=5 |
| ) |
|
|