MCIの機能を使うと、MIDIデバイスを使って手軽にSMFファイルを再生することができました。ただ、MCIを使った再生は「すでに用意されたファイルを再生する」ことしかできないので、自分でMIDIを操作して自由な演奏を実現するシーケンサなどを開発するのは無理があり、ゲームのBGMに使うのもちょっと不便ですね。 今回は、APIでMIDIデバイスにメッセージを送ってMIDIデバイスを初期化したり音を出す、という処理を試してみます。 MIDIデバイスへのメッセージ送信WindowsアプリケーションからMIDIデバイスを制御する処理は、最初にデバイスをオープンし、続いてデバイスの設定や音色変更、発音・消音といったいくつかのコマンドを送ってデバイスを使用した後に解放する、という形で行います。オープンや解放は専用のAPIがあり、また発音や消音、音色の変更などのコマンドもコマンドを32ビットの数値にまとめて送るAPIがあるので、特に複雑な処理は必要ありません。 実際に処理を行う時に中心になるのは、発音と消音でしょう。MIDIでは、16本のチャネルが用意され、チャネルごとに音色を設定し発音することが可能です。発音(ノートオン)を行う場合は、チャネルと音程、音の強さ(ベロシティ)を組み込んだ32ビットのメッセージを作成し、それをmidiOutShortMsg()で送信します。音色やベロシティに指定できる数値の範囲は7ビット(0-127)で、チャネル番号は実際の番号から1を引いて0-15で指定します。 発音メッセージの実際の32ビットの数値は、以下のようになっています。 24-31ビット 0 23-16ビット ベロシティ 15- 8ビット ノート番号 8- 1ビット 0xC0 + チャネル番号(-1) もともとのMIDIの規格では、チャネル番号n(+1)のノートオンを行う0xCnというコマンドがあり、そのコマンドの配列が、0xCn、ノート番号、ベロシティ、というものでした。MIDI機器につないだケーブルを通してこのコマンドを送ると、音を出すことができたのです。midiOutShortMsg()に渡す32ビットの数値も、メモリ上の配置では、0xCn、ノート番号、ベロシティ、0、とMIDIコマンドの配列とまったく同じになる(バイトオーダーに注意)ので、midiOutShortMsg()はちょうどMIDIコマンドをそのままMIDIデバイスに流すケーブルの役割を果たすわけですね。 MIDIデバイスにメッセージを送るには、事前にデバイスをオープンし、デバイスのハンドルを取得する必要があります。ハンドルを取得したら、あとはそのハンドルとメッセージを引数にmidiOutShortMsg()を呼び出し、デバイスを使い終わったら、midiOutClose()でデバイスを閉じる、というのが処理の流れです。 MIDIHDR mhMidi; /* MIDIデバイスオープン */ midiOutOpen(&g_hMidi, MIDIMAPPER, 0, 0, 0); /* MIDIチャネル1でノート番号0x3cの音をベロシティ0x40で発音 */ midiOutShortMsg(g_hMidi, 0x00403c90); Sleep(1000); /* MIDIチャネル1でノート番号0x3cの音を消音 */ midiOutShortMsg(g_hMidi, 0x00003c90); /* MIDIデバイスクローズ */ midiOutClose(g_hMidi); 消音に関しては専用のメッセージもありますが、ベロシティを0にすることでも実現できます。 MIDIデバイスに送るメッセージには、音を鳴らすショートメッセージのほかに、デバイスの設定を行うシステムエクスクルーシブメッセージというものがあります。といっても、このメッセージを送る機会は通常それほどあるわけではなく、デバイスを初期化するときに念のためGM音源として初期化するGMリセットメッセージを送る、という場面で使うことがある程度でしょう。 エクスクルーシブメッセージ送信例MIDIHDR mhMidi; /* GMリセット用データ */ BYTE abyGMReset[] = {0xf0, 0x7e, 0x7f, 0x09, 0x01, 0xf7}; ZeroMemory(&mhMidi, sizeof(mhMidi)); /* GMリセット送信用バッファ設定 */ mhMidi.lpData = (LPSTR)abyGMReset; mhMidi.dwBufferLength = 6; mhMidi.dwBytesRecorded = 6; midiOutPrepareHeader(g_hMidi, &mhMidi, sizeof(mhMidi)); /* GMリセットメッセージ送信 */ midiOutLongMsg(g_hMidi, &mhMidi, sizeof(mhMidi)); /* GMリセット完了待機 */ while ((mhMidi.dwFlags & MHDR_DONE) == 0); midiOutUnprepareHeader(g_hMidi, &mhMidi, sizeof(mhMidi)); 上の例では、メッセージ送信後デバイスドライバの処理が終わると設定されるMIDIHDR構造体のMHDR_DONEフラグが立つのを待って、次の処理に移っています。 プログラムプログラムを実行すると、MIDIデバイスをオープン・GMリセットします。その後、C/Dボタンをクリックすると、500ms音を鳴らします。 |