サウンドを読み込んで鳴らす


取っ掛かりは簡単だが
ゲームとして使えるように
するには一番大変なのが
サウンドである


●注意、ウインドウズは一度に一音が基本

WAVなどでサウンドを鳴らす時の基本がこれ。
つまり、ある音が鳴っている時に別な音を鳴らそうとすると
前の音が途切れる。または鳴らそうとした音が鳴らない。
どうしても鳴らしたい場合、複雑な手順が必要になってくる。

このせいで、WAVでBGMを鳴らしながら効果音をつけたり
レーザー音を鳴らし続けながら敵の破壊音を重ねたり
ビートマニアのように一度に複数の音を鳴らすものが難しい。

同時に鳴らすには、前もって音を合成しておくなどの処置が必要。
このための機能として、DirectXと呼ばれているものがある。
ただし、これはC言語用に作られており、Delphiで使用するには
かなり苦労するらしい。

合成はかなり難しいので、今回は合成無しの説明のみとする。

●DELPHIに用意されている、サウンドを鳴らす方法

Delphiには、最初からサウンドを再生するための
コンポーネントが用意されている。
SYSTEMにあるMEDIAPLAYERがそれ。

これをFORMに置くと下のようなツールが出てくる。

これは、再生や停止を制御するためのボタン群だが
ゲームに使うことはないので、インスペクタのVisibleFALSEにして
表示させないようにしておく。

他に設定するインスペクタは

DEVICETYPE:dtAutoSelect→dtWaveAudio
 再生するものの種類。音なのでWAVに。
 が、そのままのオートセレクトでも問題は無い。

FileName:名前指定
 再生する音を指定。上のデバイスタイプがオートならMIDIの指定も可能。

この二つ。合わせて三ヶ所を変更するだけでOK。
そしてここからはプログラム。
procedure TForm1.FormCreate(Sender: TObject);
begin
 sound1.Open;
end;
Openは、準備のための命令。オブジェクトのクリエイトのようなもの。
ただしFREEでの解放を書く必要がない。
これをFormCrateに書いておく。

鳴らす時は
sound1.Rewind;
sound1.Play;
の命令で鳴らせる。Rewindは鳴らす位置を先頭にリセットする命令。
これが無いと途中で止めた後で再開すると、途中から鳴り始めてしまう。

途中で止めたり、停止したりするのは
sound1.Stop;
これでOK。

ただし、これで音を鳴らす方法はやや面倒。
一音で1コンポーネントが必要になってくる上に
止めたい時にどの音が鳴っているかを認識していないといけない。

●APIで音を鳴らす方法

APIに用意されている音を鳴らす方法にPlaySoundがある。
uses
〜〜〜, MMsystem; //←最後にこれを追加

PlaySound('sound2.wav',0,SND_ASYNC or SND_NODEFAULT or SND_FILENAME);
使い方は上の通り。
usesにMMsystemを書き加えておき、鳴らしたい所に
「鳴らすもの」「0」「鳴らす方法」の順に書いていく。
「0」については、今は深く考えなくてもよし。

鳴らす方法の全解説は以下の通り。pszSoundは一番目の「鳴らすもの」にあたる。
いくつか同時指定する時はorを使えばいい。
指令名 内容
SND_APPLICATION アプリケーション定義のサウンドを再生
SND_ALIAS pszSoundは、システムイベント名
(SND_FILENAME、またはSND_RESOUCEと組み合わせない)
SND_ALIAS_ID 定義済みサウンドID
SND_ASYNC 非同期で再生(演奏終了を待たずに次の命令へ)
SND_FILENAME pszSoundはファイル名
SND_LOOP 繰り返し再生(中断する場合はファイル名にvbNullStringを指定)
SND_MEMORY サウンドデータをメモリ中にロード(pszSoundはメモリ領域のアドレス)
SND_NODEFAULT 指定のファイルがなくてもデフォルトを実行しない
SND_NOSTOP ほかに再生中の場合は何もしない
SND_NOWAIT サウンドドライバを使用中のとき、演奏しないですぐ戻る
SND_PURGE 再生を中止する
pszSoundがvbNullStringでないとき、
指定のサウンドのすべてのインスタンスを中止する
SND_RESOURCE pszSoundはりソースid(hmodはインスタンスハンドルを設定)
SND_SYNC 演奏終了まで戻らない
色々あるが、ゲームで実際によく使用するものは限られる。

音を送ったらすぐに次の事をするのでSND_ASYNCの指定は当然になるし
WAVをBGMとして使う場合にはSND_LOOPを使用。
読み込み失敗時に「ポーン」という音を鳴らしたくないのでSND_NODEFAULTも必要。
「鳴らすもの」の指定には、最初はSND_MEMORYSND_RESOURCEが使われる。

●APIで音を鳴らす方法その2

上では、HD上のファイルを直接指定して鳴らしていたが
この方法だと音を鳴らすたびにHDへアクセスすることになり、あまり良くない。
そこで最初にメモリ上へ読み込んでから鳴らすようにしてみる。
//まずは宣言。メモリストリームという形式の変数を作る。
  private
  { Private 宣言 }

   WebMem01:TMemoryStream; //WAVメモリ
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
//クリエイトの時に初期化する。
procedure TForm1.FormCreate(Sender: TObject);
begin
//メモリストリームを初期化する
 WebMem01:=TMemoryStream.Create;
