diff options
Diffstat (limited to 'doc/Design.md')
| -rw-r--r-- | doc/Design.md | 141 |
1 files changed, 123 insertions, 18 deletions
diff --git a/doc/Design.md b/doc/Design.md index 1ce8aeb..525a22e 100644 --- a/doc/Design.md +++ b/doc/Design.md @@ -25,7 +25,7 @@ LLMがMML(Music Macro Language)を記述することで、レトロチップ | 対象 | 内容 | |---|---| | 実装範囲 | Pulse1 / Pulse2 / Triangle / Noiseの4ch MML解析、WAV合成、バリデーション、テンプレート生成 | -| 構文拡張 | Pyxel `@ENV` / `@VIB` / `@GLI`をIRへ保持し、合成未対応のwarningを返す | +| 構文拡張 | Pyxel `@ENV` / `@VIB` / `@GLI`、ppmck `@v` / `@@` / `@MP` / `@EP` / `@EN` / `D` / `s` / `v+` / `v-` / `^` をIRへ保持し、合成未対応のwarningを返す | | 対象外 | GUI / Webフロントエンド、DPCM、NSF出力、未定義のppmck拡張構文 | ### 1.4 技術スタック @@ -241,7 +241,10 @@ ChannelSequence = { } Event = NoteEvent | RestEvent | VolumeEvent | DutyEvent - | TempoEvent | RepeatEvent + | TempoEvent | RepeatEvent | QuantizeEvent | DetuneEvent + | SweepEvent | RelativeVolumeEvent + | VolumeEnvelopeEvent | DutyEnvelopeEvent | LfoEvent + | PitchEnvEvent | NoteEnvEvent | EnvelopeEvent | VibratoEvent | GlideEvent ``` @@ -281,13 +284,16 @@ MML文字列 | オクターブ | `o`/`O` + 数値, `>`, `<` | | | 音長 | `l`/`L` + 数値, 音符直後の数値, `.` | | | 音量 | `v`/`V` + 数値 | | -| デューティ/音色 | `q`+数値(ppmck), `@`+数値(pyxel) | | +| デューティ/音色 | `@`+数値(ppmck/pyxel), `q`+数値(ppmck クオンタイズ) | | | テンポ | `t`/`T` + 数値 | | -| ゲートタイム | `Q` + 数値 (pyxel) | | +| ゲートタイム | `q`+数値(ppmck 1-8), `Q` + 数値 (pyxel 0-100) | | | トランスポーズ | `K` + 符号付き整数 (pyxel) | 例: `K-12`, `K+7` | -| ディチューン | `Y` + 符号付き整数 (pyxel) | 例: `Y-25`, `Y+10` | -| タイ/スラー | `&` | | +| ディチューン | `D` + 符号付き整数 (ppmck), `Y` + 符号付き整数 (pyxel) | 例: `D-10`, `Y-25` | +| 相対音量 | `v+` / `v-` (ppmck) | IR保持のみ | +| スイープ | `s`+数値,数値 (ppmck) | IR保持のみ | +| タイ | `^` (ppmck), `&` (両モード) | `^`はタイ、`&`はスラー | | リピート | `[`, `]` + 数値 | ppmckはASTへ保持してwarning、Pyxelは展開 | +| エンベロープ/LFO | `@v`/`@@`/`@MP`/`@EP`/`@EN` (ppmck), `@ENV`/`@VIB`/`@GLI` (pyxel) | IR保持のみ、合成未対応warning | | 小節線 | `\|` | 視覚用、再生に影響しない | | トラック識別 | ppmck: `A`/`B`/`T`/`N`/`L`, pyxel: `数字:` | | | ヘッダー | `#` で始まる行 (ppmck) | | @@ -305,7 +311,8 @@ Parserの責務は**トークン列からASTを生成するのみ**。値範囲ã - `#` ヘッダーを認識 - 各コマンドをASTノードに変換 - リピートの開始・終了をAST文として保持し、未対応warningを返す -- タイ `&` の結合対象をASTで保持 +- タイ `^` / スラー `&` の結合対象をASTで保持 +- `@v`/`@@`/`@MP`/`@EP`/`@EN` エンベロープ定義 `{...|...}` と使用をASTで保持 #### pyxelパーサ @@ -332,6 +339,9 @@ SemanticAnalyzerの責務は**ASTを検証・解釈してNoteSequence IRを生æˆ - Triangle音量無視 - Noise音高無視 - 全体ループ `L` の処理 +- `@` = デューティ比、`q` = クオンタイズ(gate_time = value / 8)として解釈 +- `^` タイを前の音符の音長延長として解釈 +- `D`/`s`/`v+`/`v-`、各種エンベロープ/LFOをIRへ保持し `SEMANTIC_UNSUPPORTED_FEATURE` warningを返す - リピートなど合成未対応のASTに対するwarning #### pyxel固有 @@ -341,6 +351,7 @@ SemanticAnalyzerの責務は**ASTを検証・解釈してNoteSequence IRを生æˆ - `@` コマンドのチャンネル適合性チェック - `@ENV`/`@VIB`/`@GLI` のIR保持と合成未対応warning - 無限リピートの2回打ち切りwarning +- `&` スラーを前の音符の音長延長として解釈 --- @@ -417,6 +428,89 @@ 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 @@ -506,7 +600,14 @@ class ErrorPhase(Enum): <track_header>::= "A" | "B" | "T" | "N" | "L" <ppmck_statement> ::= <note> | <rest> | <octave_cmd> | <length_cmd> - | <volume_cmd> | <duty_cmd> | <tempo_cmd> | <tie_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> ``` @@ -530,7 +631,7 @@ class ErrorPhase(Enum): BNFの各規則は Lexer → AST → IR/診断の一貫経路で検証する。`tests/test_mml_conformance.py`は、音符・休符・コマンド・トラック・ヘッダー・拡張コマンドについて正例、負例、境界値、位置情報を検査する。Hypothesisによる固定seedの生成テストで、有効音列のAST/IR保存と単一トークン破壊の非黙殺も確認する。 -ppmckの将来予約構文は現在の受理言語に含めない。Pyxel拡張は空白区切り引数だけを受理し、AST/IRへ保持したうえで`SEMANTIC_UNSUPPORTED_FEATURE`をwarningとして返す。 +ppmckの将来予約構文は現在の受理言語に含めない。ppmckの `@v`/`@@`/`@MP`/`@EP`/`@EN` および `D`/`s`/`v+`/`v-` は構文解析してIRへ保持し、合成未対応を `SEMANTIC_UNSUPPORTED_FEATURE` でwarningとして返す。Pyxel拡張も同様に空白区切り引数を受理し、AST/IRへ保持したうえで `SEMANTIC_UNSUPPORTED_FEATURE` warningを返す。 ## 9. APU音声合成エンジン @@ -548,13 +649,17 @@ NoteSequence IRを入力としてPulse1、Pulse2、Triangle、Noiseを合成しã | 項目 | ppmckモード | pyxelモード | |---|---|---| -| **大文字/小文字** | 小文字 `c d e` | 大文字 `C D E` | -| **コマンド** | `o` `l` `v` `t` `q` | `O` `L` `V` `T` `Q` `@` `K` `Y` | +| **大文字/小文字** | 小文字 `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 | -| **デューティ比** | `q0`〜`q3` で4種 | `@1`(50%), `@2`(25%) の2種のみ | +| **デューティ比** | `@0`〜`@3` で4種(Pulse系のみ) | `@0`〜`@3`(Pulse系のみ有効) | +| **クオンタイズ/ゲート** | `q1`〜`q8`(gate_time = value / 8) | `Q0`〜`Q100`(%) | +| **タイ/スラー** | `^` = タイ、`&` = スラー | `&` = スラー(タイなし) | +| **ディチューン** | `D-127`〜`D126`(IR保持のみ) | `Y-127`〜`Y127`(セント) | +| **エンベロープ/LFO** | `@v`/`@@`/`@MP`/`@EP`/`@EN`(IR保持のみ) | `@ENV`/`@VIB`/`@GLI`(IR保持のみ) | | **リピート** | `L`(構文上のループトラック)、`[...]`(AST保持・warning、合成未反映) | `[...]`(回数指定、省略時は2回で打ち切り、ネスト対応) | --- @@ -578,13 +683,13 @@ NoteSequence IRを入力としてPulse1、Pulse2、Triangle、Noiseを合成しã | カテゴリ | 実装内容 | |---|---| | MCPツール | `compose_mml`(compose/validate/template) | -| Lexer | ppmck/pyxel 両モード対応、共通トークン定義、INVALIDトークン | -| Parser | ppmck/pyxel 別Parser、AST生成 | -| SemanticAnalyzer | ppmck/pyxel 別Analyzer、AST→IR変換 | -| IR | NoteSequence、各種Event、ErrorDetail(phase付き) | +| 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モード | -| テスト | pytestの固定・統合テスト、Hypothesisの生成テスト、ruffによるリント/フォーマット | +| Templates | basic/melody/chord/drum/empty の5種 × 2モード。ppmckテンプレートは `@` でデューティ比を指定 | +| テスト | pytestの固定・統合テスト、Hypothesisの生成テスト、ruffによるリント/フォーマット。全200テスト通過 | --- |
