TOP > プログラミング関係解説&調査 > 音声

音声

当ページの内容は、ある程度のプログラミングの知識を必要とする。
まず、プログラミング関係解説&調査をひととおり読んでいただきたい。

メニュー画面→音声についての仕様やサブルーチンを紹介する。
性質上、サウンドについても触れるので、サウンドについても合わせて参照のこと。

メニュー画面→音声を選ぶと、

  • ステレオ
  • モノラル
  • ♪

とコマンドが並ぶ。
ただし一番下の「♪」、つまりBGMを聞ける「LISTENING ROOM」は、ひとつ以上シナリオをクリアしなければ出現しない。
また、「LISTENING ROOM」のデータは全セーブデータで共有されている。

LISTENING ROOMで、LISTENING ROOMの詳細なサブルーチンなどを紹介しているので参照していただきたい。

「♪」があってもなくても、「音声」を選んだ時、現在のステレオ・モノラル状態を反映し、ステレオならステレオに(初期設定でもある)、モノラルならモノラルにカーソル位置があっている、というのも特徴である。これにより現在出力されているサウンドがステレオなのかモノラルなのかが判断できるようになっている。

メモリアドレス

ステレオ・モノラルの値は$00:0AB5の上1ビット(Bit7)に入っており、初期値は%0(ステレオ)、モノラルだと%1。
また、ARAMのアドレス$0085がステレオ・モノラルの値を記録するメモリアドレスになっており、$00だとステレオ、$01だとモノラルで、$00:0AB5の上1ビット(Bit7)と同期して変動する。

「LISTENING ROOM」が表示されるかどうかはシナリオのクリア状況が関係してくるが、シナリオのクリア状況が入る$00:0A01の値及び、セーブデータ共有値の$30:7FFE~$30:7FFFに入る値で決まる。

アドレスセーブデータ
$00:0A00プレイ中のシナリオ
$00:0A01シナリオのクリア状況
(8ビットが各シナリオ対応。
上の桁から順に中世、原始、SF、功夫、西部、現代、近未来、幕末)
$00:0AB4セーブ回数 下位バイト
セーブ回数が999回($03E7)を越えると0に戻る
$00:0AB5Bit0~6:セーブ回数 上位バイト
Bit7:%0だとステレオ、%1だとモノラル メニュー→音声の設定で切り替え

ただしメニュー画面だと、バンク$7Eで呼び出されることが多いようだ。

セーブデータはバンク$30に入っており、今回の処理で重要なのは$30:7FFE$30:7FFFの2バイト。

アドレスセーブデータ
$30:6000~$30:67F7セーブデータ1
$30:67F8~$30:6FEFセーブデータ2
$30:6FF0~$30:77E7セーブデータ3
$30:77E8~$30:7FEFセーブデータ4
$30:7FF1$30:7FF0初期値$FFFF、セーブすると$E41Bが入る
$30:7FF3$30:7FF2初期値$FFFF、最後にセーブしたセーブデータ番号$0000~$0003
(セーブ・ロード画面の初期カーソル位置判定に使う)
$30:7FFE$30:7FFF最終編ベストエンドクリア判定値
(クリア前は$0000、クリア後は$E41B)
(LISTENING ROOMの曲判定)

メニュー画面のコマンド番号

メニュー画面は左から順に装備、持ち物、技、隊形、音声、セーブとコマンドが並ぶが、各コマンドを選択すると、左から順に01からナンバリングを振った値が、[$00:056A][$00:0569]に入る。

コマンド[$00:056A][$00:0569]
装備$0001
持ち物$0002
技$0003
隊形$0004
音声$0005
セーブ$0006

メニュー画面のカーソル位置

[$00:0544][$00:0545]の2バイトで、現在のカーソル位置の値が入る。
[$00:0544]はメニュー画面ならどの階層(ページ)でもカーソルの位置が入る。

サブルーチン

アイテム並び替えのサブルーチンで、まず、メニュー画面を開いた時の処理を紹介しているので合わせて参照していただきたい。
この時に、
[$7E:0A00](シナリオID)→[$00:054D]
と書き込まれている。

カーソル位置処理

