summaryrefslogtreecommitdiff
path: root/doc/Design.md
blob: 92f8dbc44918ec5b69175132178abc8c15f351f3 (plain)
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
# レトロチップ音源作曲MCPサーバ — 統合設計書

> 作成日: 2026-07-11 | 更新日: 2026-07-12 | ステータス: **現行実装準拠**

---

## 1. 概要

### 1.1 目的

LLMがMML(Music Macro Language)を記述することで、レトロチップ音源(2A03 APU)のAPU音源をエミュレートした音楽を作曲・再生できるMCPサーバを設計・実装する。

### 1.2 設計目標

| 目標 | 内容 |
|---|---|
| **LLMフレンドリ** | LLMが生成しやすいMML構文、明確なエラーメッセージ |
| **2モード対応** | `ppmck`モード(クラシックAPU準拠)と`pyxel`モード(Pyxel準拠)を切り替え可能 |
| **正確な音源エミュレーション** | NTSC 2A03 timer、段階波形、Noise LFSR、Sweep、非線形ミキサを再現 |
| **拡張性** | 共通AST・IRによりモード別の構文と音声合成を分離 |
| **単一ツール** | `compose_mml` の1ツールで作曲・検証・テンプレート生成を完結 |

### 1.3 スコープ

| 対象 | 内容 |
|---|---|
| 実装範囲 | Pulse1 / Pulse2 / Triangle / Noiseの4ch MML解析、WAV合成、バリデーション、テンプレート生成 |
| 構文拡張 | Pyxel `@ENV` / `@VIB` / `@GLI`、ppmck `@v` / `@@` / `@MP` / `@EP` / `@EN` / `D` / `s` / `v+` / `v-` / `^` をIR v2から合成へ反映 |
| 対象外 | GUI / Webフロントエンド、DPCM、NSF出力、未定義のppmck拡張構文 |

### 1.4 技術スタック

| 項目 | 選定 | 理由 |
|---|---|---|
| 実装言語 | Python 3.14 | FastMCP対応、numpyで音声合成、LLM親和性 |
| MCP SDK | FastMCP >= 2.1.0 | 優良SDK |
| 音声合成 | numpy >= 2.0.0 | 波形生成の数値計算 |
| 音声出力 | 標準ライブラリ wave | WAV形式、外部依存なし |
| テスト | pytest, Hypothesis, ruff | 単体・生成テスト、リント、フォーマット |

---

## 2. システム全体アーキテクチャ

### 2.1 全体構成

```
┌──────────────────────────────────────────────────────────┐
│                     LLM Client (Claude等)                 │
│                  ┌──────────────────┐                    │
│                  │   compose_mml    │                    │
│                  └────────┬─────────┘                    │
└───────────────────────────┼─────────────────────────────┘
                            │
┌───────────────────────────┴─────────────────────────────┐
│                    MCP Server Layer                       │
│                                                            │
│  ┌──────────────────────────────────────────────────────┐ │
│  │                 Tool Dispatcher                       │ │
│  │   action パラメータで動作を切り替え                      │ │
│  │   (compose / validate / template)                     │ │
│  └──────────────────┬───────────────────────────────────┘ │
│                     │                                      │
│  ┌──────────────────┴───────────────────────────────────┐ │
│  │              MML Parser Pipeline                       │ │
│  │                                                        │ │
│  │  ┌─────────┐     ┌──────────────┐     ┌────────────┐ │ │
│  │  │  Lexer   │────▶│   Parser     │────▶│    AST     │ │ │
│  │  │ (共通)   │     │(ppmck/pyxel) │     │  (共通)    │ │ │
│  │  └─────────┘     └──────────────┘     └─────┬──────┘ │ │
│  │                                              │         │ │
│  │  ┌───────────────────────────────────────────┘         │ │
│  │  │           SemanticAnalyzer                         │ │
│  │  │     (ppmck/pyxel) ──▶ NoteSequence IR              │ │
│  │  └────────────────────────────────────────────────────│ │
│  └─────────────────────────────────────────────────────────┘ │
│                                                  │          │
│  ┌───────────────────────────────────────────────┴────────┐ │
│  │              APU Synthesis Engine                      │ │
│  │                                                        │ │
│  │  ┌────────┐  ┌────────┐  ┌──────────┐  ┌───────────┐  │ │
│  │  │ Pulse1 │  │ Pulse2 │  │ Triangle │  │  Noise    │  │ │
│  │  └───┬────┘  └───┬────┘  └────┬─────┘  └─────┬─────┘  │ │
│  │      └───────────┴───────────┴───────────────┘         │ │
│  │                    │                                   │ │
│  │              ┌─────┴─────┐                            │ │
│  │              │  Mixer     │                            │ │
│  │              │ (4ch→WAV)  │                            │ │
│  │              └───────────┘                            │ │
│  └────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
```

