/
/
/
1"""Tests for the smart-fades transition plan value objects."""
2
3from __future__ import annotations
4
5import pytest
6
7from music_assistant.controllers.streams.smart_fades.models import (
8 EqPlan,
9 TempoPlan,
10 TransitionPlan,
11 TransitionTier,
12)
13
14
15class TestTempoPlan:
16 """Cover the TempoPlan stretch-savings integral and truthiness."""
17
18 def test_empty_plan_is_falsy_and_saves_nothing(self) -> None:
19 """An empty ramp stretches no time."""
20 plan = TempoPlan()
21 assert not plan
22 assert plan.savings_until(45.0) == 0.0
23
24 def test_non_empty_plan_is_truthy(self) -> None:
25 """A plan with steps is truthy so renderers gate on it directly."""
26 assert TempoPlan(steps=[(0.0, 1.05)])
27
28 def test_speed_up_saves_positive_time(self) -> None:
29 """A ratio > 1 (faster) removes time from the rendered stream."""
30 plan = TempoPlan(steps=[(35.0, 1.0), (40.0, 1.05)])
31 # ratio-1.0 segment [35,40] saves nothing; [40,45] runs at 1.05
32 assert plan.savings_until(45.0) == pytest.approx(5.0 * (1.0 - 1.0 / 1.05))
33 assert plan.savings_until(40.0) == 0.0
34
35 def test_slow_down_lengthens_stream(self) -> None:
36 """A ratio < 1 (slower) yields negative savings (stream lengthened)."""
37 plan = TempoPlan(steps=[(35.0, 1.0), (40.0, 0.95)])
38 assert plan.savings_until(45.0) == pytest.approx(5.0 * (1.0 - 1.0 / 0.95))
39
40 def test_first_step_after_zero_stretches_from_start(self) -> None:
41 """Rubberband starts at the first step's ratio, so the pre-step span is stretched."""
42 plan = TempoPlan(steps=[(20.0, 1.004)])
43 assert plan.savings_until(10.0) == pytest.approx(10.0 * (1.0 - 1.0 / 1.004))
44 assert plan.savings_until(45.0) == pytest.approx(45.0 * (1.0 - 1.0 / 1.004))
45
46
47def test_transition_plan_defaults_to_neutral_eq() -> None:
48 """TransitionPlan can be created without eq_plan and defaults to neutral."""
49 plan = TransitionPlan(
50 tier=TransitionTier.QUICK_FADE, fade_out_window=10.0, crossfade_duration=5.0
51 )
52 assert plan.eq_plan.low_out is None
53 assert plan.eq_plan.mid_out is None
54
55
56def test_eq_plan_neutral_factory() -> None:
57 """EqPlan.neutral() factory creates a plan with all schedules None."""
58 eq = EqPlan.neutral(swap_at=2.5)
59 assert eq.swap_at == 2.5
60 assert all(
61 s is None for s in (eq.low_out, eq.low_in, eq.high_out, eq.high_in, eq.mid_out, eq.mid_in)
62 )
63