■ 新・ゲーム開発講座 |
■ へっぽこプログラミング入門♪ |
■第34夜:BGM(MIDI)を鳴らす 効果音が鳴ったところで、今度はMIDIに挑戦してみましょう。MIDIファイルを演奏するのもマルチメディアライブラリにお任せで簡単に参ります(うーむ
^^;)。前回同様、winmm.lib と mmsystem.h が必要になりますが、前回のワークスペースを流用し、従来ソースに書き足す形で進んでいきますので特に心配は要りません。ライブラリの追加方法については第33夜に記述してありますので参照して頂きたいと思います。さて、そんな訳でコマンド書式は以下のように決めて進行しましょう。ソースはこちらです。
■MIDI演奏について スクリプトコマンドの実装に入る前に、MIDIファイルの扱い方を簡単に説明致します。MIDI再生には幾つかの手法がありますが、最も簡単なのは mciSendString() を使った方法でしょう。mciSendString() は、文字列の形でMCIコマンドを送る関数です。MCI (Media Control Interface ) はMIDI、CD-DA、ビデオなどを簡単なコマンド送信で扱えるようししたインタフェイスですが、ここではあまり難しく考えずに使ってみましょう。ともかく、以下のようにすればMIDIを演奏することができます。 【MIDIファイルのOPEN】
type sequencer alias _MIDI_ という部分ですが、MCIの中でMIDIファイルを _MIDI_ (←注:好きな名前を付けられます)という識別名で扱うよ、という意味です。プログラム的には曲名などはどうでも良くて、ここで名づけた _MIDI_ というシンボル名が重要です。なお、ファイルOPENに成功したかどうか確かめるには、以下のようにします。さっそく、_MIDI_ のシンボルを使っていますね♪(^^)
capability なんたら〜・・・というのは、演奏可能かどうかの問い合わせです。このコマンド文字列を送ると、演奏可能であれば buf に "true" の文字列が返ってきます。なおここでは buf のサイズをフィーリングで32にしていますが、このサイズに特に意味がある訳ではありません。周囲の事例を見渡すと20くらいのバッファ長でプログラムしている例を多く見かけますので、もう少し短くても良いような気がします(爆 ^^) 【演奏開始】
コマンド文字列中の "notify" というのは、「演奏が終了したら通知してくれ」という指定です。通知って何だ? ・・・と思った方、鋭いです(^^)。実は演奏が終了した、というのもイベントの一つですので、イベントハンドラ中にメッセージが降ってくるのです。そのために、最後の引数にウィンドウのハンドルを指定しておきます(ここで指定したWindowのイベントハンドラに、メッセージが降って来ます) このメッセージの名前は MM_MCINOTIFY といい、イベントハンドラの引数 wprm の内容が MCI_NOTIFY_SUCCESSFUL だったら演奏が正常終了したことを示します。演奏終了を検知したところでもういちど演奏を開始すれば、ループ再生になります。 【演奏停止】
なお演奏が終了したら、開いたMIDIファイルは以下のようにクローズしておきます。
基礎的なことが分かったところで、これをスクリプトコマンドの形で実装してみましょう。ここでは以下の要領で進行することにします。 Mainloopで参照している構造体 Mode_stat のメンバに追加はありません。 ■コマンド解析部 TextEngine.cpp の Command_call() に、以下の要領でコマンド追加を行います。 |
void Command_call() {
|
■ステータス保持用構造体 WAV演奏のときと同様に、演奏状態を保持する構造体を定義しておきます(Text_com_03.h)。音を鳴らすだけなら不要ともいえる構造体ですが、LOAD/SAVE対応を考えた場合には、演奏状態と演奏ファイル名の保持はどうしても必要になってきます。名前は _WAV_stat と同様のノリで _MIDI_stat とでも致しましょう。 |
struct _MIDI_stat {
|
■演奏の開始 演奏開始の要領は↑既に解説した通りですので、素直にインプリメントして参りましょう(下記リスト参照)。コマンド処理では、まず
MIDI_stat の値を調べて、既になにか演奏中ならメッセージを表示してそのまま戻るようにします。構造体のメンバはVC++では0に初期化されるので、プログラム起動時は自動的にOFF(0に#defineされています)が適用されることになり、最初に演奏開始するときにはここでは引っかかりません。 OPENコマンドを送信したら、"capability _MIDI_ can play wait" コマンドでそれが成功したかどうかを確認します。"true"以外の文字列が返ってきた場合には失敗なので、エラーメッセージを表示して戻ります。成功の場合は "play _MIDI_ notify" を送信し、終了通知付きで演奏を開始します。最後に MIDI_stat に「演奏中」のフラグと「MIDIファイル名」をセットして戻ります。 |
_MIDI_stat MIDI_stat; //MIDI演奏のステータス保持用 int Com_play_midi()
|
■演奏の終了 終了はMCIコマンドそのままでインプリメントできますが、「何も演奏していない場合」の対応を忘れずに行いましょう。サウンドカードはピンからキリまでいろいろなので、演奏状態でもないのに停止やデバイスCLOSEコマンドを送ると機種によっては何が起こるか予想がつきません(^^;)そんな訳で、まずは MIDI_stat をチェックして演奏中でない場合はなにもせずに戻るようにしておきます。おお、意味が無いかと思いきや、少しは役に立ってますねこの構造体…♪ (爆死 ^0^) 演奏中であることが確認できたら、stop コマンドを発行して演奏を停止し、さらに close コマンドでMIDIデバイスを閉じます。最後に MIDI_stat の中身をクリアして完了です。 |
int Com_stop_midi() {
|
■ループ演奏 ループ演奏は、終了通知を受けたところでもういちどMIDI再生を行うことで実現できる・・・と上の方で書きました。通知を受け取るのはイベントハンドラで、イベント名は
MM_MCINOTIFY になります。イベントを捕まえたら、MIDI_stat のフラグを確認した上で、演奏リスタート用の関数
Com_restart_midi() を呼ぶようにしておきます。(Com_restart_midi()
は Text_Com_03.cpp に記述してあります) |
LRESULT WndProc(HWND hwnd,UINT msg,WPARAM
wprm,LPARAM lprm) {
} |
リスタートコマンド(リスタートって、漢字で書くと"再再生"?:笑) Com_restart_midi() の内容は以下の通りです。ここではMIDIデバイスを一旦クローズして再生のやりなおしをしています。我ながら、いちいち MIDI デバイスまでクローズしなくても、データの先頭にシークしなおすだけで良いような気がしているのですが・・・まあ、これで動いているのだから気にしないことにしましょうか(爆)。そのうちもう少し高度なワザを mciSendCommand() で実現したら、seek コマンドを使った例も紹介したいと思います。 |
int Com_restart_midi() {
} |
■終了対策 さて、ここで重要な忘れ物があります。本来はWAV再生の項目で扱っておかなければならなかった項目なのですが、終了対策を行っておく必要があるのです。WAVやMIDI、CD−DAなどを再生したままプログラム本体を終了してしまうと、次回プログラムを起動したときにデバイスを認識しなくなったり、演奏が正しく開始できなくなる場合があるのです。 そんな訳で、いままで PostQuitMessage(0) で済ませてきたプログラムの終了処理を、専用の終了関数に置き替えます。ここではその名もズバリ quit.cpp とゆーソースファイルに、Sys_exit() という関数を新設しましょう。内容は以下の通りで、今まで のソースで PostQuitMessage(0) と記述していた部分をすべてこの関数で置き替えます。(たとえばウィンドウ右上の×ボタンを押してもちゃんと演奏終了してくれるように・・・ ^^;) |
void Sys_exit() {
|
さて、これでMIDIを鳴らす環境は整いましたね。難しいようでしたら、理屈はとりあえず脇に置いておいて、まずは実際に音を鳴らして動作を確認してみて下さい。せっかくソースリストがあるのですから、いろいろ手を加えて実験しながら覚えるのが吉ではないかと思います。単純なテキスト操作と違って、音や絵が出る項目は成果が確認しやすくて覚えやすいですから…♪ なお今回もサンプルMIDI曲は某所の某氏(ただいまインドの山奥で修行中につき情報秘匿中 ^^;)に提供して頂きました。元がチャルメラだなてちょっと想像がつかない仕上がりぶりです♪ 多謝、多謝 m(_ _)m |
|