### 2.2 処理パイプライン

```
MML文字列
  │
  ▼
[Lexer] ──トークン列──▶ [Parser] ──AST──▶ [SemanticAnalyzer] ──NoteSequence──▶ [Synthesizer] ──WAV
                           │                      │
                      構文エラー              意味エラー
                      (SyntaxError)          (SemanticError)
```

| ステップ | コンポーネント | 役割 | 検出するエラー |
|---|---|---|---|
| 1 | Lexer | MML文字列をトークン列に変換。両モード共通のトークン定義 | 不正文字(SyntaxError) |
| 2 | Parser | トークン列を構文解析し、ASTを生成。ppmckとpyxelで別のパーサー | 構文エラー(SyntaxError) |
| 3 | SemanticAnalyzer | ASTを検証・解釈し、NoteSequence(中間表現)を生成 | 意味エラー(SemanticError) |
| 4 | Synthesizer | NoteSequenceを入力として、numpyでAPU各チャンネルの波形を合成 | 実行時エラー(RuntimeError) |

---

## 3. MCPツール仕様

### 3.1 ツール一覧

| ツール名 | 機能 |
|---|---|
| `compose_mml` | MMLの作曲・コンパイル・検証・テンプレート生成を統合した単一ツール |

### 3.2 `compose_mml` 詳細仕様

#### パラメータ

| パラメータ | 型 | required | デフォルト | 説明 |
|---|---|---|---|---|
| `action` | string | yes | — | 動作モード: `"compose"` / `"validate"` / `"template"` |
| `mml` | string | compose/validate時 | `""` | MML文字列 |
| `mode` | string | compose/validate時 | `""` | `"ppmck"` / `"pyxel"` |
| `template` | string | template時 | `"basic"` | テンプレート種別: `"basic"` / `"melody"` / `"chord"` / `"drum"` / `"empty"` / `"expressive_lead"` / `"vibrato_lead"` / `"pitch_motion"` |
| `sample_rate` | int | no | `44100` | 出力サンプリング周波数(Hz) |
| `normalize` | bool | no | `true` | 出力振幅の正規化 |

#### action別の動作と戻り値

**`action: "compose"`** — MMLをコンパイルしてWAVを生成

```json
{
  "success": true,
  "wav_path": "./data/output_20260711_120000_123.wav",
  "duration_sec": 2.0,
  "note_sequence": { ... },
  "validation": {
    "errors": [],
    "warnings": []
  }
}
```

| フィールド | 型 | 説明 |
|---|---|---|
| `success` | boolean | 合成・WAV出力が成功したか |
| `wav_path` | string \| null | 生成されたWAVファイルパス(失敗時はnull) |
| `duration_sec` | number | 生成された音声の長さ(秒) |
| `note_sequence` | object \| null | 中間表現(IR)のNoteSequence |
| `validation.errors` | array | エラーのリスト |
| `validation.warnings` | array | 警告のリスト |