メニュー画面が開いた後、「音声」を選択すると、まずは選択時の効果音を鳴らす処理が入る($C2/3C35~$C2/3C40)。
続いてカーソル位置[$00:0544]の値が入る。
通常なら、メニュー画面で何かしらを選んだ直後は一番最初の項目にカーソルが合うから、[$00:0544]に$00が書き込まれる。
だが、「音声」の場合、現在の音声出力がステレオなら「ステレオ」、モノラルなら「モノラル」にカーソルが合う、という他項目にはない処理が入る。
順に見ていくと、現在の音声出力がステレオであってもモノラルであっても、まずは[$00:0544]に$00が書き込まれる。

$C2/07E0がメニュー→「音声」選択時専用処理のようだが、カーソル位置書き込みの$C2/0680~から紹介する。

$C2/0680 LDA $7E8E01,x[$7E:8E51] ;Aに[$7E:8E51]($00)をロード
$C2/0684 STA $4A    [$00:054A]   ;Aを[$00:054A]に書き込み
$C2/0686 LDA $7E8E00,x[$7E:8E50] ;Aに[$7E:8E50]($10)をロード
$C2/068A STA $48    [$00:0548]   ;Aを[$00:0548]に書き込み
$C2/068C BMI $22    [$06B0]      ;ネガティブフラグが立っているとき[$06B0]分岐
$C2/068E CMP $42    [$00:0542]   ;Aと[$00:0542]を減算比較(ステータスレジスタ変更のみ)
$C2/0690 BEQ $04    [$0696]      ;ゼロフラグが立っているとき[$0696]分岐
$C2/0692 LDA #$40                ;Aに$40をロード
$C2/0694 TSB $4C    [$00:054C]   ;Aと[$00:054C]で論理和を取り、メモリに書き戻し
$C2/0696 LDA $40    [$00:0540]   ;Aに[$00:0540]をロード
$C2/0698 STA $3A    [$00:053A]   ;Aを[$00:053A]に書き込み
$C2/069A LDA $42    [$00:0542]   ;Aに[$00:0542]をロード
$C2/069C STA $3C    [$00:053C]   ;Aを[$00:053C]に書き込み
$C2/069E LDA $44    [$00:0544]   ;Aに[$00:0544](カーソル位置)をロード
$C2/06A0 STA $3E    [$00:053E]   ;Aを[$00:053E]に書き込み
$C2/06A2 LDA $46    [$00:0546]   ;Aに[$00:0546]をロード
$C2/06A4 STA $40    [$00:0540]   ;Aを[$00:0540]に書き込み
$C2/06A6 LDA $48    [$00:0548]   ;Aに[$00:0548]をロード
$C2/06A8 STA $42    [$00:0542]   ;Aを[$00:0542]に書き込み
$C2/06AA LDA $4A    [$00:054A]   ;Aに[$00:054A]($00)をロード
$C2/06AC STA $44    [$00:0544]   ;A($00)を[$00:0544]に書き込み カーソル位置:ステレオ
$C2/06AE BRA $04    [$06B4]      ;フラグにかかわりなく常に分岐[$06B4]
$C2/06B4 BIT $4C    [$00:054C]   ;Aと[$00:054C]で論理積(ステータスフラグ変更のみ)
$C2/06B6 BMI $13    [$06CB]      ;ネガティブフラグが立っているとき[$06CB]分岐
$C2/06B8 BVS $1B    [$06D5]      ;オーバーフローフラグON時[$06D5]分岐
$C2/06D5 LDX #$6FC3              ;Xに$6FC3をロード
$C2/06D8 LDA $3C    [$00:053C]   ;Aに[$00:053C]をロード
$C2/06DA JSR $0723  [$C2:0723]   ;[$C2:0723]へジャンプ

カーソル位置[$00:0544]には、[$7E:8E51]($00)→[$00:054A]→[$00:0544]という順で$00が書き込まれている。
これは、現在の音声出力がステレオでもモノラルでも関係なく共通処理である。
このままだと、モノラル設定でも初期カーソル位置がステレオになってしまう。

ということで、この先で分岐処理がある。

