"""How loud UI sounds are, and the album picker's two sounds.""" from __future__ import annotations import io import wave from collections.abc import Callable import pytest from musicmouse.blubb import RATE, confirm_wav, tick_wav from musicmouse.sound import MIN_PLAYER_VOLUME, ui_level def test_ui_sounds_follow_the_music_volume() -> None: assert ui_level(0.8, 1.0) == pytest.approx(0.8) assert ui_level(1.0, 0.5) == pytest.approx(0.5) def test_ui_sounds_never_drop_below_the_minimum_player_volume() -> None: # A muted player must not mute the timer, nor make the album picker silent. assert ui_level(0.0, 1.0) == pytest.approx(MIN_PLAYER_VOLUME) assert ui_level(0.0, 0.5) == pytest.approx(MIN_PLAYER_VOLUME * 0.5) def test_parents_can_still_turn_ui_sounds_off() -> None: assert ui_level(0.8, 0.0) == 0.0 @pytest.mark.parametrize(("make", "longest"), [(tick_wav, 0.05), (confirm_wav, 0.4)]) def test_the_picker_sounds_are_short_mono_wavs(make: Callable[..., bytes], longest: float) -> None: with wave.open(io.BytesIO(make(gain=0.5))) as wav: assert (wav.getnchannels(), wav.getsampwidth(), wav.getframerate()) == (1, 2, RATE) assert 0 < wav.getnframes() / RATE < longest def test_the_gain_sets_the_peak() -> None: def peak(data: bytes) -> int: with wave.open(io.BytesIO(data)) as wav: frames = wav.readframes(wav.getnframes()) return max( abs(int.from_bytes(frames[i : i + 2], "little", signed=True)) for i in range(0, len(frames), 2) ) assert peak(tick_wav(gain=1.0)) == pytest.approx(32767, abs=1) assert peak(tick_wav(gain=0.25)) == pytest.approx(32767 / 4, abs=1)