> `compose` は必ず検証を内包する。エラーがあればWAVは生成せず、検証結果のみ返す。
>
> **WAVファイル名**: 固定名 `output.wav` ではなく、生成時刻を含む `output_YYYYMMDD_HHMMSS_mmm.wav` 形式で出力される。

**`action: "validate"`** — MMLの構文チェックのみ(音声生成なし)

```json
{
  "valid": true,
  "errors": [],
  "warnings": [],
  "note_sequence": { ... },
  "channel_summary": [
    {
      "channel": "Pulse1",
      "note_count": 4,
      "octave_range": [4, 5],
      "duration_ticks": 768
    }
  ]
}
```

**`action: "template"`** — MMLテンプレートを生成

```json
{
  "mml": "0: T120 L8 O4 V100 @1\n   C D E F | ...",
  "description": "基本的な4ch構成(メロディ+和音+ベース+リズム)"
}
```

### 3.3 テンプレート種別

| 種別 | 内容 |
|---|---|
| `basic` | 基本的な4ch構成(メロディ+和音+ベース+リズム) |
| `melody` | メロディ重視(Pulse1主旋律、他は伴奏最小限) |
| `chord` | コード伴奏重視(Pulse2で和音、Triangleでベース) |
| `drum` | リズム重視(Noise中心のビートパターン) |
| `empty` | 各チャンネルのヘッダーのみ(空のテンプレート) |

---

## 4. 中間表現: NoteSequence IR

### 4.1 設計方針

| 項目 | 内容 |
|---|---|
| 共通IR | 両モード(ppmck / pyxel)のSemanticAnalyzerが共通で出力する |
| 後段分離 | 後段のSynthesizerはNoteSequenceのみを見れば動く(MMLの文法を知らない) |
| JSON互換 | JSON互換の構造体で表現し、MCPレスポンスに直接埋め込める |

### 4.2 時間解像度

```
1拍(4分音符) = 192 ticks
```

### 4.3 データ構造

```
NoteSequence = {
  version: "1.0",
  bpm: int,
  ticks_per_quarter: 192,
  channels: {
    "Pulse1":   ChannelSequence,
    "Pulse2":   ChannelSequence,
    "Triangle": ChannelSequence,
    "Noise":    ChannelSequence
  }
}

ChannelSequence = {
  channel_type: "pulse" | "triangle" | "noise",
  events: [Event, ...],
  total_ticks: int
}

Event = NoteEvent | RestEvent | VolumeEvent | DutyEvent
      | TempoEvent | RepeatEvent | QuantizeEvent | DetuneEvent
      | SweepEvent | RelativeVolumeEvent
      | VolumeEnvelopeEvent | DutyEnvelopeEvent | LfoEvent
      | PitchEnvEvent | NoteEnvEvent
      | EnvelopeEvent | VibratoEvent | GlideEvent
```

各イベントは位置・音長・音高・音量など、合成とMCPレスポンスに必要な値を保持する。

---

## 5. MMLパーサ設計

### 5.1 4フェーズ分離

```
MML文字列
  │
  ▼
[Lexer] ──トークン列──▶ [Parser] ──AST──▶ [SemanticAnalyzer] ──NoteSequence IR
                           │                      │
                      SyntaxError            SemanticError
```

| フェーズ | 入力 | 出力 | エラー |
|---|---|---|---|
| Lexer | MML文字列 | トークン列 | 不正文字(SyntaxError) |
| Parser | トークン列 | AST | 構文エラー(SyntaxError) |
| SemanticAnalyzer | AST | NoteSequence IR | 意味エラー(SemanticError) |

### 5.2 Lexer(共通)

両モードでトークン型を共有し、モード別の字句規則でトークン化する。ppmckは小文字、Pyxelは大文字のコマンドを受理し、音名だけAST生成時に小文字へ正規化する。

#### 主なトークン種別

