Aya / tests /test_performance.py
Ransom070's picture
Aya: a Reachy Mini that sings songs written on the 0G Compute Network
5ac2df3 verified
Raw History Blame Contribute Delete
4.66 kB
"""Tests for rendering and performing.
This is where the audio clock and the dance clock have to agree, and where stopping
has to actually stop.
"""
from __future__ import annotations
import threading
import numpy as np
import pytest
from aya.band import render_band
from aya.score import Score
from tests.conftest import FakeRobot, FakeVoice
from aya.performance import AnyEvent, Performance, render_song
def test_a_rendered_song_carries_its_score_and_stays_inside_range(voice: FakeVoice, song_score: Score) -> None:
song = render_song(song_score, voice)
assert song.score is song_score
assert song.duration_s > song_score.duration_seconds
assert float(np.max(np.abs(song.audio))) <= 1.0
def test_the_band_can_be_left_out(voice: FakeVoice, song_score: Score) -> None:
with_band = render_song(song_score, voice, with_band=True)
without = render_song(song_score, voice, with_band=False)
# Both mixes are normalised to the same peak, so comparing loudness proves nothing,
# and comparing frequency bands proves nothing either when the voice is a low one.
# Correlating each mix against the backing track asks the question directly: is the
# band in this recording?
band = render_band(song_score, with_band.sample_rate)
lead_in = int(with_band.lead_in_s * with_band.sample_rate)
length = min(band.size, with_band.audio.size - lead_in, without.audio.size - lead_in)
present = float(np.dot(with_band.audio[lead_in : lead_in + length], band[:length]))
absent = float(np.dot(without.audio[lead_in : lead_in + length], band[:length]))
assert present > absent * 2
def test_rendering_reports_the_stages_it_goes_through(voice: FakeVoice, song_score: Score) -> None:
stages: list[str] = []
render_song(song_score, voice, on_progress=lambda stage, fraction: stages.append(stage))
assert "singing" in stages
assert "band" in stages
assert stages[-1] == "done"
def test_playing_a_song_pushes_all_of_its_audio(
voice: FakeVoice, song_score: Score, robot: FakeRobot, running: threading.Event
) -> None:
song = render_song(song_score, voice, with_band=False)
finished = Performance(song).play(robot, running)
assert finished is True
expected = song.duration_s * robot.media.get_output_audio_samplerate()
assert robot.media.total_played >= expected * 0.98
assert robot.media.playing is False
def test_playing_a_song_drives_the_robot(
voice: FakeVoice, song_score: Score, robot: FakeRobot, running: threading.Event
) -> None:
song = render_song(song_score, voice, with_band=False)
Performance(song).play(robot, running)
assert len(robot.targets) > 10
assert all("head" in target for target in robot.targets)
assert all(target["head"].shape == (4, 4) for target in robot.targets)
def test_a_stopped_song_stops_and_drops_what_was_queued(voice: FakeVoice, song_score: Score, robot: FakeRobot) -> None:
song = render_song(song_score, voice, with_band=False)
stopped = threading.Event()
stopped.set()
finished = Performance(song).play(robot, stopped)
assert finished is False
assert robot.media.cleared == 1
assert robot.media.playing is False
def test_the_dance_follows_musical_time_not_wall_clock(voice: FakeVoice, song_score: Score) -> None:
song = render_song(song_score, voice)
performance = Performance(song, lead_in_s=0.5)
# During the lead-in the dance has not started; a beat later it is at beat one.
quiet = performance.pose_at(0.0)
first_beat = performance.pose_at(0.5 + song_score.seconds_per_beat)
assert quiet is not None and first_beat is not None
assert performance.pose_at(0.5).body_yaw_deg == pytest.approx(performance.pose_at(0.5).body_yaw_deg)
def test_speaking_an_announcement_plays_audio_and_moves(
voice: FakeVoice, song_score: Score, robot: FakeRobot, running: threading.Event
) -> None:
song = render_song(song_score, voice, with_band=False)
Performance(song).speak(robot, voice, "Here is a test.", running)
assert robot.media.total_played > 0
assert robot.targets
def test_speaking_nothing_plays_nothing(
voice: FakeVoice, song_score: Score, robot: FakeRobot, running: threading.Event
) -> None:
song = render_song(song_score, voice, with_band=False)
Performance(song).speak(robot, voice, " ", running)
assert robot.media.total_played == 0
def test_any_event_is_set_when_any_of_its_events_is() -> None:
first, second = threading.Event(), threading.Event()
combined = AnyEvent(first, second)
assert combined.is_set() is False
second.set()
assert combined.is_set() is True