主にMSX関係のコンテンツが置いてあります。

ドライバ処理の流れ/PSGの処理

ドライバの処理を大きく分けると、チャンネルごとに処理を行い結果をワークエリアに出力する部分と、ワークエリアの情報を元に音源のレジスタへ書き込み実際に音を鳴らす部分に大別できるかと思います。

ここでは各チャンネル処理が終了し、音源に書き込む部分の処理を先に見てみます。

順番はPSGの処理、SCCの処理で、1/60秒の割り込みの度に実行されることになります。

PSGの処理

PSGの処理は、
  • ノイズ/トーンフラグ調整
  • 分周比、音量書き込み
  • ノイズ周波数の書き込み
  • ハードウェアエンベロープの書き込み
  • レジスタ7の書き込み
から構成されています。

ノイズ/トーンフラグ調整

ここでの処理は、Ch.AとCh.Cのノイズが両方とも有効なら、Ch.Aのノイズを無効にしCh.Aのトーンを有効にするという処理が行われます。

Ch.Aではハードウェアエンベロープを使ったPSGドラムなどを担当するチャンネルで、Ch.CはSE専用です。

※動画や音声は特に事情のない限りはエミュレータでの実行です

確かに変更されていることは確認できましたが、これは何のためなんでしょうか。

同時使用すると何か変になるためにその対応でしょうか。

ちょっと試してみましょう。

同時に鳴らした場合、ノイズ周波数の変更で変に聞こえる可能性はありそうです。

ただBGMの演奏が変になるのを目立たなくするのなら消音するのが一番目立たないと思うので、わざわざ矩形波を鳴らしている点を考えると、矩形波だけでも鳴らしておきたいというケースがあるのかも知れません。

ゲーム用のドライバならではの処理でしょうか。

分周比、音量書き込み

分周比は無条件に毎回(割り込みの度に)書き込みをしています。

ワークエリアの(+0BH)(+0AH)[分周比データ]にある値をレジスタに書き込んでいます。

音量書き込みではまず(+0DH)のb3[音量書き込みスキップフラグ]をチェックし、1(スキップ)なら書き込みを行いません。

0なら(+0CH)[音量データ]を書き込んだ後、b2[ハードウェアエンベロープ使用フラグ]をチェックし、1(使用)ならb3[音量書き込みスキップフラグ]を1にします。

[音量書き込みスキップフラグ]はトーン/ノイズの設定、演奏終了時、休符、ハードウェアエンベロープoff時等で0に戻ります。

ここの動作はハードウェアエンベロープ未使用なら毎回音量を(+0CH)から書き込む。使用なら1回書き込みした後は、スキップフラグが0になるまで書き込まない、という動作になります。

ノイズ周波数の書き込み

E29CH[PSGフラグ]b0[Ch.C用ノイズ周波数書き込みフラグ]をチェックし、1ならb0を0、b1[Ch.C用ノイズ周波数書き込み完了フラグ]を1にしてE29AH[ノイズ周波数 Ch.C用]をレジスタ6に書き込みます。

0なら今度はb1をチェックし、ここが1なら何もせず終了します。

b0b1も0のときだけb2[Ch.A/B用ノイズ周波数書き込みフラグ]をチェックします。

1なら同じようにb2を0、b3[Ch.A/B用ノイズ周波数書き込み完了フラグ]を1にして、E29BH[ノイズ周波数 Ch.A/B用]をレジスタ6に書き込みます。

b2も0の場合は何もせず終了です。

ここの流れは最初に曲データ中のE4H[ノイズ周波数設定]コマンドから始まります。

そのコマンドの次のバイトでノイズ周波数データを受け取りますが、この時の処理チャンネルがPSG Ch.Cかその他かで処理が分岐します。

処理チャンネルに応じた箇所のノイズ周波数書き込みフラグを1にし、完了フラグを0にして、ノイズ周波数データをE29AHE29BHに書き込みます。

ノイズ周波数の書き込みの処理でやっていることは曲データのE4H[ノイズ周波数設定]を読んだ後、1回だけそのノイズ周波数を書くという動作と、Ch.Cがノイズ周波数を書き込んだ後は、Ch.AやCh.BでE4Hのデータを読んでもノイズ書き込みは無視するということになります。