| カテゴリ | トークン | 備考 |
|---|---|---|
| 音符 | `c`-`b` (ppmck) / `C`-`B` (pyxel) | `+` / `#` / `-` は音名直後、音長より前 |
| 休符 | `r` / `R` | |
| オクターブ | `o`/`O` + 数値, `>`, `<` | |
| 音長 | `l`/`L` + 数値, 音符直後の数値, `.` | |
| 音量 | `v`/`V` + 数値 | |
| デューティ/音色 | `@`+数値(ppmck/pyxel), `q`+数値(ppmck クオンタイズ) | |
| テンポ | `t`/`T` + 数値 | |
| ゲートタイム | `q`+数値(ppmck 1-8), `Q` + 数値 (pyxel 0-100) | |
| トランスポーズ | `K` + 符号付き整数 (pyxel) | 例: `K-12`, `K+7` |
| ディチューン | `D` + 符号付き整数 (ppmck), `Y` + 符号付き整数 (pyxel) | 例: `D-10`, `Y-25` |
| 相対音量 | `v+` / `v-` (ppmck) | 0〜15へクランプして合成 |
| スイープ | `s`+数値,符号付き数値 (ppmck) | 2A03 Pulse sweepへ反映 |
| タイ | `^` (ppmck), `&` (両モード) | `^`はタイ、`&`はスラー |
| リピート | `[`, `]` + 数値 | ppmckはASTへ保持してwarning、Pyxelは展開 |
| エンベロープ/LFO | `@v`/`@@`/`@MP`/`@EP`/`@EN` (ppmck), `@ENV`/`@VIB`/`@GLI` (pyxel) | IR v2定義表を参照して合成 |
| 小節線 | `\|` | 視覚用、再生に影響しない |
| トラック識別 | ppmck: `A`/`B`/`T`/`N`/`L`, pyxel: `数字:` | |
| ヘッダー | `#` で始まる行 (ppmck) | |
| コメント | `;` 以降行末 (ppmck) | |
| 拡張コマンド | `@ENV`/`@VIB`/`@GLI` (pyxel) | 空白区切りの数値引数をIR v2へ保持して合成 |
| 不正文字 | `INVALID` | Parserが構文エラーとして報告 |

### 5.3 Parser(モード別)

Parserの責務は**トークン列からASTを生成するのみ**。値範囲チェックやチャンネル適合性の判断は行わない。

#### ppmckパーサ

- トラックヘッダー `A`/`B`/`T`/`N`/`L` を認識
- `#` ヘッダーを認識
- 各コマンドをASTノードに変換
- リピートの開始・終了をAST文として保持し、未対応warningを返す
- タイ `^` / スラー `&` の結合対象をASTで保持
- `@v`/`@@`/`@MP`/`@EP`/`@EN` エンベロープ定義 `{...|...}` と使用をASTで保持

#### pyxelパーサ

- トラックヘッダー `0:`〜`3:` を認識
- `@ENV`/`@VIB`/`@GLI` のパラメータをASTノードに保持
- リピート `[...]N` の開始・終了をAST文として保持し、SemanticAnalyzerでネストを展開

### 5.4 SemanticAnalyzer(モード別)

SemanticAnalyzerの責務は**ASTを検証・解釈してNoteSequence IRを生成**する。

#### 共通処理

- 状態管理(`octave`, `velocity`, `duty`, `tick_position`)
- MIDI音番号変換
- tick計算
- タイ処理
- リピート展開(pyxel)
- 値範囲チェック
- 音域チェック

#### ppmck固有

- Triangle音量無視
- Noise音高無視
- 全体ループ `L` の処理
- `@` = デューティ比、`q` = クオンタイズ(gate_time = value / 8)として解釈
- `^` タイを前の音符の音長延長として解釈
- `D`/`s`/`v+`/`v-`、各種エンベロープ/LFOをIRへ保持し `SEMANTIC_UNSUPPORTED_FEATURE` warningを返す
- リピートなど合成未対応のASTに対するwarning

#### pyxel固有

