diff options
| author | tanaka takashi <isazaka@gmail.com> | 2026-07-11 19:35:37 +0900 |
|---|---|---|
| committer | tanaka takashi <isazaka@gmail.com> | 2026-07-11 19:35:37 +0900 |
| commit | c2e5098d030fe902eff258e10d2fefc01226e4bf (patch) | |
| tree | 70885d7bae28fde887580d7b631cfa987ea12f19 /src/mml_composemusic_mcp | |
| parent | 93ca8050531ea99fa1d10ffb7d3f2c965e3a4ebe (diff) | |
テスト追加と修正
Diffstat (limited to 'src/mml_composemusic_mcp')
| -rw-r--r-- | src/mml_composemusic_mcp/ir.py | 1 | ||||
| -rw-r--r-- | src/mml_composemusic_mcp/parser_ppmck.py | 2 | ||||
| -rw-r--r-- | src/mml_composemusic_mcp/parser_pyxel.py | 44 | ||||
| -rw-r--r-- | src/mml_composemusic_mcp/server.py | 1 | ||||
| -rw-r--r-- | src/mml_composemusic_mcp/templates.py | 4 |
5 files changed, 11 insertions, 41 deletions
diff --git a/src/mml_composemusic_mcp/ir.py b/src/mml_composemusic_mcp/ir.py index 05ef37a..87385df 100644 --- a/src/mml_composemusic_mcp/ir.py +++ b/src/mml_composemusic_mcp/ir.py @@ -61,6 +61,7 @@ class NoteEvent: velocity: int = 15 duty: int = 2 gate_time: float = 1.0 + detune_cents: float = 0.0 @dataclass diff --git a/src/mml_composemusic_mcp/parser_ppmck.py b/src/mml_composemusic_mcp/parser_ppmck.py index c2a1fa3..34a7dd2 100644 --- a/src/mml_composemusic_mcp/parser_ppmck.py +++ b/src/mml_composemusic_mcp/parser_ppmck.py @@ -382,7 +382,7 @@ class PpmckParser: ch = self.note_sequence.channels[self.current_channel] # type: ignore[index] for ev in reversed(ch.events): if isinstance(ev, (NoteEvent, RestEvent)): - return ev.tick_position + return ev.tick_position + ev.duration return self.tick_position def _extend_last_note(self, ticks: int) -> None: diff --git a/src/mml_composemusic_mcp/parser_pyxel.py b/src/mml_composemusic_mcp/parser_pyxel.py index 7d3707b..3bacb5b 100644 --- a/src/mml_composemusic_mcp/parser_pyxel.py +++ b/src/mml_composemusic_mcp/parser_pyxel.py @@ -139,7 +139,6 @@ class PyxelParser: TokenType.LENGTH: self._parse_length, TokenType.VOLUME: self._parse_volume, TokenType.GATE_TIME: self._parse_gate_time, - TokenType.DUTY: self._parse_duty, TokenType.AT: self._parse_at, TokenType.TEMPO: self._parse_tempo, TokenType.TRANSPOSE: self._parse_transpose, @@ -213,6 +212,7 @@ class PyxelParser: velocity=self._normalize_velocity(self.velocity), duty=self.duty, gate_time=self.gate_time, + detune_cents=float(self.detune_cents), ) self._add_event(event) self.tick_position += ticks @@ -228,12 +228,12 @@ class PyxelParser: def _parse_octave(self) -> None: token = self.ctx.advance() value = int(token.value) - if not -1 <= value <= 9: + if not 0 <= value <= 7: self.ctx.add_error( code=ErrorCode.SYNTAX_VALUE_OUT_OF_RANGE, line=token.line, column=token.column, - message=f"'O' の値 {value} は範囲外です。有効範囲: -1〜9。", + message=f"'O' の値 {value} は範囲外です。有効範囲: 0〜7。", severity="error", hint="例: O4 のように指定してください。", context=self._context_line(token), @@ -243,12 +243,12 @@ class PyxelParser: def _parse_octave_up(self) -> None: self.ctx.advance() - if self.octave < 9: + if self.octave < 7: self.octave += 1 def _parse_octave_down(self) -> None: self.ctx.advance() - if self.octave > -1: + if self.octave > 0: self.octave -= 1 def _parse_length(self) -> None: @@ -308,38 +308,6 @@ class PyxelParser: return self.gate_time = value / 100.0 - def _parse_duty(self, cmd_raw: str = "Q") -> None: - # Q command maps to duty per Design_mml legacy section - token = self.ctx.peek() - value = int(token.value) - if not 0 <= value <= 3: - self.ctx.add_error( - code=ErrorCode.SYNTAX_VALUE_OUT_OF_RANGE, - line=token.line, - column=token.column, - message=f"'{cmd_raw}' の値 {value} は範囲外です。有効範囲: 0〜3。", - severity="error", - hint=f"例: {cmd_raw}2 のように指定してください。", - context=self._context_line(token), - ) - self.ctx.advance() - return - if self.current_channel not in PULSE_CHANNELS: - self.ctx.add_error( - code=ErrorCode.SYNTAX_CHANNEL_MISMATCH, - line=token.line, - column=token.column, - message=f"チャンネル '{self.current_channel}' で '{cmd_raw}' は使用できません。", - severity="warning", - hint=f"{self.current_channel}チャンネルではデューティ比を持ちません。{cmd_raw} コマンドを削除してください。", - context=self._context_line(token), - ) - self.ctx.advance() - return - self.duty = value - self._add_event(DutyEvent(tick_position=self.tick_position, value=value)) - self.ctx.advance() - def _parse_at(self) -> None: token = self.ctx.advance() value = token.value @@ -442,7 +410,7 @@ class PyxelParser: ch = self.note_sequence.channels[self.current_channel] # type: ignore[index] for ev in reversed(ch.events): if isinstance(ev, (NoteEvent, RestEvent)): - return ev.tick_position + return ev.tick_position + ev.duration return self.tick_position def _extend_last_note(self, ticks: int) -> None: diff --git a/src/mml_composemusic_mcp/server.py b/src/mml_composemusic_mcp/server.py index 1205b70..90d4dab 100644 --- a/src/mml_composemusic_mcp/server.py +++ b/src/mml_composemusic_mcp/server.py @@ -88,6 +88,7 @@ def compose_mml( return { "valid": not _is_error(errors), "errors": errors_list, + "warnings": warnings_list, "note_sequence": note_sequence_dict, "channel_summary": build_channel_summary(note_sequence_dict) if note_sequence_dict diff --git a/src/mml_composemusic_mcp/templates.py b/src/mml_composemusic_mcp/templates.py index f351583..1a644fc 100644 --- a/src/mml_composemusic_mcp/templates.py +++ b/src/mml_composemusic_mcp/templates.py @@ -6,14 +6,14 @@ TEMPLATES = { "melody": '#TITLE "Melody Lead"\n#COMPOSER "LLM"\n\nA t120 l8 o5 v15 q2\n c e g e | d f a f | e g b g | c4 r4\n\nB l8 o4 v10 q1\n c e g e | c e g e | d f a f | c4 r4\n\nT l4 o3 v7\n c2 g2 | a2 f2 | g2 d2 | c2 r2\n\nN l8 v8\n c r c r | c r c r | c r c r | c r c r\n', "chord": '#TITLE "Chord Backing"\n#COMPOSER "LLM"\n\nA t120 l4 o4 v12 q2\n c e g c | f a > c f | g b > d g | c e g c\n\nB l4 o4 v12 q1\n e g b e | f a > c f | d g b d | e g b e\n\nT l2 o3 v7\n c2 | f2 | g2 | c2\n\nN l8 v8\n r c r c | r c r c | r c r c | r c r c\n', "drum": '#TITLE "Drum Beat"\n#COMPOSER "LLM"\n\nA t120 l8 o4 v15 q2\n c r e r | g r e r | c r e r | g r c r\n\nB l8 o3 v12 q1\n e r g r | e r g r | e r g r | e r g r\n\nT l4 o2 v7\n c2 c2 | c2 c2 | c2 c2 | c2 c2\n\nN l16 v10\n c r c r c r c r | c r c r c r c r\n c r c r c r c r | c r c r c r c r\n', - "empty": '#TITLE "Empty Template"\n#COMPOSER "LLM"\n\nA t120 l4 o4 v15 q2\n \n\nB l4 o3 v12 q1\n \n\nT l4 o3 v7\n \n\nN l4 v10\n \n', + "empty": '#TITLE "Empty Template"\n#COMPOSER "LLM"\n\nA t120 l4 o4 v15 q2\n r1\n\nB l4 o3 v12 q1\n r1\n\nT l4 o3 v7\n r1\n\nN l4 v10\n r1\n', }, "pyxel": { "basic": "0: T120 L8 O4 V100 @1\n C D E F | G A B >C\n\n1: L8 O3 V80 @2\n E G B R | E G B R\n\n2: L4 O3 V60\n C2 G2 E2 C2\n\n3: L8 V80\n C R C R | C R C R\n", "melody": "0: T120 L8 O5 V100 @1\n C E G E | D F A F | E G B G | C4 R4\n\n1: L8 O4 V80 @2\n C E G E | C E G E | D F A F | C4 R4\n\n2: L4 O3 V60\n C2 G2 | A2 F2 | G2 D2 | C2 R2\n\n3: L8 V70\n C R C R | C R C R | C R C R | C R C R\n", "chord": "0: T120 L4 O4 V90 @1\n C E G C | F A >C F | G B >D G | C E G C\n\n1: L4 O4 V90 @2\n E G B E | F A >C F | D G B D | E G B E\n\n2: L2 O3 V60\n C2 | F2 | G2 | C2\n\n3: L8 V70\n R C R C | R C R C | R C R C | R C R C\n", "drum": "0: T120 L8 O4 V100 @1\n C R E R | G R E R | C R E R | G R C R\n\n1: L8 O3 V80 @2\n E R G R | E R G R | E R G R | E R G R\n\n2: L4 O2 V60\n C2 C2 | C2 C2 | C2 C2 | C2 C2\n\n3: L16 V80\n C R C R C R C R | C R C R C R C R\n C R C R C R C R | C R C R C R C R\n", - "empty": "0: T120 L4 O4 V100 @1\n \n\n1: L4 O3 V80 @2\n \n\n2: L4 O3 V60\n \n\n3: L4 V80\n \n", + "empty": "0: T120 L4 O4 V100 @1\n R1\n\n1: L4 O3 V80 @2\n R1\n\n2: L4 O3 V60\n R1\n\n3: L4 V80\n R1\n", }, } |
