002-01  前へ←  ホームへ  →次へ  003-01

No.002 - 02 MIDI再生のソース


MIDI再生
のサンプルソースって意外と落ちてないね。
以下ソース、No 001のリスト構造使用。
再生はフォーマットタイプ0のみ対応。(ていうかフォーマット0以外見たことナイ)

midi_player.h
#include <windows.h>
#include <math.h>
#include "qlist.h"

#define MIDIMSG(stat , data1 , data2) (DWORD)(stat | (data1 << 8) | (data2 << 16))

dialog(char* s){ MessageBox(NULL, s, "エラー", MB_OK); }

// メモリの並びを逆にする
mem_reverse(void *p, int size){
        char tmp, *head_p, *tail_p;
        int i, len=size/2;
        head_p=(char*)p;
        tail_p=((char*)p)-1+size;
        for(i=0;i<len;i++){
                tmp=*head_p;
                *head_p=*tail_p;
                *tail_p=tmp;
                head_p++; tail_p--;
        }
}

//==========================================
//      MIDI_MSG                                                                
//==========================================
class MIDI_MSG{
        public :
        int m_delta_time;
        unsigned char m_code, m_scale, m_vel;
        char *m_ext;
        int m_ext_len;
        MIDI_MSG(){
                m_ext=NULL;
                m_code=m_scale=m_vel=0;
                m_delta_time=0;
        }
        ~MIDI_MSG(){
                delete[] m_ext;
        }
        reset(){
                delete[] m_ext;
                m_ext=NULL;
        }
};
//==========================================
//      MIDI_TRUCK                                                              
//==========================================
class MIDI_TRUCK{
        public :
        int m_data_len, m_play_index, m_last_play_index;
        QLIST<MIDI_MSG> m_msgs;
};

//==========================================
//      MIDI_PLAYER クラス                                              
//==========================================
#define BASE_TIMER_DIFF 5
#define minmax(a,x,b) (min (max (x, a), b))
class MIDI_PLAYER {
        public :
        int m_tempo;    // テンポ(μsec)
        short m_file_format_type, m_truck_chunck_len, m_time_base;
        char m_division[2];             // 分解能
        char m_rhythm[2];               // [0]/[1] 拍子
        char m_clef[2];                 /* 調号 [sf:mi]シャープまたはフラット記号の数を表すsf、メジャー/マイナーを示すmi。
                                                        sfはフラットの数を表すときはマイナス数値になる。また、miはメジャーのとき0、マイナのとき1になる。*/

        PLIST<MIDI_TRUCK> m_trucks;     // トラック本体
        HMIDIOUT m_hmidi;               // MIDIデバイス・ハンドル
        UINT m_time_diff;               // プロシージャ呼び出し時間誤差
        int m_timer_id;                 // タイマーID

        play_music_start();             // 音楽再生
        play_music_proc();              // 再生プロシージャ
        play_music_end();               // 再生停止
        delta_to_ms(int delta);         // デルタタイム→ミリ秒
        static void CALLBACK midi_timer_proc (UINT id, UINT msg, DWORD user, DWORD dw1, DWORD dw2);

        read_file(char *name);          // ファイル読込
        read_data(FILE *fp);
        read_num(int *num, FILE **p_fp);                // 数値取得(ファイル読込版)
        get_msg_size(int ti){ return (m_trucks[ti])? m_trucks[ti]->m_msgs.size() : 0; }
        get_step_size(int ti);
        MIDI_PLAYER();          // コンストラクタ
        ~MIDI_PLAYER();         // デストラクタ

