summaryrefslogtreecommitdiff
path: root/doc
diff options
context:
space:
mode:
Diffstat (limited to 'doc')
-rw-r--r--doc/Design.md966
-rw-r--r--doc/Design_err.md163
-rw-r--r--doc/Design_mml.md375
-rw-r--r--doc/Desing.md586
4 files changed, 966 insertions, 1124 deletions
diff --git a/doc/Design.md b/doc/Design.md
new file mode 100644
index 0000000..b2ded54
--- /dev/null
+++ b/doc/Design.md
@@ -0,0 +1,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サーバの統合設計書です。
diff --git a/doc/Design_err.md b/doc/Design_err.md
deleted file mode 100644
index d74f3a8..0000000
--- a/doc/Design_err.md
+++ /dev/null
@@ -1,163 +0,0 @@
-ErrorDetail に `code`(機械処理用のエラーコード)を追加して、エラー種類ごとにメッセージテンプレートとhintを規定します。大きく **構文エラー** と **システムエラー** の2種に分けます。
-
----
-
-## ErrorDetail 構造(改訂)
-
-```
-{
- code: string, # エラーコード(機械処理用、LLMがパターン認識可能)
- line: int, # 行番号(1始まり)
- column: int, # 列番号(1始まり)
- message: string, # エラーメッセージ(プレースホルダ展開済み)
- severity: "error" | "warning",
- hint: string, # LLM向けの修正ヒント
- context: string # 該当箇所の周辺テキスト(任意)
-}
-```
-
-> `code` を追加しました。LLMは `code` を見てエラーパターンを分類し、`hint` に従って修正できます。`context` は該当行のテキスト前後を切り出したもので、LLMが位置を特定しやすくなります。
-> **注意**: `line` と `column` は **1始まり** です(最初の行・最初の列が1)。システムエラーやAPIレベルエラーなどで位置情報がない場合は `0` を使用します。
-
----
-
-## エラーコード仕様
-
-### 構文エラー(SYNTAX_*)
-
-LLMがMML記述を修正する対象。`hint` に具体的な修正例を含める。
-
-| 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})。オクターブ {octave} の音符 '{note}' は生成できません。` | `オクターブを調整してください。有効範囲: o{min_oct}〜o{max_oct}。` | warning(クランプ) |
-| `SYNTAX_CHANNEL_MISMATCH` | チャンネルに不適切なコマンド | `チャンネル '{channel}' で '{command}' は使用できません。` | `{channel_description}。コマンドを削除するか、適切なチャンネルに移動してください。` | 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 |
-
----
-
-### システムエラー(SYSTEM_*)
-
-LLMの修正対象外。内部処理の失敗。`hint` は空または「再試行してください」程度。
-
-| 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では構文エラー以外は返らない設計。
-
----
-
-### APIレベルエラー(VALIDATION_*)
-
-MML構文ではなく、APIパラメータのバリデーションエラー。`line` / `column` は `0` となります。
-
-| 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 |
-
----
-
-### エラーレスポンスの実例
-
-#### 構文エラーの例(LLMが修正可能)
-
-```json
-{
- "code": "SYNTAX_UNTERMINATED_REPEAT",
- "line": 5,
- "column": 18,
- "message": "リピート '[' に対応する ']' が見つかりません。",
- "severity": "error",
- "hint": "] を追加してリピートを閉じてください。回数指定(例: ]2)も可能です。",
- "context": " [ c d e f | g a b > c"
-}
-```
-
-LLMはこれを見て、「5行目の `[` に `]` を追加すればいい」と判断できる。
-
-#### 値範囲外の例
-
-```json
-{
- "code": "SYNTAX_VALUE_OUT_OF_RANGE",
- "line": 2,
- "column": 7,
- "message": "'v' の値 20 は範囲外です。有効範囲: 0〜15。",
- "severity": "error",
- "hint": "例: v15 のように指定してください。",
- "context": "A t150 l8 o4 v20 q2"
-}
-```
-
-#### システムエラーの例
-
-```json
-{
- "code": "SYSTEM_SYNTHESIS_FAILED",
- "line": 0,
- "column": 0,
- "message": "音声合成中にエラーが発生しました: frequency calculation overflow at note_number 135",
- "severity": "error",
- "hint": "MMLの内容を確認の上、再度お試しください。問題が続く場合は、短いMMLから試してください。",
- "context": ""
-}
-```
-
----
-
-### エラー発生時のcompose_mml戻り値(改訂)
-
-```
-compose action で ERROR がある場合:
-
-{
- success: false,
- wav_path: null, # WAVは生成しない
- duration_sec: 0,
- note_sequence: null, # 解析途中で停止した場合はnull
- validation: {
- errors: [ErrorDetail, ...], # 構文エラー・システムエラー
- warnings: [ErrorDetail, ...] # warningはerrorsとは別に格納
- }
-}
-```
-
-> warnings も ErrorDetail 構造で返す(severity = "warning")。LLMは warnings を見て「修正しなくても動くが、改善できる」ことを把握できる。
-
----
-
-これで、エラーまわりの仕様を規定しました。構文エラーは14種類、システムエラーは3種類、APIレベルエラーは3種類。LLMが `code` を見てパターン認識し、`hint` に従って修正できる構造になっています。
diff --git a/doc/Design_mml.md b/doc/Design_mml.md
deleted file mode 100644
index debb82d..0000000
--- a/doc/Design_mml.md
+++ /dev/null
@@ -1,375 +0,0 @@
----
-
-## 🎼 MML設計仕様書
-
-### 基本方針
-
-- **2つのモードを用意し、ツールのパラメータで切り替える**
-- モード間で書式を混合しない
-- 各モードはそれぞれ独立した仕様を持つ
-- どちらも出力する中間表現(NoteSequence)は共通
-
----
-
-## モード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-` | 音長減算 | タイの逆、少し短く(第2段階) |
-
-音長とテンポの関係: `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=ショート周期 |
-
----
-
-## モード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 V15 # トラック0 (Pulse1)
- C D E F G A B >C
-
-1: L8 O3 V12 # トラック1 (Pulse2)
- C R G R C R G R
-
-2: L4 O3 V7 # トラック2 (Triangle)
- C2 C2 G2 G2
-
-3: L8 V10 # トラック3 (Noise)
- R C R C R C R C
-```
-
-### コマンド仕様
-
-#### トラックヘッダー
-
-| 記法 | 意味 | 備考 |
-|---|---|---|
-| `0:` `1:` `2:` `3:` | トラック番号 | 0-3の4トラック |
-| — | 0: Pulse1, 1: Pulse2, 2: Triangle, 3: Noise | Pyxelは4ch構成 |
-
-> PyxelはFCの5chからDPCMを省いた4ch。DPCMは非対応。
-
-#### 音高
-
-| 記法 | 意味 | 備考 |
-|---|---|---|
-| `C` `D` `E` `F` `G` `A` `B` | ド レ ミ ファ ソ ラ シ | **大文字** |
-| `C+` または `C#` | ド♯ | |
-| `C-` | ド♭ | |
-| `O4` | オクターブ4 | `O0`〜`O7` |
-| `>` | 1オクターブ上 | |
-| `<` | 1オクターブ下 | |
-
-#### 音長
-
-| 記法 | 意味 | 備考 |
-|---|---|---|
-| `L4` | デフォルト音長 = 4分音符 | |
-| `C4` | 4分音符のド | |
-| `C8` | 8分音符のド | |
-| `C4.` | 付点4分音符 | |
-| `R` | 休符 | 音長指定可 `R4` |
-| `&` | タイ | `C4&C4` |
-
-#### 音量
-
-| 記法 | 意味 | 備考 |
-|---|---|---|
-| `V15` | 音量15 | 0-15 |
-| `V0` | 無音 | |
-
-> Triangle(トラック2)ではPyxel仕様に従う。PyxelはTriangleにも音量を設定可能(FC実機とは異なる)
-
-#### テンポ
-
-| 記法 | 意味 | 備考 |
-|---|---|---|
-| `T150` | テンポ150 | BPM |
-
-#### デューティ比
-
-| 記法 | 意味 | 備考 |
-|---|---|---|
-| `Q0`〜`Q3` | デューティ比 | 0=12.5%, 1=25%, 2=50%, 3=75% |
-
-> Pyxelの公式仕様でデューティ比コマンドが存在するか要確認。Pyxelは波形タイプで指定する方式の可能性あり(`SQR`, `PLS`, `TRI`, `NOISE`)
-
-#### 第2段階で追加予定
-
-| コマンド | 内容 |
-|---|---|
-| ノイズ周期モード | `M0` / `M1`(要確認) |
-| エンベロープ | Pyxel仕様に準拠(要確認) |
-
----
-
-## 両モード比較サマリー
-
-| 項目 | ppmckモード | pyxelモード |
-|---|---|---|
-| **大文字/小文字** | 小文字 `c d e` | 大文字 `C D E` |
-| **チャンネル指定** | `A` `B` `T` `N`(文字) | `0:` `1:` `2:` `3:`(番号) |
-| **コマンドの大文字** | `o` `l` `v` `t` `q`(小文字) | `O` `L` `V` `T` `Q`(大文字) |
-| **チャンネル数** | 4ch(DPCMは第2段階) | 4ch(DPCM非対応) |
-| **Triangle音量** | 無視(FC仕様準拠) | 設定可(Pyxel仕様) |
-| **コメント** | `;` | 要確認 |
-| **ヘッダー** | `#TITLE` 等 | トラック番号のみ |
-| **ループ** | `L`(全体) | 要確認 |
-| **小節線** | `\|`(視覚用) | `\|`(視覚用) |
-
----
-
-これが公式の完全版ですね…! ありがとうございます。今まで断片的だったのが、これで全部揃いました。保存しておきますね。重要な訂正がいくつかありました。これまでの調査と比べて、全部整理します。
-
----
-
-## 📄 Pyxel MML 公式コマンド仕様(確定版)
-
-**出典:** https://kitao.github.io/pyxel/docs/mml-commands.md (全文提供いただきました)
-
----
-
-### 完全コマンド一覧
-
-#### テンポ・音長・音量
-
-| コマンド | 記法 | 意味 | 範囲 | デフォルト |
-|---|---|---|---|---|
-| テンポ | `T150` | BPM | 1- | 120 |
-| デフォルト音長 | `L4` | n分音符。`L12`で8分3連符 | 1-192 | 4 |
-| 音量 | `V100` | 音量 | **0-127** | 100 |
-| ゲートタイム | `Q80` | ノート実発音割合(%)。100=隙間なし、0=無音 | 0-100 | 80 |
-
-#### 音色・ピッチ
-
-| コマンド | 記法 | 意味 | 範囲 | デフォルト |
-|---|---|---|---|---|
-| 音色(トーン) | `@0` | 波形タイプ。0:Triangle / 1:Square / 2:Pulse / 3:Noise | 0-3 | 0 |
-| トランスポーズ | `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 | -1〜9 | 4 |
-| 相対上 | `>` | 1オクターブ上(最大9) | — | — |
-| 相対下 | `<` | 1オクターブ下(最小-1) | — | — |
-| タイ/スラー | `&` | 同音高=タイ、異音高=レガート。**`C4&16` のように音長だけ指定も可** | — | — |
-
-#### リピート
-
-| コマンド | 記法 | 意味 | 範囲 |
-|---|---|---|---|
-| リピート開始 | `[` | 区間リピートの開始 | — |
-| リピート終了 | `]2` | 区間リピートの終了。回数指定。省略時は**無限リピート**。**ネスト対応** | 1- |
-
-> ⚠️ **重要な訂正:** これまでソースコード調査から `:...:` がリピート記法だと推測していましたが、公式ドキュメントでは **`[...]` が正しいリピート記法** でした。`RepeatStart` / `RepeatEnd` トークンは `[` と `]` に対応していたようです。ごめんなさい。
-
-#### エンベロープ `@ENV`
-
-| 記法 | 意味 |
-|---|---|
-| `@ENV0` | エンベロープOFF |
-| `@ENV1` | スロット1に切り替え |
-| `@ENV1 { 30, 20, 100, 50, 0 }` | スロット1を定義して切り替え |
-
-定義形式: `{ 初期音量, [継続tick, 目標音量]... }`
-- 1 tick = 4分音符の 1/48
-- スロット0は指定不可(OFF専用)
-
-#### ビブラート `@VIB`
-
-| 記法 | 意味 |
-|---|---|
-| `@VIB0` | ビブラートOFF |
-| `@VIB1` | スロット1に切り替え |
-| `@VIB1 { 24, 12, 100 }` | スロット1を定義して切り替え |
-
-定義形式: `{ ディレイtick, 周期tick, 深さcent }`
-- 1 tick = 4分音符の 1/48
-- スロット0は指定不可
-
-#### グライド(ピッチスライド)`@GLI`
-
-| 記法 | 意味 |
-|---|---|
-| `@GLI0` | グライドOFF |
-| `@GLI1` | スロット1に切り替え |
-| `@GLI1 { -100, 24 }` | スロット1を定義して切り替え |
-| `@GLI1 { *, * }` | `*`で前の音との差分を自動適用 |
-
-定義形式: `{ 初期ピッチオフセットcent, 0に戻るまでのtick }`
-- 1 tick = 4分音符の 1/48
-- スロット0は指定不可
-
----
-
-### これまでの調査からの主な訂正ポイント
-
-| 項目 | 修正前 | 修正後 |
-|---|---|---|
-| **リピート記法** | `:...:` | **`[...]`** (`[` 開始、`]` 終了、回数指定可、省略時無限、ネスト対応) |
-| **タイの音長指定** | `C4&C4` のみ | **`C4&16`** のように音長だけ指定可能 |
-| **`S` コマンド** | 「スピード」と推測 | **公式ドキュメントに存在しない**。code-exampleの `S` は別物だったか誤読 |
-| **`:` 記法** | リピート記法と推測 | **公式ドキュメントに記載なし**。ソースコードの別用途の可能性 |
-
----
-
-### ppmckモード vs pyxelモード — 最終比較(確定版)
-
-| 項目 | 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 |
-| **音量デフォルト** | — | 100 |
-| **デューティ比** | `q0`〜`q3` で4種指定 | `@1`(50%), `@2`(25%) の2種のみ |
-| **ゲートタイム** | 第2段階で検討 | `Q0`〜`Q100`(デフォルト80) |
-| **Triangle音量** | 無視(FC仕様) | 設定可(Pyxel仕様) |
-| **エンベロープ** | `@v`(第2段階) | `@ENV` スロット定義+切り替え |
-| **ビブラート** | `MP`(第3段階) | `@VIB` スロット定義+切り替え |
-| **ピッチスライド** | `s` スイープ(第2段階) | `@GLI` スロット定義+切り替え |
-| **トランスポーズ** | `K`(第2段階) | `K`(半音単位) |
-| **ディチューン** | `D`(第3段階) | `Y`(セント単位) |
-| **リピート** | `L`(全体), `[...]`(第2段階) | `[...]`(回数指定、省略時無限、ネスト対応) |
-| **タイの音長指定** | `c4&c4` のみ | `C4&C4` または `C4&16`(音長のみ可) |
-| **tick単位** | なし | 1 tick = 4分音符の1/48 |
-| **コメント** | `;` | 公式記載なし |
-| **ヘッダー** | `#TITLE` 等 | なし |
-
----
diff --git a/doc/Desing.md b/doc/Desing.md
deleted file mode 100644
index 44c4c5f..0000000
--- a/doc/Desing.md
+++ /dev/null
@@ -1,586 +0,0 @@
-全部確定ですね。設計書に反映して、確定版を出します。メモリにも保存しておきます。はい、記録も残りました。それでは確定版の設計書を出しますね。
-
----
-
-# ファミコン音源作曲MCPサーバ — 仕様・基本設計書(確定版)
-
-> 作成日: 2026-07-11 | ステータス: **確定**(実装前)
-
----
-
-## 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 スコープ外
-
-- 実装コードの作成
-- DPCMチャンネル(第2段階)
-- NSFファイル出力(第2段階で検討)
-- GUI / Webフロントエンド
-
-### 1.4 技術スタック
-
-| 項目 | 選定 | 理由 |
-|---|---|---|
-| 実装言語 | Python | FastMCP対応、numpyで音声合成、LLM親和性 |
-| MCP SDK | FastMCP | 優良SDK |
-| 音声合成 | numpy | 波形生成の数値計算 |
-| 音声出力 | 標準ライブラリ wave | WAV形式、外部依存なし |
-
----
-
-## 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"` / `"melody"` / `"chord"` / `"drum"` / `"empty"` |
-| `sample_rate` | int | no | 出力サンプリング周波数 (default: 44100) |
-| `normalize` | bool | no | 出力のノーマライズ (default: true) |
-
-#### action別の動作と戻り値
-
-**`action: "compose"`** — MMLをコンパイルしてWAVを生成
-
-```
-入力: mml, mode, (sample_rate, normalize)
-
-戻り値:
-{
- success: bool,
- wav_path: string, # 生成されたWAVファイルパス
- duration_sec: float, # 演奏時間
- note_sequence: object, # NoteSequence IR
- validation: { # コンパイル時に内包される検証結果
- errors: [ErrorDetail],
- warnings: [string]
- }
-}
-```
-
-> composeは必ず検証を内包する。エラーがあればWAVは生成せず、検証結果のみ返す。
-
-**`action: "validate"`** — MMLの構文チェックのみ(音声生成なし)
-
-```
-入力: mml, mode
-
-戻り値:
-{
- valid: bool,
- errors: [ErrorDetail],
- note_sequence: object, # 解析成功時のみ
- channel_summary: [ # チャンネル別サマリー
- { channel: string, note_count: int, octave_range: [int,int], duration_ticks: int }
- ]
-}
-```
-
-**`action: "template"`** — MMLテンプレートを生成
-
-```
-入力: mode, template
-
-戻り値:
-{
- mml: string, # テンプレートMML文字列
- description: string # テンプレートの説明
-}
-```
-
-#### ErrorDetail 構造
-
-```
-{
- line: int,
- column: int,
- message: string,
- severity: "error" | "warning",
- hint: string # 修正のヒント(任意)
-}
-```
-
-### 3.3 テンプレート種別
-
-| 種別 | 内容 |
-|---|---|
-| `basic` | 基本的な4ch構成(メロディ+和音+ベース+リズム) |
-| `melody` | メロディ重視(Pulse1主旋律、他は伴奏最小限) |
-| `chord` | コード伴奏重視(Pulse2で和音、Triangleでベース) |
-| `drum` | リズム重視(Noise中心のビートパターン) |
-| `empty` | 各チャンネルのヘッダーのみ(空のテンプレート) |
-
----
-
-## 4. 中間表現: NoteSequence IR
-
-### 4.1 設計方針
-
-- 両モード(ppmck / pyxel)のパーサが共通で出力する
-- 後段のSynthesizerはNoteSequenceのみを見れば動く(MMLの文法を知らない)
-- 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モードの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: "note",
- tick_position: int, # 絶対開始位置
- duration: int, # 音長(ticks)
- note_number: int, # MIDI音番号 (69 = A4 = 440Hz)
- velocity: int, # 音量 (0-15, Triangleはppmckで無視、pyxelで有効)
- duty: int, # デューティ比 (0-3, Pulseのみ)
- gate_time: float # ゲートタイム割合 0.0-1.0
-}
-
-RestEvent = {
- type: "rest",
- tick_position: int,
- duration: int
-}
-
-VolumeEvent = {
- type: "volume",
- tick_position: int,
- value: int # 0-15(pyxelは0-127から正規化)
-}
-
-DutyEvent = {
- type: "duty",
- tick_position: int,
- value: int # 0-3
-}
-
-TempoEvent = {
- type: "tempo",
- tick_position: int,
- bpm: int
-}
-
-RepeatEvent = {
- type: "repeat",
- start_tick: int,
- end_tick: int,
- repeat_count: int # 0 = 無限
-}
-```
-
-#### 拡張イベント(第2段階実装、第1段階は保持のみ)
-
-```
-EnvelopeEvent = {
- type: "envelope",
- tick_position: int,
- slot: int, # 1-15 (0はOFF)
- points: [ # 定義内容(切り替えのみの場合は空)
- { target_volume: int, duration_ticks: int }
- ]
-}
-
-VibratoEvent = {
- type: "vibrato",
- tick_position: int,
- slot: int, # 1-15 (0はOFF)
- params: { # 定義内容(切り替えのみの場合は空)
- delay_ticks: int,
- period_ticks: int,
- depth_cents: int
- }
-}
-
-GlideEvent = {
- type: "glide",
- tick_position: int,
- slot: int, # 1-15 (0はOFF)
- params: { # 定義内容(切り替えのみの場合は空)
- initial_offset_cents: int,
- duration_ticks: int
- }
-}
-```
-
-> 第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) + `@0`(Triangle) | ⚠️ 警告。チャンネルはPulse1のまま、`@0`は無視 |
-| トラック2(Triangle) + `@1`(Square) | ⚠️ 警告。チャンネルはTriangleのまま、`@1`は無視 |
-| トラック3(Noise) + `@0`(Triangle) | ⚠️ 警告。チャンネルはNoiseのまま、`@0`は無視 |
-
-#### 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) | |
-| タイ/スラー | `&` | |
-| リピート | `[`, `]` + 数値 (pyxel) | |
-| 小節線 | `\|` | 視覚用、再生に影響しない |
-| トラック識別 | ppmck: `A`/`B`/`T`/`N`/`L`, pyxel: `数字:` | |
-| ヘッダー | `#` で始まる行 (ppmck) | |
-| コメント | `;` 以降行末 (ppmck) | |
-
-**モード別正規化:**
-
-- ppmck: 音符・コマンドは小文字に正規化、`#` 行はヘッダー、`;` 以降はコメント
-- pyxel: 音符・コマンドは大文字に正規化、`数字:` はトラックヘッダー
-
-### 5.2 Parser(モード別)
-
-#### ppmckパーサ
-
-- トラックヘッダー `A`/`B`/`T`/`N` でチャンネルを決定
-- `L` は全体ループフラグ
-- `#TITLE` 等のヘッダーはメタ情報として保持
-- デフォルト値: octave=4, volume=15, length=4, duty=2
-- Triangleチャンネルでは `v` コマンドを無視(警告なし)
-- タイ(`&`)で同音高を結合
-- 第2段階: エンベロープ(`@v`), スイープ(`s`), 区間ループ(`[...]`), トランスポーズ(`K`), ノイズモード(`m`)
-
-#### pyxelパーサ
-
-- トラック番号 `0:`〜`3:` でチャンネルを決定
-- `@` コマンドはPulseチャンネルでのみデューティ比として有効。他チャンネルでは警告して無視
-- デフォルト値: octave=4, volume=100, length=4, gate=80, tone=0
-- 音量 `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の小数部として保持
-
-### 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`=最大振幅
- - NoteSequenceは両モードで `velocity` を保持するが、ppmckモードのSynthesizerはそれを無視する
-- **周波数**: `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. 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 |
-| **デューティ比** | `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`(半音単位) |
-| **リピート** | `L`(全体), `[...]`(第2段階) | `[...]`(回数指定、省略時無限、ネスト対応) |
-| **タイ** | `c4&c4` のみ | `C4&C4` または `C4&16`(音長のみ可) |
-| **コメント** | `;` | 公式記載なし |
-| **ヘッダー** | `#TITLE` 等 | なし |
-
----
-
-## 8. 確定事項一覧
-
-| # | 項目 | 決定内容 |
-|---|---|---|
-| 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段階は線形ミキシング |
-
----
-
-以上が確定版の仕様・基本設計書です。
-
-……全部揃いましたね。6つあった未決定事項が全部決まって、表の最後が「確定」で埋まったのを見ると、なんだか安心します。設計書はこれで完成です。実装に入るときに、この書類があれば迷わないはずです。
-
-何か足りないところや直したいところがあれば、いつでも言ってくださいね。 \ No newline at end of file