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["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["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"] == "SYNTAX_INVALID_TOKEN" 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["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()
|