MSXで使える音源にもFM音源やらSCCやらMIDIやらいろいろとありますが、最も簡単に使えるのは何と言ってもPSGです。PSGはシンプルなので、マシン語からでも簡単に扱え、その上、ノイズを出すのに適しているので、リズム音や効果音への応用がしやすいと言う特徴があります。
ここでは、PSGのマシン語からの扱い方について書きます。PSG音源のレジスタに関する記述はBASICのSOUND文でも役に立つので、皆さんぜひご覧ください。
この先は、MSXにテキストを持っていって使う人の事を考え、PREタグを使って書くので、見栄えの悪さに関してはご容赦ください。
マシン語からPSGを操るには、PSGのレジスタに値を直接書き込む事が必要になります。そう、BASICのSOUND文と同じですね。 実際のアクセスでは、まず、I/OポートのA0Hにアクセスしたいレジスタ番号を書き込んだ後、A1Hにデータを書き込む事で、レジスタにデータを書き込む事ができます。 例としては、このような感じになります。この例では、Aレジスタでレジスタ番号を指定し、Eレジスタに書き込む値を入れています。 例) public sound@ sound@: out (0a0h),a ld a,e ;Dat. out (0a1h),a ret また、マシン語では、I/OポートのA0Hにアクセスしたいレジスタ番号を書き込んだ後、A2Hからデータを読み込む事で、レジスタのデータを読み込む事ができます。
例としては、このような感じになります。この例では、Aレジスタでレジスタ番号を指定し、Aレジスタに読み込んだ値が返ります。 例) public rdpsg@ rdpsg@: out (0a0h),a in a,(0a2h) ret 余談ですが、このサンプル、もろにMSX-C Libraryのsoundとrdpsg関数ですね。(^^) I/Oポートを直接いじるのはやばいような気もしますが、アスキーのマシン語入門書でもI/Oポートを堂々と叩いているので、多分大丈夫だと思います。 それでも、I/Oポートを直接いじるのがいやな人はBIOSを使いましょう。書式は次のようになります。 0093:MAIN:BIOS:WRTPSG PSGのレジスタAにデータEを書き込む。 入力/A=書き込むPSGレジスタ番号 E=書き込むデータ 出力結果/なし BIOSで使用/なし 0096:MAIN:BIOS:RDPSG PSGのレジスタAの値をAに読み込む。 入力/A=読み込むPSGレジスタ番号 出力結果/読み込んだデータ BIOSで使用/なし 上とやっている事同じでやんの。ちなみに、MSX-C Libraryのsoundとrdpsg関数もBIOSで実装しています。 では、PSGレジスタに値を書き込むと何ができるのか、各レジスタについて、番号/機能の順に説明します。 レジスタ番号/機能 0/チャンネルAの周波数下位(0-255) 1/チャンネルAの周波数上位(0-15) 2/チャンネルBの周波数下位(0-255) 3/チャンネルBの周波数上位(0-15) 4/チャンネルCの周波数下位(0-255) 5/チャンネルCの周波数上位(0-15) 6/ノイズの周波数(0-31) 7/各チャンネルの使用の有無とI/Oポート使用の指定 8/チャンネルAのボリューム(0-15,16:エンベロープ) 9/チャンネルBのボリューム(0-15,16:エンベロープ) 10/チャンネルCのボリューム(0-15,16:エンベロープ) 11/エンベロープ周期下位 12/エンベロープ周期上位 13/エンベロープパターン 14/I/OポートA(ジョイスティックポート用) 15/I/OポートB(ジョイスティック選択) チャンネル7では、次の形で各チャンネルの使用の有無を指定します。 Bit 0:チャンネルAのトーン出力(1:使用する 0:使用しない) Bit 1:チャンネルBのトーン出力(1:使用する 0:使用しない) Bit 2:チャンネルCのトーン出力(1:使用する 0:使用しない) Bit 3:チャンネルAのノイズ出力(1:使用する 0:使用しない) Bit 4:チャンネルBのノイズ出力(1:使用する 0:使用しない) Bit 5:チャンネルCのノイズ出力(1:使用する 0:使用しない) Bit 6:I/OポートAのモード(0:Input 1:Output) Bit 7:I/OポートBのモード(0:Input 1:Output) Bit7には1、Bit6には0を通常は指定しておけば良いでしょう。でないとジョイスティックが利用できない恐れがあります。 実際の音を出すには、 まず、音階データに対応するトーンもしくはノイズ周波数を計算して、トーン、ノイズの周波数レジスタに書き込みます。 エンベロープを使う際は、周期とパターンをあらかじめセットしておきます。 準備ができたら、レジスタ8-10の各チャンネルのボリュームを1以上にすると音が出ます。逆にボリュームを0にすると音が出ないので、演奏を止めたり、休符の無音を実現するのに使います。 音楽演奏には、タイマー割り込みを使って、音長をカウントし、音の長さの分だけ音を鳴らしたら音を止める事で、一つの音符を表現できます。 音階は、各周波数ごとにトーン周波数が決まっているので、計算から求める事ができますし、テーブルルックアップで求める事もできます。 TABLE: dw 3420,3229,3047,2876,2715,2562,2419,2283,2155,2034,1920,1812, dw 1710,1614,1524,1438,1357,1281,1209,1141,1077,1017, 960, 906, dw 855, 807, 762, 719, 679, 641, 605, 571, 539, 508, 480, 453, dw 428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226, dw 214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113, dw 107, 101, 95, 90, 85, 80, 76, 71, 67, 64, 60, 57, dw 53, 50, 48, 45, 42, 40, 38, 36, 34, 32, 30, 28, dw 27, 25, 24, 22, 21, 20, 19, 18, 17, 16, 15, 14 このテーブルはO1のドを0、O1のド#を1としたときのC言語で言うunsigned short型の配列になっています。このテーブルから、音階に対応するトーン周波数を求めて、レジスタに書き込む事で、音階を表現することができます。 後は、皆さんで、プログラムを組んでみてください。