        get_format_type(){      return m_file_format_type;      }       // フォーマット・タイプ返値
        get_tempo(){ return m_tempo; }
        reset();
};
// static プロシージャ
XLIST<MIDI_PLAYER*> g_midi_player_list;
void CALLBACK MIDI_PLAYER::midi_timer_proc (UINT id, UINT msg, DWORD user_data, DWORD dw1, DWORD dw2){
        int i, len=g_midi_player_list.size();
        for(i=0;i<len;i++){
                if (g_midi_player_list[i]==(MIDI_PLAYER*)user_data) g_midi_player_list[i]->play_music_proc();
                return;
        }
        return;
}
// コンストラクタ
MIDI_PLAYER::MIDI_PLAYER(){
        m_file_format_type = m_truck_chunck_len =0;
        m_time_base = 480;
        m_tempo=500000; // テンポ120=500000μsec
        m_division[0] = m_division[1] =0;
        m_rhythm[0] = m_rhythm[1] =4;
        m_clef[0] = m_clef[1] =0;

        g_midi_player_list.push(this);
        m_timer_id=0;
        m_hmidi=NULL;
        m_time_diff=BASE_TIMER_DIFF;
}
// デストラクタ
MIDI_PLAYER::~MIDI_PLAYER(){
        int i, len=g_midi_player_list.size();
        for(int i=0;i<len;i++){
                if (this==g_midi_player_list[i]) {
                        g_midi_player_list.pop(i);
                        break;
                }
        }
}

// ステップ・サイズ取得
MIDI_PLAYER::get_step_size(int ti){
        MIDI_TRUCK *ptruck;
        ptruck=m_trucks[ti];
        if (!ptruck) return -1;
        int i, len=get_msg_size(ti), step_len=0;
        for(i=0;i<len;i++) if (ptruck->m_msgs[i]->m_delta_time) step_len++;
        return step_len;
}
// 数値読込(MIDI特化)
MIDI_PLAYER::read_num(int *num, FILE **p_fp){           // 数値取得(ファイル読込版)
        int i, n;
        BYTE c;
        *num=0;
        for(i=0;i<4;i++){
                if (fread(&c, 1, 1, *p_fp)!=1) return -1;
                n=((*num)<<7) | (c & 0x7f);
                *num=n;
                if (!(c & 0x80)) return i+1;
        }
}
// デルタタイム→ミリ秒
MIDI_PLAYER::delta_to_ms(int delta){
        return (m_division[0]>=0)?
                        delta*m_tempo/(1000*m_time_base)                                // ディヴィジョン=4分音符
                :       delta * 1000/(-m_division[0] * m_division[1]);  // ディヴィジョン=分解能
}

// ファイル読込
MIDI_PLAYER::read_file(char *name){
        int res;
        FILE *fp=fopen(name, "rb");
        if (!fp) return -1;
        res=read_data(fp);      // midi ファイル読込
        fclose(fp);
        return res;
}

