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
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
|
# ファミコン音源作曲MCPサーバ — 統合設計書
> 作成日: 2026-07-11 | ステータス: **実装済み(第1段階)**
---
## 1. 概要
### 1.1 目的
LLMがMML(Music Macro Language)を記述することで、ファミコン(NES)のAPU音源をエミュレートした音楽を作曲・再生できるMCPサーバを設計・実装する。
### 1.2 設計目標
| 目標 | 内容 |
|---|---|
| **LLMフレンドリ** | LLMが生成しやすいMML構文、明確なエラーメッセージ |
| **2モード対応** | `ppmck`モード(FC準拠)と`pyxel`モード(Pyxel準拠)を切り替え可能 |
| **正確な音源エミュレーション** | NES APU 4チャンネルの特性を忠実に再現 |
| **拡張性** | DPCM、エンベロープ等の第2段階機能を前提とした設計 |
| **単一ツール** | `compose_mml` の1ツールで作曲・検証・テンプレート生成を完結 |
### 1.3 スコープ
| 対象 | 内容 |
|---|---|
| 第1段階(実装済み) | 4ch MML解析、WAV合成、バリデーション、テンプレート生成、基本エラーコード |
| 第2段階(将来) | DPCMチャンネル、NSFファイル出力、FC実機風非線形ミキシング、エンベロープ合成、区間ループ(ppmck) |
| 対象外 | GUI / Webフロントエンド |
### 1.4 技術スタック
| 項目 | 選定 | 理由 |
|---|---|---|
| 実装言語 | Python 3.14 | FastMCP対応、numpyで音声合成、LLM親和性 |
| MCP SDK | FastMCP >= 2.1.0 | 優良SDK |
| 音声合成 | numpy >= 2.0.0 | 波形生成の数値計算 |
| 音声出力 | 標準ライブラリ wave | WAV形式、外部依存なし |
| テスト | pytest, ruff | 単体テスト・リント・フォーマット |
---
## 2. システム全体アーキテクチャ
### 2.1 全体構成
```
┌──────────────────────────────────────────────────────────┐
│ LLM Client (Claude等) │
│ ┌──────────────────┐ │
│ │ compose_mml │ │
│ └────────┬─────────┘ │
└───────────────────────────┼─────────────────────────────┘
│
┌───────────────────────────┴─────────────────────────────┐
│ MCP Server Layer │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Tool Dispatcher │ │
│ │ action パラメータで動作を切り替え │ │
│ │ (compose / validate / template) │ │
│ └──────────────────┬───────────────────────────────────┘ │
│ │ │
│ ┌──────────────────┴───────────────────────────────────┐ │
│ │ MML Parser Pipeline │ │
│ │ │ │
│ │ ┌─────────┐ ┌──────────────┐ ┌────────────┐ │ │
│ │ │ Lexer │────▶│ Parser │────▶│NoteSequence│ │ │
│ │ │ (共通) │ │(ppmck/pyxel) │ │ (共通IR) │ │ │
│ │ └─────────┘ └──────────────┘ └─────┬──────┘ │ │
│ └──────────────────────────────────────────────┼────────┘ │
│ │ │
│ ┌───────────────────────────────────────────────┴────────┐ │
│ │ APU Synthesis Engine │ │
│ │ │ │
│ │ ┌────────┐ ┌────────┐ ┌──────────┐ ┌───────────┐ │ │
│ │ │ Pulse1 │ │ Pulse2 │ │ Triangle │ │ Noise │ │ │
│ │ └───┬────┘ └───┬────┘ └────┬─────┘ └─────┬─────┘ │ │
│ │ └───────────┴───────────┴───────────────┘ │ │
│ │ │ │ │
│ │ ┌─────┴─────┐ │ │
│ │ │ Mixer │ │ │
│ │ │ (4ch→WAV) │ │ │
│ │ └───────────┘ │ │
│ └────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
```
### 2.2 処理パイプライン
```
MML文字列
│
▼
[Lexer] ──トークン列──▶ [Parser] ──NoteSequence──▶ [Synthesizer] ──WAV
│
(validateの場合はここで停止)
(templateの場合はテンプレート文字列を返却)
```
| ステップ | コンポーネント | 役割 |
|---|---|---|
| 1 | Lexer | MML文字列をトークン列に変換。両モード共通のトークン定義を持ち、大文字/小文字の正規化を行う |
| 2 | Parser | トークン列を構文解析し、NoteSequence(中間表現)を生成。ppmckとpyxelで別のパーサーを使用 |
| 3 | Synthesizer | NoteSequenceを入力として、numpyでAPU各チャンネルの波形を合成し、WAV形式で出力 |
---
## 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"` |
| `sample_rate` | int | no | `44100` | 出力サンプリング周波数(Hz) |
| `normalize` | bool | no | `true` | 出力振幅の正規化 |
#### action別の動作と戻り値
**`action: "compose"`** — MMLをコンパイルしてWAVを生成
```json
{
"success": true,
"wav_path": "./data/output.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は生成せず、検証結果のみ返す。
**`action: "validate"`** — MMLの構文チェックのみ(音声生成なし)
```json
{
"valid": true,
"errors": [],
"warnings": [],
"note_sequence": { ... },
"channel_summary": [
{
"channel": "Pulse1",
"note_count": 4,
"octave_range": [4, 5],
"duration_ticks": 768
}
]
}
```
| フィールド | 型 | 説明 |
|---|---|---|
| `valid` | boolean | MMLにerrorがないか |
| `errors` | array | エラーのリスト |
| `warnings` | array | 警告のリスト |
| `note_sequence` | object \| null | 中間表現(IR)のNoteSequence |
| `channel_summary` | array | チャンネルごとの統計情報 |
**`action: "template"`** — MMLテンプレートを生成
```json
{
"mml": "0: T120 L8 O4 V100 @1\n C D E F | ...",
"description": "基本的な4ch構成(メロディ+和音+ベース+リズム)"
}
```
| フィールド | 型 | 説明 |
|---|---|---|
| `mml` | string | 生成されたMMLテンプレート |
| `description` | string | テンプレートの説明 |
### 3.3 テンプレート種別
| 種別 | 内容 |
|---|---|
| `basic` | 基本的な4ch構成(メロディ+和音+ベース+リズム) |
| `melody` | メロディ重視(Pulse1主旋律、他は伴奏最小限) |
| `chord` | コード伴奏重視(Pulse2で和音、Triangleでベース) |
| `drum` | リズム重視(Noise中心のビートパターン) |
| `empty` | 各チャンネルのヘッダーのみ(空のテンプレート) |
---
## 4. 中間表現: NoteSequence IR
### 4.1 設計方針
| 項目 | 内容 |
|---|---|
| 共通IR | 両モード(ppmck / pyxel)のパーサが共通で出力する |
| 後段分離 | 後段のSynthesizerはNoteSequenceのみを見れば動く(MMLの文法を知らない) |
| JSON互換 | JSON互換の構造体で表現し、MCPレスポンスに直接埋め込める |
### 4.2 時間解像度
```
1拍(4分音符) = 192 ticks
```
| 特徴 | 内容 |
|---|---|
| 192の理由 | 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96 で割り切れる |
| 対応リズム | 3連符、16分音符、32分音符など主要な分割に対応 |
| pyxel整合性 | pyxelモードのtick(1 tick = 4分音符の1/48)と整合: 192 / 48 = 4 |
### 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
| EnvelopeEvent | VibratoEvent | GlideEvent
```
#### 基本イベント
| イベント | フィールド | 型 | 説明 |
|---|---|---|---|
| `NoteEvent` | `type` | string | `"note"` |
| | `tick_position` | int | 絶対開始位置 |
| | `duration` | int | 音長(ticks) |
| | `note_number` | int | MIDI音番号 (69 = A4 = 440Hz) |
| | `velocity` | int | 音量 (0-15) |
| | `duty` | int | デューティ比 (0-3, Pulseのみ) |
| | `gate_time` | float | ゲートタイム割合 0.0-1.0 |
| | `detune_cents` | float | ディチューン(セント単位、pyxel Yコマンド) |
| `RestEvent` | `type` | string | `"rest"` |
| | `tick_position` | int | 絶対開始位置 |
| | `duration` | int | 音長(ticks) |
| `VolumeEvent` | `type` | string | `"volume"` |
| | `tick_position` | int | 絶対位置 |
| | `value` | int | 0-15(pyxelは0-127から正規化) |
| `DutyEvent` | `type` | string | `"duty"` |
| | `tick_position` | int | 絶対位置 |
| | `value` | int | 0-3 |
| `TempoEvent` | `type` | string | `"tempo"` |
| | `tick_position` | int | 絶対位置 |
| | `bpm` | int | テンポ |
| `RepeatEvent` | `type` | string | `"repeat"` |
| | `tick_position` | int | イベント位置 |
| | `start_tick` | int | リピート開始tick |
| | `end_tick` | int | リピート終了tick |
| | `repeat_count` | int | 0 = 無限 |
| | `duration` | int | リピート区間の長さ(ticks) |
#### 拡張イベント(第1段階は保持のみ、合成無視)
| イベント | フィールド | 型 | 説明 |
|---|---|---|---|
| `EnvelopeEvent` | `type` | string | `"envelope"` |
| | `tick_position` | int | 絶対位置 |
| | `slot` | int | 1-15 (0はOFF) |
| | `points` | list[dict] | `{target_volume, duration_ticks}` のリスト |
| `VibratoEvent` | `type` | string | `"vibrato"` |
| | `tick_position` | int | 絶対位置 |
| | `slot` | int | 1-15 (0はOFF) |
| | `params` | dict | `{delay_ticks, period_ticks, depth_cents}` |
| `GlideEvent` | `type` | string | `"glide"` |
| | `tick_position` | int | 絶対位置 |
| | `slot` | int | 1-15 (0はOFF) |
| | `params` | dict | `{initial_offset_cents, duration_ticks}` |
> 第1段階: パーサーはこれらをNoteSequenceに保持する。Synthesizerは無視。validateで「第1段階では未サポート、無視されます」とWARNING。
### 4.4 主要変換仕様
#### 音高変換
```
MIDI音番号 = (オクターブ + 1) * 12 + 音階オフセット + シャープ/フラット
音階オフセット: c=0, d=2, e=4, f=5, g=7, a=9, b=11
例: o4 a = 69 (A4 = 440Hz)
o4 c = 60 (C4 = 中央C)
```
#### 音量正規化
| モード | MML表記 | IR内部値 | 備考 |
|---|---|---|---|
| ppmck | `v15` | 15 | 0-15そのまま |
| pyxel | `V100` | 12 | `round(100 / 127 * 15)` |
> NoteSequenceは両モードで `velocity` を保持する。ppmckモードのSynthesizerはTriangleの `velocity` を無視する。pyxelモードでは振幅スケールとして適用する。
#### チャンネル割り当て
| ppmck | pyxel | チャンネル | 備考 |
|---|---|---|---|
| `A` | `0:` | Pulse1 | 矩形波 |
| `B` | `1:` | Pulse2 | 矩形波 |
| `T` | `2:` | Triangle | 三角波 |
| `N` | `3:` | Noise | ノイズ |
#### pyxelモードの `@` コマンド処理
> `@` はPulseチャンネルでのみデューティ比として有効。Triangle・Noiseチャンネルでは無視する。トラック番号と `@` が矛盾する場合、トラック番号を優先し、`@` は警告の上無視。
| 状況 | 処理 |
|---|---|
| トラック0(Pulse1) + `@1`(Square) | ✅ 正常。デューティ比50%として扱う |
| トラック0(Pulse1) + `@2`(Pulse) | ✅ 正常。デューティ比25%として扱う |
| トラック0(Pulse1) + `@0`(Triangle) | ⚠️ 警告。チャンネルはPulse1のまま、`@0`は無視 |
| トラック2(Triangle) + `@1`(Square) | ⚠️ 警告。チャンネルはTriangleのまま、`@1`は無視 |
| トラック3(Noise) + `@0`(Triangle) | ⚠️ 警告。チャンネルはNoiseのまま、`@0`は無視 |
| トラック3(Noise) + `@3`(Noise) | ⚠️ 警告。チャンネルはNoiseのまま、`@3`は無視 |
#### Noiseチャンネルの note_number → period マッピング
| モード | 処理 |
|---|---|
| **ppmck** | 音高不使用。Noiseチャンネルの音符はすべて `period=8, mode=0`(中域デフォルト)。音長と音量のみ反映 |
| **pyxel** | note_number を noise period にマッピング |
pyxelモードのマッピング式:
```
period = clamp(15 - floor(note_number / 8), 0, 15)
mode = 1 if period >= 13 else 0
```
| note_number | period | mode | 特徴 |
|---|---|---|---|
| 0(最低音) | 15 | 1 (short) | 最高ノイズ |
| 48(中域) | 9 | 0 (long) | 中域ノイズ |
| 96(高域) | 3 | 0 (long) | 低ノイズ |
| 120(最高音) | 0 | 0 (long) | 最低ノイズ |
---
## 5. MMLパーサ設計
### 5.1 Lexer(共通)
両モードの字句解析を統一。モードごとに大文字/小文字を正規化した後にトークン化する。
#### 主なトークン種別
| カテゴリ | トークン | 備考 |
|---|---|---|
| 音符 | `c`-`b` (ppmck) / `C`-`B` (pyxel) | 大文字小文字問わず判定 |
| 休符 | `r` / `R` | |
| オクターブ | `o`/`O` + 数値, `>`, `<` | |
| 音長 | `l`/`L` + 数値, 音符直後の数値, `.` | |
| 音量 | `v`/`V` + 数値 | |
| デューティ/音色 | `q`+数値(ppmck), `@`+数値(pyxel) | |
| テンポ | `t`/`T` + 数値 | |
| ゲートタイム | `Q` + 数値 (pyxel) | |
| トランスポーズ | `K` + 数値 (pyxel) | |
| ディチューン | `Y` + 数値 (pyxel) | |
| タイ/スラー | `&` | |
| リピート | `[`, `]` + 数値 (pyxel) | |
| 小節線 | `\|` | 視覚用、再生に影響しない |
| トラック識別 | ppmck: `A`/`B`/`T`/`N`/`L`, pyxel: `数字:` | |
| ヘッダー | `#` で始まる行 (ppmck) | |
| コメント | `;` 以降行末 (ppmck) | |
| 拡張コマンド | `@ENV`/`@VIB`/`@GLI` (pyxel) | 第1段階は保持のみ |
#### モード別正規化
| モード | 正規化ルール |
|---|---|
| ppmck | 音符・コマンドは小文字に正規化、`#` 行はヘッダー、`;` 以降はコメント |
| pyxel | 音符・コマンドは大文字に正規化、`数字:` はトラックヘッダー |
### 5.2 Parser(モード別)
#### ppmckパーサ
| 項目 | 内容 |
|---|---|
| トラックヘッダー | `A`/`B`/`T`/`N` でチャンネルを決定 |
| 全体ループ | `L` は全体ループフラグ。省略時は1回のみ再生 |
| メタ情報 | `#TITLE` 等のヘッダーはメタ情報として保持 |
| デフォルト値 | octave=4, volume=15, length=4, duty=2 |
| Triangle音量 | `v` コマンドを無視(警告なし) |
| タイ | `&` で同音高を結合。音長のみの指定も可 |
| 区間リピート | `[...]` は第2段階。現状はwarningで無視 |
| 第2段階予定 | エンベロープ(`@v`), スイープ(`s`), 区間ループ(`[...]`), トランスポーズ(`K`), ノイズモード(`m`) |
#### pyxelパーサ
| 項目 | 内容 |
|---|---|
| トラック番号 | `0:`〜`3:` でチャンネルを決定 |
| `@` コマンド | Pulseチャンネルでのみデューティ比として有効。他チャンネルでは警告して無視 |
| デフォルト値 | octave=4, volume=100, length=4, gate=80, duty=2 |
| 音量 | `V0`〜`V127` → IR内部で 0〜15 に正規化 |
| ゲートタイム | `Q0`〜`Q100` → gate_time 0.0〜1.0 |
| リピート | `[...]N` を展開(ネスト対応、省略時無限) |
| タイ | `&` は同音高=タイ、異音高=レガート、音長のみ=音長延長 |
| 拡張コマンド | `@ENV`/`@VIB`/`@GLI`: NoteSequenceにイベントとして保持。第1段階は合成に反映しない |
| トランスポーズ | `K`: note_numberに加算 |
| ディチューン | `Y`: note_numberの小数部として `detune_cents` に保持 |
#### リピート処理の違い
| モード | 記法 | 動作 |
|---|---|---|
| ppmck | `L` | 全体ループフラグ。最後にRepeatEventを追加 |
| ppmck | `[...]` | 第2段階。現状はwarningで無視 |
| pyxel | `[...]N` | 区間リピートを即座に展開。N省略時は無限(2回で打ち切りwarning) |
| pyxel | ネスト | 対応済み |
### 5.3 エラーハンドリング
| カテゴリ | 例 | severity |
|---|---|---|
| 構文エラー | 不正なトークン、未終端のリピート | error |
| 範囲エラー | オクターブ範囲外、音量範囲外 | error または warning |
| チャンネルエラー | Triangleへの `q` コマンド、Noiseへの音高指定(ppmck) | warning(無視して継続) |
| 音域エラー | MIDI音番号0-127を逸脱 | warning(クランプ) |
| 参照エラー | 未定義エンベロープ/ビブラート参照 | error |
| 未サポート | `@ENV`/`@VIB`/`@GLI`(第1段階) | warning(「第1段階では未サポート、無視されます」) |
#### 回復戦略
| 状況 | 動作 |
|---|---|
| ERROR時 | そのトークンで解析を停止し、エラーを返す |
| WARNING時 | 警告を記録しつつ、妥当な値にクランプして処理を継続 |
| compose action | ERRORがある場合WAVを生成せず検証結果のみ返す |
---
## 6. APU音声合成エンジン
### 6.1 NES APU ハードウェア仕様
#### 概要
| 項目 | 仕様 |
|---|---|
| CPUクロック (NTSC) | 1.7897725 MHz |
| フレームカウンタ | ~240Hz (envelope/length), ~60Hz (IRQ) |
| 出力 | 4チャンネル → ミキサー → DAC |
| サンプリングレート(本サーバ) | 44100 Hz(デフォルト、変更可) |
#### チャンネル一覧
| チャンネル | 種別 | レジスタ | MML対応 | 特徴 |
|---|---|---|---|---|
| Ch1 | Pulse 1 | $4000-$4003 | A / 0: | 矩形波、デューティ比4種、音量0-15、スイープ |
| Ch2 | Pulse 2 | $4004-$4007 | B / 1: | 矩形波、同上 |
| Ch3 | Triangle | $4008-$400B | T / 2: | 32段階三角波、音量固定、linear counter |
| Ch4 | Noise | $400C-$400F | N / 3: | LFSRノイズ、音量0-15、2周期モード |
| Ch5 | DMC | $4010-$4013 | — | 第2段階 |
#### Pulse チャンネル
| 項目 | 内容 |
|---|---|
| 波形 | 矩形波、4種のデューティ比(12.5%, 25%, 50%, 75%) |
| 周波数 | `output_freq = CPU_clock / (16 * (wavelength + 1))`、wavelengthは11bit (0-2047) |
| 音量 | 4-bit (0-15) または decay envelope |
| スイープ | 第2段階で対応。`Wavelength ± (Wavelength >> S)` のピッチ変動 |
#### Triangle チャンネル
| 項目 | 内容 |
|---|---|
| 波形 | 32段階三角波(0→15→0の階段状) |
| 音量 | 制御なし(固定振幅) |
| ppmckモード | `v` コマンドを無視、振幅は固定最大値 |
| pyxelモード | `V` の値を振幅スケールとして適用。`V0`=無音、`V127`=最大振幅 |
| 周波数 | `output_freq = CPU_clock / (32 * (wavelength + 1))` |
#### Noise チャンネル
| 項目 | 内容 |
|---|---|
| 波形 | 15-bit LFSR(Linear Feedback Shift Register)による擬似乱数 |
| 音量 | 4-bit (0-15) |
| 周期 | 4-bit period register (0-15) + mode flag (0=long 32767samples, 1=short 93samples) |
| ppmckモード | 音高不使用。すべて `period=8, mode=0`(中域デフォルト)。音長と音量のみ反映 |
| pyxelモード | `period = clamp(15 - floor(note_number / 8), 0, 15)`, `mode = 1 if period >= 13 else 0` |
### 6.2 合成フロー
```
NoteSequence
│
├─ 1. テンポ変換: ticks → samples
│ sample = tick × (60 / bpm) × (sample_rate / 192)
│
├─ 2. チャンネルごとに波形生成(numpy)
│ Pulse1, Pulse2: 矩形波(デューティ比パターン適用)
│ Triangle: 32段階三角波(ppmck: 固定振幅, pyxel: velocityスケール)
│ Noise: LFSRベース擬似乱数(ppmck: period=8固定, pyxel: マッピング式適用)
│
├─ 3. ミキシング
│ 第1段階: 線形ミキシング (ch1+ch2+ch3+ch4)/4
│ 第2段階: NES非線形ミキシングテーブル
│
└─ 4. ノーマライズ → 16bit PCM → WAV出力
```
### 6.3 ミキシング仕様
| 項目 | 第1段階(線形) | 第2段階(FC実機風) |
|---|---|---|
| 方式 | 単純平均 `(ch1+ch2+ch3+ch4)/4` | NES非線形ミキシング |
| クリッピング | -1.0 ~ 1.0 でクランプ | 実機と同様の段階的ミックス |
| ノーマライズ | 有効時ピークを-1.0に正規化 | 無効化可能 |
> FC実機の非線形ミキシング: `output = 95.88 - (8128 / (sum_of_active_channels + 1))`。第2段階で実装。
---
## 7. MMLコマンド仕様
### 7.1 モード1: `ppmck` — PPMCKインスパイア独自形式
PPMCKの文法をベースにしつつ、不要な複雑さを排除した独自サブセット。LLMが生成しやすく、FC音源の主要機能をカバーする。
#### 参考ドキュメント
| 内容 | URL |
|---|---|
| PPMCK mckc.txt(MMLコンパイラ仕様書) | https://github.com/munshkr/ppmck/blob/master/doc/mckc.txt |
| MCK Wiki MMLリファレンス | https://wikiwiki.jp/mck/MMLリファレンス |
| NES APUハードウェア仕様 (midi2nes) | https://github.com/matiaszanolli/midi2nes/blob/master/docs/NES_APU_REFERENCE.md |
| NES APU仕様 (problemkaputt.de ミラー) | https://github.com/RigleGit/nes-specs/blob/main/audioprocessingunitapu.md |
#### トラック構成例
```
#TITLE "My Song"
#COMPOSER "Luno"
A t150 l8 o4 v15 q2 # Pulse1
c d e f | g a b > c
B l8 o3 v12 q1 # Pulse2
c r g r c r g r
T l4 o3 v7 # Triangle(音量は無視、固定振幅)
c2 c2 g2 g2
N l8 v10 # Noise
r c r c r c r c
L # 全体ループ(省略可)
```
#### トラックヘッダー
| 記法 | 意味 | 対象 | 備考 |
|---|---|---|---|
| `A` | Pulse1 チャンネル | — | 矩形波 |
| `B` | Pulse2 チャンネル | — | 矩形波 |
| `T` | Triangle チャンネル | — | 三角波、音量固定 |
| `N` | Noise チャンネル | — | ノイズ |
| `L` | 全体ループ | トラック末尾 | 省略時は1回のみ再生 |
#### 音高
| 記法 | 意味 | 備考 |
|---|---|---|
| `c` `d` `e` `f` `g` `a` `b` | ド レ ミ ファ ソ ラ シ | 小文字 |
| `c+` または `c#` | ド♯ | シャープ |
| `c-` | ド♭ | フラット |
| `o4` | オクターブ4(中央C基準) | `o0`〜`o7` |
| `>` | 1オクターブ上へ | 相対移動 |
| `<` | 1オクターブ下へ | 相対移動 |
> Noiseチャンネルでは音高は使わない(周波数はノイズテーブルで固定)
#### 音長
| 記法 | 意味 | 備考 |
|---|---|---|
| `l4` | デフォルト音長 = 4分音符 | トラック内で随時変更可 |
| `c4` | 4分音符のド | 音符の直後に数字 |
| `c8` | 8分音符のド | |
| `c1` | 全音符のド | |
| `c4.` | 付点4分音符 | +半分の長さ |
| `c4..` | 複付点4分音符 | +半分+半分の半分 |
| `r4` | 4分休符 | 休符にも音長指定可 |
| `c4&c4` | タイ | 同音高の音符を結合 |
| `c4&4` | 音長のみのタイ | 第1段階で対応 |
音長とテンポの関係: `4分音符の秒数 = 60 / BPM`
#### 音量
| 記法 | 意味 | 対象 | 備考 |
|---|---|---|---|
| `v15` | 音量15(最大) | A, B, N | 0-15、FC準拠 |
| `v0` | 音量0(無音) | A, B, N | |
> Triangleチャンネルでは `v` は無視(ハードウェア仕様、音量制御なし)
#### デューティ比
| 記法 | 意味 | 対象 | 備考 |
|---|---|---|---|
| `q0` | 12.5% | A, B のみ | |
| `q1` | 25% | A, B のみ | |
| `q2` | 50% | A, B のみ | デフォルト |
| `q3` | 75% | A, B のみ | |
#### テンポ
| 記法 | 意味 | 備考 |
|---|---|---|
| `t120` | BPM 120 | 最初のトラックで指定、全体に適用 |
#### 区切り・フォーマット
| 記法 | 意味 |
|---|---|
| `\|` | 小節線(視覚用、再生には影響しない) |
| 改行 | トラック内の区切り(視覚用) |
| `#` 行 | ヘッダー・メタ情報 |
| 空行 | 無視 |
| `;` 以降 | コメント(行末まで無視) |
#### 第2段階で追加予定
| コマンド | 構文 | 内容 |
|---|---|---|
| カウント音長 | `c%48` | 全音符=192カウント基準 |
| エンベロープ定義 | `@v0 = { 15, 12, 8, 0 }` | 音量エンベロープ |
| エンベロープ指定 | `@v0` | 定義済みエンベロープ使用 |
| デューティ比音色 | `@0 = { 0, 1, \| 2 }` | 発音中のデューティ比変化 |
| スイープ | `s1,2` | ピッチスイープ(Pulseのみ) |
| 区間ループ | `[ c d \| e f ]2` | 指定回数繰り返し |
| トランスポーズ | `K2` | 全体キーシフト |
| ノイズモード | `m0` / `m1` | Noise ch: 0=ロング周期, 1=ショート周期 |
### 7.2 モード2: `pyxel` — Pyxel MML準拠形式
PyxelのMML仕様に準拠。Pyxelのサウンドシステムに合わせた記法。
#### 参考ドキュメント
| 内容 | URL |
|---|---|
| Pyxel Audio API (DeepWiki) | https://deepwiki.com/kitao/pyxel/5.3-audio-api |
| Pyxel MML Studio | https://kitao.github.io/pyxel/web/mml-studio/ |
| Pyxel MMLコマンドリファレンス | https://kitao.github.io/pyxel/docs/mml-commands.md |
| Pyxel GitHub(ソース) | https://github.com/kitao/pyxel |
#### トラック構成例
```
0: T150 L8 O4 V100 @1 # トラック0 (Pulse1)
C D E F G A B >C
1: L8 O3 V80 @2 # トラック1 (Pulse2)
E G B R E G B R
2: L4 O3 V60 # トラック2 (Triangle)
C2 G2 E2 C2
3: L8 V80 # トラック3 (Noise)
C R C R C R C R
```
#### 完全コマンド一覧
##### テンポ・音長・音量
| コマンド | 記法 | 意味 | 範囲 | デフォルト |
|---|---|---|---|---|
| テンポ | `T150` | BPM | 1- | 120 |
| デフォルト音長 | `L4` | n分音符。`L12`で8分3連符 | 1-192 | 4 |
| 音量 | `V100` | 音量 | **0-127** | 100 |
| ゲートタイム | `Q80` | ノート実発音割合(%)。100=隙間なし、0=無音 | 0-100 | 80 |
##### 音色・ピッチ
| コマンド | 記法 | 意味 | 範囲 | デフォルト |
|---|---|---|---|---|
| 音色(トーン) | `@1` | 波形タイプ。1:Square(50%) / 2:Pulse(25%) | 1-2(Pulse系のみ有効) | — |
| トランスポーズ | `K12` | 半音単位の移調。12で1オクターブ | — | 0 |
| ディチューン | `Y100` | セント単位の微調整。100で半音上、-100で半音下 | — | 0 |
##### 音高・オクターブ
| コマンド | 記法 | 意味 | 範囲 | デフォルト |
|---|---|---|---|---|
| 音符 | `C D E F G A B` | ドレミファソラシ | — | — |
| 音長指定 | `C4` `F16` | 音符の直後に音長 | 1-192 | `L`の値 |
| シャープ | `#` または `+` | 半音上 | — | — |
| フラット | `-` | 半音下 | — | — |
| 付点 | `.` | 音長を1.5倍。複数可 `C4..` | — | — |
| 休符 | `R` | 休符。`R8`のように音長指定可 | 1-192 | `L`の値 |
| オクターブ | `O4` | オクターブ指定。`O4`のA=440Hz | 0〜7 | 4 |
| 相対上 | `>` | 1オクターブ上(最大7) | — | — |
| 相対下 | `<` | 1オクターブ下(最小0) | — | — |
| タイ/スラー | `&` | 同音高=タイ、異音高=レガート。`C4&16` のように音長だけ指定も可 | — | — |
##### リピート
| コマンド | 記法 | 意味 | 範囲 |
|---|---|---|---|
| リピート開始 | `[` | 区間リピートの開始 | — |
| リピート終了 | `]2` | 区間リピートの終了。回数指定。省略時は**無限リピート**。**ネスト対応** | 1- |
##### エンベロープ `@ENV`
| 記法 | 意味 |
|---|---|
| `@ENV0` | エンベロープOFF |
| `@ENV1` | スロット1に切り替え |
| `@ENV1 30 20 100 50 0` | スロット1を定義して切り替え(簡易パース) |
> 本実装では `{...}` ブレース記法ではなく、フラットな数値列を簡易パースする。1 tick = 4分音符の1/48。スロット0は指定不可(OFF専用)。
##### ビブラート `@VIB`
| 記法 | 意味 |
|---|---|
| `@VIB0` | ビブラートOFF |
| `@VIB1` | スロット1に切り替え |
| `@VIB1 24 12 100` | スロット1を定義して切り替え(簡易パース) |
##### グライド(ピッチスライド)`@GLI`
| 記法 | 意味 |
|---|---|
| `@GLI0` | グライドOFF |
| `@GLI1` | スロット1に切り替え |
| `@GLI1 -100 24` | スロット1を定義して切り替え(簡易パース) |
---
## 8. エラー仕様
### 8.1 ErrorDetail 構造
```json
{
"code": "SYNTAX_INVALID_TOKEN",
"line": 1,
"column": 5,
"message": "無効な文字 'x' が見つかりました。",
"severity": "error",
"hint": "MMLコマンド(c,d,e,f,g,a,b,r,o,l,v,t,q など)を使用してください。",
"context": "A t150 l8 o4 x c"
}
```
| フィールド | 型 | 説明 |
|---|---|---|
| `code` | string | エラーコード(機械処理用、LLMがパターン認識可能) |
| `line` | integer | 発生行(1始まり。システム/APIエラー時は0) |
| `column` | integer | 発生列(1始まり。システム/APIエラー時は0) |
| `message` | string | 人間が読めるメッセージ |
| `severity` | string | `"error"` または `"warning"` |
| `hint` | string | LLM向けの修正ヒント |
| `context` | string | 該当行のコンテキスト |
> **注意**: `line` と `column` は **1始まり** です(最初の行・最初の列が1)。システムエラーやAPIレベルエラーなどで位置情報がない場合は `0` を使用します。
### 8.2 構文エラー(SYNTAX_*)
| code | trigger | message テンプレート | hint | severity |
|---|---|---|---|---|
| `SYNTAX_INVALID_TOKEN` | 解析不能な文字 | `無効な文字 '{char}' が見つかりました。` | `MMLコマンド(c,d,e,f,g,a,b,r,o,l,v,t,q など)を使用してください。` | error |
| `SYNTAX_INVALID_NUMBER` | 数値を期待した位置に数値以外 | `'{command}' の後に数値が必要です。` | `例: {command}{example} のように数値を指定してください。` | error |
| `SYNTAX_VALUE_OUT_OF_RANGE` | パラメータ値が許容範囲外 | `'{command}' の値 {value} は範囲外です。有効範囲: {min}〜{max}。` | `例: {command}{valid_example} のように指定してください。` | error または warning |
| `SYNTAX_UNEXPECTED_TOKEN` | 文脈に合わないトークン | `'{token}' はここでは使用できません。` | `期待される要素: {expected}。直前のコマンドを確認してください。` | error |
| `SYNTAX_UNTERMINATED_REPEAT` | `[` に対応する `]` がない | `リピート '[' に対応する ']' が見つかりません。` | `] を追加してリピートを閉じてください。回数指定(例: ]2)も可能です。` | error |
| `SYNTAX_UNMATCHED_REPEAT_END` | `]` に対応する `[` がない | `']' に対応する '[' が見つかりません。` | `直前の [ を確認するか、余分な ] を削除してください。` | error |
| `SYNTAX_UNTERMINATED_TIE` | `&` の後に音符がない | `タイ '&' の後に音符が見つかりません。` | `& の後に音符(例: {note_example})を続けてください。音長のみ(例: &16)も可能です(pyxelモード)。` | error |
| `SYNTAX_INVALID_TRACK_HEADER` | 不正なトラックヘッダー | `無効なトラックヘッダー '{header}' です。` | ppmck: `A, B, T, N, L のいずれかを使用してください。` / pyxel: `0:, 1:, 2:, 3: のいずれかを使用してください。` | error |
| `SYNTAX_DUPLICATE_TRACK` | 同じトラックが複数回定義 | `トラック '{track}' が複数回定義されています。` | `各トラックは1回のみ定義できます。重複定義を削除してください。` | error |
| `SYNTAX_EMPTY_TRACK` | トラックに音符・休符がない | `トラック '{track}' に音符または休符がありません。` | `少なくとも1つの音符または休符を記述してください。` | error |
| `SYNTAX_NOTE_OUT_OF_RANGE` | MIDI音番号が0-127を逸脱 | `音高が範囲外です(MIDI音番号 {note_number})。` | `オクターブを調整してください。有効範囲: o0〜o7。` | warning(クランプ) |
| `SYNTAX_CHANNEL_MISMATCH` | チャンネルに不適切なコマンド | `チャンネル '{channel}' で '{command}' は使用できません。` | `コマンドを削除するか、適切なチャンネルに移動してください。` | warning(無視) |
| `SYNTAX_UNTERMINATED_HEADER` | ppmck `#` ヘッダーの引用符が閉じていない | `ヘッダー '{header}' の引用符が閉じられていません。` | `二重引用符 " で値を閉じてください。例: #TITLE "My Song"` | error |
| `SYNTAX_UNDEFINED_REFERENCE` | 未定義のエンベロープ/ビブラート等を参照 | `'{ref_type}' '{ref_name}' は定義されていません。` | `使用前に定義してください。例: {definition_example}` | error |
#### チャンネル不適合の具体例(SYNTAX_CHANNEL_MISMATCH)
| 状況 | message | hint |
|---|---|---|
| Triangle + `q2` (ppmck) | `チャンネル 'Triangle' で 'q' は使用できません。` | `Triangleチャンネルはデューティ比を持ちません。q コマンドを削除してください。` |
| Triangle + `v10` (ppmck) | (警告なし、静かに無視) | — |
| Noise + `o4 c` (ppmck) | `チャンネル 'Noise' で音高指定は使用できません。` | `Noiseチャンネルでは音高ではなく音長と音量のみ指定してください。` |
| トラック2(Triangle) + `@1` (pyxel) | `チャンネル 'Triangle' で '@1' (Square) は使用できません。` | `トラック番号がチャンネルを決定します。@ コマンドを削除するか、Pulseチャンネル(0: または 1:)に移動してください。` |
#### 値範囲外の具体例(SYNTAX_VALUE_OUT_OF_RANGE)
| 状況 | message | hint | severity |
|---|---|---|---|
| `o9` (ppmck/pyxel) | `'o' の値 9 は範囲外です。有効範囲: 0〜7。` | `例: o4 のように指定してください。` | error |
| `v20` (ppmck) | `'v' の値 20 は範囲外です。有効範囲: 0〜15。` | `例: v15 のように指定してください。` | error |
| `V200` (pyxel) | `'V' の値 200 は範囲外です。有効範囲: 0〜127。` | `例: V100 のように指定してください。` | error |
| `l0` (両モード) | `'l' の値 0 は範囲外です。有効範囲: 1〜192。` | `例: l8 のように指定してください。` | error |
| `t0` (両モード) | `'t' の値 0 は範囲外です。有効範囲: 1以上。` | `例: t150 のように指定してください。` | error |
### 8.3 システムエラー(SYSTEM_*)
| code | trigger | message テンプレート | hint | severity |
|---|---|---|---|---|
| `SYSTEM_SYNTHESIS_FAILED` | 音声合成中のnumpy演算エラー等 | `音声合成中にエラーが発生しました: {detail}` | `MMLの内容を確認の上、再度お試しください。問題が続く場合は、短いMMLから試してください。` | error |
| `SYSTEM_WAV_WRITE_FAILED` | WAVファイル出力の失敗 | `WAVファイルの出力に失敗しました: {detail}` | `しばらく待ってから再度お試しください。` | error |
| `SYSTEM_INTERNAL_ERROR` | 予期しない例外 | `内部エラーが発生しました: {detail}` | `MMLの内容を確認の上、再度お試しください。` | error |
> システムエラーは `compose` actionでのみ発生しうる。`validate` / `template` actionでは構文エラー以外は返らない設計。
### 8.4 APIレベルエラー(VALIDATION_*)
| code | trigger | message テンプレート | hint | severity |
|---|---|---|---|---|
| `VALIDATION_MISSING_PARAMETER` | compose/validateで必須パラメータ(mml, mode)が欠落 | `mml と mode は compose/validate の必須パラメータです。` | `mml に MML 文字列、mode に 'ppmck' または 'pyxel' を指定してください。` | error |
| `VALIDATION_INVALID_MODE` | 未知のmodeが指定された | `未知のモード '{mode}' です。` | `mode は 'ppmck' または 'pyxel' を指定してください。` | error |
| `VALIDATION_INVALID_ACTION` | 未知のactionが指定された | `未知の action '{action}' です。` | `action は 'compose', 'validate', 'template' のいずれかを指定してください。` | error |
### 8.5 エラー発生時のcompose_mml戻り値
`compose` action で ERROR がある場合:
```json
{
"success": false,
"wav_path": null,
"duration_sec": 0,
"note_sequence": null,
"validation": {
"errors": [ErrorDetail, ...],
"warnings": [ErrorDetail, ...]
}
}
```
> warnings も ErrorDetail 構造で返す(severity = "warning")。LLMは warnings を見て「修正しなくても動くが、改善できる」ことを把握できる。
---
## 9. 2モード比較サマリー
| 項目 | ppmckモード | pyxelモード |
|---|---|---|
| **大文字/小文字** | 小文字 `c d e` | 大文字 `C D E` |
| **コマンド** | `o` `l` `v` `t` `q` | `O` `L` `V` `T` `Q` `@` `K` `Y` |
| **チャンネル指定** | `A` `B` `T` `N`(文字) | `0:`〜`3:`(番号) |
| **チャンネル数** | 4ch(DPCM第2段階) | 4ch(DPCM非対応) |
| **音量範囲** | 0-15(FC準拠) | 0-127 |
| **音量デフォルト** | 15 | 100 |
| **デューティ比** | `q0`〜`q3` で4種 | `@1`(50%), `@2`(25%) の2種のみ(Pulse系のみ有効) |
| **ゲートタイム** | 第2段階で検討 | `Q0`〜`Q100`(default 80) |
| **Triangle音量** | 無視(FC仕様) | 設定可(Pyxel仕様、振幅スケールとして適用) |
| **Noise音高** | 不使用(period=8固定) | note_number → period マッピング |
| **エンベロープ** | `@v`(第2段階) | `@ENV` スロット定義+切替(第1段階は保持のみ) |
| **ビブラート** | 第3段階 | `@VIB` スロット定義+切替(第1段階は保持のみ) |
| **ピッチスライド** | `s` スイープ(第2段階) | `@GLI` スロット定義+切替(第1段階は保持のみ) |
| **トランスポーズ** | `K`(第2段階) | `K`(半音単位) |
| **ディチューン** | 未対応 | `Y`(セント単位) |
| **リピート** | `L`(全体), `[...]`(第2段階) | `[...]`(回数指定、省略時無限、ネスト対応) |
| **タイ** | `c4&c4` / `c4&4` | `C4&C4` または `C4&16`(音長のみ可) |
| **tick単位** | なし | 1 tick = 4分音符の1/48 |
| **コメント** | `;` | 公式記載なし |
| **ヘッダー** | `#TITLE` 等 | なし |
---
## 10. 確定事項一覧
| # | 項目 | 決定内容 |
|---|---|---|
| 1 | pyxel `@` とトラック番号の矛盾 | トラック番号優先。`@` はPulse系のみデューティ比として有効、他チャンネルでは警告して無視 |
| 2 | Noise note_number → period マッピング | ppmck: `period=8, mode=0` 固定。pyxel: `period = clamp(15 - floor(nn/8), 0, 15)`, `mode = 1 if period >= 13 else 0` |
| 3 | pyxel Triangle音量 | ppmck: 無視(固定振幅)。pyxel: 振幅スケールとして適用。NoteSequenceは両モードでvelocity保持 |
| 4 | 出力フォーマット | WAV確定。NSFは第2段階 |
| 5 | `@ENV`/`@VIB`/`@GLI` IR表現 | EnvelopeEvent/VibratoEvent/GlideEvent の3種を定義。第1段階は保持のみ、合成無視、validateでWARNING |
| 6 | 非線形ミキシング | 第2段階で実装。第1段階は線形ミキシング |
| 7 | ErrorDetail line/column | 1始まり。システム/APIエラー時は0 |
| 8 | pyxel 無限リピート | 省略時は2回で打ち切り、WARNINGを返す |
| 9 | pyxel `@ENV`/`@VIB`/`@GLI` パース | 第1段階は `{...}` ブレースではなくフラット数値列を簡易パース |
---
## 11. 実装状態と今後の予定
### 11.1 第1段階実装済み機能
| カテゴリ | 実装内容 |
|---|---|
| MCPツール | `compose_mml`(compose/validate/template) |
| Lexer | ppmck/pyxel 両モード対応、共通トークン定義 |
| Parser | ppmckパーサ、pyxelパーサ、エラーハンドリング |
| IR | NoteSequence、各種Event、ErrorDetail、ErrorCode |
| Synthesizer | Pulse/Triangle/Noise 4ch合成、線形ミキシング、WAV出力 |
| Templates | basic/melody/chord/drum/empty の5種 × 2モード |
| CLI | `--output-dir`, `--transport`, `--host`, `--port` 対応 |
| テスト | pytestによる各種テスト、ruffによるリント/フォーマット |
### 11.2 第2段階予定機能
| カテゴリ | 予定内容 |
|---|---|
| DPCMチャンネル | 5ch目のDPCM対応 |
| NSF出力 | NSFファイル出力の検討 |
| 非線形ミキシング | FC実機風ミキシングテーブル |
| エンベロープ合成 | `@v`(ppmck), `@ENV`(pyxel) の合成反映 |
| ppmck 区間リピート | `[...]` の展開 |
| ppmck スイープ | `s` コマンド |
| その他 | カウント音長、ノイズモード、トランスポーズ等 |
### 11.3 設計書と実装の差分
| 項目 | 設計書(旧) | 実装(最新) |
|---|---|---|
| `NoteEvent` | `detune_cents` なし | `detune_cents: float = 0.0` 追加 |
| `RepeatEvent` | `start_tick`, `end_tick`, `repeat_count` のみ | `tick_position`, `duration` も追加 |
| pyxel `@` コマンド | `@1`(50%), `@2`(25%) の2種 | 同じだが `@0`, `@3` は警告で拒否 |
| pyxel repeat | ネスト対応・省略時無限 | 実装済み、無限時は2回で打ち切り(warning) |
| ppmck repeat `[...]` | 第2段階 | 実装も第2段階(warning で無視) |
| pyxel `@ENV`/`@VIB`/`@GLI` | `{...}` ブレース記法 | 実装はフラット数値列で簡易パース |
| pyxel `Y`(ディチューン) | note_numberの小数部として保持 | `detune_cents` フィールドとして保持 |
| トランスポート | 設計書に記載なし | `stdio`/`http`/`sse`/`streamable-http` + CLI引数 |
| ErrorDetail line/column | 旧資料で表記揺れあり | 実装は1始まり(システムエラー時は0) |
---
以上が、ファミコン音源作曲MCPサーバの統合設計書です。
|