PC-6001 PSG音源ドライバ用 MMLコンパイラ (C言語コマンドライン版)
- 現在のバージョン: 0.1.0 (2025/12/07)
- 更新履歴は 更新履歴 項を参照
よっしゅさん作の PC-6001用PSG音源ドライバ ver1.1c 向けの MMLソーステキストを、音源ドライバがそのまま演奏可能な バイナリデータにコンパイルするコマンドラインツールです。
同様のツールとして TINY野郎さん作の
MML2P6DRVがありますが、
個人的にPC-6001デモをNetBSD開発環境で作成する際に
「MML作成後、エミュレータ用P6バイナリと実機CLOAD用 wavファイル作成までを
make 一発で行えるようにする」
ことを目標に作成しています。
まずはオリジナルの PC-6001版 MMLコンパイラと同等の演奏ができることを前提に C言語で書き直しています。
- 入力: テキスト形式の MML ファイル
- 出力: 各チャンネル先頭アドレス + 3チャンネル分の PSGデータを含むバイナリ
- 想定環境: PC-6001 および それを流用した互換環境上のPSGドライバ
※PSG演奏ドライバ本体は含まれません。
自分用に作ったものなので issue や pull request には対応できない場合があります。
- C コンパイラ (C99 相当)
- NetBSD や Linux といった今どきのPOSIX +αの環境を想定
難しい関数やライブラリは使っていないので、以下程度の関数があればいいはず……getopt(3)basename(3)(<libgen.h>)err(3),errx(3)(<err.h>)
NetBSD / Linux などの Unix系OSでの使用を想定しています。
configure などの自動設定は未サポートです。
makeエラーが出る場合は適当に Makefile などを修正してください。
DEBUG マクロを定義すると、簡単なデバッグ出力 (DPRINTF) が有効になります。
make -DDEBUGp6psgmmlc [-b addr] input.mml output.bininput.mmlコンパイル対象の MML テキストファイルoutput.binコンパイル結果の PSG データバイナリ-b addr出力データのベースアドレス (16bit)。 ドライバから見たロードアドレスに合わせて指定します。 書式は0x8000のような 16 進数も使用可能です。
コンパイル結果の PSGデータバイナリの先頭は以下の構造になっています (リトルエンディアン):
| オフセット | 内容 |
|---|---|
| 0 | チャンネル 1 (D) 先頭アドレス (word) |
| 2 | チャンネル 2 (E) 先頭アドレス (word) |
| 4 | チャンネル 3 (F) 先頭アドレス (word) |
| 6 | 予約 (未使用) |
| 8〜 | 各チャンネルの PSG データ |
各チャンネルのデータ末尾には、ドライバ仕様に従って 0xFF が付加されます。
-
行頭の 1 文字でチャンネルを指定します:
D… トーン 1E… トーン 2F… トーン 3
-
MML記述例
D t8,3L48L+32 m12,1,2,1 J
D e24f48f+16&f+48f32e2.&e32&e24
D v12o6r4e16r4.r16e16r4.
D r16e16r4.r16e16r8.
D e16r8.e16r4r16<b16r16>a16r8.
E t8,3L8L+4 s-2,4,-1,0,0 J
E <I112e8>I113e8<I114e8>I115e8<I112e8>I113e8<I114e8>I115e8
E <I0e8>I1e8<I2e8>I3e8<I0e8>I1e8<I2e8>I3e8
E <I0c8>I1c8<I2c8>I3c8<I0c8>I1c8<I2c8>I3c8
E <I0d8>I1d8<I2d8>I3d8<I0d8>I1d8<I2d8>I3d8
F t8,3L8L+16 m8,1,2,1 J
F v13o5e4>e16r16<c+8f+4>e16r16<d+8
F g+8a8g+8e8&e24f48f+8&f+24f48e4
F e16c16<b16>c16e16c16<b16>c16e16c16<b16>c16e16c16<b16>c16
F d8.r16c8.r16<b16a16g16.r32f+16.r32d16.r32
- 行頭に空白やタブがある場合はスキップされ、
その次の文字が
D/E/Fの場合に その行がコンパイル対象になります。 D/E/Fの各パートを記述する順序は任意です。 (先頭から行単位で走査して出てきたパート毎にコンパイルします)
-
行頭の
Xで、その行以降の各行のコンパイルをトグルします。X ; ここから D/E/F 行を無視 D O4CDEF ; 無視される X ; ここから再び有効 D O5CDEF ; コンパイルされる
ここでは、主に C 実装側で解釈している範囲を記載します。 詳細な意味やデフォルト値はオリジナルのドライバ仕様書を参照してください。
A B C D E F G… 音符R… 休符#/+… 半音上げ-… 半音下げ.… 付点 (複数指定可能)^… タイ相当の音長加算&… タイフラグ
例:
D O4 C4. D8 E16^E16 F4&
On… オクターブ設定 (1〜8)>n… オクターブを n 上げる (n 省略時は 1)<n… オクターブを n 下げる (n 省略時は 1)
オリジナルの PC-6001用音源ドライバ用コンパイラでは、 「演奏上のオクターブ」と「最後に出力したオクターブ」を分けて管理し、 音符コマンド出力時にオクターブが変化していた場合のみ音符コマンド前に オクターブコマンドを出力する、という仕様のようなので、 このコンパイラもその仕様に合わせています。
このため、ドライバの初期オクターブ値 (オリジナルのドライバでは O4)
がコンパイラの想定と異なる場合はコンパイラ側の初期値の修正が必要です。
(MML側の先頭で明示的にO4を指定してもオクターブコマンドは出力されない)
Ln ; 通常の L 音長
L+n ; L+ (サブ音長)
nは 1,2,3,4,6,8,12,16,24,32,48,96 のいずれか- 内部的には 96 分音符単位に変換して管理しています
C%96 ; 96 (96分音符単位で全音符相当) を直接指定
L%24 ; L 音長を 24 (4分音符相当) に直接指定
L%+192 ; L+ 音長を 192 に直接指定
C%nなどの音符のnは 1〜32767L%nのデフォルト音長のnは 1〜255- 範囲外の指定はエラーになります。
.を 1 つ付けると 1/2 分..で 1/2 + 1/4 分- それ以上も同様
付点計算前の実音長が奇数の場合 (32.など) はエラーになります。
実装されている主なコマンドと対応バイト列は以下の通りです。
On… オクターブ設定 (1〜8) →0x80 + nですが、前述の通り音出力時に出力する仕様<n/>n… オクターブアップ / ダウン (1〜8, 省略時 1) → 音源ドライバ仕様としては相対値指定はなく、コンパイル時の絶対値管理Vn… ボリューム設定 (0〜15) →0x90 + n(n/)n… ボリュームアップ / ダウン (1〜15, 省略時 1) →0xB0 + n/0xA0 + nIn… ワークエリアへの変数書き込み (0〜255) →0xF4, nJ… 曲の終わりでこの位置に戻る →0xFE- ネスト中 (
[ ]の内側) では使用不可
- ネスト中 (
M… ビブラート設定 (4 パラメータ /M%n形式)N… ビブラート有効/無効スイッチ →0xF6Pn… ノイズモード (1〜3) →0xED/0xEE/0xEFQn… ゲートタイム (0〜255) →0xFA, nS ...… ソフトウェアエンベロープ (S n1,n2,n3,n4,n5)n1 == 0の場合は OFF 扱いで、以降のパラメータは出力しません
T n1,n2… テンポ設定 →0xF8, n1, n2U%n,U+n,U-n… デチューン (-127〜+127) →0xFB/0xFCWn,W+n,W-n… ノイズ周波数 (0〜31 / -31〜+31) →0xEB/0xEC_n… 転調 (-12〜+12)- 各音符ごとに「論理オクターブ + 転調」による実効オクターブを計算し、 範囲外になる場合はコンパイルエラーとします。
[/]/:… ループ構文 (ネスト 4段まで)X… 当該チャンネルのコンパイル停止 (現状実装では「このチャンネルの MML解析終了」は対応していません);… 行末までコメント
エラーが発生した場合は標準エラー出力に以下の形式で表示されます:
エラー: <メッセージ> (<行番号> 行目, <桁番号> 桁目)
<該当行の内容>
^ ← エラー位置
例:
エラー: 音長の値が不正です (1,2,3,4,6,8,12,16,24,32,48,96) (9 行目, 5 桁目)
D L64 C D E ; L64 は許可されていない音長
^
ネストを閉じないままファイル末尾に到達した場合なども、 最後にそのチャンネルでコンパイルした行を表示してエラー位置を示します。
- 一般(?)の方はTINY野郎さんのMML2P6PSGDRVを使ったほうが便利かと思います。
- MMLの解釈およびバイナリデータ仕様については 音源ドライバマニュアル記載とPC-6001版コンパイラのZ80コード実装に なるべく寄せていますが、一般的なMML記述で演奏に影響の少ない部分については 厳密な1:1対応はさせてはいません。
- あまり使わないコマンドについては (まだ) きちんとテストしていません。
- 投稿がしたいのに 一千光年 feat. PC6001VX のPSG演奏デモはそれなりに鳴っています。
- オリジナル Z80コンパイラの仕様とはエラー判定など一部の仕様が異なります。
L音長指定でも^が使用可能、音符の32768〜65535 の音長がエラーにならない、など
(オリジナルではC〜B,RとLとでパーサーが別だがこのコンパイラでは共用)
- 前述の通り、初期オクターブの動作はドライバ側のデフォルト値に依存します。
- コンパイル途中でエラーが発生した場合でも、MMLファイルのコンパイルは
ファイルの最後まで継続しますが、
[:]などのループコマンドで ネスト関連エラーがある場合、仕様上それ以降の行におけるネスト異常判定は 正しく行われません。 - オリジナルのドライバのマニュアルで「ネストに関する謎」という説明にあるとおり
[:]のループ内部で音長指定やオクターブ指定をした場合の挙動は 仕様定義がドライバ実装都合寄りになっています。- TINY野郎さんの MML2P6PSGDRVではこのネストの仕様をオリジナルから変更されていますが、
このコンパイラではオリジナルコンパイラの実装設計意図に合わせています。
(オリジナルコンパイラの「多重ネストで音長とオクターブが正しく再設定されない」バグは修正してあります) - これらの仕様差異に影響されないように、MML記述時にはループ内では音長指定を行わない、 ループ内で音長指定行う場合はループ脱出後に音長の再設定を行う、としたほうがよいと思います。
- TINY野郎さんの MML2P6PSGDRVではこのネストの仕様をオリジナルから変更されていますが、
このコンパイラではオリジナルコンパイラの実装設計意図に合わせています。
- v0.1.0 (2025/12/07)
2条項BSDライセンスとしていますが、詳細は各ソースファイル冒頭のライセンス文を参照してください。