"""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 # Duration should be approximately the same (no time stretch) assert abs(len(result) - len(audio)) / sr < 0.1 # within 100ms 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 # within 200ms 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 )