Skip to content

pmd ファイルフォーマット

Autch edited this page Dec 19, 2025 · 2 revisions

全体の構造

ワード(S1C33用語では「ハーフワード」)以上の値のバイトオーダはリトルエンディアンである。

#include <stdint.h>
  • パート数定義
  • ヘッダ
  • タイトル
  • タイトル2
  • ドラムパターン定義
  • パートA定義
  • パートB定義
  • パートC定義
  • パートD定義
  • パートE定義
  • パートF定義
  • ...
  • パート $N$ 定義
  • ドラムパターンオフセット定義

パート数は純正の mucc では最大 6, mucc-next による拡張では最大 32

タイトル、パート定義、ドラムパターン定義、ドラムパターンオフセット定義はヘッダからオフセットとして参照するので、ファイル中のどこにあるかは決められていない。ここでは mucc が生成するフォーマットを例に挙げる。

ただしドラムパターンオフセット定義はその個数が決められていないため、ファイル終端に置かれることになる。

ファイル全体を通して、「長さ」を定義する項目が存在しない。このため、ヘッダに細工することで簡単にバッファオーバーランを起こすことができ、ネットワークから直接ファイルを再生させるような処理系の実装は困難である(mucc 自身も、MML にエラーがあると壊れた .pmd ファイルを生成することがあるし、そのようなファイルを muslib に与えると TRAP する。auPiECE.kpi も同様)。

各部構造の説明

パート数定義

歴史的経緯から、パート数定義には2つのバージョンがある。

ファイル先頭のオクテットが 0 のときはその次のオクテットがパート数定義であり、0 でないときはそのオクテットがパート数定義となる。 MMLで7個以上のパートを定義しても、 mucc はそれらを出力しない(はず。auPiECE.kpi で確認)。

mucc は前者の、0 を前置する形式で出力する。

struct pmd_file_header_v0 {
  uint8_t zero; // == 0x00
  uint8_t num_of_parts; // == 6
}

または、

struct pmd_file_header_v1 {
  uint8_t num_of_parts; // == 6
}

mucc-next では最大 32 パートになる。理論上それ以上も定義できるが、ヘッダやデータ中に現れる各種オフセットが 64KB を超えると再生不能になる。

ヘッダ

ヘッダでは各パートの演奏データへのオフセットや、選曲時に利用できるタイトルなどへのオフセットを保持している。

これらのオフセットはファイル先頭を 0 とした値であり、対応する値がないときは 0x0000 が入っている(例えばMML上でFパートが未使用の時、part_offset[5] には 0x0000 が入る)。

struct pmd_file_header {
  uint16_t part_offset[num_of_parts];
  uint16_t drums_offset;
  uint16_t title_offset;
  uint16_t title2_offset;
}
part_offset
各パートの演奏データへのオフセット。そのパートのデータが存在しない時は 0x0000
drums_offset
ドラムパターンオフセット定義へのオフセット。ドラムパターン定義が存在しない時は 0x0000
title_offset, title2_offset
それぞれMMLで #Title, #Title2 で定義したタイトル文字列へのオフセット。存在しない時は 0x0000

タイトル、タイトル2

タイトルはシフトJISエンコードされた文字列である。'\0'で終端する。

MMLの#Title2の行に ; が含まれるとき、muccはこれを \x0d\x0a(つまりCRLF)に置換して出力する。

ドラムパターン定義

MMLで!!から始まる行として定義したドラムパターンがここに並べられる。

パターンの開始オフセットはドラムパターンオフセット定義に書かれている。

データの内容としてはパート定義と同じなのでそちらを参照。

ドラムパターンオフセット定義

前項のドラムパターン定義が現れるオフセットを定義する。 オフセット定義の終端はファイルの終端をもってこれに代える。

struct pmd_file_drums {
  uint16_t offset_to_drum_def[...]
}

=1 と宣言されたパートでは、音符は本来の意味を失い、そのノート番号は!!行で定義されたドラムパターンの何番目を呼び出すかの意味で使われる。MML中で !!名前 として宣言されたドラムパターンは、その出現順にドラムパターン定義に配置され、ドラムパートの音符はドラムパターンのn番目をコールする意味になる。

パート定義

各パートの演奏データがコマンドとその引数の羅列として定義されている。定義の先頭アドレスはヘッダの pmd_file_header::part_offset[n] から求められる。

