summaryrefslogtreecommitdiff
path: root/tests/test_compose_mml_tool.py
blob: eb1b09355c5f00bf18ad93583733f681ccd6c3b2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
"""Exhaustive tests for the compose_mml MCP tool."""

from pathlib import Path

VALID_PPMCK = """#TITLE "Test"
A t120 l4 o4 v15 q2
  c e g c
B o3
  c e g
T l4 o3 v7
  c2 g2
N l4 v10
  c r c r
"""

VALID_PYXEL = """0: T120 L4 O4 V100 @1
  C E G C
1: O3
  C E G
2: L4 O3 V60
  C2 G2
3: L4 V80
  C R C R
"""


def test_compose_ppmck(compose_mml, tmp_output_dir):
    result = compose_mml(action="compose", mml=VALID_PPMCK, mode="ppmck")
    assert result["success"] is True
    assert result["wav_path"] is not None
    assert Path(result["wav_path"]).exists()
    assert result["duration_sec"] > 0
    assert result["note_sequence"] is not None
    assert "validation" in result
    assert "errors" in result["validation"]
    assert "warnings" in result["validation"]


def test_compose_pyxel(compose_mml, tmp_output_dir):
    result = compose_mml(action="compose", mml=VALID_PYXEL, mode="pyxel")
    assert result["success"] is True
    assert result["wav_path"] is not None
    assert Path(result["wav_path"]).exists()
    assert result["duration_sec"] > 0


def test_validate_ppmck(compose_mml):
    result = compose_mml(action="validate", mml=VALID_PPMCK, mode="ppmck")
    assert result["valid"] is True
    assert "warnings" in result
    assert result["channel_summary"]
    for ch in result["channel_summary"]:
        assert "channel" in ch
        assert "note_count" in ch
        assert "octave_range" in ch
        assert "duration_ticks" in ch


def test_validate_pyxel(compose_mml):
    result = compose_mml(action="validate", mml=VALID_PYXEL, mode="pyxel")
    assert result["valid"] is True
    assert "warnings" in result
    assert result["channel_summary"]


def test_template_all(compose_mml):
    for mode in ("ppmck", "pyxel"):
        for template in ("basic", "melody", "chord", "drum", "empty"):
            result = compose_mml(action="template", mode=mode, template=template)
            assert "mml" in result
            assert "description" in result
            assert result["mml"]
            assert result["description"]
            validated = compose_mml(action="validate", mml=result["mml"], mode=mode)
            assert validated["valid"] is True, (
                f"{mode}/{template} template invalid: {validated['errors']}"
            )


def test_template_invalid_fallback(compose_mml):
    result = compose_mml(action="template", mode="pyxel", template="unknown")
    assert result["description"] == "基本的な4ch構成(メロディ+和音+ベース+リズム)"


def test_compose_missing_mml(compose_mml):
    result = compose_mml(action="compose", mml="", mode="ppmck")
    assert result["success"] is False
    assert any(e["severity"] == "error" for e in result["validation"]["errors"])


def test_compose_missing_mode(compose_mml):
    result = compose_mml(action="compose", mml="A c", mode="")
    assert result["success"] is False
    assert any(e["severity"] == "error" for e in result["validation"]["errors"])


def test_validate_missing_mode(compose_mml):
    result = compose_mml(action="validate", mml="A c", mode="")
    assert result["valid"] is False


def test_compose_unknown_mode(compose_mml):
    result = compose_mml(action="compose", mml="A c", mode="xyz")
    assert result["success"] is False
    assert any(
        e["code"] == "VALIDATION_INVALID_MODE" for e in result["validation"]["errors"]
    )


def test_compose_with_syntax_error(compose_mml):
    result = compose_mml(action="compose", mml="A t0\n  c", mode="ppmck")
    assert result["success"] is False
    assert result["wav_path"] is None
    assert result["duration_sec"] == 0
    assert any(e["severity"] == "error" for e in result["validation"]["errors"])


def test_compose_with_warning(compose_mml, tmp_output_dir):
    mml = """2: T120 L4 O3 V60 @1
  C4
"""
    result = compose_mml(action="compose", mml=mml, mode="pyxel")
    assert result["success"] is True
    assert result["validation"]["warnings"]


def test_action_invalid(compose_mml):
    result = compose_mml(action="unknown")
    assert result["success"] is False
    assert any("action" in e["message"] for e in result["validation"]["errors"])


def test_compose_sample_rates(compose_mml, tmp_output_dir):
    for rate in (22050, 44100, 48000):
        result = compose_mml(
            action="compose",
            mml="A t120 l4 o4 v15 q2\n  c4",
            mode="ppmck",
            sample_rate=rate,
        )
        assert result["success"] is True, f"sample_rate={rate} failed"
        assert result["duration_sec"] > 0


def test_compose_normalize(compose_mml, tmp_output_dir):
    base = """0: T120 L4 O4 V100 @1
  C4
"""
    on = compose_mml(action="compose", mml=base, mode="pyxel", normalize=True)
    off = compose_mml(action="compose", mml=base, mode="pyxel", normalize=False)
    assert on["success"] and off["success"]
    assert Path(on["wav_path"]).exists()
    assert Path(off["wav_path"]).exists()