- 音量 `V0-V127` → `0-15` 正規化
- ゲートタイム `Q0-Q100` → `0.0-1.0`
- `@` コマンドのチャンネル適合性チェック
- `@ENV`/`@VIB`/`@GLI` のIR v2定義、選択、解除、合成
- 無限リピートの2回打ち切りwarning
- `&` スラーを前の音符の音長延長として解釈

---

## 6. 抽象構文木(AST)

### 6.1 設計方針

- **モード共通**: ppmck/pyxel両方を1つのASTノード型セットで表現
- **位置情報**: 全ノードに `line`, `column` を保持
- **構造保持**: リピート開始・終了とタイの結合対象を、意味解析可能な文列として保持

### 6.2 主要ASTノード

```python
@dataclass
class Program(ASTNode):
    tracks: list[Track]

@dataclass
class Track(ASTNode):
    track_id: str      # "A"/"B"/"T"/"N"/"L" or "0"-"3"
    channel: str       # "Pulse1"/"Pulse2"/"Triangle"/"Noise"/"Loop"
    mode: str          # "ppmck"/"pyxel"
    statements: list[Statement]
    headers: list[Header]

@dataclass
class NoteStmt(ASTNode):
    note_name: str     # "c"-"b"(正規化済み)
    accidental: int    # +1=sharp, -1=flat, 0=none
    length: int | None
    dots: int

@dataclass
class RestStmt(ASTNode):
    length: int | None
    dots: int

@dataclass
class OctaveStmt(ASTNode):
    value: int | None
    direction: str | None  # "up"/"down"

@dataclass
class LengthStmt(ASTNode):
    value: int

@dataclass
class VolumeStmt(ASTNode):
    value: int

@dataclass
class DutyStmt(ASTNode):
    value: int

@dataclass
class TempoStmt(ASTNode):
    value: int

@dataclass
class GateTimeStmt(ASTNode):
    value: int

@dataclass
class TransposeStmt(ASTNode):
    value: int

@dataclass
class DetuneStmt(ASTNode):
    value: int

@dataclass
class TieStmt(ASTNode):
    target: NoteStmt | RestStmt | int | None

@dataclass
class TieCmdStmt(ASTNode):
    target: NoteStmt | int | None

@dataclass
class QuantizeStmt(ASTNode):
    value: int

@dataclass
class DetuneCmdStmt(ASTNode):
    value: int

@dataclass
class RelativeVolumeStmt(ASTNode):
    up: bool
    value: int | None

@dataclass
class SweepStmt(ASTNode):
    x: int
    y: int

@dataclass
class VolumeEnvelopeUseStmt(ASTNode):
    slot: int

@dataclass
class VolumeEnvelopeDefStmt(ASTNode):
    slot: int
    values: list[int]
    loop: list[int] | None

@dataclass
class DutyEnvelopeUseStmt(ASTNode):
    slot: int

@dataclass
class DutyEnvelopeDefStmt(ASTNode):
    slot: int
    values: list[int]
    loop: list[int] | None

@dataclass
class LfoUseStmt(ASTNode):
    slot: int

@dataclass
class LfoDefStmt(ASTNode):
    slot: int
    params: list[int]

@dataclass
class LfoOffStmt(ASTNode):
    pass

@dataclass
class PitchEnvUseStmt(ASTNode):
    slot: int

@dataclass
class PitchEnvDefStmt(ASTNode):
    slot: int
    values: list[int]
    loop: list[int] | None

@dataclass
class PitchEnvOffStmt(ASTNode):
    pass

@dataclass
class NoteEnvUseStmt(ASTNode):
    slot: int

@dataclass
class NoteEnvDefStmt(ASTNode):
    slot: int
    values: list[int]
    loop: list[int] | None

@dataclass
class NoteEnvOffStmt(ASTNode):
    pass

@dataclass
class RepeatStartStmt(ASTNode):
    pass

@dataclass
class RepeatEndStmt(ASTNode):
    count: int | None  # None=無限

@dataclass
class BarStmt(ASTNode):
    pass

@dataclass
class ExtCmdStmt(ASTNode):
    cmd: str           # "ENV"/"VIB"/"GLI"
    slot: int
    params: list[int]
```

