From bf36a773918959eeb6ffe7dae2fb8b0874438eb4 Mon Sep 17 00:00:00 2001 From: tanaka takashi Date: Sun, 12 Jul 2026 17:58:19 +0900 Subject: feat(ppmck): mckc準拠の構文拡張とIR保持コマンド追加 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - @をデューティ比、qをクオンタイズに修正(mckc原本準拠) - ^タイ、Dディチューン、sスイープ、v+/v-相対音量を追加 - @v/@@/@MP/@EP/@EN エンベロープ・LFO系を構文解析してIR保持 - 上記IR保持コマンドは合成未対応を SEMANTIC_UNSUPPORTED_FEATURE で警告 - Design.md / MML_BNF.md を更新 - 全200テスト + ruff チェック通過 --- src/mml_composemusic_mcp/ir.py | 117 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 114 insertions(+), 3 deletions(-) (limited to 'src/mml_composemusic_mcp/ir.py') diff --git a/src/mml_composemusic_mcp/ir.py b/src/mml_composemusic_mcp/ir.py index 8d1bc25..4dd7978 100644 --- a/src/mml_composemusic_mcp/ir.py +++ b/src/mml_composemusic_mcp/ir.py @@ -133,6 +133,35 @@ class RepeatEvent: duration: int = 0 +@dataclass +class QuantizeEvent: + type: str = "quantize" + tick_position: int = 0 + value: int = 8 # 1-8 + + +@dataclass +class DetuneEvent: + type: str = "detune" + tick_position: int = 0 + value: int = 0 # -127 to 126 + + +@dataclass +class SweepEvent: + type: str = "sweep" + tick_position: int = 0 + speed: int = 0 + depth: int = 0 + + +@dataclass +class RelativeVolumeEvent: + type: str = "rel_volume" + tick_position: int = 0 + delta: int = 0 + + @dataclass class EnvelopeEvent: type: str = "envelope" @@ -141,6 +170,61 @@ class EnvelopeEvent: points: list[dict[str, int]] = field(default_factory=list) +@dataclass +class VolumeEnvelopeEvent: + type: str = "vol_envelope" + tick_position: int = 0 + slot: int = 0 + points: list[int] = field(default_factory=list) + loop_points: list[int] = field(default_factory=list) + is_definition: bool = False + + +@dataclass +class DutyEnvelopeEvent: + type: str = "duty_envelope" + tick_position: int = 0 + slot: int = 0 + points: list[int] = field(default_factory=list) + loop_points: list[int] = field(default_factory=list) + is_definition: bool = False + + +@dataclass +class LfoEvent: + type: str = "lfo" + tick_position: int = 0 + slot: int = 0 + delay: int = 0 + speed: int = 0 + depth: int = 0 + transition: int = 0 + is_definition: bool = False + is_off: bool = False + + +@dataclass +class PitchEnvEvent: + type: str = "pitch_envelope" + tick_position: int = 0 + slot: int = 0 + points: list[int] = field(default_factory=list) + loop_points: list[int] = field(default_factory=list) + is_definition: bool = False + is_off: bool = False + + +@dataclass +class NoteEnvEvent: + type: str = "note_envelope" + tick_position: int = 0 + slot: int = 0 + points: list[int] = field(default_factory=list) + loop_points: list[int] = field(default_factory=list) + is_definition: bool = False + is_off: bool = False + + @dataclass class VibratoEvent: type: str = "vibrato" @@ -164,7 +248,16 @@ Event = ( | DutyEvent | TempoEvent | RepeatEvent + | QuantizeEvent + | DetuneEvent + | SweepEvent + | RelativeVolumeEvent | EnvelopeEvent + | VolumeEnvelopeEvent + | DutyEnvelopeEvent + | LfoEvent + | PitchEnvEvent + | NoteEnvEvent | VibratoEvent | GlideEvent ) @@ -209,9 +302,27 @@ class NoteSequence: def event_to_dict(event: Event) -> dict[str, Any]: if isinstance( - event, (NoteEvent, RestEvent, VolumeEvent, DutyEvent, TempoEvent, RepeatEvent) + event, + ( + NoteEvent, + RestEvent, + VolumeEvent, + DutyEvent, + TempoEvent, + RepeatEvent, + QuantizeEvent, + DetuneEvent, + SweepEvent, + RelativeVolumeEvent, + EnvelopeEvent, + VolumeEnvelopeEvent, + DutyEnvelopeEvent, + LfoEvent, + PitchEnvEvent, + NoteEnvEvent, + VibratoEvent, + GlideEvent, + ), ): return event.__dict__ - if isinstance(event, (EnvelopeEvent, VibratoEvent, GlideEvent)): - return event.__dict__ raise TypeError(f"Unsupported event type: {type(event)}") -- cgit v1.2.3