$C2/0726 BCS $06    [$072E]      ;キャリーフラグが立っているとき[$072E]分岐
$C2/0728 PER $0002  [$072D]      ;PCの値にオペランド16bit値を足した値をスタックに積み[$072D]
$C2/072B JMP ($0532)[$C2:210F]   ;[$C2:210F]へジャンプ(レジスタをスタックに積まない)
$C2/210F LDA #$000A              ;Aに$000Aをロード
$C2/2112 BIT $0AB4  [$7E:0AB4]   ;Aと[$7E:0AB5][$7E:0AB4]で論理積(ステータスフラグ変更のみ)
$C2/2115 BPL $01    [$2118]      ;ネガティブフラグが立っていないとき[$2118]分岐
;ネガティブフラグON(モノラル)
$C2/2117 INC A                   ;Aをインクリメント($000B)
;ネガティブフラグOFF(ステレオ)
$C2/2118 STA $40    [$00:0540]   ;Aを[$00:0541][$00:0540]に書き込み
$C2/211A JSR $077C  [$C2:077C]   ;[$C2:077C]へジャンプ

$C2/077C PHP                     ;ステータスレジスタをスタックへプッシュ
$C2/077D SEP #$20                ;MフラグON Aレジスタは8bit幅
$C2/077F JSR $076E  [$C2:076E]   ;[$C2:076E]へジャンプ

$C2/2112で、BITによるAと[$7E:0AB5][$7E:0AB4]の論理積のステータスフラグ判定がある。
[$7E:0AB5][$7E:0AB4]は、2バイトで見た場合、一番上のビット(Bit15)にサウンド出力(ステレオ・モノラル)の値が入り、それ以外のBit14~Bit0はセーブ回数の値が入る。
つまり、[$7E:0AB5]の一番上のビットがサウンド出力の値で、%0ならステレオ、%1ならモノラルである。
$C2/2112のニーモニック、BITだが、

  • ネガティブフラグとオーバーフローフラグの判定は、BITで読み込んだメモリの値に対して行う
  • ゼロフラグの判定は、AレジスタとBITで読み込んだメモリの論理積の値に対して行う

という仕様なので、$C2/2115のネガティブフラグ判定は、[$7E:0AB5][$7E:0AB4]に対して行う。
ネガティブフラグは一番上のビットが1ならON、0ならOFFだから、ネガティブフラグが立つのは現在のサウンド出力がモノラルの場合。
ネガティブフラグが立たないのはステレオの場合ということになって、処理が分岐する。
すると、$C2/2118で[$00:0541][$00:0540]に書き込む値が変わり、

サウンド出力[$00:0541][$00:0540]
ステレオ$000A
モノラル$000B

このようになる。

$C2/076E LDA $40    [$00:0540]   ;Aに[$00:0540]($0Aか$0B)をロード
$C2/0770 PHP                     ;ステータスレジスタをスタックへプッシュ
$C2/0771 REP #$20                ;Aを16bit幅に変更、Mフラグをクリア
$C2/0773 AND #$00FF              ;Aと$00FFで論理積
$C2/0776 ASL A                   ;Aを算術左シフト *2
$C2/0777 ASL A                   ;Aを算術左シフト *2
$C2/0778 ASL A                   ;Aを算術左シフト *2
$C2/0779 TAX                     ;Aの値をXレジスタに転送
$C2/077A PLP                     ;Pレジスタにスタックからプル
$C2/077B RTS                     ;サブルーチン戻り

すると、上のサブルーチンで、$C2/077Bの時にA及びXレジスタに入る値が変わる。
$C2/0779までで、[$00:0540]*8を計算しているから、$C2/077Bの時点のXは、

サウンド出力X
ステレオ$0050
モノラル$0058

となる。

