PC-8001 SD-DOS その1 ~ストリーム再生でVGMを鳴らすまで(と、その苦労)~

PC-8001PC-8001 SD-DOS その1 ~ストリーム再生でVGMを鳴らすまで(と、その苦労)~

以前、中華液晶や手元のモニタにPC-8001をつないで遊んでいた話を書きました。最近はそのPC-8001で、SDカードからファイルを読める SD-DOS をいじっています。

SD-DOSは chiqlappe さんが作られた、PC-8001用のFAT16 SDカードDOSです。$6000 からの空きROM領域に載るようになっていて、なかなか良くできています。これを8KB RAMボードや64KB RAM機に載せて使っています。

使っているうちに、だんだん「ファイルを全部メモリに読まずに、少しずつ読みながら処理したい」と思うようになりました。PC-8001のRAMは64KBしかないので、大きいファイルは丸ごとは読めません。そこでSD-DOSに「ストリームで読む機能」を足してみることにしました。利用例として、VGM(ゲーム音楽のレジスタ書き込みログ形式)をロードしながら鳴らすプレイヤーを作ります。

先に結論を言うと、機能そのものは割とすんなりできたのですが、実機で気持ちよく鳴らすところで「だいぶ」ハマりました。今回はその苦労話です。

フォークと、ライセンスのお願い

SD-DOSは他人様のリポジトリなので、改造して公開するならライセンスをはっきりさせたいところです。ところが、元のリポジトリにはライセンス表記がありませんでした。

ダメ元で chiqlappe さんに「フォークして拡張したいのですが、ライセンスを明記してもらえませんか」とお願いしてみたところ、快く元リポジトリに MIT ライセンスのファイルを置いてくださいました。おかげで、安心してフォークして公開できます。ありがたいお話でございます。

VGM特化にしない ~ 汎用ストリームAPIにする

最初に方針を決めました。SD-DOSに「VGM再生機能」を入れるのではなく、あくまで「ファイルをストリームで読む汎用機能」を入れる、という方向です。VGMの解釈や音源への出力は、サンプルのプレイヤー側の仕事にします。

具体的には、SD-DOS本体の先頭に固定ジャンプテーブルを置いて、STRM_OPEN(6005H)、STRM_READ(6008H)、STRM_CLOSE(600BH)の3つを生やしました。プレイヤーはこの番地をCALLするだけです。こうしておけば、VGM以外のものを流したくなっても使い回せます。

一括ロードからインクリメンタル読みへ

最初の実装は、512バイトのセクタを一気に読む普通のやり方でした。でも実機で鳴らすと、セクタを読むたびにプツッともたつきます。

PC-8001のSDアクセスは8255のビットバンギングなので、1セクタ512バイトを読むのに約0.1秒かかります。その間CPUが固まるので、演奏が引っかかるわけです。

そこで、ブロック読みを開いたまま1バイトずつ取り出す「インクリメンタル読み」に変えました。さらにプレイヤー側に先読みリングバッファを持たせて、演奏のウェイト時間に次のデータを先読みしておく形にしました。読み込みの時間を、音を伸ばしている間に隠してしまう作戦です。

ストリーム再生の構成

この方針自体はうまくいって、もたつきはだいぶ減りました。……が、ここからが本番でした。

「VGM ENDが一瞬出てから落ちる」謎

しばらく再生していると、毎回ある所で「Syntax Error」や「Disk Basic」のエラーが出て止まります。しかも出るエラーが回によって違う。いかにもN-BASICのワークを壊している感じです。

最初はSDの読み込みを疑いました。バッファあふれ?スタックあふれ?それともSDカードの特定セクタが不良?と、片っ端から疑っていきました。

ところが、自分で書いた小さなZ80エミュレータ(検証用ハーネス)で実ファイルを通しで流してみると、最後まできれいに読み切って完走します。FAT追跡もメモリ書き込みも全部シロ。ロジックは正しいのです。

よく見ると、エラーの直前に「VGM END」が一瞬だけ画面に出ていました。つまり曲は最後まで鳴っていて、「終わって戻るところ」で落ちている。