// データ消去
MIDI_PLAYER::reset(){
        int i, mi, mlen;
        for(i=0;i<m_truck_chunck_len;i++){
                mlen=m_trucks[i]->m_msgs.size();
                for(mi=0;mi<mlen;mi++) m_trucks[i]->m_msgs[mi]->reset();
                Sleep(0);
                m_trucks[i]->m_msgs.del_all();
        }
        m_trucks.del_all();
        Sleep(0);       // メモリの大量解放時はタスクをOSにまわす
        return 0;
}
// midi ファイル読込
MIDI_PLAYER::read_data(FILE *fp){
        BYTE rs[5];
        MIDI_TRUCK *ptruck;
        MIDI_MSG *pmsg;
        int i, mi, delta, res;
        reset();
        fread(rs, 1, 4, fp);
        if (strncmp(rs, "MThd", 4)) return dialog("MIDIファイルではありません");

        // データ長     常にフォーマット、トラック数、時間単位のデータ長の合計6
        fread(&i, 1, 4, fp);            mem_reverse(&i, 4);
        if (i!=6) return dialog("このMIDIファイルは壊れています。 ");
        fread(&m_file_format_type, 1, 2, fp);   mem_reverse(&m_file_format_type, 2);
        fread(&m_truck_chunck_len, 1, 2, fp);   mem_reverse(&m_truck_chunck_len, 2);
        fread( m_division,         1, 2, fp);
        memcpy(&m_time_base, m_division, 2);    mem_reverse(&m_time_base, 2);

        for(i=0;i<m_truck_chunck_len;i++){
                if (fread(rs, 1, 4, fp)!=4) return -1;
                if (strncmp(rs, "MTrk", 4)) return dialog("このMIDIファイルは壊れています。");
                ptruck = m_trucks.a_push();
                if (fread(&(ptruck->m_data_len), 1, 4, fp)!=4) return -1;
                mem_reverse(&(ptruck->m_data_len), 4);
                mi=ptruck->m_data_len-4;
                while(mi>0){
                        pmsg=ptruck->m_msgs.a_push();
                        res=read_num(&pmsg->m_delta_time, &fp);
                        if (res<0) return 0;
                        mi-=res;
                        if (fread(&pmsg->m_code, 1, 1, fp)!=1) return -1;       mi--;
                        switch(pmsg->m_code & 0xF0){
                        case 0x90 :
                        case 0xA0 :
                        case 0xB0 :
                        case 0xE0 :
                                if (fread(&pmsg->m_scale, 1, 1, fp)!=1) return -1; mi--;
                                if (fread(&pmsg->m_vel,   1, 1, fp)!=1) return -1; mi--;
                                break;
                        case 0x80 :
                        case 0xC0 :
                        case 0xD0 :
                                if (fread(&pmsg->m_scale, 1, 1, fp)!=1) return -1; mi--;
                                break;
                        case 0xF0 :
                                switch(pmsg->m_code){
                                case 0xF0 :
                                        res=read_num(&pmsg->m_ext_len, &fp);
                                        if (res<0) return -1;
                                        mi-=res;
                                        pmsg->m_ext= new char[pmsg->m_ext_len+1];
                                        if (fread(pmsg->m_ext, 1, pmsg->m_ext_len, fp)!=pmsg->m_ext_len) return -1;
                                        mi-=pmsg->m_ext_len;
                                        break;
                                case 0xFF :     // メタ・イベント
                                        if (fread(&pmsg->m_scale, 1, 1, fp)!=1) return -1; mi--;
                                        res=read_num(&pmsg->m_ext_len, &fp);
                                        if (res<0) return -1;
                                        mi-=res;
                                        pmsg->m_ext= new char[pmsg->m_ext_len+1];
                                        if (fread(pmsg->m_ext, 1, pmsg->m_ext_len, fp)!=pmsg->m_ext_len) return -1;
                                        mi-=pmsg->m_ext_len;
                                        break;
                                }
                                break;
                        default:return -1;
                        }
                        if (mi<=0) break;
                }
                fread(rs, 1, 4, fp);
                if (rs[0]!=0x0 || rs[1]!=0xff || rs[2]!=0x2f || rs[3]!=0x00) {
                        dialog("トラック終端の異常");
                        return 0;
                }
        }
        return 0;
}
// 音楽再生の開始
MIDI_PLAYER::play_music_start(){
        TIMECAPS tc ;
        timeGetDevCaps (&tc, sizeof (TIMECAPS)) ;
        m_time_diff = minmax ((int)tc.wPeriodMin, (int)BASE_TIMER_DIFF, (int)tc.wPeriodMax) ;
        timeBeginPeriod (m_time_diff) ;

        midiOutOpen(&m_hmidi , MIDIMAPPER , 0 , 0 , 0);
        midiOutReset(m_hmidi);  // 出てる音がないように

        m_timer_id = timeSetEvent(max ((int)m_time_diff, 100), m_time_diff, midi_timer_proc, (UINT)this, TIME_ONESHOT) ;
        if (m_timer_id == 0) {
                timeEndPeriod (m_time_diff) ;
                midiOutClose (m_hmidi) ;
                return FALSE ;
         }

        int ti;
        for(ti=0;ti<m_truck_chunck_len;ti++){
                m_trucks[ti]->m_play_index=m_trucks[ti]->m_last_play_index=0;
        }
        return 0;
}

