"""Deferred sequence start on beat / downbeat.""" import asyncio import os import sys PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) SRC_PATH = os.path.join(PROJECT_ROOT, "src") if SRC_PATH not in sys.path: sys.path.insert(0, SRC_PATH) from util import sequence_playback as sp # noqa: E402 def test_normalize_wait_for(): assert sp._normalize_wait_for({"wait_for": "beat"}) == "beat" assert sp._normalize_wait_for({"start_on": "downbeat"}) == "downbeat" assert sp._normalize_wait_for({"wait_for": "next_beat"}) == "beat" assert sp._normalize_wait_for({}) is None assert sp._play_options_without_wait({"wait_for": "beat", "zone_id": "1"}) == {"zone_id": "1"} def test_pending_play_status_empty(): sp.clear_pending_play() assert sp.pending_play_status() == {"pending": False} def test_queue_and_clear_pending(): sp.clear_pending_play() sp._queue_pending_start("z1", "s1", "p1", {"simulated_bpm": 120}, "beat", bpm=120.0) st = sp.pending_play_status() assert st["pending"] is True assert st["wait_for"] == "beat" assert st["sequence_id"] == "s1" sp.clear_pending_play() assert sp.pending_play_status()["pending"] is False def test_try_consume_pending_beat(): sp.clear_pending_play() sp._queue_pending_start("z1", "s1", "p1", None, "beat", bpm=120.0) async def fake_start(*_a, **_k): return None sp._start_immediate = fake_start # type: ignore[method-assign] assert asyncio.run(sp._try_consume_pending_play(is_downbeat=False)) is True assert sp.pending_play_status()["pending"] is False def test_try_consume_pending_downbeat_skips_upbeat(): sp.clear_pending_play() sp._queue_pending_start("z1", "s1", "p1", None, "downbeat", bpm=120.0) assert asyncio.run(sp._try_consume_pending_play(is_downbeat=False)) is False assert sp.pending_play_status()["pending"] is True async def fake_start(*_a, **_k): return None sp._start_immediate = fake_start # type: ignore[method-assign] assert asyncio.run(sp._try_consume_pending_play(is_downbeat=True)) is True sp.clear_pending_play() def test_downbeat_start_counts_trigger_beat(monkeypatch): """The downbeat that starts playback is beat 1 of the step, not beat 0.""" sp.clear_pending_play() sp.stop() async def fake_start(_z, _s, _p, _opts): sp._beat_run = { "lanes": [[{"preset_id": "1", "beats": 4}]], "lane_states": [{"stepIdx": 0, "beatCount": 0, "done": False}], "num_lanes": 1, "sequence_loop_beat": 0, } monkeypatch.setattr(sp, "_start_immediate", fake_start) sp._queue_pending_start("z1", "s1", "p1", None, "downbeat", bpm=120.0) async def run(): assert await sp._try_consume_pending_play(is_downbeat=True) is True await sp.process_active_beat_advance() asyncio.run(run()) assert sp._beat_run["lane_states"][0]["beatCount"] == 1 sp.stop()