Ch.A/Bノイズ周波数書き込みの復活

このままでは、一度SEでノイズ周波数を使うと、以降はCh.A、Ch.Bでノイズ周波数を変更することができなくなります。

Ch.A/Bがノイズを変更するように戻るには

  1. Ch.Cの演奏終了
  2. 曲データE8H[ハードウェアエンベロープoff]の読み込み時

のどちらかが必要です。

1では終了処理でCh.Cのノイズ周波数書き込みフラグと完了フラグを0クリアします。

その時、Ch.A/Bのノイズ周波数書き込みフラグが1であれば、その割り込み処理で反映されることになります。

2の場合はハードウェアエンベロープoffと書いてありますが、E29CH[PSGフラグ]自体を0にするので、PSGのノイズ周波数関係フラグもまとめて0クリアされます。

E8Hコマンドはスネア表現などで、頭1クロックにハードウェアエンベロープを使い、以降はボリューム操作するというような時に使われているので、割と頻繁に出てくるコマンドです。

そうなるともし、ノイズ周波数を1度しか設定しない長めのSEが鳴った時に、PSGドラムの演奏でCh.Aのノイズ周波数に変更されてしまうような気がします。

そのような状況ではどう聞こえるのでしょうか。

なるほど、思った通りとなりました。

Ch.Cの優先度を維持するには頻繁なノイズ周波数の設定が必要のようです。

ハードウェアエンベロープの書き込み

ハードウェアエンベロープ書き込み処理は、E29CH[PSGフラグ]b7からチェックされていきます。

b7[ハードウェアエンベロープ周期下位書き込みフラグ]が0なら終了、1ならE29EH[ハードウェアエンベロープ周期下位]から書き込みb7を0にして次へ。

b6[ハードウェアエンベロープ周期上位書き込みフラグ]が0なら終了、1ならE29FH[ハードウェアエンベロープ周期上位]から書き込みb6を0にして次へ。

b5[ハードウェアエンベロープパターン書き込みフラグ]が0なら終了、1ならE29DH[ハードウェアエンベロープパターン]から書き込みb5を0にして終了。

これだけです。ここはあっさりしていますね。

レジスタ7の書き込み

ここはまた少し手間がかかります。

というのも、処理はチャンネル単位で実行していくので、レジスタもチャンネル単位の方が扱いやすいのですが、このレジスタは3チャンネルまとめたデータ構造なので、プログラムでワークエリアから3チャンネル分調べてデータを作る必要があるからです。

その手法がちょと面白いと思ったのですが、ワーク(+0DH)[波形/キーオンフラグ]b0[PSGノイズ],b1[PSGトーン]を使ってテーブル処理にしていました。

チャンネルごとにトーン、ノイズの組み合わせのビットの並び4パターンを用意するわけです(トーンオフ/ノイズオフ,トーンオン/ノイズオフ,トーンオフ/ノイズオン,トーンオン/ノイズオン)。

それをb1b0、2bitのインデックスとしてビットデータを取得します。

後はチャンネルごとの値をORで結合するという方法です。

レジスタ7に書き込むデータは0が有効1が無効になっていてワークエリアのものと逆ですが、その変換も同時に行えることになります。

他レジスタの書き込みと違い、書き込んだ値をE298H[PSG レジスタ7書き込みコピー]に保存して終了となります。

PSG処理のまとめ

処理内容をまとめますと、
  • 分周比とレジスタ7は割り込みの度に毎回書き込み
  • ノイズ周波数やハードウェアエンベロープ関係は曲データから取得したコマンドの後、1回のみ書き込み
  • 音量は0-15なら毎回書き込み。
  • ノイズ関係はCh.C優先
  • Ch.AとCh.Cのノイズが重なるとCh.Aはトーン出力に変更される(はっきりした意図は不明)
こんな感じです。

ノイズに関しては、処理上はCh.A/BとCh.Cの切り分けですが、曲とSEが鳴る可能性のある状態でCh.Bがノイズを鳴らしているデータは無いようなので、実質Ch.A(曲のドラムパートのノイズ)とCh.C(SEのノイズ)の優先度と考えて良いのかもしれません。