- カウンタ値
PC-8001 の周辺機器である PCG8100 に搭載されている発音チップ 8253 は、発音したい周波数をそのまま設定はできません。カウンタ値という値を設定する事になります。このカウンタ値は分周比から計算して、各音程毎にテーブル化しておきます。音程は有名なところで「ラ」の音が 440Hzです。この音が基本となります。
実行速度優先のため、同一オクターブの12音は全て周波数決め打ちのテーブルで考えます。分周比は動作クロックを周波数で割る事で得られます。PCG8100 の動作クロックは水晶発振の 15.9744MHzを4分周しますので、3.9936MHzとなります。Hzに換算すると 3993440Hzですから、この値で周波数を割るとカウント値が得られます。
例えば、ドの音 261.626Hz を出力する場合は、3993440÷261.626=15263.926≓15264=0x3BA0 となります。
音階 | MML | 周波数 | カウント値 |
ド | C | 261.626 | 0x3BA0 |
ド# | C+ | 277.183 | 0x3847 |
レ | D | 293.665 | 0x351F |
レ# | D+ | 311.127 | 0x3223 |
ミ | E | 329.628 | 0x2F53 |
ファ | F | 349.228 | 0x2CAB |
ファ# | F+ | 369.994 | 0x2A29 |
ソ | G | 391.995 | 0x27CB |
ソ# | G+ | 415.305 | 0x2590 |
ラ | A | 440 | 0x2374 |
ラ# | A+ | 466.164 | 0x2177 |
シ | B | 493.883 | 0x1F96 |
このカウント値を 8253に設定すると目的の音が鳴るというわけです。オクターブが一つ変わると、このカウント値は2倍または1/2になります。計算し続けていくと、どこかで 16ビットより大きくなったり、ゼロに近づきすぎて音階の差が無くなったりと、破綻する瞬間が来ます。そこがこの 8253というチップで出せる音の限界という事になります。
上記の値を仮にオクターブ O3 とします。計算上、O1 でほぼギリギリの低音が出ます。
音階 | MML | 周波数 | カウント値 |
ド | C | 65.406 | 0xEE80 |
ド# | C+ | 69.296 | 0xE11D |
レ | D | 73.416 | 0xD47B |
レ# | D+ | 77.782 | 0xC88D |
ミ | E | 82.407 | 0xBD4C |
ファ | F | 87.307 | 0xB2AC |
ファ# | F+ | 92.499 | 0xA8A5 |
ソ | G | 97.999 | 0x9F2E |
ソ# | G+ | 103.826 | 0x963F |
ラ | A | 110 | 0x8DD0 |
ラ# | A+ | 116.541 | 0x85DA |
シ | B | 123.471 | 0x7E57 |
音階 | MML | 周波数 | カウント値 |
ド | C | 2093.005 | 0x0774 |
ド# | C+ | 2217.461 | 0x0709 |
レ | D | 2349.318 | 0x06A4 |
レ# | D+ | 2489.016 | 0x0644 |
ミ | E | 2637.02 | 0x05EA |
ファ | F | 2793.826 | 0x0595 |
ファ# | F+ | 2959.955 | 0x0545 |
ソ | G | 3135.963 | 0x04F9 |
ソ# | G+ | 3322.438 | 0x04B2 |
ラ | A | 3520 | 0x046F |
ラ# | A+ | 3729.31 | 0x042F |
シ | B | 3951.066 | 0x03F3 |
さて、オクターブ1(O1)からオクターブ6(O6)までの、全域の周波数カウント値テーブルは以下のようになります。このテーブルから、発音したい音階のカウント値を取り出して、8253 に設定すると、目的の音が出るようになります。
FRQTBL: dw 0xEE80, 0xE11D, 0xD47B, 0xC88D, 0xBD4C, 0xB2AC ; O1
dw 0xA8A5, 0x9F2E, 0x963F, 0x8DD0, 0x85DA, 0x7E57
dw 0x7740, 0x708F, 0x6A3D, 0x6447, 0x5EA6, 0x5956 ; O2
dw 0x5453, 0x4F97, 0x4B1F, 0x46E8, 0x42ED, 0x3F2C
dw 0x3BA0, 0x3847, 0x351F, 0x3223, 0x2F53, 0x2CAB ; O3
dw 0x2A29, 0x27CB, 0x2590, 0x2374, 0x2177, 0x1F96
dw 0x1DD0, 0x1C24, 0x1A8F, 0x1912, 0x17AA, 0x1656 ; O4
dw 0x1515, 0x13E6, 0x12C8, 0x11BA, 0x10BB, 0x0FCB
dw 0x0EE8, 0x0E12, 0x0D48, 0x0C89, 0x0BD5, 0x0B2B ; O5
dw 0x0A8A, 0x09F3, 0x0964, 0x08DD, 0x085E, 0x07E5
dw 0x0774, 0x0709, 0x06A4, 0x0644, 0x05EA, 0x0595 ; O6
dw 0x0545, 0x04F9, 0x04B2, 0x046F, 0x042F, 0x03F3
- 8253 から発音するために必要なポート
8253 の音に関係するポートは以下の通りです。
* 0x02 キーオンフラグ
* 0x0C ch1 カウント値設定
* 0x0D ch2 カウント値設定
* 0x0E ch3 カウント値設定
* 0x0F カウント値設定開始指定
1ch から O4C(ド)の音を出すのは下記のようになります。
* 0x02 キーオンフラグ
* 0x0C ch1 カウント値設定
* 0x0D ch2 カウント値設定
* 0x0E ch3 カウント値設定
* 0x0F カウント値設定開始指定
1ch から O4C(ド)の音を出すのは下記のようになります。
N-BASIC からでもテストできます。
ld a, SNDWRT.@1 ; ch1 下位/上位
out (0x0F), a ; カウント値設定開始宣言
ld c, 0x0C ; ch1 カウンタ値周力ポート
ld hl, 0x1DD0 ; HL O4C 音階カウント値
out (c), l ; 下位カウント値設定
nop ; 4サイクル時間待ち
out (c), h ; 上位カウント値設定
ld a, KEYON.@1 ; ch1 キーオンフラグを
out (0x02), a ; 設定して音を出す
out(2),0
out(15),&H36
out(12),&HD0
out(12),&H1D
out(2),8
- 0x02 キーオンフラグ
例えば out (0x02), KEYON.@1 (実際には Acc または C レジスタを介して設定します)とすると、ch1 のみ音が出て、ch2,ch3 は音が止まります。全ての音を止めたければ out (0x02), 0 とします。逆に全てのチャンネルから音を出したければ、out (0x02), KEYON.@1 | KEYON.@2 | KEYON.@3 とします。面倒なので out (0x02), KEYON.All としています、
KEYON: ; キーオンフラグ out(0x02) に出力する
.@1 equ %00001000 ; ch.1
.@2 equ %01000000 ; ch.2
.@3 equ %10000000 ; ch.3
.All equ (.@1 | .@2 | .@3)
キーオンフラグは音楽再生では休符処理で使用します。発音カウント値に 0 を入れても音は消えますが正しい処理ではありません。キーオンフラグ制御を心がけてください(めちゃめちゃ面倒ですけどね…)
さて、実はこの 0x02 にはもう一つ大事な機能があります。4ビット目 %00010000 の部分が、PCG 定義開始ビットになっています。そのため、ゲーム中に PCGを書き換えたい場合は、このフラグの操作時にサウンドのキーオン情報が狂わないように配慮する必要があります。通常は最初に定義を終えてしまうので問題はありませんが、拙作の Newシティヒーローというゲームでは、1/60毎に高速に PCG再定義を行っていますので、この 0x02 のフラグ管理がかなり大変でした。PCG 書き換えを音楽を鳴らしながら行いたい場合は、かなり注意が必要です。※ IN してもキーボードの状態がとれるたけですしね…。
- 0x0F カウント値設定開始指定
ch1 のカウント値を設定するためには、下記のように設定します。
SNDWRT: ; カウンタ値出力開始フラグ 下位/上位
.@1 equ %00110110 ; ch.1
.@2 equ %01110110 ; ch.2
.@3 equ %10110110 ; ch.3
ld a, SNDWRT.@1 ; ch1 下位/上位
out (0x0F), a ; カウント値設定開始宣言
- 0x0C, 0x0D, 0x0E カウント値設定
この例で注意が一つ。下位カウント値設定と上位カウント値設定の間に nop を挟んでいます。OBF等の高速互換ボードだと nop は不要ですが、初期型 PCG8100 では間髪入れず連続してポートに設定値を送り込むと、正しく設定されない事があります。そのため、最低4サイクル、待ち時間を入れてください。この間に次の処理のためのレジスタ代入とかしておくと無駄がないです。
ld c, 0x0C; ; ch1 カウンタ値周力ポート
ld hl, 0x1DD0 ; HL O4C 音階カウント値
out (c), l ; 下位カウント値設定
nop ; 4サイクル時間待ち
out (c), h ; 上位カウント値設定
ld a, KEYON.@1 ; ch1 キーオンフラグを
out (0x02), a ; 設定して音を出す
これで自分で好きな音が出せるようになります。
第1回:ASM サウンドドライバの構造
第2回:PCG8100 から発音させる
コメント