$C2/0782 REP #$20                ;Aを16bit幅に変更、Mフラグをクリア
$C2/0784 LDA $7E8E02,x[$7E:8E52] ;Aに[$7E:8E53][$7E:8E52]($32AC)または[$7E:8E5B][$7E:8E5A]($42AC)をロード
$C2/0788 STA $000700[$00:0700]   ;Aを[$00:0701][$00:0700]に書き込み
$C2/078C LDA $34    [$00:0534]   ;Aに[$00:0535][$00:0534]($3E04)をロード
$C2/078E STA $000702[$00:0702]   ;Aを[$00:0703][$00:0702]に書き込み
$C2/0792 SEP #$20                ;MフラグON Aレジスタは8bit幅
$C2/0794 LDA $7E8E00,x[$7E:8E50] ;Aに[$7E:8E50]($10)または[$7E:8E58]($10)をロード
$C2/0798 STA $42    [$00:0542]   ;Aを[$00:0542]に書き込み
$C2/079A LDA $7E8E01,x[$7E:8E51] ;Aに[$7E:8E51]($00)または[$7E:8E59]($01)をロード
$C2/079E STA $44    [$00:0544]   ;Aを[$00:0544](カーソル位置)に書き込み
$C2/07A0 JSR $4D35  [$C2:4D35]   ;[$C2:4D35]へジャンプ

ここで、サウンド出力状況を考慮したカーソル位置の値の更新が行われる。
$7E8E02,x, $7E8E00,x, $7E8E01,xは、Xレジスタの値により呼び出される値が変わる。
結果、$C2/079Aで呼び出した$7E8E01,xが、新たな[$00:0544]、つまりカーソル位置の値になるのだが、

サウンド出力$7E8E01,x値
ステレオ$7E:8E51$00
モノラル$7E:8E59$01

モノラルの場合のみ、カーソルが初期位置からひとつ下にずれて、「モノラル」が初期位置に設定されるのである。
これが、「音声」を選択した時のカーソル位置設定処理になる。

♪(LISTENING ROOM)コマンドの有無

カーソル初期位置が決まった後で、3番目のコマンド「♪」こと、「LISTENING ROOM」が表示されるかどうかの判定を行う。
OAM転送処理($C2/4D35~$C2/4D54)を挟んで、$C2/211D~からが判定になる。

$C2/211D LDX #$7CD3              ;Xに$7CD3をロード
$C2/2120 LDA $0A01  [$7E:0A01]   ;Aに[$7E:0A02][$7E:0A01]をロード
$C2/2123 AND #$00FF              ;Aと$00FFで論理積
$C2/2126 BNE $09    [$2131]      ;ゼロフラグが立っていないとき[$2131]分岐
;ゼロフラグON シナリオを1つもクリアしていない
$C2/2128 LDA $307FFE[$30:7FFE]   ;Aに[$30:7FFF][$30:7FFE]をロード
$C2/212C CMP #$E41B              ;Aと$E41Bを減算比較(ステータスレジスタ変更のみ)
$C2/212F BNE $03    [$2134]      ;ゼロフラグが立っていないとき[$2134]分岐
;ゼロフラグON 最終編ベストエンドのクリアデータあり
$C2/2131 LDX #$7CE5              ;Xに$7CE5をロード
;処理合流
$C2/2134 JSR $35AD  [$C2:35AD]   ;[$C2:35AD]へジャンプ

まず、$C2/2126のゼロフラグ判定だが、処理を追っていくと実質、[$7E:0A01]が$00かどうかの判定である。
[$7E:0A01]には最終編以外のクリア済みのシナリオの値が各ビットに入る。
上の桁、つまりBit7から順に中世、原始、SF、功夫、西部、現代、近未来、幕末である。
クリアしたシナリオがひとつもないのなら$00であるから、ゼロフラグが立って$C2/2128に進む。

$C2/212Fでのゼロフラグ判定は、[$30:7FFF][$30:7FFE](2バイト値)が、$E41Bかどうかの判定と等しい。
[$30:7FFF][$30:7FFE]は、セーブデータのセーブ・ロードで記してある通り、セーブデータの共有部のアドレスで、最終編ベストエンドクリア判定値である。
[$30:7FFF][$30:7FFE]は初期値$0000だが、最終編ベストエンドの「Live over Again」が流れるタイミングで$E41Bを書き込む処理がある。
つまり、$C2/212Fのゼロフラグ判定は、最終編ベストエンドのクリアデータを見たことがあるかどうかである。
(仮に、一度ベストエンドを見たデータがすべて上書きされて消えたとしても、セーブデータの共有部の値なので変更されない)

以上の分岐処理から、

  • 何らかのシナリオをクリアしている
  • クリアしたシナリオがひとつもない場合でも、一度最終編ベストエンドを見たことがある

