BGM演奏技法


 BGM(バック・グラウンド・ミュージック)とはある処理(ゲーム等)と音楽の演奏を同時に行う事の総称です。
 パソコンやゲーム機だと音楽専用のLSIを搭載しているのでBGMは簡単に処理出来るんだけど、我らがポケコンにはそんな高級なものは搭載されていない(中には音源チップを繋いでいる人達もいますが)し、それどころか機種によっては発声機能(BEEP音)をも持たないものまであるというのが現状です。
これはポケコンが単なる計算機の延長としてしか(製作者サイドには)捕らえられていなかったということなんでしょうが、実に残念なことです。
 しかし、幸いにもFX-890Pにはブザーが搭載されているので単に音を出すだけなら比較的簡単に出来ますが、さすがにこれで音楽の演奏を行うとなるとそれなりに高度で特殊な知識が必要となってしまいます。

 このレポートはポケコンでBGMを行うための技術についてまとめたものです。
基本的にアセンブラでBGMを行う事を前提としてる(だってBASICだけでBGMするなんて不可能だもの)ので、アセンブラが使えない人はあらかじめ勉強しておきましょう。
ここではサンプルとしてFX-890Pを例に取り上げているけど、恐らく他機種でも同じような方法が使える事でしょう。

サンプルプログラムです。

1.「音」とは?

 知っての通り「音」の正体は「物体の振動」だというのはいいですね?
この振動の周期が音階、振幅が音量、周期と振幅の変化が音色を決定し、これらを操作することを俗に「音楽を奏でる」と呼ぶわけですから、コンピュータで音楽をやるにはこの3要素をどう実現するのかを知ればいい訳です。

 これから解説するのは「音階」と「音量」についてで、「音色」については解説していないけど、これは「音階」「音量」を操作すれば実現できるから各自で工夫してみましょう。

2.音階を出そう

 それでは音階を出す方法について説明…の前に(^^; 、「物体の振動」を起こす方法を知らないと話にならないので、まずはそれからいきます。

 「物体の振動」を起こすにはFX-890P内蔵の圧電スピーカを使うのが一番簡単な方法でしょう。
圧電スピーカの操作はいたって簡単で、I/Oの206Hに3を出力してやればスピーカにかかる電圧が0[V]←→5[V]と変化するのでこれで音を出すことができます。
ただこの結果出てくる音の波形はサインカーブには程遠い形をしていて、どちらかというとパルス的なものなのでどうしても音が金属的になってしまうけど、まあそれは仕方無いでしょう。

例)

MOV DX,206H
MOV AL,3
OUT DX,AL

 さて、音の出し方が分かったところで本題にいきます。
 ある音程の音を出すためには、ある決まった周期ごとに振動(音)を発生させてやればいいんだけど、それには音程と周期の関係を知る必要があります。
各音階と周波数の関係は、

オクターブが一つ上がると周波数は2倍になる。
オクターブ4の「ラ」は440[Hz]

ということから私は

周波数[Hz] = 440 * 2^(O-4 + C/12)