//そこへWAVファイルを読み込ませる
 WebMem01.LoadFromFile('sound003.wav');
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
//実際に鳴らしたい時は以下を実行
procedure TForm1.ABtnClick(Sender: TObject);
begin
 PlaySound(WebMem01.Memory,0,SND_MEMORY or SND_ASYNC or SND_NODEFAULT);
end;
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
//こうして音を止める。
procedure TForm1.stopBtnClick(Sender: TObject);
begin
 PlaySound(nil,0, SND_PURGE);
end;
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
//破棄。使い終わったメモリストリームを処分しておく。
procedure TForm1.FormDestroy(Sender: TObject);
begin
 WebMem01.Free;
end;
こうすればWAVを最初に読み込んでおき、メモリ上のWAVで音を鳴らすことが可能。

メモリに記憶させて鳴らすのには、リソースを使うという方法もある。
これは、外部ファイルではなく、実行ファイルに直接WAVを含ませる方法で
一つにまとめられて使用者は扱いが楽になるが、開発の途中で
ファイルを変更したくなった時に不便なので、自分はあまりお勧めしない。

●ゲームとしてBGMを鳴らしながら効果音を使えるようにする方法

上の方法のままでは、BGMを鳴らしながら効果音を鳴らすことができない。
同時2音は出せないという制限はかなり重い。
だが、MIDIは別なルートで音を鳴らしているのでWAVと重ねることが可能。

MIDIでBGMを鳴らしながらWAVで効果音を鳴らすという方法が
簡単で最善な方法になる。(それでも効果音は1音のみだが・・・)

●MIDIで鳴らした音のループを制御

APIのPlaySoundではMIDIを鳴らすことができないので
MIDIをBGMで鳴らすためにはMEDIAPLAYERが必須。

ここで問題が出てくる。PlaySoundで音をループさせるには
命令にSND_LOOPを付け加えるだけでよかったが
MEDIAPLAYERではやり方が少々複雑になる。
まず演奏の有無を指定するための変数を一つ用意する。
  private
  { Private 宣言 }
   BgmOn :Integer; //BGMを鳴らすか止めるかの判定
TrueとFalseのBoolinで制御せず、Integerで宣言しているのは
BGMの種類を増やした時のため。OFFを0か-1として、それ以上ならBGMの番号。
と設定しておけば後々楽になる。

初期化と、音を鳴らす時には以下のようにする。
//初期化
BgmOn := 0;
〜〜〜〜〜〜
//演奏
BgmOn := 1;
midi01.Play;
続いて再演奏の設定。MEDIAPLAYERのオブジェクトインスペクタに
OnNotifyというイベントがある。ここをダブルクリック。
出てきたプロシージャに命令を書き込む。
//演奏終了時、自動で再開。
procedure TForm1.midi01Notify(Sender: TObject);
begin

 if BgmOn = 1 then
  begin
   midi01.Notify := true;
   midi01.Play;
  end;
end;
このTForm1.midi01Notifyは演奏終了時に自動で立ち上げるプロシージャ。
midi01.Notifytrueにしておくことで立ち上がる。

そして音を止める時には下の様にする。
midi01.Stop;
BgmOn := 0;
つまりBgmOn = 1の時のみ再演奏が行われるようにしている。

なぜこうするのか?というとmidi01.Stopで止めただけの場合
TForm1.midi01Notifyが成立して、再び音が鳴り出すから。

●MIDIでのループによる不具合

MIDIでBGMをループさせる場合、少し不具合がある。
鳴り初めに、必ず音のでない時間ができてしまうのがその問題点だ。

これはMIDIを鳴らす時に「音源リセット」が行われるからで
リズムや楽器の設定をするために必要なものなので回避は無理。
どうしても間を空けたくない場合には、MIDIを編集できる知識が必要になる。

回避方法その1:音源リセットの個別化

音源リセットは、鳴らし始めには必ず必要だが
同じ曲を再度鳴らす時にはその必要性は無い。
なので、音源リセット部分と演奏部分を別にしておき
再演奏の時は演奏部分のMIDIのみを使用すれば切れ目は無くなる。
ただし、途中で音源の設定を変えてしまうような曲には無効になる。
(滅多に無いが)

回避方法その2:演奏を数回分まとめてしまう

MIDIのデーター量はWAVに比べるとかなり少ない。
そこで1プレイ分以上の長さをMIDIデーター内でループさせてしまい
1プレイの間に1曲が終わらないようにすれば切れ目は無くなる。
ただし、思考型パズルゲームなどの何時間でも放っておけるようなものには通用しない。

回避方法その3:フェードアウトで気にしないようにする

いきなり音が途絶えるのが気になるなら、少しずつ音量を下げていけば
多少は気にならなくなる。が、完全に無視できるようにはできないので
その2の数回分まとめる方法と兼用。毎回フェードアウトしないようにすれば
気になる部分の回数を格段に減らすことができる。

 戻る