---

## 7. エラーハンドリング

### 7.1 エラー分類

```python
class ErrorPhase(Enum):
    LEXER = "lexer"
    SYNTAX = "syntax"
    SEMANTIC = "semantic"
    RUNTIME = "runtime"
    API = "api"
```

| 層 | コード接頭辞 | 発生フェーズ | 例 |
|---|---|---|---|
| **SyntaxError** | `SYNTAX_*` | Parser | 無効なトークン、未終端のリピート、不正なトラックヘッダー |
| **SemanticError** | `SEMANTIC_*` | SemanticAnalyzer | 値範囲外、チャンネル不適合、音域外、未定義参照 |
| **RuntimeError** | `RUNTIME_*` | Synthesizer | 音声合成失敗、WAV出力失敗、内部エラー |
| **APIError** | `VALIDATION_*` | Tool Dispatcher | パラメータ欠落、無効なmode/action |

### 7.2 ErrorDetail構造

```json
{
  "code": "SEMANTIC_VALUE_OUT_OF_RANGE",
  "phase": "semantic",
  "line": 1,
  "column": 5,
  "message": "'o' の値 9 は範囲外です。有効範囲: 0〜7。",
  "severity": "error",
  "hint": "例: o4 のように指定してください。",
  "context": "A t120 l8 o9 c"
}
```

### 7.3 回復戦略

| 状況 | 動作 |
|---|---|
| Parser ERROR時 | 同一フェーズ内で回復可能な位置まで読み進めて複数の診断を収集し、意味解析へは進まない |
| Semantic ERROR時 | 診断を収集し、不正な状態更新を行わず後続ASTの検証を継続 |
| WARNING時 | 警告を記録し、仕様で定めた無視・クランプ・有限回展開などの代替動作で継続 |
| compose action | ERRORがある場合WAVを生成せず検証結果のみ返す |

---

## 8. BNF文法(概要)

受理言語の正本は [MML_BNF.md](MML_BNF.md) とする。値を要求するコマンドで数値を省略した場合は既定値へ置換せず、`SYNTAX_INVALID_NUMBER`を返す。全トラックIDはモード内で一度だけ定義でき、ppmckヘッダーは既知のキーを用いてトラックより前に置き、値を指定する場合は二重引用符で囲む。

### 8.1 共通要素

```bnf
<note_name>   ::= "c" | "d" | "e" | "f" | "g" | "a" | "b"
<accidental>  ::= "+" | "#" | "-"
<length>      ::= <number> <dot>*
<octave_up>   ::= ">"
<octave_down> ::= "<"
<tie>         ::= "&"
```

### 8.2 ppmckモード

```bnf
<ppmck_mml>   ::= <ppmck_header>* <ppmck_track>*
<ppmck_track> ::= <track_header> <ppmck_statement>*
<track_header>::= "A" | "B" | "T" | "N" | "L"
<ppmck_statement>
              ::= <note> | <rest> | <octave_cmd> | <length_cmd>
                | <volume_cmd> | <relative_volume_cmd> | <duty_cmd>
                | <quantize_cmd> | <tempo_cmd> | <tie_cmd> | <slur_cmd>
                | <detune_cmd> | <sweep_cmd>
                | <vol_envelope_def> | <vol_envelope_use>
                | <duty_envelope_def> | <duty_envelope_use>
                | <lfo_def> | <lfo_use> | <lfo_off>
                | <pitch_env_def> | <pitch_env_use> | <pitch_env_off>
                | <note_env_def> | <note_env_use> | <note_env_off>
                | <repeat_start> | <repeat_end> | <bar>
```

### 8.3 pyxelモード

