From 7249ca3a6fdf51f6b7ea66ac6854edd5e02ae5ec Mon Sep 17 00:00:00 2001 From: tanaka takashi Date: Wed, 15 Jul 2026 20:00:24 +0900 Subject: feat: MMLパーサーと意味解析を厳格化 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ppmckの終端分岐付きリピートとL制御トラックを実装 - case、構文構造、音長、エフェクト値域の検証を強化 - Pyxel拡張コマンドのarityと参照検証を追加 - リピート展開上限と構文・意味エラー時の合成停止を追加 - BNF、設計書、適合監査、回帰テストを更新 --- doc/Design.md | 56 ++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 22 deletions(-) (limited to 'doc/Design.md') diff --git a/doc/Design.md b/doc/Design.md index f345d82..7c3d076 100644 --- a/doc/Design.md +++ b/doc/Design.md @@ -292,10 +292,10 @@ MML文字列 | 相対音量 | `v+` / `v-` (ppmck) | 0〜15へクランプして合成 | | スイープ | `s`+数値,符号付き数値 (ppmck) | 2A03 Pulse sweepへ反映 | | タイ | `^` (ppmck), `&` (両モード) | `^`はタイ、`&`はスラー | -| リピート | `[`, `]` + 数値 | ppmckはASTへ保持してwarning、Pyxelは展開 | +| リピート | `[`, `]` + 数値, `\|` | 両モードで構造検証。ppmckのリピート内`\|`は終端分岐 | | エンベロープ/LFO | `@v`/`@@`/`@MP`/`@EP`/`@EN` (ppmck), `@ENV`/`@VIB`/`@GLI` (pyxel) | IR v2定義表を参照して合成 | | 小節線 | `\|` | 視覚用、再生に影響しない | -| トラック識別 | ppmck: `A`/`B`/`T`/`N`/`L`, pyxel: `数字:` | | +| トラック識別 | ppmck: `A`/`B`/`T`/`N`/`L`, pyxel: `0:`〜`3:` | | | ヘッダー | `#` で始まる行 (ppmck) | | | コメント | `;` 以降行末 (ppmck) | | | 拡張コマンド | `@ENV`/`@VIB`/`@GLI` (pyxel) | 空白区切りの数値引数をIR v2へ保持して合成 | @@ -303,14 +303,16 @@ MML文字列 ### 5.3 Parser(モード別) -Parserの責務は**トークン列からASTを生成するのみ**。値範囲チェックやチャンネル適合性の判断は行わない。 +Parserの責務は**トークン列から構造的に妥当なASTを生成すること**である。必須token、トップレベル順序、リピート対応、空定義を検証する。値範囲チェックやチャンネル適合性の判断は行わない。構文errorが1件でもあればSemanticAnalyzerへ進まない。 #### ppmckパーサ - トラックヘッダー `A`/`B`/`T`/`N`/`L` を認識 - `#` ヘッダーを認識 - 各コマンドをASTノードに変換 -- リピートの開始・終了をAST文として保持し、未対応warningを返す +- トップレベル順序を`headers → global definitions → tracks`として検証 +- `L`を専用文法で解析し、`l`、休符、リピート、小節線以外を拒否 +- リピートの開始・終端分岐・終了を`RepeatStartStmt`/`RepeatBreakStmt`/`RepeatEndStmt`として保持し、対応、分岐重複、ネストを検証 - タイ `^` / スラー `&` の結合対象をASTで保持 - `@v`/`@@`/`@MP`/`@EP`/`@EN` エンベロープ定義 `{...|...}` と使用をASTで保持 @@ -318,7 +320,7 @@ Parserの責務は**トークン列からASTを生成するのみ**。値範囲 - トラックヘッダー `0:`〜`3:` を認識 - `@ENV`/`@VIB`/`@GLI` のパラメータをASTノードに保持 -- リピート `[...]N` の開始・終了をAST文として保持し、SemanticAnalyzerでネストを展開 +- リピート `[...]N` の対応とネストを検証し、開始・終了をAST文として保持 ### 5.4 SemanticAnalyzer(モード別) @@ -330,7 +332,7 @@ SemanticAnalyzerの責務は**ASTを検証・解釈してNoteSequence IRを生 - MIDI音番号変換 - tick計算 - タイ処理 -- リピート展開(pyxel) +- ppmck/pyxel共通のトランザクション型リピート展開(各トラック最大100,000イベント) - 値範囲チェック - 音域チェック @@ -338,11 +340,11 @@ SemanticAnalyzerの責務は**ASTを検証・解釈してNoteSequence IRを生 - Triangle音量無視 - Noise音高無視 -- 全体ループ `L` の処理 +- `L`の時間軸から全チャンネル共通`loop_start`を算出し、4音声チャンネルへ`RepeatEvent(repeat_count=0)`を追加 - `@` = デューティ比、`q` = クオンタイズ(gate_time = value / 8)として解釈 - `^` タイを前の音符の音長延長として解釈 -- `D`/`s`/`v+`/`v-`、各種エンベロープ/LFOをIRへ保持し `SEMANTIC_UNSUPPORTED_FEATURE` warningを返す -- リピートなど合成未対応のASTに対するwarning +- `D`/`s`/`v+`/`v-`、各種エンベロープ/LFOを値域・チャンネル検証後にIRへ保持 +- mckc型の`[前半|後半]N`を展開。回数省略時だけ2周期プレビューwarningを返す #### pyxel固有 @@ -350,7 +352,7 @@ SemanticAnalyzerの責務は**ASTを検証・解釈してNoteSequence IRを生 - ゲートタイム `Q0-Q100` → `0.0-1.0` - `@` コマンドのチャンネル適合性チェック - `@ENV`/`@VIB`/`@GLI` のIR v2定義、選択、解除、合成 -- 無限リピートの2回打ち切りwarning +- 無限リピートの2周期プレビューwarning - `&` スラーを前の音符の音長延長として解釈 --- @@ -361,7 +363,7 @@ SemanticAnalyzerの責務は**ASTを検証・解釈してNoteSequence IRを生 - **モード共通**: ppmck/pyxel両方を1つのASTノード型セットで表現 - **位置情報**: 全ノードに `line`, `column` を保持 -- **構造保持**: リピート開始・終了とタイの結合対象を、意味解析可能な文列として保持 +- **構造保持**: リピート開始・終端分岐・終了とタイの結合対象を、意味解析可能な文列として保持 ### 6.2 主要ASTノード @@ -514,6 +516,10 @@ class NoteEnvOffStmt(ASTNode): class RepeatStartStmt(ASTNode): pass +@dataclass +class RepeatBreakStmt(ASTNode): + pass # ppmckの現在のリピート階層に属する終端分岐 + @dataclass class RepeatEndStmt(ASTNode): count: int | None # None=無限 @@ -595,9 +601,10 @@ class ErrorPhase(Enum): ### 8.2 ppmckモード ```bnf - ::= * * - ::= * -::= "A" | "B" | "T" | "N" | "L" + ::= * * * + ::= | + ::= ("A" | "B" | "T" | "N") * + ::= "L" ( | | | )* ::= | | | | | | @@ -608,7 +615,10 @@ class ErrorPhase(Enum): | | | | | | | | | - | | | + | | +::= "[" * ("|" *)? "]" ? + ::= "[" ( | | )* + ("|" ( | | )*)? "]" ? ``` ### 8.3 pyxelモード @@ -617,12 +627,13 @@ class ErrorPhase(Enum): ::= * ::= * - ::= ":" + ::= "0:" | "1:" | "2:" | "3:" ::= | | | | | | | | | | - | | | | + | | | +::= "[" * "]" ? ``` --- @@ -651,7 +662,7 @@ NoteSequence IRを入力としてPulse1、Pulse2、Triangle、Noiseを合成し |---|---|---| | **大文字/小文字** | 小文字 `c d e`(`D` ディチューンは大文字のみ) | 大文字 `C D E` | | **コマンド** | `o` `l` `v` `v+`/`v-` `t` `@` `q` `^` `&` `D` `s` | `O` `L` `V` `T` `Q` `@` `K` `Y` `&` | -| **チャンネル指定** | `A` `B` `T` `N`(文字) | `0:`〜`3:`(番号) | +| **チャンネル指定** | `A` `B` `T` `N`、制御用`L` | `0:`〜`3:`(番号) | | **Parser** | `parser_ppmck.py` | `parser_pyxel.py` | | **SemanticAnalyzer** | `semantic_ppmck.py` | `semantic_pyxel.py` | | **音量範囲** | 0-15(クラシックAPU準拠) | 0-127 | @@ -660,7 +671,7 @@ NoteSequence IRを入力としてPulse1、Pulse2、Triangle、Noiseを合成し | **タイ/スラー** | `^` = タイ、`&` = スラー | `&` = スラー(タイなし) | | **ディチューン** | `D-127`〜`D126`(セント) | `Y-127`〜`Y127`(セント) | | **エンベロープ/LFO** | `@v`/`@@`/`@MP`/`@EP`/`@EN`(合成対応) | `@ENV`/`@VIB`/`@GLI`(合成対応) | -| **リピート** | `L`(構文上のループトラック)、`[...]`(AST保持・warning、合成未反映) | `[...]`(回数指定、省略時は2回で打ち切り、ネスト対応) | +| **リピート** | `L`からloop_startを算出。`[...]N`と`[前半\|後半]N`をネスト展開 | `[...]N`をネスト展開 | --- @@ -675,6 +686,7 @@ NoteSequence IRを入力としてPulse1、Pulse2、Triangle、Noiseを合成し | 5 | pyxel `@` とトラック番号の矛盾 | トラック番号優先。`@` はPulse系のみデューティ比として有効 | | 6 | Noise note_number → period マッピング | ppmck: `period=8, mode=0` 固定。pyxel: マッピング式適用 | | 7 | 出力構成 | `YYYYMMDD_HHMMSS_mmm/output.wav`, `YYYYMMDD_HHMMSS_mmm/output.mml` | +| 8 | リピート安全上限 | 各トラックの展開後イベント数は100,000件以下。超過時は展開前に意味error | --- @@ -684,12 +696,12 @@ NoteSequence IRを入力としてPulse1、Pulse2、Triangle、Noiseを合成し |---|---| | MCPツール | `compose_mml`(compose/validate/template) | | Lexer | ppmck/pyxel 両モード対応、共通トークン定義、INVALIDトークン。ppmck向けに `@v`/`@@`/`@MP`/`@EP`/`@EN`/`^`/`D`/`s`/`v+`/`v-` トークンを追加 | -| Parser | ppmck/pyxel 別Parser、AST生成。ppmckでエンベロープ定義 `{...\|...}` と各種IR保持コマンドをパース | -| SemanticAnalyzer | ppmck/pyxel 別Analyzer、AST→IR変換。ppmckで `@`=duty、`q`=quantize、`^`=tie を解釈し、未対応コマンドをIR保持+warning | +| Parser | ppmck/pyxel 別Parser。必須token、トップレベル順序、case、リピート対応を検証し、ppmckの`RepeatBreakStmt`と専用L文法を構築 | +| SemanticAnalyzer | ppmck/pyxel別Analyzer、AST→IR変換。共通反復展開、事前イベント上限、値域→チャンネル→状態→IR順の検証、Lのloop_start算出を実装 | | IR | NoteSequence、各種Event(Quantize/Detune/Sweep/RelativeVolume/VolumeEnvelope/DutyEnvelope/Lfo/PitchEnv/NoteEnv 等)、ErrorDetail(phase付き) | | Synthesizer | Pulse/Triangle/Noise 4ch合成、線形ミキシング、WAV出力 | | Templates | basic/melody/chord/drum/empty の5種 × 2モード。ppmckテンプレートは `@` でデューティ比を指定 | -| テスト | pytestの固定・統合テスト、Hypothesisの生成テスト、ruffによるリント/フォーマット。全200テスト通過 | +| テスト | pytestの固定・統合テスト、Hypothesisの生成テスト、ruffによるリント/フォーマット | --- -- cgit v1.2.3