このいずれかであれば、$C2/2131にジャンプして、Xに$7CE5をロードする。
そうでない場合、つまり「クリアしたシナリオがひとつもなくて、最終編ベストエンドを見たこともない」場合は、$C2/211DでXに$7CD3をロードしたままである。
最終的に$C2/2134で合流し、$C2/35ADへジャンプするが、Xの値によりこの先の処理が分岐する。
結局、コマンド「♪」こと、「LISTENING ROOM」が表示される条件が、

  • 何らかのシナリオをクリアしている
  • クリアしたシナリオがひとつもない場合でも、一度最終編ベストエンドを見たことがある

これだった、ということになる。
実際の表示の処理などは省略する。

ステレオ・モノラル切り替え

「音声」でステレオ・モノラル切り替えを行った時の処理を見てみよう。
上のカーソル初期位置判定で述べた通り、現在のステレオ・モノラルの状態は、$7E:0AB5の一番上のビットに入っており、%0ならステレオ、%1ならモノラルである。

ステレオを選択した場合、カーソル位置は一番上だから[$00:0545][$00:0544]に$0000が入るし、モノラルを選択した場合、カーソル位置は2番目だから[$00:0545][$00:0544]に$0001が入る。
カーソル位置書き換え処理は省略するとして、[$00:0545][$00:0544]の更新後の処理を見てみる。

$C2/20B7 LDA #$8000              ;Aに$8000をロード
$C2/20BA TRB $0AB4  [$7E:0AB4]   ;Aのビット反転値と[$7E:0AB5][$7E:0AB4]で論理積を取り書き戻し
$C2/20BD LDX $44    [$00:0544]   ;Xに[$00:0545][$00:0544](カーソル位置)をロード
$C2/20BF BEQ $03    [$20C4]      ;ゼロフラグが立っているとき[$20C4]分岐
;ゼロフラグOFF モノラル選択
$C2/20C1 TSB $0AB4  [$7E:0AB4]   ;Aと[$7E:0AB5][$7E:0AB4]で論理和を取り、メモリに書き戻し
;ゼロフラグON ステレオ選択
$C2/20C4 LDA $0AB4  [$7E:0AB4]   ;Aに[$7E:0AB5][$7E:0AB4]をロード
$C2/20C7 JSR $3C95  [$C2:3C95]   ;[$C2:3C95]へジャンプ

$C2/20BAのTRBは「Aのビット反転値とメモリアドレスの値で論理積を取りメモリアドレスに書き戻し」という命令である。
$C2/20B7で、Aに$8000(%1000 0000 0000 0000)をロードしているから、このすべてのビットを反転させると、%0111 1111 1111 1111、つまり$7FFFのことである。
よって、$7FFF & [$7E:0AB5][$7E:0AB4]と論理積を計算して、[$7E:0AB5][$7E:0AB4]に書き戻せばよい。
結局、[$7E:0AB5][$7E:0AB4]の上1ビットを取り除いて(0にして)、残り下15ビット分(セーブ回数)のみにしろ、ということである。

$C2/20BDでXにロードするのは[$00:0545][$00:0544](カーソル位置)で、ステレオを選択した時は$0000、モノラルを選択した時は$0001が入っている。
よって、$C2/20BFでゼロフラグが立つのはステレオ選択時である。
ゼロフラグOFFはモノラル選択で、$C2/20C1で、$8000と[$7E:0AB5][$7E:0AB4]で論理和を取り、[$7E:0AB5][$7E:0AB4]に書き戻す。
ということは実質、[$7E:0AB5][$7E:0AB4]の一番上のビットに1を立てる、つまり[$7E:0AB5]の一番上のビットに1を立てることになる。
$C2/20C4に進んで、Aに[$7E:0AB5][$7E:0AB4]をロードする。

一方、ゼロフラグONのステレオ選択は、$C2/20C1の処理をスキップするのみ。
$C2/20C4に進みそのままAに[$7E:0AB5][$7E:0AB4]をロードしている。つまり、つまり[$7E:0AB5]の一番上のビットは0のままである。
これがステレオ・モノラル切り替え時に、[$7E:0AB5]の一番上のビットを更新する処理ということになる。
現在の設定がステレオで再びステレオを選ぶ、またはモノラルの時にモノラルを選んでも、処理がスキップされるということはないようである。