// 再生停止
MIDI_PLAYER::play_music_end(){
        // stop the timer
        if (m_timer_id) timeKillEvent (m_timer_id) ;
        timeEndPeriod (m_time_diff);

        midiOutReset(m_hmidi);
        midiOutClose(m_hmidi);
        m_hmidi=NULL;
        return 0;
}
// 音楽再生プロシ−ジャ
MIDI_PLAYER::play_music_proc(){
        int ti, mi;
        MIDI_MSG *pmsg;
        for(ti=0;ti<m_truck_chunck_len;ti++){
                for(mi=0;;mi++){
                        pmsg = m_trucks[ti]->m_msgs[m_trucks[ti]->m_play_index];
                        if (!pmsg) { play_music_end(); return 0; }
                        if (pmsg->m_delta_time>0 && m_trucks[ti]->m_last_play_index!=m_trucks[ti]->m_play_index) {
                                m_timer_id = timeSetEvent(max((int)m_time_diff, delta_to_ms(pmsg->m_delta_time)),
                                                                        m_time_diff, midi_timer_proc, (UINT)this, TIME_ONESHOT) ;
                                m_trucks[ti]->m_last_play_index=m_trucks[ti]->m_play_index;
                                break;
                        }
                        m_trucks[ti]->m_play_index++;   // インデックスを進める
                        switch(pmsg->m_code){
                        case 0xf0:
                                break;
                        case 0xf7:
                                switch(pmsg->m_scale){
                                case 0x51:
                                        m_tempo=0; memcpy(&m_tempo, pmsg->m_ext, 3); mem_reverse(&m_tempo, 4);
                                        break;
                                case 0x58:      /* 拍子 [nn:dd:cc:bb]拍子記号の分子nn、2のdd乗で表される分母(ddが2の場合4、3の場合8)、
                                                                メトロノーム1カウントあたりのMIDIクロック数cc、4分音符中の32分音符数bb。*/
                                        m_rhythm[0]=pmsg->m_ext[0]; m_rhythm[1]=(char)pow(2, pmsg->m_ext[1]);
                                        break;
                                case 0x59:      /* 調号 [sf:mi]シャープまたはフラット記号の数を表すsf、メジャー/マイナーを示すmi。
                                                        sfはフラットの数を表すときはマイナス数値になる。また、miはメジャーのとき0、マイナのとき1になる。*/
                                        m_clef[0]=pmsg->m_ext[0]; m_clef[1]=pmsg->m_ext[1];
                                        break;
                                }
                                break;
                        default :
                                midiOutShortMsg(m_hmidi , MIDIMSG(pmsg->m_code , pmsg->m_scale , pmsg->m_vel));
                        }
                }
        }
        return 0;
}


         

●リファレンス

●高水準関数

read_file(char *name);
ファイル名からMIDIデータを読み込みます。
play_music_start();
その時点で読み込んであるMIDIデータを再生します。
play_music_end();
その時点で再生しているMIDIデータを停止します。


●内部関数

read_file(FILE *fp);
ファイルポインタからMIDIデータを読み込みます。
ユーザはこの関数を意識する必要はありません。
read_num(int *num, FILE **p_fp);
デルタタイムなどの特殊な数値形式を読み込みます。
ユーザはこの関数を意識する必要はありません。
play_music_proc();
MIDI再生時にタイマで呼ばれる再生プロシージャです。
ユーザはこの関数を意識する必要はありません。
static void CALLBACK midi_timer_proc (
        UINT id, UINT msg, DWORD user, DWORD dw1, DWORD dw2);
MIDI再生時にタイマで呼ばれる再生プロシージャです。
APIに渡すコールバック関数はグローバルである必要があるため、一度この関数が呼ばれます。
そしてココからメンバ関数版の再生プロシージャ呼びます。
ただしユーザはこの関数を意識する必要はありません。
delta_to_ms(int delta);
デルタタイムをミリ秒に変換します。
ユーザはこの関数を意識する必要はありません。
get_msg_size(int ti);
トラック[ti] のメッセージ数を取得します。
ユーザはこの関数を意識する必要はありません。
get_step_size(int ti);
トラック[ti] のステップ数を取得します。
ユーザはこの関数を意識する必要はありません。
get_format_type();
現在のMIDIデータのフォーマットタイプを取得します。
get_tempo();
テンポをマイクロ秒値で取得します。

 midi_player.cpp (サンプル)
#include <windows.h>
#include "midi_player.h"

#define IDM_FILE_OPEN           0x100
#define IDM_PLAY_MUSIC          0x101
#define IDM_STOP_MUSIC          0x102

