基礎知識
戦闘関係
※サウンド関係は、筆者もわからないことが多いのでわかる範囲でのみ説明をする。また、筆者の理解不足で間違いがあるかもしれない。
「どうやって音を出しているのか」や「どのタイミングでどんなデータを読み込んで曲データを演奏しているのか」あたりがわかれば良いという程度のため、更に詳しく知りたいという場合は調べていただきたい。
このページでは、スーパーファミコンのサウンド機能についての説明、本作における大まかなサウンド関係の仕組みなどを説明する。
スーパーファミコンのサウンドは、
を、ひとまとめにしたAPU(Audio Processing Unit)で演奏して音を出している。と、考えて良いらしい。
(スーパーファミコン用音源チップSPC700はソニー製で、SPC700の後継がPlayStationのSPUなどらしい。SPU - Wikipediaに、SPC700についての説明もあるので参照していただきたい)
スーパーファミコンのメインCPUからは、ゲームカセット内に入っている、演奏用のサンプリングデータ(音の波形データ)とシーケンスデータ(Sequence, 楽譜のデータ)をI/Oポートアドレスを通してSPC700用RAMに送る。
すると、SPC700はそのデータから各種演算処理をして演奏処理、S-DSPでサンプリングデータのデータ圧縮を展開し、各種エフェクトをかけて、DACから音を出力する。
というような仕組みらしい。
SPC700用RAMのことは以降「ARAM」とする。
スーパーファミコン解説サイトなどでもだいたい「サンプリングデータ」「シーケンスデータ」と書いてあるので、当コンテンツでもそれに従うことにする。
英語でSPC700 Samplesなどとあったら「サンプリングデータ」のことだと思って良い。
スーパーファミコンのAPUは、独立して動く音楽演奏用コンピュータみたいなものである。
サンプリングデータとシーケンスデータさえあれば、そこからSPC700が各種演算を行って音を出力してくれる(もちろん、SPC700内にデータ処理・計算をするためのプログラムが必要だが)。
演算のため、独自にアセンブリ言語(MOS 6502というCPU用のアセンブリ言語を拡張したもの)を使っている。
SPC700 プログラミング リファレンスなどに掲載されているので参照していただきたい。
スーファミのCPUの65C816用アセンブリ言語と似ているし、AレジスタやX, Yレジスタなども存在しているが16ビット値は扱えないなどの違いもある。
APUはスーパーファミコンのCPUとは別に演算処理と演奏をしてくれるので、例えばCPUが戦闘中に技のダメージ量を計算して……などという間にサウンドの処理をCPU側でやる必要はない。
本作は基本的に戦闘BGMが戦闘中に変化することがないので(最終編のピュアオディオ戦は別)、戦闘開始時に戦闘BGMのサンプリングデータとシーケンスデータを送り演奏開始した後は、戦闘中、効果音を出すタイミングで、「効果音を演奏しろ」という指示をAPUに出すのみである。
スーパーファミコンでは同時に8音まで音を鳴らすことができる。
スーパーファミコン解説サイトなどではチャネル0~7とナンバリングしてあることが多いので、当コンテンツでも同じようにナンバリングする。
ARAMは64KBあり、メモリアドレスだと$0000~$FFFFと割り振られる。
| アドレス | 内容 |
|---|---|
$0000~$00EF | メモリ領域(0ページ, 高速アクセス) |
$00F0~$00FF | I/Oポート用レジスタ(CPUやDSPとのやりとり) |
$0100~$01FF | メモリ領域(1ページ, 主にスタック領域) |
$0200~$FFBF | メモリ領域(本作だとプログラムや音源データなどが入る) |
$FFC0~$FFFF | IPL ROM 64バイト(IPL ROM はハードの初期化用プログラムが入っている) |
| アドレス | 名称 | 内容 |
|---|---|---|
$00F0 | TEST | テスト機能 |
$00F1 | CONTROL | I/Oとタイマーの調整レジスタ |
$00F2 | DSPADDR | DSPレジスタ番号 |
$00F3 | DSPDATA | DSPレジスタデータ |
$00F4 | CPUI0, CPUO0 | CPU通信レジスタ0 CPUの$00:2140と通信 |
$00F5 | CPUI1, CPUO1 | CPU通信レジスタ1 CPUの$00:2141と通信 |
$00F6 | CPUI2, CPUO2 | CPU通信レジスタ2 CPUの$00:2142と通信 |
$00F7 | CPUI3, CPU03 | CPU通信レジスタ3 CPUの$00:2143と通信 |
$00F8 | AUXIO4 | 補助レジスタ4 |
$00F9 | AUXIO5 | 補助レジスタ5 |
$00FA | T0TARGET | タイマー0スケーリングターゲット |
$00FB | T1TARGET | タイマー1スケーリングターゲット |
$00FC | T2TARGET | タイマー2スケーリングターゲット |
$00FD | T0OUT | タイマー0出力カウンタ |
$00FE | T1OUT | タイマー1出力カウンタ |
$00FF | T2OUT | タイマー2出力カウンタ |
※$00F8, $00F9は、実際にはどこにも接続されていないので、該当レジスタは実質的に汎用レジスタのように機能し、自由に使うことが可能。
とりあえずわかっておくと良いことは、CPU側とAPU側の通信に使うI/Oポートアドレス4種である。
| CPU側 | APU側 | |||||
|---|---|---|---|---|---|---|
| アドレス | 名称 | 内容 | アドレス | 名称 | 内容 | |
$00:2140 | APUIO0 | APU通信レジスタ0 | ↔ | $00F4 | CPUI0, CPUO0 | CPU通信レジスタ0 |
$00:2141 | APUIO1 | APU通信レジスタ1 | ↔ | $00F5 | CPUI1, CPUO1 | CPU通信レジスタ1 |
$00:2142 | APUIO2 | APU通信レジスタ2 | ↔ | $00F6 | CPUI2, CPUO2 | CPU通信レジスタ2 |
$00:2143 | APUIO3 | APU通信レジスタ3 | ↔ | $00F7 | CPUI3, CPU03 | CPU通信レジスタ3 |
CPU側で$00:2140~$00:2143に何か書き込んでいるのならAPUにデータを送信しているということだし、$00:2140~$00:2143を読み取っているのならAPU側のアドレス$00F4~$00F7が何かしらを送信している、またはAPU側の何かしらの状況を読み取る必要があるということである。
なお、$00:2140~$00:2143は$00:2144~$00:217Fにミラーリングされるそうだが、本作ではそのあたりは使っていない気がする。
SPC700で演算した音データは、音にエフェクトをかけるS-DSPでデジタル音声信号として出力される。
また、後で述べるが、スーパーファミコンのサンプリングデータはビットレート削減してデータ圧縮されており、S-DSPはデータ展開して再生する役目もある。
なので、エフェクトをかけるのであってもなくても、必ずS-DSPを通すことになる。
最終的に、S-DSPから送信されたデジタルオーディオ信号は、APU内のDAC(Digital to Analog Converter)を通してアナログオーディオ信号に変換、スピーカーから音が鳴る。
こちらにもRAMがあり、アドレス$0000~$007Fの$80バイトの領域で、ARAMとのI/Oポートレジスタでもある。
SPC700は、ARAMのI/Oポートレジスタ$00F2と$00F3でS-DSPに指示を出す。
スーパーファミコンでは同時に8音まで音を鳴らすことができ、それぞれをチャネル0~7とするが、各チャネルに10個のレジスタがある。
たとえばチャネル0なら、$0000~$0009の10個のレジスタに、ステレオ出力時の左右ボリュームとか再生ピッチなどを書き込む。
8チャネル分だと、$0000~$0009……$0070~$0079である。
その他、$000C~$007Dなどもサウンド全体のレジスタになるが、I/Oポートレジスタとしては使われていない領域もある、らしい(通常のメモリ領域として使用可能)。
本項目では後々で、アドレス$00n4(V0SRCN~V7SRCN, nはチャネル0~7)に入る、サンプリングデータのナンバリング値には少し触れるが、他については特に説明などはしない。
S-DSPでかける音のエフェクトは、音量調整(左右ボリューム)、再生ピッチ(スロー再生など)、エコー、ノイズなどなど。
「ライブ・ア・ライブ」なら、再生ピッチを変えることで「いいお天気でしょっ!」の夜バージョンにしたり、通常の音にノイズをかけて環境音(風の音とか、破壊音などなど)に使うなどしている。
以上が非常に大雑把なスーパーファミコンの音の仕組みである。
当コンテンツではサウンドについてあまり深く踏み込むつもりはない(筆者にもよくわかっていない)。
「ライブ・ア・ライブ」では、何をしたらどの曲が鳴るのか? というあたりを中心に説明していく。
SPC700側で何をやっているのかは横に置いておくとして、CPU側から書き込み(データ転送)とプログラム実行指示をする時は、
$00:2142~$00:2143に、書き込み先のARAMアドレスを2バイトで指定$00:2141に書き込みたいデータの最初の値を書き込む(1バイト)$00:2140に$00を書き込み、$00:2140の値をロードして、$00が戻ってくるかをループ処理でチェックする。これにより、SPC700側のCPU通信レジスタ[$00F4]に転送を終えたかどうかが確認できる。$00:2141に次の値を書き込み、$00:2140には先程書き込んだ値を入れて、同じくループ処理で転送処理チェック。後は繰り返し。というような感じで使うようだ。
また、基本的には$00:1D00~$00:1D03の値を$00:2140~$00:2143へコピー、または$00:2140~$00:2143が受け取った値を$00:1D00~$00:1D03にコピー、としているようである。
BGMや効果音のデータは、演奏用のサンプリングデータとシーケンスデータで成り立っている。
電子楽器・音楽に詳しい人ならMIDIと似たような仕組みだと言えばわかるかもしれない。
サンプリングデータは、楽器の音などのデータのこと。
楽器の音を実際に録音してサンプルを取り音の波形データとする、という意味で「サンプリングデータ」と呼ばれている。
といっても、スーパーファミコンのハードの仕組みやデータ量の限界もあって、例えばピアノの音を録音して、高音質のまま、つまり本物のピアノの音と限りなく近い状態でゲーム機で鳴らすということは不可能であった。
(スーパーファミコン以降の世代のゲーム機ならば可能だが、リメイク版発売の2020年代のゲーム機では、サンプリングデータをシーケンスデータに従って鳴らすこと自体がほとんどない。実際の演奏データ、つまり音の波形データを高音質のまま圧縮して、それをゲームハードの機能で展開・再生することがほとんどのはず)
スーパーファミコンのサンプリングデータは、BRR(Bit Rate Reduction, ビットレート削減)してデータ圧縮してある。音質を犠牲にしてデータ量を減らしているということである。
スーパーファミコンの音関係の解説サイトだと、BRR圧縮とか、BRRコーデックなどと書かれているかもしれないが、BRR自体はスーファミ専用用語ではなく一般的な音声関係の技術用語である。
そのあたりはあまり気にしないで読み進めていただきたい。
また、当コンテンツではスーファミのBRRについて踏み込んだ説明はしないので(筆者もよくわからないので)、詳細な解説は以下などを参照していただきたい。
BRRフォーマット
ゲームソフトのカセットの中に入っているサンプリングデータはBRR圧縮されているから、実際に鳴らす場合はデータを展開(解凍)しなければならない。
その役目はS-DSPが担当しているが、そのあたりの処理についてもここでは省略させていただく。
サンプリングデータは、データ圧縮されているとはいえ、それなりに大きいサイズであり、ゲーム内のサンプリングデータすべてを常にARAMに置いておく余裕はない。
よって、演奏する段階でARAMに転送するのだが、一度ARAMに転送したサンプリングデータが他の曲でも兼用の時は再度送信せず、ARAMに残っているデータを使う仕組みがある。
例えば西部編の楽曲はどれも口笛のサンプリングデータを使うが、西部編プレイ中は口笛のサンプリングデータがずっとARAMに置いてあり、曲が切り替わってもそのまま使っている。
データをARAMに送るための時間を少しでも減らす工夫である。
もちろんセーブデータをロードして西部編の途中からプレイする場合は、ゲームをロードした時にARAMに再度送ることになるが。
また、LISTENING ROOMが解放済みの場合、他シナリオの曲を再生するなどして、口笛のサンプリングデータが上書きされた場合は、メニュー画面に戻る時に再度口笛のサンプリングデータをARAMに送る必要がある。
「ライブ・ア・ライブ」では、幾つかの効果音に実際の人間の声を録音して使用していることが明かされている。
功夫編の男性キャラボイス(老師、ユン、サモ)、レイ、高原日勝、ポゴ、ゴリなど、スタッフロールにボイス担当の開発スタッフの名前がある。
これらは録音後に、サンプリングデータをデータ圧縮してゲーム内で鳴らしているが、スーパーファミコンというハードの限界ゆえにどうしても音質は低めである。
「ライブ・ア・ライブ」においては、筆者が調べた限りだと、サンプリングデータは効果音用とBGM用に分かれ、効果音用のサンプリングデータはBGMにも使われる。
効果音用サンプリングデータは常にARAMに置かれており、必要な時にすぐに効果音を鳴らせるようになっている。
一方、BGM用サンプリングデータは75種類あり、必要な時にARAMに転送される。
BGM変更の時に多少時間がかかるのは(かかるといっても1~2秒あるかないかだが)、BGM用サンプリングデータやシーケンスデータをARAMに転送するのに時間がかかるためである。
画像データならDMA転送を使って高速にVRAMなどにデータを転送できるが、BGM関連のデータについては無理らしい。
後で説明をしていくが、BGM用のサンプリングデータの種類についてはサンプリングデータIDとしてナンバリングしていくことにする。
シーケンスデータは、どのサンプリングデータをどのタイミングでどれくらい流すか、またどんなエフェクト(DSP)をかけるか(ボリューム調整、エコーやノイズをかけるなど)、といった楽譜のデータである。
シーケンスデータも演奏する段階でARAMに転送するが、効果音についてはサンプリングデータと同じく、ARAMにシーケンスデータが常に置いてある。
先に述べた通り、スーパーファミコンでは同時に8音まで音を鳴らすことができるので(チャネル0~7)、楽譜でいえば8段分である。
普通の楽譜であれば、1段ごとにひとつの楽器の楽譜だが、スーパーファミコンではチャネル毎に楽器、つまりサンプリングデータを演奏しつつ切り替えることができる。
つまり、「同時に8音まで音を鳴らすことができる」というのは、「1曲に楽器8種類しか使えない」という意味ではない。楽器は8種類以上使えるが、一度に使える楽器は8種類まで、ということである。
このため、曲演奏前に、サンプリングデータを8種類以上ARAMに転送する曲もある。逆に1種類のみという場合もある(例えば、近未来編ちびっこハウスでオルガンを弾く時、オルガンのサンプリングデータしか必要ないから、サンプリングデータは1種類のみがARAMに送られる)。
スーパーファミコンのゲームの中には、シーケンスデータの演奏を開始した後に追加でARAMにシーケンスデータを送るゲームなど、サウンド関連の面白い仕様もあるらしいが、本作は基本的に、ARAMにサンプリングデータ・シーケンスデータどちらとも置いた状態で曲を再生している(はず)。
本作のシーケンスコマンドは、同じくスクウェアから発売されたスーパーファミコンソフト「ロマンシング・サガ2」のシーケンスコマンドとほとんど同一のようである。
また、「ロマンシング・サガ2」のシーケンスコマンドについては、詳細な解説サイト様が既に存在している。
シーケンスコマンド一覧 - Romancing Sa・Ga 2 - スーパーファミコンのサウンドドライバ - Gnilda のページ
を参照しつつ、
に本作のシーケンスデータ・シーケンスコマンドについてまとめている。
ARAMに設置したサンプリングデータとシーケンスデータに対し、実際に「演奏しろ」「演奏終了しろ」「フェードインしながら演奏開始しろ」「フェードアウトしながら演奏終了しろ」「一部パートをミュートして演奏しろ」……などなどといった指示を出すコマンドがサウンドドライバファンクションである、と考えていただきたい。
これらの指示を使うことで、「いいお天気でしょっ!」の夜バージョン(演奏速度やキーを変更など)など、バージョン違いの曲の演奏も行っている。
名称は先のGnilda のページ様からお借りした。
本作のサウンドドライバファンクションについては、
サウンドドライバファンクション一覧 - Romancing Sa・Ga 3 - スーパーファミコンのサウンドドライバ - Gnilda のページ
を参照しつつ、
にまとめている。
メニュー画面の「音声」で、サウンドのステレオ・モノラル出力を選択できる。
詳細な処理については音声 > ステレオ・モノラル切り替えにまとめているので参照のこと。
ステレオに設定すると、シーケンスデータの設定通りに8チャネルそれぞれの左右のボリュームを出力するのだが(最終的なボリューム調整はS-DSPが行う)、モノラルに設定すると、各チャネルの左右ボリュームを平均値に変えて、左右どちらからも同じボリュームで演奏するようになる。
全体のマスターボリューム自体は変動しない。
ゲーム開始時における、本作の処理を紹介しておく。
実際に音を鳴らすまでの前準備のようなものだから、興味がなければ適当に流し読みでも構わない。
上で、ARAMの$FFC0~$FFFFには「IPL ROM」というデータが入っていると紹介した。
IPLは「Initial Program Loader」、SPC700の初期化に必要なプログラムで、ゲーム開始時(リセット時)にはまず、SPC700が$FFC0~$FFFFに入っているIPL ROMのプログラムを処理する。
パソコンのBIOSみたいなもの(だと思う)。
サウンドを使うスーパーファミコンのゲームソフトなら必ずIPL ROMを実行しているはずである(例外もあるかもしれない)。
「ライブ・ア・ライブ」でもゲーム開始時、リセット直後にはIPL ROMを実行している。
IPL ROMを実行する時にはCPU側から$00:2140~$00:2143を通してデータを送る必要がある。
本作のサウンド関係のプログラムはバンク$C3に格納されている。
ゲームを開始した時点で、最初にバンク$C3のプログラムが処理されるのは以下であり、IPL ROMを実行するためのデータ転送も行っている。
$C0/01CC JSL $C30000[$C3:0000] ;[$C3:0000]へジャンプ $C3/0000 JMP $0034 [$C3:0034] ;[$C3:0034]へジャンプ(レジスタをスタックに積まない) $C3/0034 PHB ;DBレジスタをスタックにプッシュ $C3/0035 PHD ;Dレジスタをスタックへプッシュ $C3/0036 PHP ;ステータスレジスタをスタックへプッシュ $C3/0037 REP #$20 ;Aを16bit幅に変更、Mフラグをクリア $C3/0039 REP #$10 ;X/Yレジスタを16bit幅に変更、Xフラグをクリア $C3/003B PHA ;Aレジスタをスタックにプッシュ $C3/003C PHX ;Xレジスタをスタックにプッシュ $C3/003D PHY ;Yレジスタをスタックへプッシュ $C3/003E SEP #$20 ;MフラグON Aレジスタは8bit幅 $C3/0040 LDA #$00 ;Aに$00をロード $C3/0042 PHA ;Aレジスタをスタックにプッシュ $C3/0043 PLB ;DBレジスタにスタックからプル $C3/0044 LDX #$1D00 ;Xに$1D00をロード $C3/0047 PHX ;Xレジスタをスタックにプッシュ $C3/0048 PLD ;Dレジスタにスタックからプル $C3/0049 LDX #$BBAA ;Xに$BBAAをロード $C3/004C LDY #$F0FF ;Yに$F0FFをロード $C3/004F STY $2140 [$00:2140] ;Y($F0FF)を[$00:2141][$00:2140]に書き込み $C3/0052 CPX $2140 [$00:2140] ;X($BBAA)と[$00:2141][$00:2140]を減算比較(ステータスレジスタ変更のみ) $C3/0055 BNE $F8 [$004F] ;ゼロフラグが立っていないとき[$004F]分岐 $C3/0057 LDX #$0000 ;Xに$0000をロード $C3/005A LDA $C3001C[$C3:001C] ;Aに[$C3:001C]をロード $C3/005E STA $2142 [$00:2142] ;A($00)を[$00:2142]に書き込み $C3/0061 LDA $C3001D[$C3:001D] ;Aに[$C3:001D]をロード $C3/0065 STA $2143 [$00:2143] ;A($02)を[$00:2143]に書き込み $C3/0068 LDA #$CC ;Aに$CCをロード $C3/006A STA $2141 [$00:2141] ;A($CC)を[$00:2141]に書き込み $C3/006D STA $2140 [$00:2140] ;A($CC)を[$00:2140]に書き込み ;ループ処理([$00:2140]が$CCを返すまで待機) $C3/0070 CMP $2140 [$00:2140] ;Aと[$00:2140]を減算比較(ステータスレジスタ変更のみ) $C3/0073 BNE $FB [$0070] ;ゼロフラグが立っていないとき[$0070]分岐 ;ループ処理ここまで
ここまでがIPL ROMを実行するための処理である。
APU/IPL - SNES Spec - atwiki(アットウィキ)
などに手順があるので、なぜ上のように値を書き込むのかは省略する。
とにかく上の手順でSPC700の初期化が済んで、$C3/0075に処理が続く。
なお、$C3/005E~$C3/0065で[$00:2143][$00:2142]に書き込んだ$0200は、この後のARAMへのデータ転送時、ARAM開始アドレスとなる。
$C3/0075 LDA #$00 ;Aに$00をロード $C3/0077 XBA ;Aレジスタの上位バイトと下位バイトを交換 $C3/0078 LDA $C30010,x[$C3:0010] ;Aに[$C3:0010]をロード $C3/007C STA $10 [$00:1D10] ;A($44)を[$00:1D10]に書き込み $C3/007E LDA $C30011,x[$C3:0011] ;Aに[$C3:0011]をロード $C3/0082 STA $11 [$00:1D11] ;A($07)を[$00:1D11]に書き込み $C3/0084 LDA #$C3 ;Aに$C3をロード $C3/0086 STA $12 [$00:1D12] ;A($C3)を[$00:1D12]に書き込み $C3/0088 LDY #$0000 ;Yに$0000をロード $C3/008B LDA [$10],y[$C3:0744] ;Aに[$C3:0744]($C9)をロード $C3/008D CLC ;キャリーフラグクリア $C3/008E ADC #$02 ;A($C9) + $02 $C3/0090 STA $1C [$00:1D1C] ;A($CB)を[$00:1D1C]に書き込み $C3/0092 INY ;Yをインクリメント +1 $C3/0093 LDA [$10],y[$C3:0745] ;Aに[$C3:0745]をロード $C3/0095 ADC #$00 ;A($17) + $00 $C3/0097 STA $1D [$00:1D1D] ;A($17) を[$00:1D1D]に書き込み $C3/0099 INY ;Yをインクリメント +1 ;ループ処理 $C3/009A LDA [$10],y[$C3:0746] ;Aに[$C3:0746~$C3:1F0E] $C3/009C STA $2141 [$00:2141] ;Aを[$00:2141]に書き込み $C3/009F XBA ;Aレジスタの上位バイトと下位バイトを交換 $C3/00A0 STA $2140 [$00:2140] ;Aを[$00:2140]に書き込み ;ループ処理([$00:2140]が[$00:2140]に書き込んだ値を返すまで待機) $C3/00A3 CMP $2140 [$00:2140] ;Aと[$00:2140]を減算比較(ステータスレジスタ変更のみ) $C3/00A6 BNE $FB [$00A3] ;ゼロフラグが立っていないとき[$00A3]分岐 ;ループ処理ここまで $C3/00A8 INC A ;Aをインクリメント +1 $C3/00A9 XBA ;Aレジスタの上位バイトと下位バイトを交換 $C3/00AA INY ;Yをインクリメント +1 $C3/00AB CPY $1C [$00:1D1C] ;Yと[$00:1D1D][$00:1D1C]($17CB)を減算比較(ステータスレジスタ変更のみ) $C3/00AD BNE $EB [$009A] ;ゼロフラグが立っていないとき[$009A]分岐 ;ループ処理ここまで $C3/00AF XBA ;Aレジスタの上位バイトと下位バイトを交換 $C3/00B0 INC A ;Aをインクリメント +1 $C3/00B1 INC A ;Aをインクリメント +1 $C3/00B2 INC A ;Aをインクリメント +1 $C3/00B3 BNE $01 [$00B6] ;ゼロフラグが立っていないとき[$00B6]分岐 $C3/00B6 INX ;Xをインクリメント +1 $C3/00B7 INX ;Xをインクリメント +1 $C3/00B8 CPX #$000C ;Xと$000Cを減算比較(ステータスレジスタ変更のみ) $C3/00BB BEQ $1D [$00DA] ;ゼロフラグが立っているとき[$00DA]分岐→ループ抜ける $C3/00BD XBA ;Aレジスタの上位バイトと下位バイトを交換 $C3/00BE LDA $C3001C,x[$C3:001E] ;Aに[$C3:001E]($00)をロード $C3/00C2 STA $2142 [$00:2142] ;Aを[$00:2142]に書き込み $C3/00C5 LDA $C3001D,x[$C3:001F] ;Aに[$C3:001F]($2C)をロード $C3/00C9 STA $2143 [$00:2143] ;Aを[$00:2143]に書き込み $C3/00CC XBA ;Aレジスタの上位バイトと下位バイトを交換 $C3/00CD STA $2141 [$00:2141] ;Aを[$00:2141]に書き込み $C3/00D0 STA $2140 [$00:2140] ;Aを[$00:2140]に書き込み ;ループ処理 $C3/00D3 CMP $2140 [$00:2140] ;Aと[$00:2140]を減算比較(ステータスレジスタ変更のみ) $C3/00D6 BNE $FB [$00D3] ;ゼロフラグが立っていないとき[$00D3]分岐 ;ループ処理ここまで $C3/00D8 BRA $9B [$0075] ;フラグにかかわりなく常に分岐[$0075]
$C3/0075~$C3/0097は、[$00:1D10]~[$00:1D1D]初期値の設定である。
| アドレス | 値 |
|---|---|
$00:1D10 | $44 |
$00:1D11 | $07 |
$00:1D12 | $C3 |
[$00:1D1D][$00:1D1C] | $17CB(転送量) |
$C3/0099~$C3/00D8が、最初の転送処理である。
$C3/0099の時点でA:0017である。
まず、[$C3:0746]($20)を[$00:2141]を書き込む。
Aレジスタの上位バイトと下位バイトを交換しているから、$C3/00A0で[$00:2140]に書き込むのは$00である。
$C3/00A3~$C3/00A6のループ処理は、[$00:2140]をロードして、[$00:2140]に書き込んだ$00を返すかどうか待機するための処理。
というのは、先に述べた通りに$00:2140~$00:2143はARAMに値を転送するだけではなく、ロードすることでARAMから値を受け取ることもできるレジスタである。[$00:2140]に書き込んだ値と同じ値が[$00:2140]をロードした時に戻ってきたのなら、SPC700側のCPU通信レジスタ[$00F4]に転送を終えたという意味である。
APU側は音の演奏も行うので、演奏していないタイミング、(おそらく)1フレームに1回、[$00:2140]に送られた値をチェックし、何か書き込まれていたら受け取り、受け取ったという合図を逆に[$00:2140]に返してくる。
このように、CPU側の転送タイミングとAPU側のデータ受取にタイムラグがあるため、ループ処理が設けられているのである。
これで、[$00:2141]に書き込んだ値が、まずはARAMアドレス$0200に書き込まれたことになる($C3/005E~$C3/0065で設定された開始位置アドレス)。
ループはY:0002から開始して、1回ループでインクリメントされて$17CBまでなので、$C3:1F0Eまで転送することになる。
ループを抜けると$C3/00AFに進むのだが、ここで一度転送先のアドレスを変更する。
$C3/00BE~$C3/00C9で、[$00:2143][$00:2142]に$2C00を書き込むから、次はARAMアドレス$2C00が開始位置。
以降、$C3:23C8~$C3:3FC7まで転送。
更に、ARAMアドレス$4800を開始位置として、$C3:1F11~$C3:23C5を転送する。
ということで、
| データアドレス | ARAM転送先 |
|---|---|
$C3:0746~$C3:1F0E | $0200~ |
$C3:23C8~$C3:3FC7 | $2C00~ |
$C3:1F11~$C3:23C5 | $4800~ |
順番は入れ違うが、$C3:0746~$C3:3FC7にはARAMに転送するためのデータが格納されていたことがわかる。
もう少し後になるが、$C3:2366~$C3:23C5もARAMアドレス$1A00~$1B2Bに入るようで(処理省略)、このあたりが初期設定のようである。
続きを見てみよう。
$C3/00DA LDY #$0200 ;Yに$0200をロード $C3/00DD STY $2142 [$00:2142] ;Yを[$00:2143][$00:2142]に書き込み $C3/00E0 XBA ;Aレジスタの上位バイトと下位バイトを交換 $C3/00E1 LDA #$00 ;Aに$00をロード $C3/00E3 STA $2141 [$00:2141] ;Aを[$00:2141]に書き込み $C3/00E6 XBA ;Aレジスタの上位バイトと下位バイトを交換 $C3/00E7 STA $2140 [$00:2140] ;Aを[$00:2140]に書き込み ;ループ処理 $C3/00EA CMP $2140 [$00:2140] ;Aと[$00:2140]を減算比較(ステータスレジスタ変更のみ) $C3/00ED BNE $FB [$00EA] ;ゼロフラグが立っていないとき[$00EA]分岐 ;ループ処理ここまで $C3/00EF XBA ;Aレジスタの上位バイトと下位バイトを交換 $C3/00F0 STA $2140 [$00:2140] ;Aを[$00:2140]に書き込み
上もARAMへの値の転送だが、これはARAMへの送信終了用処理である。
[$00:2141]に$00を書き込んでARAMに送信することで、
「送信を終了したので、[$00:2143][$00:2142]に書き込んだ、ARAMアドレス$0200からのプログラムを実行しろ」
という命令になる。
つまり、ARAMアドレス$0200~が、本作におけるSPC700用プログラムになり、実際に実行される。
$0200~に入る値が、$C3:0746~の20 C0 CD FF BD E8 00 5D AF C8 F0 D0 FB 1A C6 E8……となるのだが、これをSPC700のアセンブリ言語で読むと以下のようになる。
0200 CLRP ;ダイレクトページフラグクリア 0201 DI ;割込禁止フラグをクリア 0202 MOV X,#$FF ;Xに$FFを書き込み 0204 MOV SP,X ;スタックポインタにXを書き込み 0205 MOV A,#$00 ;Aに#$00書き込み 0207 MOV X,A ;XにA書き込み ;ループ処理 0208 MOV (X)+,A ;XにA+1書き込み 0209 CMP X,#$F0 ;Xと$F0を減算比較 020B BNE $0208 ;ゼロフラグが立っていないとき[$0208]に分岐 ;ループ処理ここまで ;……
筆者はSPC700のアセンブリ言語についてはよくわかっていないので、これ以上は踏み込まない。
とにかく、SPC700側でサウンドの処理が始まるということである。
以下、$C3/00F0の続き。
$C3/00F3 LDX #$0100 ;Xに$0100をロード ;ループ処理 $C3/00F6 STA $1CFF,x[$00:1DFF] ;Aを[$00:1DFF~$00:1D00]に書き込み $C3/00F9 DEX ;Xをデクリメント -1 $C3/00FA BNE $FA [$00F6] ;ゼロフラグが立っていないとき[$00F6]分岐 ;ループ処理ここまで $C3/00FC LDA #$FF ;Aに$FFをロード $C3/00FE STA $05 [$00:1D05] ;Aを[$00:1D05]に書き込み $C3/0100 REP #$20 ;Aを16bit幅に変更、Mフラグをクリア $C3/0102 LDA $C31F0F[$C3:1F0F] ;Aに[$C3:1F10][$C3:1F0F]をロード A:0453 $C3/0106 CLC ;キャリーフラグクリア $C3/0107 ADC #$4800 ;A + $4800 $C3/010A STA $F8 [$00:1DF8] ;Aを[$00:1DF9][$00:1DF8]に書き込み A:4C53 $C3/010C STA $A0 [$00:1DA0] ;Aを[$00:1DA1][$00:1DA0]に書き込み A:4C53 $C3/010E LDX #$0800 ;Xに$0800をロード ;ループ処理 $C3/0111 DEX ;Xをデクリメント -1 $C3/0112 BNE $FD [$0111] ;ゼロフラグが立っていないとき[$0111]分岐 ;ループ処理ここまで $C3/0114 SEP #$20 ;MフラグON Aレジスタは8bit幅 $C3/0116 LDA #$00 ;Aに$00をロード $C3/0118 STA $FA [$00:1DFA] ;Aを[$00:1DFA]に書き込み $C3/011A LDA #$C3 ;Aに$C3をロード $C3/011C STA $FB [$00:1DFB] ;Aを[$00:1DFB]に書き込み $C3/011E LDA #$05 ;Aに$05をロード $C3/0120 STA $F0 [$00:1DF0] ;Aを[$00:1DF0]に書き込み $C3/0122 ASL A ;Aを算術左シフト *2 $C3/0123 ASL A ;Aを算術左シフト *2 $C3/0124 ASL A ;Aを算術左シフト *2 $C3/0125 EOR #$FF ;Aと$FFで排他的論理和XOR $C3/0127 INC A ;Aをインクリメント +1 $C3/0128 CLC ;キャリーフラグクリア $C3/0129 ADC #$F5 ;A + $F5 $C3/012B STA $F2 [$00:1DF2] ;Aを[$00:1DF2]に書き込み $C3/012D STZ $F1 [$00:1DF1] ;[$00:1DF1]に$00を書き込み $C3/012F BRA $40 [$0171] ;フラグにかかわりなく常に分岐[$0171] $C3/0171 SEP #$20 ;MフラグON Aレジスタは8bit幅 $C3/0173 STZ $00 [$00:1D00] ;[$00:1D00]に$00を書き込み $C3/0175 REP #$20 ;Aを16bit幅に変更、Mフラグをクリア $C3/0177 REP #$10 ;X/Yレジスタを16bit幅に変更、Xフラグをクリア $C3/0179 PLY ;Yレジスタにスタックからプル $C3/017A PLX ;Xレジスタにスタックからプル $C3/017B PLA ;Aレジスタにスタックからプル $C3/017C PLP ;Pレジスタにスタックからプル $C3/017D PLD ;Dレジスタにスタックからプル $C3/017E PLB ;DBレジスタにスタックからプル $C3/017F RTL ;サブルーチン戻り $C0/01D0 RTS ;サブルーチン戻り
後は$00:1D00~$00:1DFFの初期化である。詳細は省く。
ここまでがあくまでもゲーム開始時の初期処理である。
ゲーム開始時に最初に鳴る音は、ボタンを押さず待機していると、タイトルロゴ表示時のサウンド「SQUARE SOFT」ボイスが流れる場面であるが、それは上処理よりもずっと後である。
なお、最初にARAMに転送したデータだが、実際には、
| データアドレス | ARAM転送先 | 内容 |
|---|---|---|
$C3:0746~$C3:1F0E | $0200~ | SPC700用プログラム |
$C3:23C8~$C3:3FC7 | $2C00~ | 効果音用シーケンスデータ |
$C3:1F11~$C3:23C5 | $4800~ | 効果音用サンプリングデータ |
ということになる。
ただし、$C3:2366~$C3:23C5はARAM$1A00~に入るようである。
効果音は指定したタイミングですぐ鳴らす必要があるので(例えばプレイヤーがカーソルを移動させた時の音など)、常にARAMにデータを置きっぱなしにしているのである。
サンプリングデータやシーケンスデータについては次ページ以降で説明していく。