以上の処理の後は、サウンドの処理として、ARAMにも値を転送する必要がある。

$C2/3C95 PHP                     ;ステータスレジスタをスタックへプッシュ
$C2/3C96 REP #$20                ;Aを16bit幅に変更、Mフラグをクリア
$C2/3C98 PHA                     ;Aレジスタをスタックにプッシュ
$C2/3C99 AND #$8000              ;Aと$8000で論理積
$C2/3C9C BEQ $05    [$3CA3]      ;ゼロフラグが立っているとき[$3CA3]分岐
;ゼロフラグOFF モノラル選択
$C2/3C9E LDA #$00F4              ;Aに$00F4をロード
$C2/3CA1 BRA $03    [$3CA6]      ;[$C2:3CA6]へジャンプ
;ゼロフラグON ステレオ選択
$C2/3CA3 LDA #$00F3              ;Aに$00F3をロード
;処理合流
$C2/3CA6 JSR $3CAC  [$C2:3CAC]   ;[$C2:3CAC]へジャンプ

$C2/3CAC STA $1D00  [$7E:1D00]   ;Aを[$7E:1D01][$7E:1D00]に書き込み
$C2/3CAF JSL $C30004[$C3:0004]   ;[$C3:0004]へジャンプ

Aに新たな[$7E:0AB5][$7E:0AB4]が入った状態で、$C2/3C99にて$8000と論理積を取ってゼロフラグ分岐である。
結論としては、

アドレスステレオモノラル
[$7E:1D00]$F3$F4
[$7E:1D01]$00$00

となって、ステレオなら$F3が、モノラルなら$F4が、この後の$C3/0004~$C3/017FでARAMに転送されることになる。
スーパーファミコンのCPU側の処理はそこまでである。

一応APU(SPC700)の処理も見てみよう。
ARAMアドレス$0085に、ステレオ・モノラルのフラグがあり、初期値$00(ステレオ)、$01(モノラル)が入る。
SPC700 プログラミング リファレンス
も参照していただきたいが、SPC700用ニーモニックはスーファミCPUの65C816用ニーモニックと似ているもののそれなりに違いもある。
A, X, Yレジスタは1バイト値のみ、2バイト値を扱いたい時はYとAレジスタを上位バイト・下位バイトとしてYAとして扱う、プロセッサステータスレジスタはプログラムステータスワードという名称で内容がそこそこ異なる、などなど、違いはあるが、65C816の知識があればなんとか読める……ような気がする。
とりあえず、I/Oポートアドレスで、

  • CPUの$00:2140↔APUの$00F4
  • CPUの$00:2141↔APUの$00F5
  • CPUの$00:2142↔APUの$00F6
  • CPUの$00:2143↔APUの$00F7

と通信しているから、ここを手がかりにして中身を見てみる。
つまり、APUの$00F4に、ステレオ切り替え時は$F3、モノラル切り替え時は$F4が送信されて、SPC700は何らかの形でこの値をロードしているはずだ。
それが下処理である。

$09B3 MOV X,$F4            ;Xに[$00F4]($F3または$F4)をロード
$09B5 BEQ $09EC            ;ゼロフラグがONのとき[$09EC]分岐
$09B7 MOV X,$F4            ;Xに[$00F4]をロード
$09B9 CMP X,$F4            ;Xと[$00F4]を減算比較(ステータスレジスタ変更のみ)
$09BB BNE $09B7            ;ゼロフラグがOFFのとき$09B7分岐
$09BD MOVW YA,$F6          ;YAに[$00F7][$00F6]($00FF)をロード ※CPU側の値
$09BF MOVW $8D,YA          ;[$008E][$008D]にYAをロード
$09C1 MOVW YA,$F4          ;YAに[$00F6][$00F5]($00F3または$00F4)をロード ※CPU側の値
$09C3 MOVW $8B,YA          ;[$008C][$008B]にYAをロード [$008B]に$F3または$F4書き込み
$09C5 CALL !$0619          ;[$0619]へジャンプ

