MIDIを鳴らす

概要:MIDIを単音で鳴らしたり、音色を変更したり

MIDIファイルが音符の羅列で構成されている事はよく知られていると思います。
それでは、その一つ一つの音を鳴らすにはどうしたら良いんでしょうか?
今回は特定の音を鳴らす方法について解説します。
ファイルの解析は大変なので、今回はパスです。

■midiOutShortMsg関数

MIDIを単音で鳴らすには midiOutShortMsg 関数を使います。
midiOutShortMsg 関数はMIDI出力デバイスにMIDIショートメッセージを送信します。

MMRESULT midiOutShortMsg(
  HMIDIOUT hmo,
  DWORD dwMsg
);


hmo にはMIDI出力デバイスのハンドルを指定します。
dwMsg にはMIDIショートメッセージを指定します。

プログラム全体の流れは次のようになります。

midiOutOpen関数midiOutShortMsg関数midiOutReset関数midiOutClose関数

midiOut○○関数はwaveOut○○関数に非常によく似ているので、
プログラムを見れば大体わかると思います。
詳細はリファレンスをご覧下さい。

■ショートメッセージとロングメッセージ

MIDIメッセージにはデータ長が3バイトあるショートメッセージと、
データ長が可変であるロングメッセージがあります。
一般的な操作はショートメッセージで行う事ができ、ロングメッセージは特殊な操作を行うのに使います。

ロングメッセージについては今回は必要ないので解説しません。

■MIDIメッセージ

MIDIメッセージの構成は次の通りです。

ステータスバイト データバイト1 データバイト2
メッセージ値 チャネル番号
1xxx xxxx 0xxx xxxx 0xxx xxxx

左の方が上位バイトです。

メッセージ値は「鍵盤を押す」「音色を変える」等です。
 最上位ビットは 1 という決まりなので、8 〜 15 までの値を指定する事ができます。
チャネル番号には操作対象のチャネル番号を指定します。
 0 〜 15 まで指定できるので、最大16音を同時に鳴らす事ができます。
 これは全てのショートメッセージで共通です。
データバイト1、データバイト2は副メッセージです。
 メッセージ値によって意味が異なります。
 最上位ビットは 0 という決まりなので、0 〜 127 までの値を指定する事ができます。

上表はショートメッセージの構成です。
ロングメッセージの場合は構成が異なり、データ長も可変です。
ロングメッセージにはメッセージ値 0xF が割り当てられています。

最上位ビットが決められているのは、可変長のデータに対して
ステータスバイトとデータバイトを区別できるようにする為です。

ショートメッセージを midiOutShortMsg 関数に渡す時はDWORD型にパックしますが、一つ問題があります。
困った事にMIDIはビックエンディアンなのです。
従って、上表の形式で入力したい場合は次のようなマクロを用意します。

#define MIDIMSG(status,channel,data1,data2) ( (DWORD)((status<<4) | channel | (data1<<8) | (data2<<16)) )

■基本的なショートメッセージ

メッセージ値:0x9 …… 鍵盤を押す

データバイト1は「音階」、データバイト2は「鍵盤を押す強さ」です。
データバイト2に 0 を指定すると「鍵盤を離す」事に相当します。
鍵盤を離しただけなので、直ぐに音が消えるとは限りません。

音階は専門用語で「ノート」、鍵盤を押す強さは「ベロシティ」と言います。

0x9 は鍵盤を押し続ける事を意味します。
ピアノなら次第に聞こえなくなるでしょうが、笛なら永遠に音が鳴り続けます。
従って、いつかは必ず鍵盤を離す必要があります。
もし鍵盤を離す前に再び 0x9 を送信した場合、
同じ音色で違う音階なら合成した音が、違う音色なら以前の音を消して新しい音が出ます。

音階ですが、中央のドが 0x3C で、中央のド#が 0x3D です。

メッセージ値:0xC …… 音色を変える

データバイト1は「新しい音色」、データバイト2は使いません。

音色は専門用語で「プログラム」と言います。

0 番がピアノです。
他にも色々あり、なかには変な音もあります。

122 …… さざ波
123 …… 小鳥のさえずり
124 …… 目覚まし時計(黒電話)
125 …… ヘリコプター
126 …… 歓声
127 …… 銃声

■midiOutReset関数

midiOutReset 関数は全てのチャネルに「鍵盤を離す」メッセージを送信します。

■サンプルプログラム

サンプルでは 0 チャンネルだけ使っています。
チャンネルを切り替えられるようにすれば、複数の音を同時に出す事もできます。
そしてもっと頑張って、タイムスケジュールに従って音を出せば、音楽になります。

// winmm.lib をリンクする
#pragma comment(lib,"winmm")

#define MIDIMSG(status,channel,data1,data2) ( (DWORD)((status<<4) | channel | (data1<<8) | (data2<<16)) )

LRESULT CALLBACK WindowProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
    static HMIDIOUT hMidiOut;
    static BYTE note=0x3C,velocity=0x40;
    static BYTE program=0;

    HDC hdc;
    PAINTSTRUCT ps;
    char str[64];

    switch(uMsg){
        case WM_CREATE:
            midiOutOpen(&hMidiOut,MIDIMAPPER,NULL,0,CALLBACK_NULL);
            return 0;
        case WM_LBUTTONDOWN:    //鍵盤を押す
            midiOutShortMsg(hMidiOut,MIDIMSG(0x9,0x0,note,velocity));
            return 0;
        case WM_RBUTTONDOWN:    //鍵盤を離す
            midiOutShortMsg(hMidiOut,MIDIMSG(0x9,0x0,note,0));
            return 0;
        case WM_KEYDOWN:
            switch(wParam){
                case 'A':        //音色変更
                    if(program>0) program--;
                    midiOutShortMsg(hMidiOut,MIDIMSG(0xC,0,program,0));
                    break;
                case 'F':        //音色変更
                    if(program<0x7F) program++;
                    midiOutShortMsg(hMidiOut,MIDIMSG(0xC,0,program,0));
                    break;
                case VK_LEFT:    //音階変更
                    if(note>0) note--;
                    break;
                case VK_RIGHT:   //音階変更
                    if(note<0x7F) note++;
                    break;
                case VK_DOWN:    //打鍵強度変更
                    if(velocity>0) velocity--;
                    break;
                case VK_UP:      //打鍵強度変更
                    if(velocity<0x7F) velocity++;
                    break;
            }
            InvalidateRect(hWnd,NULL,TRUE);
            return 0;
        case WM_PAINT:
            hdc=BeginPaint(hWnd,&ps);
            wsprintf(str,"音色 = 0x%02X , 音階 = 0x%02X , 打鍵強度 = 0x%02X",program,note,velocity);
            TextOut(hdc,0,0,str,lstrlen(str));
            EndPaint(hWnd,&ps);
            return 0;
        case WM_DESTROY:
            midiOutReset(hMidiOut);
            midiOutClose(hMidiOut);
            PostQuitMessage(0);
            return 0;
    }
    return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

★☆ ソースファイル表示 ☆★


戻る / ホーム