FC音源と$4017の罠

<2022/03/20追記>
ボクの理解が浅かった。
$4017の7bitを変えるように書き込むのは不正でも何でもありません。NTSC or PALの初期設定とはまた別の扱い。
FCで鳴らせる音声のBPMを考慮すれば (推奨されるBPMなどはありますが) すぐに間違いだと気づけるハズだったのに……
従って本記事の記述は大半が誤りになりますのでご注意ください。
……全くもってまだまだですね。
<追記ここまで>

2022/03/07今日現在、裏で色々とタスクを積みながらも、のんきに締切のない作業をしているんですが……

FC (特にRP2A03内蔵) 音源準拠のチップチューンを作っていれば、その規格(NSF, NES Sound Format)を使って様々な環境で自作曲を鳴らしたくなるのが人情。
そんなワケで最近、FC音源をエミュレーションする汎用サウンドドライバを自作しています。
ノイズの再現性にはもう少し拘りたいなと考えてはいるものの、もう九分九厘完成。しかしそう思ったところで、マズい問題が浮き彫りになってしまいました。

テスト用NSFの再生時、DPCMの再生周波数がどうもおかしいケースがしばしば見られたのです。

CDEFGABCDFBACEGC、と16段階に周波数(音高)を変えられるFCのDPCM。当時リリースされたタイトルではドラムなどのパーカッション音に使われるケースが大半ではあったものの、サンソフトの後期タイトル『ラフワールド』『へべれけ』『バットマン』『ギミック!』のようにベースで使われているケース、『サマーカーニバル’92烈火』や『スーパー魂斗羅』などのオーケストラヒットで使われているケースでは結構致命的になってきますし、ボク自身もDPCMでベースを鳴らしている曲を作ったことがある(『Enigmatic Laughter』)ため、重く見なければならない事例でした。
というかそもそもの話、一般的なエミュレータで鳴らせる音が自作のドライバで正しく鳴らせないのは由々しき事態でもあるワケで。

で、調べたところ……何と原因は当のテスト用NSFのほうに潜んでいました。何でだよ。

今回は備忘録記事の意味合いが強いので、詳細に記述していきましょう。
RP2A03の内蔵PAPUは、いつでも任意でPRG側から$4017の7bitの値によって同期を取れるようになっています。
通常ならここに書き込む値が途中で変わる必要はない(と思う、国内版FCなら0で海外版NESは1)のですが、サンプル用DPCMの一部はあろうことか再生中ここに初期化時と違う値を書き込んでいたのでした。
結果、ここの値で再生周波数が変わってしまうDPCM(とノイズ)は、妙に音痴な再生となってしまったワケ。

このことを確かめるに当たっては、NSFのコードを一部編集するハメになりました。

今回修正対象になったのは、NSFファイルにおいて、アドレス$7Aの0bitが0である(国内版として周波数を初期設定)にも拘わらず、$4017の7bitに1を書き込んでいたモノ。
コード上では、

a9 xx 8d 17 40

となっているケースでした。a9 xxでAレジスタ(アキュムレータ)に即値xxをロードし、直後8d 17 40でAレジスタの値を$4017に書き込む流れです。
ここの7bitが1になっていた場合(確認できたのは80c0のいずれか)、ここを0(それぞれ0040)に修正。
その上で再生したところ、見事想定通りの周波数で音が出力されました。

尚、修正対象となったこのコード列、PRGROM上で複数箇所に記述されているNSFもあり、出会した時は割とキレそうになりました。何でだよ。

そしてついでに余談もいくつか。
この「周波数が途中で変わってしまうNSF」、原因に気づいてすぐの段階でボクは「不正なNSF」と勝手に呼んでいたんですが……
実はこうして周波数を切り替えないとしっかりサウンドの同期が取れない互換ハードもあるらしく。
何かと言うと、Dendyと呼ばれるシロモノがその筆頭です。
詳細はここに記述せずおきますが、まあ、そういうハードが前提に据えられているのなら、こんな方法で同期を取る必要のあるPRGを「不正なNSF」呼ばわりするのも強ち間違いではない様子……
とは言え、どうやらそのNSFはこんな環境もサポートしているみたいです。先述した「NSFのアドレス$7Aの1bitが1」である場合がおそらくそれ。ネットに転がっている資料を見比べるとまちまちなものの、推察できる解答は他にないと思われます。
この辺り、テスト用ROMやNSFを適当に拾ってくるのはあんまり良くないなあ、と改めて悟ったので、自戒として。

ところで、他のエミュレータではこういったNSFにどう対応しているのか。
VirtuaNES、FCEUX、Mesenなどは、どうやらPRGROM任せで同期を取りつつDPCMやノイズの周波数だけを初期設定依存にしているようです。(誤っているかも……)
一方で驚いたのがNestopia。あろうことか最初からDendy想定でも同期を取れる設計になっていました。
この辺り、大雑把と言うべきか柔軟すぎると言うべきか分かりませんが、様々な対応があるみたい。

それにしても、バイナリを読み書きするなんてめちゃめちゃ久々でした。
vimのバイナリモードだってどれぐらいぶりに使ったか。値を正しく読み込むのにオプション-b、vim内からコマンド:%!xxdでダンプ、編集したら:%!xxd -rで復帰させて保存。
最後にこれも備忘しておくこととします。

Written on March 7, 2022