muslib のタイムベースは TPQN = 24, 全音符は 96ticks

MML でパートのまとめ書きをすると、mucc ではデータ上ではまとめ書きした部分を各パートがコールするようにコンパイルされる。この扱いは不都合が多いので、mucc-next ではまとめ書き部分は各パートのコンテキストで都度コンパイルするようになっている。

muslib のコマンド

命令バイト 追加のバイト数 対応するMMLコマンド 内容
0x00 0 パートの終了。スタックがカラであれば演奏終了、そうでなければ(コールに対する)リターンとなる。
0x01-0x7e 0 l 音長指定。単位は tick.
0x7f 1 (uint8_t ext) 音長指定2。127tick以上の長さを指定するときはこちら。
0x80-0xdf 0 cdefgab / !XXXg 音符またはドラムパターン呼び出し。音符の時は o4c = 60 (0xbc) となる。=1 と宣言したパートの時はここから 0x80 を引いた値で offset_to_drum_def を引き、求めたアドレスをコールする。
0xe0 0 r 休符。
0xe1 1 (uint8_t gate) q ゲートタイム指定。muccのソースによるとMMLの値は -((24 * gate - 2374) / 99) で求められる。
0xe2 2 (uint16_t addr) L ジャンプ。このコマンド自体はパート末尾に置かれ、MML中でLと宣言したアドレスaddrにジャンプする。
0xe3 3 (uint16_t addr, uint8_t times) コール。現在位置とtimesをスタックに積んでaddrにジャンプする。
0xe4 1 (uint8_t times) [ リピート開始。現在位置とtimesをスタックに積んでおく。
0xe5 0 ] リピート終端。スタックから開始位置とtimesを取り出して、timesが0でなければtimesをデクリメントして開始位置とともに積み、開始位置にジャンプする。timesが0ならば何もしない(実行は次の命令に移る)。
0xe6 1 (int8_t n) _ トランスポーズ設定。n半音分トランスポーズする。
0xe7 1 (uint8_t bpm) T テンポ設定。テンポを4分音符=bpm に設定する。
0xe8 1 (uint8_t inst) @ 音色選択。音色をinstに設定する。値の意味は mucc のリファレンスに書いてあるものと同じ。
0xe9 1 (uint8_t vol) V 音量設定。設定するとエクスプレッション(v, (/))が0にリセットされる。
0xea 4 (uint8_t ar, dr, sl, sr) E エンベロープ指定。値の順番と意味は mucc のリファレンスと同じ。…のはずだけど、どうも実際の鳴り方と一致しない気がする。
0xeb 1 (int8_t detune) D デチューン。
0xec 1 (uint8_t note) ? 音符その2。=1 と宣言したパートでは 0x80-0xdf の音符はドラムパターン番号として使われるので、ドラム音色の音程を指定したうえで発音するためには ?o4c などと指定する。
0xed 2 (int8_t porpd, speed) Z ポルタメントパラメータ指定。ポルタメント終了時に開始時の音符からporpd半音分上下する。MMLにコマンドは一応定義されているが、通常 {} で自動計算させているので使用しない。
0xee 0 { ポルタメント開始指定。
0xef 0 } ポルタメント終了。
0xf0 1 (uint8_t attr) = パート属性。1 のとき、そのパートはドラムパートであり、音符コマンドは音符ではなくドラムパターン定義をコールする。
0xf1 4 (uint8_t depth, speed, delay, rate) Y ビブラート定義。値の順番と意味はmuccのリファレンスと同じ。
0xf2 1 (uint8_t vol) マスターボリューム?(muslibでは未実装)
0xf3 2 (uint16_t fade) マスターフェード?(muslibでは未実装)
0xf4 2 (uint16_t fade) パートフェード?(muslibでは未実装)
0xf5 2 (int16_t bend) ピッチベンド?(muslibでは未実装)
0xf6 0 : リピート脱出。
0xf7 0 nop
0xf8 0 & レガート開始。muslibではレガートは開始と終了で音符を囲むことで表現する。
0xf9 0 レガート終了。
0xfa 1 (uint8_t exp) v エクスプレッション。ボリューム V を設定すると 0 にリセットされる。
0xfb 1 (int8_t exp) ( または ) 相対エクスプレッション。ボリューム V を設定すると 0 にリセットされる。
0xfc-0xff 0 nop