"""Reset detector must not stop the stream or clear ``running``.""" import os import sys import time 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.audio_detector import AudioBeatDetector # noqa: E402 class _FakeRuntime: def __init__(self): self.reset_calls = 0 def reset_state(self): self.reset_calls += 1 def test_reset_tracking_false_when_not_running(): det = AudioBeatDetector() assert det.reset_tracking() is False def test_reset_tracking_queues_on_audio_thread(): det = AudioBeatDetector() rt = _FakeRuntime() with det._lock: det._running = True det._runtime = rt det._status["running"] = True det._status["bpm"] = 128.0 det._status["beat_seq"] = 7 assert det.reset_tracking() is True assert rt.reset_calls == 0 assert det._pending_reset is True st = det.status() assert st["running"] is True assert st["bpm"] == 128.0 assert st["beat_seq"] == 7 det._process_pending_reset(rt) assert rt.reset_calls == 1 assert det._pending_reset is False assert det.status()["running"] is True def test_status_keeps_bpm_during_holdover(): det = AudioBeatDetector() with det._lock: det._running = True det._holdover_active = True det._status["running"] = True det._status["bpm"] = 128.0 det._status["last_beat_ts"] = time.time() - 10.0 assert det.status()["bpm"] == 128.0 class _FakeRuntimeGap: def __init__(self): self.reset_tempo_calls = 0 def reset_tempo_state(self): self.reset_tempo_calls += 1 def test_silence_gap_starts_holdover_and_resets_tempo_once(): det = AudioBeatDetector() rt = _FakeRuntimeGap() with det._lock: det._running = True det._status["running"] = True det._status["bpm"] = 120.0 det._last_real_beat_ts = time.time() - 10.0 det._maybe_recover_after_silence_gap(rt) assert rt.reset_tempo_calls == 1 assert det._holdover_active is True det._maybe_recover_after_silence_gap(rt) assert rt.reset_tempo_calls == 1 det._record_beat(120.0) assert det._holdover_active is False def test_holdover_last_beat_does_not_block_tempo_retry(): """Holdover refreshes last_beat_ts but recovery uses last real beat only.""" det = AudioBeatDetector() rt = _FakeRuntimeGap() with det._lock: det._running = True det._status["running"] = True det._status["bpm"] = 120.0 det._last_real_beat_ts = time.time() - 10.0 det._maybe_recover_after_silence_gap(rt) assert rt.reset_tempo_calls == 1 with det._lock: det._status["last_beat_ts"] = time.time() det._last_gap_tempo_reset_ts = time.time() - 10.0 det._maybe_recover_after_silence_gap(rt) assert rt.reset_tempo_calls == 2 assert det._holdover_active is True