// ファイル開く
open_file(HWND hwnd, char *file_name, MIDI_PLAYER *p_midi_player){
        OPENFILENAME ofn;
        memset(&ofn, 0, sizeof(OPENFILENAME));
        ofn.lStructSize = sizeof (OPENFILENAME);
        ofn.hwndOwner = hwnd;
        ofn.lpstrFilter =       TEXT("midi ファイル\0*.mid\0")
                                TEXT("すべて表示 \0*.*\0\0");
        ofn.lpstrCustomFilter = NULL;
        ofn.nMaxCustFilter = 0;
        ofn.nFilterIndex = 0;
        ofn.lpstrFile = file_name;
        ofn.nMaxFile = MAX_PATH;
        ofn.Flags = OFN_FILEMUSTEXIST;
        if (!GetOpenFileName(&ofn)) return -1;

        int res=p_midi_player->read_file(file_name);
        InvalidateRect(hwnd, NULL, 1);
        if (res<0) dialog(TEXT("midi 読込失敗"));
        return 0;
};

LRESULT CALLBACK WndProc(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {
        static MIDI_PLAYER midi_player;
        static TCHAR file_name[MAX_PATH]="何かファイルを選択して下さい";
        HDC hdc;
        PAINTSTRUCT ps;

        switch (msg) {
        case WM_DESTROY:
                PostQuitMessage(0);
                break;
        case WM_PAINT:
                hdc = BeginPaint(hwnd , &ps);
                TextOut(hdc, 20, 50, file_name, lstrlen(file_name));
                EndPaint(hwnd , &ps);
                break;
        case WM_COMMAND:
                switch(LOWORD(wp)){
                case IDM_FILE_OPEN :    open_file(hwnd, file_name, &midi_player);       break;
                case IDM_PLAY_MUSIC :   midi_player.play_music_start(); break;
                case IDM_STOP_MUSIC :   midi_player.play_music_end();           break;
                }
                break;
        }
        return DefWindowProc(hwnd , msg , wp , lp);
}

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance , PSTR lpCmdLine , int nCmdShow ) {
        HWND hwnd;
        MSG msg;
        WNDCLASS winc;

        winc.style              = CS_HREDRAW | CS_VREDRAW;
        winc.lpfnWndProc        = WndProc;
        winc.cbClsExtra = winc.cbWndExtra       = 0;
        winc.hInstance          = hInstance;
        winc.hIcon              = LoadIcon(NULL , IDI_APPLICATION);
        winc.hCursor            = LoadCursor(NULL , IDC_ARROW);
        winc.hbrBackground      = (HBRUSH)GetStockObject(WHITE_BRUSH);
        winc.lpszMenuName       = NULL;
        winc.lpszClassName      = TEXT("MIDI_PLAY");

        if (!RegisterClass(&winc)) return -1;

        hwnd = CreateWindow(
                TEXT("MIDI_PLAY") , TEXT("MIDIプレイヤー") , WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
                CW_USEDEFAULT, CW_USEDEFAULT, 480, 320, NULL, NULL, hInstance , NULL
        );

        CreateWindow(
                        TEXT("BUTTON"), TEXT("ファイル選択"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
                        10, 10, 100, 30, hwnd, (HMENU)IDM_FILE_OPEN, hInstance, NULL
        );
        CreateWindow(
                        TEXT("BUTTON"), TEXT("再生"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
                        10, 80, 100, 30, hwnd, (HMENU)IDM_PLAY_MUSIC, hInstance, NULL
        );
        CreateWindow(
                        TEXT("BUTTON"), TEXT("停止"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
                        10, 120, 100, 30, hwnd, (HMENU)IDM_STOP_MUSIC, hInstance, NULL
        );

        if (hwnd == NULL) return -1;

        while (GetMessage(&msg, NULL, 0, 0)) {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
        }
        return msg.wParam;
}

上記ソースはファイル選択ボタン、再生ボタン、停止ボタンが表示され
最初にファイルを選択し、それから再生ボタン押すと再生、停止ボタンで停止といったシンプルなつくりです。

上記ソースは全て自由に改編して使ってかまいませんが必ず自己責任でね。 

2004.2.14 G丸
 002-01  前へ←  ホームへ  →次へ  003-01