import pytest import app.tts.piper as piper from app.tts.voices import VOICES @pytest.fixture(autouse=True) def _fake_piper_synth(monkeypatch): async def _fake_synthesize(self, text): return b"fake-wav-bytes" monkeypatch.setattr(piper.PiperTTS, "synthesize", _fake_synthesize) def _capture_effects_call(monkeypatch): calls = [] def _fake_apply_effects(wav_bytes, **kwargs): calls.append(kwargs) return wav_bytes monkeypatch.setattr(piper, "apply_effects", _fake_apply_effects) return calls @pytest.mark.asyncio async def test_zero_instability_keeps_profile_defaults(monkeypatch): calls = _capture_effects_call(monkeypatch) voice = VOICES["lessac"] await piper.synthesize_spirit_voice("hello", voice, {"noise": 0.03, "bitcrush": 0}) assert calls[0]["noise_level"] == pytest.approx(0.03) assert calls[0]["bitcrush_bits"] == 0 @pytest.mark.asyncio async def test_instability_increases_noise_level(monkeypatch): calls = _capture_effects_call(monkeypatch) voice = VOICES["lessac"] await piper.synthesize_spirit_voice( "hello", voice, {"noise": 0.03, "bitcrush": 0}, instability=1.0 ) # Meaningfully louder than the clean baseline, but not blown out. assert calls[0]["noise_level"] > 0.03 assert calls[0]["noise_level"] <= 0.1 @pytest.mark.asyncio async def test_noise_level_scales_monotonically_with_instability(monkeypatch): voice = VOICES["lessac"] noise_levels = [] for instability in (0.0, 0.25, 0.5, 0.75, 1.0): calls = _capture_effects_call(monkeypatch) await piper.synthesize_spirit_voice( "hello", voice, {"noise": 0.03, "bitcrush": 0}, instability=instability ) noise_levels.append(calls[0]["noise_level"]) assert noise_levels == sorted(noise_levels) assert noise_levels[0] < noise_levels[-1] @pytest.mark.asyncio async def test_bitcrush_stays_off_below_threshold(monkeypatch): voice = VOICES["lessac"] for instability in (0.0, 0.2, 0.5): calls = _capture_effects_call(monkeypatch) await piper.synthesize_spirit_voice( "hello", voice, {"noise": 0.03, "bitcrush": 0}, instability=instability ) assert calls[0]["bitcrush_bits"] == 0 @pytest.mark.asyncio async def test_bitcrush_kicks_in_above_threshold(monkeypatch): calls = _capture_effects_call(monkeypatch) voice = VOICES["lessac"] await piper.synthesize_spirit_voice( "hello", voice, {"noise": 0.03, "bitcrush": 0}, instability=1.0 ) assert calls[0]["bitcrush_bits"] > 0 assert calls[0]["bitcrush_bits"] < 16 @pytest.mark.asyncio async def test_instability_is_clamped_to_unit_range(monkeypatch): calls = _capture_effects_call(monkeypatch) voice = VOICES["lessac"] await piper.synthesize_spirit_voice( "hello", voice, {"noise": 0.03, "bitcrush": 0}, instability=5.0 ) clamped_high = calls[0] calls = _capture_effects_call(monkeypatch) await piper.synthesize_spirit_voice( "hello", voice, {"noise": 0.03, "bitcrush": 0}, instability=1.0 ) at_one = calls[0] assert clamped_high == at_one @pytest.mark.asyncio async def test_instability_adds_on_top_of_profile_bitcrush(monkeypatch): calls = _capture_effects_call(monkeypatch) voice = VOICES["lessac"] await piper.synthesize_spirit_voice( "hello", voice, {"noise": 0.03, "bitcrush": 4}, instability=1.0 ) assert calls[0]["bitcrush_bits"] > 4