"""Bar phase (beat-in-bar) tracking for audio beat detection.""" import os import sys PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if PROJECT_ROOT not in sys.path: sys.path.insert(0, PROJECT_ROOT) from tests.beat_detect import BarPhaseTracker # noqa: E402 def test_bar_phase_increments_on_non_kick_beats(): tr = BarPhaseTracker(beats_per_bar=4) r1 = tr.on_beat(1.0, "snare", 1.3, bpm=120.0) assert r1["bar_beat"] == 1 r2 = tr.on_beat(1.5, "snare", 1.2, bpm=120.0) assert r2["bar_beat"] == 2 r3 = tr.on_beat(2.0, "hat", 1.1, bpm=120.0) assert r3["bar_beat"] == 3 def test_kick_near_bar_boundary_resets_to_downbeat(): tr = BarPhaseTracker(beats_per_bar=4) tr.on_beat(0.0, "kick", 1.4, bpm=120.0) tr.on_beat(0.5, "snare", 1.2, bpm=120.0) tr.on_beat(1.0, "snare", 1.2, bpm=120.0) tr.on_beat(1.5, "snare", 1.2, bpm=120.0) r = tr.on_beat(2.0, "kick", 1.5, bpm=120.0) assert r["bar_beat"] == 1 assert r["is_downbeat"] is True def test_anchor_downbeat_sets_confidence(): tr = BarPhaseTracker(beats_per_bar=4) tr.anchor_downbeat(10.0) assert tr.bar_beat == 1 assert tr.confidence >= 0.85 def test_reset_tempo_preserves_bar_phase(): from argparse import Namespace from tests.beat_detect import BeatDetectRuntime # noqa: E402 args = Namespace( mode="custom", hop_size=256, win_mult=2, min_band_hz=45.0, max_band_hz=180.0, energy_weight=0.7, flux_weight=0.3, threshold_multiplier=1.35, ema_alpha=0.08, min_ioi_ms=100.0, bpm_window=8, aubio_method="default", aubio_threshold=0.12, beats_per_bar=4, ) rt = BeatDetectRuntime(args) rt.setup(44100) rt.bar_phase.on_beat(0.0, "kick", 1.5, bpm=120.0) rt.bar_phase.on_beat(0.5, "snare", 1.2, bpm=120.0) assert rt.bar_phase.bar_beat == 2 rt.reset_tempo_state() assert rt.bar_phase.bar_beat == 2 rt.reset_state() assert rt.bar_phase.bar_beat == 1