ロットロット (FC) 攻略/解析

オーディオドライバ

オーディオドライバは毎フレーム tick され、音楽および効果音を適宜 tick し、その結果を元に調停を行いつつ APU レジスタへの書き込みを行う。

音楽および効果音は仮想マシン上のバイトコードで実装されている。詳細は個別の項目を参照:

オーディオドライバは以下の状態を持つ:

オーディオドライバインターフェース概要

オーディオドライバは以下の外部インターフェースを備えている:

機能説明
オーディオドライバ(再)初期化オーディオドライバの状態を初期化する。再生中の音楽/効果音は停止する。
メインスレッドから呼ばれる。
オーディオドライバの tickオーディオドライバを tick し、音楽/効果音の再生を進める。
NMI スレッドから呼ばれる。
音楽再生要求指定した音楽を再生要求する。
メインスレッドから呼ばれる。
効果音再生要求指定した効果音を再生要求する。
メインスレッドから呼ばれる。

オーディオドライバインターフェース詳細

オーディオドライバ(再)初期化ルーチン

本ルーチンが呼ばれると、以下の(再)初期化が行われる:

NOTE: 本ルーチンを呼んだ後に音楽を再生するには、事前に何か効果音を再生して効果音 APU チャンネルマスクを更新する必要がある。効果音ID 0 はこれを行うためのダミーと考えられる。

オーディオドライバ tick ルーチン

本ルーチンは毎フレーム NMI スレッドから呼ばれ、以下の処理を行う (疑似コード):

APU フレームカウンタレジスタ $4017 に値 0xC0 (5-step) を書き、VBLANK との同期をとる

// 音楽処理。
// 音楽バイトコードは常に無限ループするので、終了処理は存在しない。
音楽仮想マシンを 1 回 tick
// ボーナスタイム中は音楽のテンポを 1.5 倍にする。
if (現在の面の目標スコアを達成済み)
    NMI カウンタが偶数ならばさらに音楽仮想マシンを 1 回 tick

// 効果音処理。再生中の効果音IDが 0 (ダミー) ならば処理全体をスキップする。
if (再生中の効果音ID) != 0
    if (効果音ウェイトカウンタ) != 0
        効果音仮想マシンを 1 回 tick
    else
        // この場合、効果音が終了しているか、もしくは未再生。
        (再生中の効果音ID) = 0xFF
        (再生中の効果音の nice 値) = 0xFF
        // 未再生の場合、効果音 APU チャンネルマスクは 0xFF のままになる (音楽が鳴らない状態が続く)。
        if (再生中の効果音の APU チャンネルマスク bit7 が 0)
            (再生中の効果音の APU チャンネルマスク) = 0

// 音楽側の APU レジスタへの書き込み要求を処理。
for (APU チャンネル) in [矩形波A, 矩形波B, 三角波, ノイズ]
    // 効果音は音楽より優先される。
    if (効果音 APU チャンネルマスクにこのチャンネルが含まれる)
        continue
    for (APU レジスタ) in (このチャンネルに対応する APU レジスタ 4 つ) // 降順
        value = (音楽側で書き込み要求した値)
        // 既に APU レジスタ値キャッシュに同じ値があればスキップ (2 重書き込みを避ける)。
        if value != (APU レジスタ値キャッシュ内の値)
            (APU レジスタ) = value
            (対応する APU レジスタ値キャッシュエントリ) = value

音楽側の 2 重書き込み防止処理により、1 つの音符内では発音タイミングでのみ APU レジスタへの書き込みが行われるようになっている。

音楽再生要求ルーチン

本ルーチンは音楽IDを引数にとる。本作の音楽は 1 つしかないので、引数の音楽IDは常に 0 である。

本ルーチンが呼ばれると、以下のように音楽関連の状態が初期化される:

効果音再生要求ルーチン

本ルーチンは効果音ID 0..=14 を引数にとる。

本ルーチンが呼ばれると、以下の処理を行う (疑似コード):

// 再生中の効果音の方が優先度が高ければ何もしない。
if (要求された効果音の nice 値) > (再生中の効果音の nice 値)
    return

(再生中の効果音の nice 値) = (要求された効果音の nice 値)

// 同じ効果音を多重再生しようとした場合、何もしない。
// ただし効果音 11 (ミス音) のみは例外。
if (要求された効果音ID) == (再生中の効果音ID) && (要求された効果音ID) != 11
    return

(再生中の効果音ID) = (要求された効果音ID)

再生中の効果音の APU チャンネルマスクを更新

効果音仮想マシンに対して要求された効果音IDを与えて初期化を行う

不具合

本作は RAM $07AF の初期値が 0xFF だと音楽/効果音のノイズチャンネルが発音されない。以下の環境でこの問題の発生を確認している:

これは、音楽側の APU レジスタ値バッファ $07A0-$07AF が初期化されないことが原因。本作はノイズチャンネルについてはオーディオドライバ tick 処理内の音楽側 APU レジスタ値書き込みによって 1 回だけ音長無限で発音させ、後は音量調整のみによってノイズを制御するようになっている (音楽/効果音バイトコード内には $400F への書き込み命令が存在しない)。しかしこれは音楽側の書き込み要求値 $07AF が 0xFF でないことに依存しており、この条件が満たされない場合、APU レジスタ値キャッシュの初期値が 0xFF であることから、2 重書き込み防止処理によって $400F には書き込みが行われなくなる。

また、上記の通りノイズチャンネル発音タイミングは音楽の再生開始後となるので、電源投入/リセット直後の 1 面開始時はジングルのノイズチャンネルが発音されない。これは RAM の初期値によらず発生する。

他に、電源投入/リセット直後は音楽の再生開始前に三角波を使う効果音を再生しないと音楽の三角波が無音になる問題もあるが、面開始ジングルは三角波を使うのでこの問題は表面化していない。これは APU レジスタ値キャッシュが 0xFF で初期化されることが原因 (音楽は三角波制御レジスタ $4008 に 0xFF を書いて音長を無限とするよう要求するが、キャッシュされた値が 0xFF の場合、書き込み要求が無視される)。