```bnf
<pyxel_mml>   ::= <pyxel_track>*
<pyxel_track> ::= <pyxel_track_header> <pyxel_statement>*
<pyxel_track_header>
              ::= <number> ":"
<pyxel_statement>
              ::= <note> | <rest> | <octave_cmd> | <length_cmd>
                | <volume_cmd> | <gate_cmd> | <tone_cmd> | <tempo_cmd>
                | <transpose_cmd> | <detune_cmd> | <tie_cmd>
                | <repeat_start> | <repeat_end> | <bar> | <ext_cmd>
```

---

### 8.4 文法適合性の保証

BNFの各規則は Lexer → AST → IR/診断の一貫経路で検証する。`tests/test_mml_conformance.py`は、音符・休符・コマンド・トラック・ヘッダー・拡張コマンドについて正例、負例、境界値、位置情報を検査する。Hypothesisによる固定seedの生成テストで、有効音列のAST/IR保存と単一トークン破壊の非黙殺も確認する。

ppmckの将来予約構文は現在の受理言語に含めない。ppmck/pyxelの拡張はIR v2の共有定義表とチャンネル制御イベントへ変換して合成する。ppmckは構文モード名であり、PPMCK/mckcとのドライバ互換は保証しない。

## 9. APU音声合成エンジン

NoteSequence IRを入力としてPulse1、Pulse2、Triangle、Noiseを合成し、線形ミキシングと任意の正規化を経てWAVへ出力する。

---

## 10. MMLコマンド仕様

コマンド、値範囲、デフォルト値の完全な定義は [MML_BNF.md](MML_BNF.md) を参照する。設計書と実装で差異が生じた場合はBNFを受理言語の基準として監査し、実装変更と同時に適合テストを更新する。

---

## 11. 2モード比較サマリー

| 項目 | ppmckモード | pyxelモード |
|---|---|---|
| **大文字/小文字** | 小文字 `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:`(番号) |
| **Parser** | `parser_ppmck.py` | `parser_pyxel.py` |
| **SemanticAnalyzer** | `semantic_ppmck.py` | `semantic_pyxel.py` |
| **音量範囲** | 0-15(クラシックAPU準拠) | 0-127 |
| **デューティ比** | `@0`〜`@3` で4種(Pulse系のみ) | `@0`〜`@3`(Pulse系のみ有効) |
| **クオンタイズ/ゲート** | `q1`〜`q8`(gate_time = value / 8) | `Q0`〜`Q100`(%) |
| **タイ/スラー** | `^` = タイ、`&` = スラー | `&` = スラー(タイなし) |
| **ディチューン** | `D-127`〜`D126`(セント) | `Y-127`〜`Y127`(セント) |
| **エンベロープ/LFO** | `@v`/`@@`/`@MP`/`@EP`/`@EN`(合成対応) | `@ENV`/`@VIB`/`@GLI`(合成対応) |
| **リピート** | `L`(構文上のループトラック)、`[...]`(AST保持・warning、合成未反映) | `[...]`(回数指定、省略時は2回で打ち切り、ネスト対応) |

---

## 12. 確定事項一覧

| # | 項目 | 決定内容 |
|---|---|---|
| 1 | パーサーアーキテクチャ | Lexer → Parser → SemanticAnalyzer → Synthesizer の4フェーズ分離 |
| 2 | AST | モード共通ASTノード型セットを採用 |
| 3 | エラー分類 | SyntaxError / SemanticError / RuntimeError / APIError の4層分離 |
| 4 | BNF | [MML_BNF.md](MML_BNF.md) を受理言語の正本とし、適合テストで実装との一致を保証 |
| 5 | pyxel `@` とトラック番号の矛盾 | トラック番号優先。`@` はPulse系のみデューティ比として有効 |
| 6 | Noise note_number → period マッピング | ppmck: `period=8, mode=0` 固定。pyxel: マッピング式適用 |
| 7 | WAVファイル名 | `output_YYYYMMDD_HHMMSS_mmm.wav` |

---

## 13. 実装状態

| カテゴリ | 実装内容 |
|---|---|
| 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 |
| 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テスト通過 |

---

以上が、レトロチップ音源作曲MCPサーバの統合設計書です。