犯人はこれでした。プレイヤーは機械語モニタの G9000(Goコマンド)で起動していたのに、終了が RET だったのです。Gコマンドはきれいな戻り先をスタックに積んでくれないので、RET すると変な所へ飛んで暴走します。終了を JP BASIC(0081H)に変えたら、ぴたりと直りました。

終了処理 ― RETだと暴走、JP BASICで解決

エミュレータでは自分でCALLして起動していたので、この終了の経路だけテストから漏れていたのでした。盲点でした。

PC-8001は実は1.84MHzらしい

タイミング調整をしていて、ずっと計算が合わない感じがありました。Z80 4MHzのつもりで見積もると、実測とどうもズレるのです。

PC-8001は画面表示中のDMAでCPUのメモリサイクルを取られるので、実効クロックは4MHzの半分弱、約1.84MHzらしいです。つまり実際は2倍くらい遅い世界。これに気づいてから、「ああ、だから手計算が当てにならなかったのか」と腑に落ちました。なので係数は全部、実機の耳で合わせる前提にしました。

最後のプレイヤー調整で超絶ハマる

ここが一番の苦労でした。テンポや先読みの強さを実機で追い込めるように、いくつかの係数を固定アドレスに置いて、LOAD 後に POKE で変えられるようにしました。

  • &H9003: 全体テンポ(WAIT_KV)
  • &H9004: 先読みの強さ(READ_SAMP)
  • &H9005: 書き込み処理の補正(WRITE_COST)
  • &H9006: YM2203のBUSY待ち上限(実験用)

最初は全体テンポ(9003)を合わせれば良いと思っていたのですが、密な小節だけ詰まったり、後半で急に「もったり」したり、症状がいろいろ出ます。

調べていくと、後半のもったりは先読みが追いつかずバッファが空になる現象でした。曲の後ろの方に密な区間があって、そこで読みが間に合わなくなるのです。

後半のバッファ枯渇(READ_SAMP=32 だと後半で枯れる)

バッファを増やせば、と思ったのですが、9000Hから載るプレイヤーはN-BASICの高位ワークの手前までしか使えず、16~18KBが限界でした。完全に消すには32KBくらい要るのに、メモリに入りません。

最終的には、先読みを強める係数(READ_SAMP)を下げる方が効きました。READ_SAMPが32のままだと後半が枯れますが、16まで下げたら最後まで走り切りました。READ_SAMPが32と16で体感のテンポは変わらなかったので、これで決定です。

あとは、曲末で最後のSDブロックを開きっぱなしにして渋るのも直して(EOFで即クローズ)、ようやく一曲をきれいに最後まで鳴らせました。動いてよかった……。

ファイルを選んで鳴らせるようにする

ファイル名をソースに直書きしているのも不便なので、SDの *.VGM を一覧表示して番号で選べるようにしました。これも汎用に倒して、ストリームAPIに「ディレクトリの全ファイル名をまとめて返す」STRM_DIRLIST(600EH)を足し、プレイヤー側で 一覧 → 番号入力 → 再生 → メニューに戻る、という流れにしています。

実機で鳴っている様子

操作はこんな感じです。

`LOAD "VGMPLAY.CMT"
MON
*G9000
-- VGM --
1: SONG1.VGM
2: SONG2.VGM
NO(0=END)? 1`

機械語モニタで G9000、現在のディレクトリの曲が番号付きで並ぶので、番号を入れてEnterで鳴ります。終わるとまたメニューに戻るので、続けて別の曲も選べます。

次にやること

YM2203では最後まで気持ちよく鳴るようになりましたが、PSGはまだ実機で鳴らせていません。手持ちのVGMがYM2203単独だったので、PSG入りのVGMで確認するのが次の宿題です。

それと、根っこのSDアクセスはビットバンギングなので、いずれ Pico あたりでドライブを作ってビットバンギング卒業、というのもやってみたいところです。実効1.84MHzでこれだけ苦労したので、その下回りが速くて確実になると、また景色が変わりそうです。

その話はまた次回にしたいと思います。

関連リンク

PC-8001