$0619 MOV $F1,#$17         ;[$00F1]に$17をロード
$061C MOV $F1,#$07         ;[$00F1]に$07をロード
$061F RET                  ;サブルーチン戻り

$09C8 MOV $F4,A            ;[$00F4]にA($F3または$F4)をロード(CPU$00:2140に送信)
$09CA MOV X,A              ;XにAをロード
$09CB BPL $09D0            ;ネガティブフラグOFFのとき[$09D0]分岐
$09CD JMP !$0C2C           ;[$0C2C]へジャンプ

$0C2C CMP X,#$F0           ;X($F3または$F4)と$F0を減算比較(ステータスレジスタ変更のみ)
$0C2E BCS $0C34            ;キャリーONのとき[$0C34]分岐
$0C34 MOV A,X              ;AにXをロード
$0C35 AND A,#$1F           ;Aと$1Fで論理積($13または$14)
$0C37 ASL A                ;Aを算術左シフト*2($26または$28)
$0C38 MOV Y,A              ;YにAをロード
$0C39 MOV A,!$1976+Y       ;Aに[$1976+Y]をロード Y:$26または$28
$0C3C PUSH A               ;Aをスタックにプッシュ A:$0D
$0C3D MOV A,!$1975+Y       ;Aに[$1975+Y]をロード Y:$39
$0C40 PUSH A               ;Aをスタックにプッシュ
$0C41 RET                  ;サブルーチン戻り

$0D39 BBC $8B.0,$0D40      ;[$008B]のBit0が0なら[$0D40]分岐
;[$008B]にはステレオは$F3(%1111 0011)、モノラルはF4(%1111 0100)が入っている
;分岐なし ステレオ処理
$0D3C CLR1 $85.0           ;[$0085]のBit0を0にクリア
$0D3E BRA $0D42            ;[$0D42]へジャンプ
;[$0D40]分岐 モノラル処理
$0D40 SET1 $85.0           ;[$0085]のBit0を1にセット
;処理合流
$0D42 MOV $DB,#$FF         ;[$00DB]に$FFをロード
$0D45 RET                  ;サブルーチン戻り

$09B3で、[$00F4]をXにロードしている。
CPUの$00:2140から$F3または$F4が送られてきたら、Xに$F3または$F4が入って、$09B5でゼロフラグがOFFの分岐になる。
次に[$00F5]経由で$F3または$F4が送られるので($C3/0004~$C3/017Fの処理で)[$00F6][$00F5]→YAレジスタ→[$008C][$008B]と経由して、[$008B]に$F3または$F4が書き込まれる。

$09C8のMOV $F4,Aは、逆に$00F4→$00:2140の送信(送信した値がARAMに入ったことをCPU側に通信)。

少し処理を飛ばして、$0D39~$0D45がステレオ・モノラル切り替え処理になる。
$0D39は、[$008B]に入っている値のBit0が0なら[$0D40]にジャンプ、という分岐処理。
[$008B]にはステレオは$F3(%1111 0011)、モノラルは$F4(%1111 0100)が入っているから、ステレオだと分岐なしで$0D3Cに進む。
この場合は[$0085]の値のBit0を0にクリアする。
一方、モノラルだと$0D40にジャンプして、[$0085]の値のBit0を1にセットする。
これがステレオ・モノラル設定の処理で、結果として、ARAMの[$0085]が$00ならステレオ出力、$01ならモノラル出力することになる。

以上はあくまでも設定で、結局サウンドのステレオ・モノラル出力は何が変わるのかというと、左右の音のボリュームである。
スーファミのサウンドは8音同時に鳴らすことができ(チャネル0~7)、SPC700で計算後、S-DSPでエフェクトをかけて演奏するのだが、S-DSPにはチャネル0~7それぞれの左ボリューム用I/Oポートアドレス$0xX0 VxVOLL, 右ボリューム用I/Oポートアドレス$0xX1 VxVOLRがある。