O:オクターブ
C:音階(0=ラ,1=ラ#,2=シ,3=オクターブ+1のド,…10=オクターブ+1のソ,11=オクターブ+1のソ#)

という計算式で周波数を求めているけど、これが正しいかどうかはわからないんであくまで参考に、ということにしといてください。(これでもそれなりにらしく聞こえてますけどね)
 周波数と周期の関係は

周期[秒] = 1 / 周波数[Hz]

で求められるので、これで目的の音程を出すための周期を知ることが出来ました。
 後はこの周期に従って圧電スピーカを振動させれば、めでたく音程を奏でることができます。

3.周期はどう取る?

 各音程を出すための周期が分かったところで次に問題になるのが「周期の取り方」でしょう。
このやり方次第でBGMが実現出来るかどうかが決まってしまうというほど、かなり重要な部分です。

 単純に「周期を取る」というだけならソフトウェア・ループで時間待ちすりゃいいんだけど、それをやってしまうと音を出している間は何も出来なくなるので今回のテーマ「BGM演奏」が実現出来なくなってしまうためこれはボツ。

 ではどうするのか?

 「周期を取る」というのは言い換えると「一定間隔で何かする」という事だから、これにぴったりの機能がコンピュータにはありますよね?
 そう!「タイマ割り込み」です。
これを使えば一定時間ごとに自動的に割り込みが発生するから、ゲーム処理と並列してBGM演奏なんてことも出来るわけ。
 FX-890Pのタイマ割り込みは割り込み周期を指定することが出来るから、割り込み周期=発音周期と設定してやれば苦もなくBGMが出来てしまうので、非常に都合がいいと言えますね。
もし割り込み周期が固定だったとしても方法はあります。
音楽演奏をメインの処理とし、ゲーム処理を割り込みで実行するんです。この方法だと音質がかなり犠牲になるでしょうけど、とりあえずはBGMを実現できます。

4.タイマー割り込み

 FX-890Pのタイマ割り込みは3系統あり、すべてポケコンのシステムが使用しているのでこれを使うとポケコンの一部の機能が使えなくなります。(ソフト次第でこれを回避することも出来ますが)
これはマシン語実行中は大して問題にならないけど、BASICでは最悪ハングアップが起きる事も考えられるので注意が必要でしょう。

 タイマ3系統のうち、タイマ1はそれほど重要なことには使われていない(WAIT,BEEP,INKEY$等で使用される)ので、これをBGM用として使う場合を例に説明します。

 タイマ1の割り込みベクタは「INT 12H」、カウンタはI/Oの38H、周期の設定はI/Oの3AHと3CHがそれぞれ周期Aと周期B、動作設定がI/Oの3EHとなります。
これを見て分かるように設定次第でタイマ周期を2つ指定することも出来ますが、今回はそんな事をしても意味が無いので周期Aのみ使用する設定にするため、

MOV AX,0E001H
OUT 3EH,AX

を実行します。
 周期はB400Hが0.05[秒]に相当するので、そこから目的の値を算出して

MOV AX,目的の値
OUT 3AH,AX
XOR AX,AX
OUT 38H,AX

で周期を設定すると指定時間ごとに「INT 12H」が呼び出されるので、そこにあらかじめ音出しルーチンを接続しておけばいいですね。

PUSH ES
XOR AX,AX
MOV ES,AX
MOV DI,48H
MOV AX,OFFSET BGM処理ルーチン
STOSW
MOV AX,CS
STOSW
POP ES

BGM処理ルーチン:

PUSH AX
PUSH DX
MOV DX,206H
MOV AL,3
OUT DX,AL
MOV AX,8000H
OUT 2,AX
POP DX
POP AX
IRET

 これら割り込み処理を使ったときは、BASICに戻る前に書き換えた部分を元に戻すのを忘れないようにしましょう。

 BGM処理の中身は大きく分けるとミュージックデータの解析と周期の設定部、圧電スピーカの操作部とに分けられるでしょうが、これらの処理に時間がかかると全体的な実行スピードが落ちてしまうから、出来る限り軽くなるようにすべきでしょうね。
 割り込み処理中の注意点は処理が終わったらEOI(I/Oの02H)に8000Hを出すこと、ストリング命令を使うときはディレクションフラグに注意すること、セグメントは必ずしも0000Hとは限らないってことですね。

5.音量を変えるには?

 これで音階を出すことができるようになったけど、それだけで満足していてはいけません。
やはり音量が変えられないと十分とは言えないでしょう。
音量が変えられれば曲全体のボリュームを変えることはもちろん、細かな音量変更で音色を表現することだって可能になるんですから。

 音量とは振幅の大きさのことだから、振幅を変えてやることができれば音量を変えられるというのは道理ですよね。
 では圧電スピーカの振幅変動を小さくするにはどうすればいいか?。
 単純に考えるとスピーカにかける電圧を小さくすればいいって事になるんだけど、そんなことはD/Aコンバータでも組み込まない限り無理です。

 そこで発想を180°変えてみましょう。

 圧電スピーカから音が出るのはスピーカにかけられる電圧が変化したときなんだけど、電圧が変化した瞬間に音が出ている訳ではないのです。
実際には電気信号の変化スピードに比べスピーカの物理的変化スピードは遅いので、音が完全に出るまで少し時間がかかります。
 ではもし音が完全に出切っていない時点でスピーカへの信号を元に戻したとしたらはたしてどうなるでしょうか?
当然普通の場合より音量が小さくなるはずですよね。
具体的にはどうするのかというと

例)

MOV DX,206H
MOV AL,3
OUT DX,AL
OUT DX,AL ;これがミソ

というようにしてやるんです。
 音量調節は最初のOUTから次のOUTを実行するまでの時間で調節することが出来ます。
ただしこの時間は非常に微妙なもので、1クロック違うだけでもかなり音量に変化が出ますから、各音量専用の発音ルーチンを用意しておくとかしないと明確な違いが出ないでしょう。

6.音符の音長

 これで音の出し方は分かったと思いますが、今度は音の長さについての話しです。
これは音楽についての知識を持ってる人にはあたりまえな事ですが、知らないと苦労することになると思うので一応書いときます。

 曲におけるテンポとは具体的に何を現してるのかというと、実は全音符の音長なんです。

全音符の音長[秒] = 240 / テンポ

2分音符や4分音符は全音符を1/2や1/4にした音長となり、これに付点が付くと音長は1.5倍ずつ増えていきます。

 実際の処理では発音回数をカウントしておけば周期とカウント数から音長を知ることができますが、これはあらかじめ計算しておいた方がいいかも。

