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
|
"""Tests for MML template generation."""
import pytest
from mml_composemusic_mcp.server import _execute_compose_mml
from mml_composemusic_mcp.templates import DESCRIPTIONS, TEMPLATES, get_template
def compose_mml(**kwargs):
return _execute_compose_mml(**kwargs).result
TEMPLATE_NAMES = (
"basic",
"melody",
"chord",
"drum",
"empty",
"expressive_lead",
"vibrato_lead",
"pitch_motion",
)
def test_all_templates_exist():
for mode in ("ppmck", "pyxel"):
for name in TEMPLATE_NAMES:
assert mode in TEMPLATES
assert name in TEMPLATES[mode]
assert TEMPLATES[mode][name]
def test_all_descriptions_exist():
for name in TEMPLATE_NAMES:
assert name in DESCRIPTIONS
assert DESCRIPTIONS[name]
def test_get_template_basic():
mml, desc = get_template("ppmck", "basic")
assert mml
assert desc == "基本的な4ch構成(メロディ+和音+ベース+リズム)"
def test_get_template_melody():
mml, desc = get_template("pyxel", "melody")
assert mml
assert desc == "メロディ重視(Pulse1主旋律、他は伴奏最小限)"
def test_get_template_chord():
mml, desc = get_template("ppmck", "chord")
assert mml
assert desc == "コード伴奏重視(Pulse2で和音、Triangleでベース)"
def test_get_template_drum():
mml, desc = get_template("pyxel", "drum")
assert mml
assert desc == "リズム重視(Noise中心のビートパターン)"
def test_get_template_empty():
mml, desc = get_template("ppmck", "empty")
assert mml
assert desc == "各チャンネルのヘッダーのみ(空のテンプレート)"
@pytest.mark.parametrize("mode", ["ppmck", "pyxel"])
@pytest.mark.parametrize("template", TEMPLATE_NAMES)
def test_template_validates(mode, template):
mml, _ = get_template(mode, template)
result = compose_mml(action="validate", mml=mml, mode=mode)
assert result["valid"] is True, f"{mode}/{template} invalid: {result['errors']}"
def test_template_ppmck_has_headers():
mml, _ = get_template("ppmck", "basic")
assert "#TITLE" in mml
def test_template_pyxel_has_track_headers():
mml, _ = get_template("pyxel", "basic")
assert "0:" in mml
assert "1:" in mml
assert "2:" in mml
assert "3:" in mml
def test_template_empty_has_rests():
mml, _ = get_template("ppmck", "empty")
# Even empty template should have rests (not truly empty)
assert "r" in mml.lower()
def test_template_invalid_name_falls_back():
result = compose_mml(action="template", mode="ppmck", template="nonexistent")
assert result["mml"]
assert result["description"]
@pytest.mark.parametrize(
("mode", "template", "event_types"),
[
("ppmck", "expressive_lead", {"vol_envelope", "duty_envelope", "rel_volume"}),
("pyxel", "expressive_lead", {"envelope"}),
("ppmck", "vibrato_lead", {"lfo", "pitch_envelope", "detune"}),
("pyxel", "vibrato_lead", {"vibrato"}),
("ppmck", "pitch_motion", {"note_envelope", "sweep"}),
("pyxel", "pitch_motion", {"glide"}),
],
)
def test_synthesis_templates_contain_expected_events(mode, template, event_types):
mml, _ = get_template(mode, template)
result = compose_mml(action="validate", mml=mml, mode=mode)
actual_types = {
event["type"]
for channel in result["note_sequence"]["channels"].values()
for event in channel["events"]
}
assert event_types <= actual_types
def test_pyxel_vibrato_template_applies_detune_to_notes():
mml, _ = get_template("pyxel", "vibrato_lead")
result = compose_mml(action="validate", mml=mml, mode="pyxel")
notes = [
event
for channel in result["note_sequence"]["channels"].values()
for event in channel["events"]
if event["type"] == "note"
]
assert any(note["detune_cents"] != 0 for note in notes)
|