前回
ファミコンエミュレータを写経してみるお話4【nestest.nesの起動】 - 196Log
やること
前回はnestest.nesを起動させ無事画面が表示されました。(既に手動でcpuテストはほぼ終わっていますが、)キーパッドによる入力ができなければ、テスト実行ができません。なので今回はキーパッドで操作できるようにします。(短いので音もチャレンジします。)
参考にするもの
- コード周りはこちらのコードを観させてもらおうと思います。また
Rustの書き方の勉強としても頼りにさせてもらいます。
ファミコンエミュレータの創り方 - Speaker Deck
- こちらに有志の皆さんが
NESの情報をまとめて下さっているので、利用させてもらいます。特に、コードを写経しているだけでは意味がないので、まずこちらを見て自分で実装を考えます。
- こちらの本も参考にさせてもらいます。1つ目のリンクの方の実装と見比べる事で、実装にマストな部分を見出すために使いました。また、こちらの方の実装順番も参考にしてます。
読み始める前に
- ファミコンの仕様についてはネット上にたくさん情報が上がっているので、ここでは詳しい事は書かないです。(自分も分からない)用語の説明もほとんどしていません。 しかし、実装手順の再現性があるサイトは個人的に少ないなと感じたので、自分の試行錯誤をここにまとめて、うまく踏み台にしてもらえたらなと考えています。言語は
Rustを使っており参考サイトもRustが中心のものが多いです。 - また、解説力も乏しいので、適宜実装が完了したと思われる
commitにジャンプできるGithubのリンクをそれぞれに置いておくので、そこからコミット履歴などを参照していただいてもらう形にします。申し訳ないです。
キーパッド
ブラウザでのキー入力を8bitにのせて、romの末尾にデータを添付する形でゲーム側に入力情報を渡しているようです。入力情報の更新は最終的に1フレームごとに行われてます。ファミコン本来では、情報の格納されたレジスタをリードするごとに対応ボタンを変更して、うまいことグルグル回しながら判定しているようです。詳しくは上の参考サイトをご覧ください、、
実装自体は参考リポジトリさんの通りの実装になっています。イベントをbuttonタグなんかにつけても良さそうですね。こちらが実装完了したリンクになります。
cpuテスト
を走らせてみたところ、全て通ってしまったので、このまま音の初期実装に映ります。ダメだった場合、前回の記事のデバッグ方法のメモを参考にしてみてください(雑ですが)

サウンドまわりの実装
結局このスライドを参考にさせてもらいます。ほんとこのスライドは要点がわかりやすく纏まっている為、実装にとっつきやすくなります。ありがたい限りです。
ファミコンエミュレータの創り方 - Speaker Deck
要点をまとめると以下の2点かと思います。
js側で波形を生成して音を表現する。- 生成する波形の指示を
Rust実装側から逐次jsのメソッドに伝えていく。
まず、音を表現するjs側の実装を写経していきます。まず矩形波を実装して見ます。参考リポジトリを見ると、duty比によって使用する波形を自分でpulse.jsに用意しているようです。duty比は矩形波の凸凹の幅の比率だそうで、NESでは4種類を使い分けられるようです。今回js側で操るWebAudioAPIには、50%の矩形波しか作れないようで、このため自分で用意したと思われます。
矩形波
で、この記事によると、ノコギリ波を組み合わせると自由に矩形波を作れるようになるそうなので、参考にさせてもらおうと思います。
ひとまず矩形波Squareを実装しました。以下のリンクがその時のブランチの先頭になります。
また同時にapuテストのROMを走らせて、動作確認をしました。
https://wiki.nesdev.com/w/index.php/Emulator_tests
付属のサンプル音源と比較すると
- 波形の切れ目がない(サンプルがあっているのかは不明)
- 音が全体的にはっきりしている
- 閾値での音階変化のテストで一部音が変化しない
の違いがありますが、ひとまず聴ける程度にはなっているはずなので、このまま進めます。
ノイズ
参考コードでは以下が正確には実装されていません。
- ノイズのシフトレジスタによるロングとショートモードの実装(乱数をjsで生成するため不要)
どうするか悩みましたが、音の再現は一番の目的から少し外れてしまうため、ここでは完璧は求めず写経するだけにします。他のリポジトリを探しましたが、タイマーなどからしっかりエミュレートしている場合が多く、再実装するには割と大規模な実装と修正になりそうでしたので、後回しにしようと思います(APU自体も240Hzで駆動する?分周器のみにしか対応していない。irq割り込みも未対応)
ノイズに関しては、次のリポジトリを参考にいつでもリファクタが可能な実装を目指しました。
実装はこんな感じです。
三角波
三角波は矩形波よりも若干楽に実装可能かと思います。今までの実装がわかればなんとかなります。
これでDCPM以外は最低限の音が出るようになりました。(DCPMは余力があれば後日)
DMCPは、今回は未実装となりました。
まとめ
今回はキーパッドとサウンド周りを実装しました。特にサウンド周りは構造を理解するのが難しく、まだ理解しきっていません。途中に貼ったGoで書かれたエミュレータのリポジトリが、サイトにまとめらている資料により近い実装だったので、そちらを見た方が理解が早いかもしれません。
とっかかりは矩形波のwriteメソッド周りから初めて、レジスタがどのように使われているのか、なんとなく掴めると良いかなと思います。
次回
ロムを吸い出したり、バグがあれば修正したり、しようかなと思っていますが、未定です。
次
ファミコンエミュレータを写経してみるお話6【ROMの吸い出し、Mapper】 - 196Log
追記
次回でも書きますが、今回の実装にいくつか修正がありました。下のリンク先のプルリクで修正しているので該当コードを見てみて下さい。