7.多重演奏するには?

 今までの説明は全て単音を出す事を前提にした説明だったんだけど、多重演奏を行う場合についても説明します…と言いたいとこなんだけど、私は未だ実際に多重演奏をやった事がないんで方法論についてのみ書きます。

ちなみに3重和音までなら3つのタイマにそれぞれ演奏ルーチンを繋げば実現できます。(サンプルプログラムが使ってる方法です)

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

1.定周期割り込みによる方法

 基本的な考え方はTSS(タイム・シェアリング・システム)と同じで、定期的に発生する割り込み処理をそれぞれのパートに順番に割り振っていくやり方です。

割り込みが発生した回数 処理を行うパート
1回目
2回目
3回目
4回目
5回目
6回目
:

・割り込み周期は最高発声音階のパート数倍(ここでは4倍)に固定しておきます。

最高発声音階をオクターブ5のラ、パート数は4とすると、

1/(880*4) = 0.28[ms]

・各パートから音を出すときの音程は、最高発声音階を何分周した音を出すかというふうに指定します。

Aパートが一つ飛びに音を出したとする→オクターブ4のラ

 この方法の問題点は、音階が高くなるほど音程がずれる、たとえ音が出ていなくても割り込みが掛かるため効率が悪い、発声パート数を増やそうとすると高い音が出しづらくなる、という点でしょう。

2.可変周期割り込みによる方法

 これのイメージは、各パートの出力波形を合成するという感じです。

__|__________|__________|__________|____ Aパート出力

__|______|______|______|______|______|__ Bパート出力

__|_______|_______|_______|_______|_____ Cパート出力

__|_________|_________|_________|_______ Dパート出力

__|______||_||__|_|___|||_|___|_|_||_|__ 合成出力

・各パート毎に音を発生するまでの時間と周期をデータとして用意しておく。

パート 周期 時間
10 10
6 6
7 7
9 9

●一番少ない時間(Bパートの6)を全てのパートから引き、この時間を割り込み周期に設定する。

パート 周期 時間
10 4
6 0
7 1
9 3

割り込み周期:6

・時間が0になっているパートに対して発声処理を行い、時間に周期を足しておく。

パート 周期 時間
10 4
6 6
7 1
9 3

・割り込み処理おわり。
 次に割り込みが掛かったら「●」から繰り返す。

・音程を変えるときは発声処理の時に周期を変更すれば良い。

 この方法の問題点は、場合によっては割り込み周期が短くなりすぎる事があるという点でしょう。

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

8.サンプル

 百聞は一見にしかずと言いますから、実際のプログラムではどうなるかという例をお見せしましょう。
 アーカイブに同封されていた「PLAY.ASM」がそのサンプルプログラムです。
(プログラムの終了は[BRK]です)

SOFLG : 音を出すか?(0-SOUND ON)
FADE : この値だけ全体の音量が下がる(0〜7)

BGMON BGM演奏の開始(と同時に曲番号0とSEの0を演奏する)
BGMOFF BGM演奏の終了
BGMCHG BGMの曲指定(AH:0以外ならその曲に固定する,AL:曲の指定)
SECHG SEの指定(AH:0以外ならそのSEに固定する,AL:SEの指定)

 データのフォーマットは

MELODY:

DW 曲番号0パートAのデータへのポインタ
DW 曲番号0パートBのデータへのポインタ
DW 曲番号1パートAのデータへのポインタ
DW 曲番号1パートBのデータへのポインタ
:

EFFECT:

DW SE0のデータへのポインタ
DW SE1のデータへのポインタ
:

BGMのデータ構造

RETURN ADDRESS,TEMPO,NOTE,LENGTH,NOTE,LENGTH,…,0FFH,0

SEのデータ構造

TEMPO,NOTE,LENGTH,NOTE,LENGTH,…,0FFH,0

NOTE LENGTH 意味
0〜59 0〜255 音符(オクターブ2ド〜オクターブ6シに対応)
60〜63 0〜255 休符(LENGTHは0と1が全休符,2が2分休符…)
80H ---- ノイズOFF
81H〜8FH ---- ノイズON
C0H〜C7H ---- 音量(小0〜7大)
FFH 0 データ終了(RETURN ADDRESSへポインタ移動)

 これはあくまでも一例にすぎませんから、あまりこれに捕らわれること無く各自で色々工夫してみてください。

9.最後に

 さて、駆け足で説明してきましたがBGM演奏について理解出来ましたか?
タイマ割り込みに関する説明が淡白すぎるような気もしますが、これについてのより詳しい説明をしていたらとてもじゃないけど切りが無いので、そういうのは専門書で調べてくださいな。

 ここで紹介した手法やプログラムは自由に使用してかまいませんが、その結果なにか問題が起こったとしても当方は一切関知しませんのでそのつもりで。

NIFTY-Serve YHW02344
村中 昭雄(STEAR)