S-DSP内容
$00チャネル0の左ボリューム V0VOLL
$10チャネル1の左ボリューム V1VOLL
$20チャネル2の左ボリューム V2VOLL
$30チャネル3の左ボリューム V3VOLL
$40チャネル4の左ボリューム V4VOLL
$50チャネル5の左ボリューム V5VOLL
$60チャネル6の左ボリューム V6VOLL
$70チャネル7の左ボリューム V7VOLL
$01チャネル0の右ボリューム V0VOLR
$11チャネル1の右ボリューム V1VOLR
$21チャネル2の右ボリューム V2VOLR
$31チャネル3の右ボリューム V3VOLR
$41チャネル4の右ボリューム V4VOLR
$51チャネル5の右ボリューム V5VOLR
$61チャネル6の右ボリューム V6VOLR
$71チャネル7の右ボリューム V7VOLR

ステレオ出力の時は、左右のボリュームを変えることで立体的な音を鳴らすことができる。
モノラル出力にすると、全チャネルの左右のボリュームを均等な値に変える。左右のボリュームの平均値がどちらにも入るようである。
テレビがモノラル出力にしか対応していない場合でも、特定のチャネルの音が聞こえにくいということがなくなる。
逆に、ステレオ出力できる環境でモノラル出力に変更すると、すべての音がスピーカーの中央あたりから聞こえるようになる。

なお、最終的な全体のボリュームは、左右のマスターボリューム(エコーボリュームも加算している模様)で決めている。
こちらは基本的に左右とも同じ音量である。

S-DSP内容
$0C左マスターボリューム MVOLL
$1C右マスターボリューム MVOLR

なお、先のSPC700のサブルーチン内にて、$0C2CのCMP X,#$F0(Xと$F0を減算比較)の後に$0C2Eでキャリーフラグ分岐があるため、$F1~$FFをARAMに送信した時用に特別な処理があるのだろう、ということがなんとなく推察できる。
実際、今回は$F3を送信したことでステレオに切り替え、$F4を送信したことでモノラルに切り替えるという、音の演奏とは異なる指示を行った。
(とりあえず今回は推測のみということで)


余談になるが、スーパーファミコンの時代、ほとんどの家庭で、スーパーファミコンはブラウン管テレビ(アナログテレビ)にRFスイッチという接続機器で繋いでいた。
ファミコンに同梱されていたRFスイッチ及び接続ケーブルが、スーパーファミコンでもそのまま使えたからである。

ファミコンとは? | ファミリーコンピュータ | 任天堂

任天堂公式サイトに、実際にファミコンに同梱されていたRFスイッチの写真がある。

アナログテレビのアンテナ端子↔RFスイッチ↔ファミコン(またはスーファミ)と繋げて、ゲーム機の画面と音をテレビに出力する。
ファミコン発売当時、アナログテレビの大半には外部入力端子はなく、ゲーム機からテレビに映像や音を出力する場合、テレビの受信と同じように、アンテナに信号を送るしかなかった。
ファミコン発売後、一般家庭向けアナログテレビにも外部入力端子が普及し始めている。

スーパーファミコン本体にはRFスイッチなどの接続機器が同梱されていなかったので、所持していない場合は別売り品を購入する必要があった。
ただ、RFスイッチでテレビに接続すると、テレビがステレオ出力対応であっても、サウンドをモノラル出力しかできない。
スーパーファミコンの音をステレオ出力したいのなら、対応したテレビを用意し(当時のテレビ自体はステレオ出力も対応していた)、赤・白・黄の3個の端子がついているRCA端子ケーブルかS端子ケーブルを別途購入して、スーパーファミコンと接続する必要があったが、スーパーファミコンのために別売りのケーブルをわざわざ購入する小中学生は当時あまり居なかったのではなかろうか。
初めて購入した家庭用ゲーム機がスーパーファミコンという場合は話が別だが。

余談の余談で、赤・白・黄の3個の端子がついているRCA端子でのテレビ接続は、任天堂だとWiiU、ソニーだとPlayStation3までは使用可能だった。現在では古い仕様となってしまったが、ゲーム機においては息の長い接続方法であった。

ということで、当時のスーパーファミコンのゲームにステレオ・モノラル出力切り替え機能がよくついていたのは、RFスイッチでサウンドをモノラル出力してプレイする家庭が多かったからであろう。
なお、ファミコンはハード自体がステレオ出力に対応していない(モノラル出力しかできない)。



このページをシェアする

上へ