スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
以後の更新内容の改善のために、是非ともご評価のほどよろしくお願いします!→

【C言語】続・MIDI(SMF)に関する統計処理(平均値・分散値・標準偏差値の算出)

ここ数回、MIDIの各種データに対して統計処理をおこなってきました。
今回もその続編です。

変更点は...
・(メタイベントのテンポ設定に指定されている)Tempo表示を追加する
・カレントディレクトリ内のMIDIファイル(拡張子「.mid」のもの)を全て解析する
...です。

(もう決まり文句ですが...) この記事に掲載するプログラムは次の記事を継承したものですので、必要であれば合わせて御覧ください。
・「C言語でMIDI(SMF)データを読んでみる!
・「C言語でMIDI(SMF)データのNoteOn/NoteOffだけ表示する!
・「【C言語】MIDI(SMF)のNoteOn-NoteOffの時間差に関する統計処理(平均値・分散値・標準偏差値の算出)
・「【C言語】MIDI(SMF)のノートの音程・ベロシティーに関する統計処理(平均値・分散値・標準偏差値の算出)

それから、このプログラムに対するテストは明らかに不十分です。
参考になさる場合は十分にご注意ください。



さて、Tempoの表示に関しては、ちょちょちょとprintfを書き足せば何とかなります。
この記事におけるTempoの処理部分を持ってくるだけです。)
MIDIファイルによっては(途中でテンポが変わるなどして)複数のTempo指定がおこなわれます。
(このプログラムでは、見つけたテンポ設定値を全て表示します。)

それから、解析するファイルの名前は、コマンドライン引数として指定するようにしました。
(この引数は省略できません)
とりあえずはそのプログラムを掲載します。
(これだけでは、"同一ディレクトリ内のMIDIファイルを全て解析"はしてくれません。)



//
//  Reading MIDI(SMF) Data
//                2010.08.07 - 2010.08.16
//        NoteOn/Off Version : 2010.12.09
//            平均値・分散値 : 2011.01.02
//  平均値・分散値【修正版】 : 2011.01.03
//      テンポ表示・引数指定 : 2011.01.05

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

//====== 設定項目 ======
#define DISP_TRACK   0   // トラック番号を表示するか否か(0:非表示、1:表示)
#define DISP_NOTEON  0   //  NoteOnを表示するか否か(0:非表示、1:表示)
#define DISP_NOTEOFF 0   // NoteOffを表示するか否か(0:非表示、1:表示)

#define CHECK_VELO_ZERO 1   // NoteOnのベロシティーが0の時に、それを計算に含めるかどうか
                            // (0:含めない、1:含める)

//====== 以降はプログラム ======
#define  ON 1   // NoteOn状態
#define OFF 0   // NoteOff状態

typedef struct st_noteonlist {
    // ノートオン情報のリスト
    int note;   // ノート番号
    int velo;   // ベロシティー
    struct st_noteonlist *next;   // 次のリストを指す
} NoteOnList;

typedef struct st_notelist {
    // ノート情報のリスト
    int tick;       // 発音の長さ
    struct st_notelist *next; // 次のリストを指す
} NoteList;

typedef struct {
    // ノートの状態を格納する構造体
    int state;   // 状態(ON/OFF)
    int time;   // NoteOnになったときの累計デルタタイム
} NoteInfo;

typedef struct {
    // トラックチャンクのデータを格納する構造体
    char type[4]; // チャンクタイプを示す文字列を格納。「MTrk」が入るはず。[4byte]
    int size;     // トラックチャンクデータのサイズ [4byte]
    char *data;   // トラックデータ(イベントの羅列)へのポインタ
} TrackChunk;

short mergeChar7bit(char x, char y){
    // charの下7bitずつを結合してshort型で出力
    // 【引数】:結合対象となる2つのchar型変数x, y
    short s;
    s = (unsigned char)x; // 上位バイトを先に入れておく
    s <<= 7;              // 7bit左シフト
    s = (s | (unsigned char)(y & 0x7f));   // 下位バイトの下7bitを合成。。。
    return s;
}

int convertEndian(void *input, size_t s){
    // エンディアン変換をおこなう関数
    // stdlib.hをインクルードしてください。
    // 【引数】: void *input...エンディアン変換対象へのポインタ
    // 【引数】: size_t    s...変換対象のバイト数

    int i;   // カウンタ
    char *temp;   // 変換時に用いる一時的配列

    if((temp = (char *)calloc(s, sizeof(char))) == NULL){
        perror("Error: Cannot get memory for temp.");
        return 0;   // 領域確保できず(失敗)
    }

    for(i=0; i<s; i++){   // inputデータをtempに一時保管
        temp[i] = ((char *)input)[i];
    }

    for(i=1; i<=s; i++){   // tempデータを逆方向にしてinputへ代入
        ((char *)input)[i-1] = temp[s-i];
    }

    free(temp);   // 確保した領域を解放

    return 1;   // 正常終了
}

int main(int argc, char *argv[]){
    int i, j, k, cnt;   // カウンタ
    FILE *fp;         // ファイルポインタ生成
    int endian; //   エンディアン判定にいろいろ使用(0:BigEndian, 1:LittleEndian)

    // 音階・ベロシティーに関する平均値・分散値を求めるための変数
    double sum_on_note = 0.0;   // NoteOnのノート番号の総和
    double sum_on_velo = 0.0;   // NoteOnの累計ベロシティー
    unsigned int cnt_on = 0;    // ノートオンの回数
    NoteOnList *on_list_top = NULL; // 先頭のリストを指すポインタ
    NoteOnList *on_list_pt;     // リスト作成などに使う一時的な変数
    NoteOnList *on_list_temp;   // リスト操作に使う一時的な変数
    double note_average  = 0;   // 音階の平均値
    double note_variance = 0;   // 音階の分散値
    double velo_average  = 0;   // ベロシティーの平均値
    double velo_variance = 0;   // ベロシティーの分散値

    // NoteOn-NoteOff間の時間差に関する平均値・分散値を求めるための変数
    unsigned int note_num = 0;   // 処理中のノート番号
    unsigned int velo_num = 0;   // 処理中のノートのベロシティー
    NoteInfo note[128];          // ノート状態(On/Off)を格納する配列
    unsigned int sum_length = 0; // ノートオンの累計時間(累計発音時間)
                                 //    (分散値算出にもこの変数を利用)
    unsigned int cnt_length = 0; // ノートオン(発音)の回数カウンタ
    NoteList *list_top = NULL;   // 先頭のリストを指すポインタ
    NoteList *list_pt;           // リスト作成などに使う一時的な変数
    double average;              // 平均値
    double variance;             // 分散値

    // ヘッダチャンク情報
    char  header_chunk_type[4]; // チャンクタイプを示す文字列を格納。「MThd」が入るはず。[4byte]
    int   header_chunk_size;    // ヘッダチャンクデータのサイズ [4byte]
    short smf_format;     // SMFのフォーマットタイプ(0か1か2) [2byte]
    short tracks;         // トラックチャンク総数 [2byte]
    short division;       // 四分音符あたりの分解能(ここではデルタタイム) [2byte]

    // トラックチャンク情報
    TrackChunk *track_chunks;   // トラックチャンク情報を格納する配列のためのポインタ
    unsigned char c;   // イベント解析の際に使用する一時保存用変数
    unsigned char status;   // ステータスバイト用の一時変数
    unsigned int delta;   // デルタタイム
    unsigned int sum_delta;   // 累計時間

    // ファイル名指定(コマンドライン引数として指定)
    if(argc == 2){
        // ファイル名が指定されたものとして以降を処理(ファイル名はargv[1]に格納)
    } else {
        printf("Error: Bad argument!
");
        return 0;
    }

    // 配列初期化
    for(i=0; i<128; i++){
        note[i].state = OFF;   // 最初は全てノートオフ状態
    }

    // エンディアン判定
    endian = 1;
    if(*(char *)&endian){   // リトルエンディアンなら...
        endian = 1;   // Little Endian
    } else {   // ビッグエンディアンなら...
        endian = 0;   // Big Endian
    }

    // MIDIファイルを開く
    if((fp = fopen(argv[1], "rb")) == NULL){   // バイナリ読み取りモードでファイルを開く
        perror("Error: Cannot open the file.
");   // 失敗したらエラーを吐く
        return 0;
    } else {
        printf("## Analyse...[%s]
", argv[1]);
    }

    // ヘッダチャンク取得
    fread(header_chunk_type, 1, 4, fp);   // チャンクタイプ
    if(endian){   // リトルエンディアンならビッグエンディアンに変換
        convertEndian(&header_chunk_size, sizeof(header_chunk_size));
    }
    if(header_chunk_type[0] != 'M' || header_chunk_type[1] != 'T' || header_chunk_type[2] != 'h' || header_chunk_type[3] != 'd'){
        perror("Error: This is not MIDI file.");
        return 0;
    }
    fread(&header_chunk_size, 4, 1, fp);  // チャンクデータサイズ
    fread(&smf_format, 2, 1, fp);   // SMFフォーマットタイプ
    fread(&tracks, 2, 1, fp);       // トラックチャンク総数
    fread(&division, 2, 1, fp);     // 分解能(デルタタイム)

    if(endian){   // リトルエンディアンならビッグエンディアンに変換
        convertEndian(&tracks, sizeof(tracks));
    }

    // トラックチャンク取得
    if((track_chunks = (TrackChunk *)calloc(tracks, sizeof(TrackChunk))) == NULL){   // トラック数に応じて領域確保
        perror("Error: Cannot get memory for track_chunks.");
        return 0;   // 領域確保できず(失敗)
    }
    for(i=0; i<tracks; i++){   // トラック数だけ繰返し
        fread(track_chunks[i].type, 1, 4, fp);   // チャンクタイプ
        fread(&track_chunks[i].size, 4, 1, fp);   // チャンクデータサイズ
        if(endian){   // リトルエンディアンなら要変換
            convertEndian(&track_chunks[i].size, sizeof(track_chunks[i].size));
        }
        if((track_chunks[i].data = (char *)calloc(track_chunks[i].size, sizeof(char))) == NULL){   // データサイズに応じて領域確保
            perror("Error: Cannot get memory for track_chunks[i].data .");
            return 0;   // 領域確保できず(失敗)
        }
        fread(track_chunks[i].data, track_chunks[i].size, sizeof(char), fp);   // データ(イベントの羅列)
    }

    // 読み取ったトラックチャンク情報を出力
    for(i=0; i<tracks; i++){   // トラック数だけ繰返し
        sum_delta = 0;   // トラックごとに累計時間を算出
        if(DISP_TRACK) printf("# Track[%02d] =====================
", i+1);
        for(j=0; j<track_chunks[i].size; j++){   // データ分だけ繰返し

            delta = 0;   // 初期化
            while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){
                delta = delta | (c & 0x7F);   // 合成
                delta <<= 7;   // 7bit左シフト
            }
            delta = delta | c;    // 合成
            sum_delta += delta;   // 累計時間に加算

            // ランニングステータスルールに対する処理
            if((track_chunks[i].data[j] & 0x80) == 0x80){
                // ランニングステータスルールが適用されない場合は、ステータスバイト用変数を更新。
                status = (unsigned char)track_chunks[i].data[j];   // ステータスバイトを保持しておく
            } else {
                //printf("@");   // ランニングステータスルール適用のしるし...は出さない
                j--;   // データバイトの直前のバイト(デルタタイムかな?)を指すようにしておく。
                       // 次の処理でj++するはずなので、そうすればデータバイトにアクセスできる。
            }

            // データ判別
            if((status & 0xf0) == 0x80){
                // ノート・オフ【ボイスメッセージ】
                j++;
                note_num = (unsigned char)track_chunks[i].data[j++];  // ノート番号取得
                velo_num = (unsigned char)track_chunks[i].data[j];    // ベロシティー取得
                if(DISP_NOTEOFF){
                    printf("%7d [+%4d]:: ", sum_delta, delta);   // デルタタイム出力
                    printf("Note Off [%02dch] : ", (status & 0x0f));
                    printf("Note%d", note_num);
                    printf("[0x%02x] ", note_num);
                    printf("Velocity=%d
", velo_num);
                }
                if(note[note_num].state == ON){   // 状態がONなら処理する
                    list_pt = (NoteList *)calloc(1, sizeof(NoteList)); // リスト要素の領域確保
                    list_pt->next = list_top;                       // 直前まで"先頭要素"だったものを"次の要素"にする
                    list_top = list_pt;                             // 今作った要素を先頭要素とする
                    list_pt->tick = sum_delta - note[note_num].time;   // 発音時間を計算
                    sum_length += list_pt->tick;   // 累計発音時間を更新
                    cnt_length++;
                }
                note[note_num].state = OFF;   // ノートの状態をOFFに更新
            } else if((status & 0xf0) == 0x90){
                // ノート・オン【ボイスメッセージ】
                j++;
                note_num = (unsigned char)track_chunks[i].data[j++];  // ノート番号取得
                velo_num = (unsigned char)track_chunks[i].data[j];    // ベロシティー取得
                if(DISP_NOTEON){
                    printf("%7d [+%4d]:: ", sum_delta, delta);   // デルタタイム出力
                    printf("Note On  [%02dch] : ", (status & 0x0f));
                    printf("Note%d", note_num);
                    printf("[0x%02x] ", note_num);
                    printf("Velocity=%d
", velo_num);
                }
                if(velo_num == 0){   // ベロシティーが0ならノートオフ扱い
                    if(note[note_num].state == ON){   // 状態がONなら処理する
                        list_pt = (NoteList *)calloc(1, sizeof(NoteList)); // リスト要素の領域確保
                        list_pt->next = list_top;                       // 直前まで"先頭要素"だったものを"次の要素"にする
                        list_top = list_pt;                             // 今作った要素を先頭要素とする
                        list_pt->tick = sum_delta - note[note_num].time;   // 発音時間を計算
                        sum_length += list_pt->tick;   // 累計発音時間を更新
                        cnt_length++;
                    }
                    note[note_num].state = OFF;  // ノートの状態をOFFに更新
                } else {
                    note[note_num].state = ON;   // ノートの状態をONに更新
                    note[note_num].time  = sum_delta;   // ONになった時点での累積デルタタイム保管
                }
                if(velo_num!=0 || CHECK_VELO_ZERO){   // ベロシティーが0の時は、事前指定(CHECK_VELO_ZERO)によって処理したりしなかったり
                    on_list_pt = (NoteOnList *)calloc(1, sizeof(NoteOnList));   // リスト要素の領域確保
                    on_list_pt->next = on_list_top;
                    on_list_top = on_list_pt;
                    on_list_pt->note = note_num;   // ノート番号を記録
                    on_list_pt->velo = velo_num;   // ベロシティーを記録
                    sum_on_note += note_num;   // ノート番号の総和を更新
                    sum_on_velo += velo_num;   // 累計ベロシティーを更新
                    cnt_on++;   // NoteOnの回数をカウント
                }
            } else if((status & 0xf0) == 0xa0){   // ポリフォニック・キー・プレッシャー【ボイスメッセージ】
                j += 2;
            } else if((status & 0xf0) == 0xb0){   // コントロールチェンジ【ボイスメッセージ】、【モードメッセージ】
                j += 2;
            } else if((status & 0xf0) == 0xc0){   // プログラム・チェンジ【ボイスメッセージ】
                j += 1;
            } else if((status & 0xf0) == 0xd0){   // チャンネル・プレッシャー【ボイスメッセージ】
                j += 1;
            } else if((status & 0xf0) == 0xe0){   // ピッチ・ベンド・チェンジ【ボイスメッセージ】
                j += 2;
            } else if((status & 0xf0) == 0xf0){
                // 【システム・メッセージ】
                switch(status & 0x0f){
                    case 0x00:   // エクスクルーシブ・メッセージ
                        j++;
                        // SysExのデータ長を取得
                        cnt = 0;   // 初期化
                        while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){   // フラグビットが1の間はループ
                            cnt = cnt | c;   // 合成
                            cnt <<= 7;   // 7bit左シフト
                        }
                        cnt = cnt | c;   // 合成
                        for(k=1; k<=cnt; k++){   // 長さの分だけデータ取得
                            j++;
                        }
                        j--;   // 行き過ぎた分をデクリメント
                        break;
                    case 0x01:   // MIDIタイムコード
                        j += 1;
                        break;
                    case 0x02:   // ソング・ポジション・ポインタ
                        j += 2;
                        break;
                    case 0x03:   // ソング・セレクト
                        j += 1;
                        break;
                    case 0x06:   // チューン・リクエスト
                        break;
                    case 0x07:   // エクスクルーシブ・メッセージ(?)
                        j++;
                        // SysExのデータ長を取得
                        cnt = 0;   // 初期化
                        while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){   // フラグビットが1の間はループ
                            cnt = cnt | c;   // 合成
                            cnt <<= 7;   // 7bit左シフト
                        }
                        cnt = cnt | c;   // 合成
                        for(k=1; k<=cnt; k++){   // 長さの分だけデータ取得
                            j++;
                        }
                        j--;   // 行き過ぎた分をデクリメント
                        break;
                    case 0x08:   // タイミング・クロック
                    case 0x0A:   // スタート
                    case 0x0B:   // コンティニュー
                    case 0x0C:   // ストップ
                    case 0x0E:   // アクティブ・センシング
                        break;
                    //case 0x0F:   // システムリセット(何か間違っている気がする。。。)
                    //    printf("System Reset");
                    case 0x0F:   // メタイベント
                        j++;
                        switch((unsigned char)track_chunks[i].data[j]){
                            case 0x00:   // シーケンス番号
                                j += 3;  // データ長の分を通り越す
                                break;
                            case 0x01:   // テキスト[可変長]
                            case 0x02:   // 著作権表示[可変長]
                            case 0x03:   // シーケンス名(曲タイトル)・トラック名[可変長]
                            case 0x04:   // 楽器名[可変長]
                            case 0x05:   // 歌詞[可変長]
                            case 0x06:   // マーカー[可変長]
                            case 0x07:   // キューポイント[可変長]
                            case 0x08:   // プログラム名(音色名)[可変長]
                            case 0x09:   // デバイス名(音源名)[可変長]
                                j += 1;
                                cnt = 0;
                                while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){
                                    cnt = cnt | (c & 0x7F);   // 合成
                                    cnt <<= 7;   // 7bit左シフト
                                }
                                cnt = cnt | (c & 0x7F);   // 合成
                                for(k=1; k<=cnt; k++){
                                    j++;
                                }
                                j--;   // 行き過ぎた分をデクリメント
                                break;
                            case 0x20:   // MIDIチャンネルプリフィックス[1byte]
                            case 0x21:   // ポート指定[1byte]
                                j += 2;  // データ長の分を通り越す
                                break;
                            case 0x2F:   // トラック終端[0byte]
                                j += 1;  // データ長の分を通り越す
                                break;
                            case 0x51:   // テンポ設定[3byte]
                                printf(" @Temp = ");
                                j += 2;   // データ長の分を通り越す
                                cnt = (unsigned char)track_chunks[i].data[j++];
                                cnt <<= 8;   // 8bit左シフト
                                cnt = cnt | (unsigned char)track_chunks[i].data[j++];
                                cnt <<= 8;   // 8bit左シフト
                                cnt = cnt | (unsigned char)track_chunks[i].data[j];
                                printf("%d
", cnt);
                                break;
                            case 0x54:   // SMPTEオフセット[5byte]
                                j += 6;  // データ長の分を通り越す
                                break;
                            case 0x58:   // 拍子設定[4byte]
                                j += 5;  // データ長の分を通り越す
                                break;
                            case 0x59:   // 調設定[2byte]
                                j += 3;  // データ長の分を通り越す
                                break;
                            case 0x7F:   // シーケンサ特定メタイベント
                                j += 1;
                                cnt = 0;
                                while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){
                                    cnt = cnt | (c & 0x7F);   // 合成
                                    cnt <<= 7;   // 7bit左シフト
                                }
                                cnt = cnt | (c & 0x7F);   // 合成
                                for(k=1; k<=cnt; k++){
                                    j++;
                                }
                                j--;   // 行き過ぎた分をデクリメント
                                break;
                            default :
                                ;
                        }
                        break;
                    default:
                        ;   // 見知らぬステータスなら(知らぬフリ...)
                }
            } else {
                ;   // 見知らぬステータスなら(知らぬフリ...)
            }
        }
    }
    
    printf("[音階]
");
    if(cnt_on){   // 1つでもNoteOnがあれば
        // まずは音階の平均値算出
        note_average = sum_on_note / (double)cnt_on;   // 平均値の計算
        printf(" @平均値  : %f
", note_average);   // 平均値を出力
        // 次に、音階の分散値算出
        sum_on_note = 0;
        on_list_temp = on_list_top;   // 一時的な変数に先頭要素アドレスをコピー
        while(on_list_pt = on_list_temp){   // NULL(末端)になるまでリスト要素にアクセス
            sum_on_note += (on_list_pt->note - note_average) * (on_list_pt->note - note_average);
            on_list_temp = on_list_pt->next;
        }
        note_variance = sum_on_note / (double)cnt_on;   // 分散値の計算
        printf(" @分散値  : %f
", note_variance);   // 分散値を出力
        printf(" @標準偏差 : %f
", sqrt(note_variance));   // 標準偏差を出力
    } else {   // NoteOnが1つもなかったら
        printf(" @NoteOnが存在しませんでした。");
    }

    printf("[ベロシティー]
");
    if(cnt_on){   // 1つでもNoteOnがあれば
        // まずはベロシティーの平均値算出
        velo_average = sum_on_velo / (double)cnt_on;   // 平均値の計算
        printf(" @平均値  : %f
", velo_average);   // 平均値を出力
        // 次に、ベロシティーの分散値算出
        sum_on_velo = 0;
        on_list_temp = on_list_top;   // 一時的な変数に先頭要素アドレスをコピー
        while(on_list_pt = on_list_temp){   // NULL(末端)になるまでリスト要素にアクセス
            sum_on_velo += (on_list_pt->velo - velo_average) * (on_list_pt->velo - velo_average);
            on_list_temp = on_list_pt->next;
        }
        velo_variance = sum_on_velo / (double)cnt_on;   // 分散値の計算
        printf(" @分散値  : %f
", velo_variance);   // 分散値を出力
        printf(" @標準偏差 : %f
", sqrt(velo_variance));   // 標準偏差を出力
    } else {   // NoteOnが1つもなかったら
        printf(" @NoteOnが存在しませんでした。");
    }

    while(on_list_pt = on_list_top){   // 確保していた領域を解放
        on_list_top = on_list_pt->next;
        free(on_list_pt);   // 確保いていた領域を解放
    }

    printf("[NoteOn-NoteOff間の時間差]
");
    if(cnt_length){   // 1つでもNoteOffがあれば
        // NoteOn-NoteOff間の時間差の平均値算出
        average = sum_length / (double)cnt_length;   // 平均値の計算
        printf(" @平均値  : %f
", average);   // 平均値を出力
        // 次に、その分散値を算出
        sum_length = 0;
        while(list_pt = list_top){   // NULL(末端)になるまでリスト要素にアクセス
            sum_length += (list_pt->tick - average) * (list_pt->tick - average);
            //printf("list_pt->tick : %d, average : %d, sum_length : %d
", list_pt->tick, (int)average, sum_length);
            list_top = list_pt->next;
            free(list_pt);   // 確保していた領域を解放
        }
        variance = sum_length / (double)cnt_length;   // 分散値の計算
        printf(" @分散値  : %f
", variance);   // 分散値を出力
        printf(" @標準偏差 : %f
", sqrt(variance));   // 標準偏差を出力
    } else {   // NoteOffが1つもなかったら
        printf(" @NoteOffが存在しませんでした。");
    }

    // track_chunks,track_chunks[i].dataはcalloc()で領域確保しているので解放し忘れないように!

    return 1;
}


次に実行結果を示します。(以下、sample.midの解析結果)

$ gcc -o program ./program.c
$ ./program sample.mid
## Analyse...[sample.mid]
 @Temp = 500000
 @Temp = 500000
[音階]
 @平均値  : 65.153846
 @分散値  : 13.822485
 @標準偏差 : 3.717860
[ベロシティー]
 @平均値  : 100.000000
 @分散値  : 0.000000
 @標準偏差 : 0.000000
[NoteOn-NoteOff間の時間差]
 @平均値  : 480.000000
 @分散値  : 0.000000
 @標準偏差 : 0.000000

解析中のMIDIファイル名を提示したのち、
「@Temp = ...」という形式でテンポ表示をおこないます。(単位はマイクロ秒)



次に、同一ディレクトリ内にあるMIDIファイルを全て解析するようにします。
これはもうひとつ別のプログラムを用意して解決しました。



#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <string.h>

#define MIDI_READER "program"

int main(int argc, char *argv[]){
    DIR *dir;
    struct dirent *dp;
    int c;
    char *name;
    char command[1024];   // 実行するコマンド文字列を格納する配列
    char *pd;   // 処理するディレクトリ

    if(argc == 2){   // 引数が1つ指定されているようなら
        // argv[1]を指定ディレクトリとみなす
        pd = argv[1];
    } else {
        // カレントディレクトリを指定したものとみなす
        pd = "./";
    }

    printf("^^^ pd = %s
", pd);
    if(!(dir = opendir(pd))){    // ディレクトリを開く
        printf("Error: Cannot open the directory.");
        exit(1);
    }

    while(dp = readdir(dir)){   // ファイル毎にループ
        name = dp->d_name;
        c = strlen(dp->d_name);
        if(name[c-1]=='d' && name[c-2]=='i' && name[c-3]=='m' && name[c-4]=='.'){
            sprintf(command, "./%s \"%s%s\"", MIDI_READER, pd, name);
            //printf(">> %s
", command);
            system(command);
        }
    }

    closedir(dir);   // ディレクトリを閉じる

    return 0;
}

このプログラムの冒頭で宣言している定数「MIDI_READER」には、解析プログラムの名前を指定してください。(ここではprogram.cをコンパイルした"program"とする)
ちなみに、このプログラムと解析プログラムは同一ディレクトリに配置してください。

このプログラムは、引数に指定されたディレクトリを開き、
そこにあるMIDIファイル(拡張子部分が「.mid」となっているもの)だけを選び出し、
そのMIDIファイルの名前を解析プログラムに渡してやります。

ただし、私はMacOS X(UNIX系)を使っているのでこれで動作しますが、
Windowsでは動作するかわかりません。。。

...というか、こちらのサイト[http://q.hatena.ne.jp/1118121349]では...
Windowsにはopendir系の関数がありません。
同等の機能は、以下のAPIで実現可能です。
FindFirstFile
FindNextFile
FindClose

とあるので、今回のプログラムは動かないかもしれません。
(ただ、その説明にある通り代替可能のよう(?)なので、いじってみてください!!^^;)


さて、このプログラムを実行した結果を以下に示します。
(同一ディレクトリ内にはrun_sample.midsample.midsample2.midがあります)


$ gcc -o accessAllMIDI ./accessAllMIDI.c
$ ./accessAllMIDI ./
## Analyse...[./run_sample.mid]
 @Temp = 500000
 @Temp = 500000
[音階]
 @平均値  : 65.153846
 @分散値  : 13.822485
 @標準偏差 : 3.717860
[ベロシティー]
 @平均値  : 100.000000
 @分散値  : 0.000000
 @標準偏差 : 0.000000
[NoteOn-NoteOff間の時間差]
 @平均値  : 480.000000
 @分散値  : 0.000000
 @標準偏差 : 0.000000
## Analyse...[./sample.mid]
 @Temp = 500000
 @Temp = 500000
[音階]
 @平均値  : 65.153846
 @分散値  : 13.822485
 @標準偏差 : 3.717860
[ベロシティー]
 @平均値  : 100.000000
 @分散値  : 0.000000
 @標準偏差 : 0.000000
[NoteOn-NoteOff間の時間差]
 @平均値  : 480.000000
 @分散値  : 0.000000
 @標準偏差 : 0.000000
## Analyse...[./sample2.mid]
 @Temp = 500000
 @Temp = 500000
[音階]
 @平均値  : 64.062500
 @分散値  : 14.308594
 @標準偏差 : 3.782670
[ベロシティー]
 @平均値  : 100.000000
 @分散値  : 0.000000
 @標準偏差 : 0.000000
[NoteOn-NoteOff間の時間差]
 @平均値  : 630.000000
 @分散値  : 164700.000000
 @標準偏差 : 405.832478

コマンドライン引数として、MIDIファイルを含むディレクトリを指定してください。
今回の例では、プログラムと同じディレクトリにあるMIDIを解析したかったので「./」を指定しました。
なお、このプログラムについては引数の省略を認めています。
省略すると、「./」を指定した時と同じ動作をします。


今回も例によってテストに使用したMIDIファイルを置いておきます。
sample.mid【普通のMIDIファイル】
run_sample.mid【sample.midにランニングステータスルールを適用したMIDIファイル】
sample2.mid【異なる長さの音が含まれるMIDIファイル】

コメンターのななしさんへ:ご要望ありがとうございました!!(^_^)/

【追記】2010.01.08 : プログラムを若干修正しました(デバッグ用の出力を削除、コメント文の追加)
スポンサーサイト
以後の更新内容の改善のために、是非ともご評価のほどよろしくお願いします!→

テーマ : サウンド・プログラミング /  ジャンル : コンピュータ

【C言語】MIDI(SMF)のノートの音程・ベロシティーに関する統計処理(平均値・分散値・標準偏差値の算出)

前回の"NoteOn-NoteOffの時間差に関する統計処理"に加え、
今度は、MIDI(SMF)のノートの音程・ベロシティーについて統計処理をしてみました!
(これも同様に、平均値・分散値・標準偏差値を算出するものです。)

(これもまた同様に...) この記事に掲載するプログラムは次の記事を継承したものですので、必要であれば合わせて御覧ください。
・「C言語でMIDI(SMF)データを読んでみる!
・「C言語でMIDI(SMF)データのNoteOn/NoteOffだけ表示する!
・「【C言語】MIDI(SMF)のNoteOn-NoteOffの時間差に関する統計処理(平均値・分散値・標準偏差値の算出)

なお、このプログラムに対するテストは明らかに不十分ですので、参考になさる場合は十分にご注意ください。


//
//  Reading MIDI(SMF) Data
//                2010.08.07 - 2010.08.16
//        NoteOn/Off Version : 2010.12.09
//            平均値・分散値 : 2011.01.02
//  平均値・分散値【修正版】 : 2011.01.03

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

//====== 設定項目 ======
#define DISP_TRACK   0   // トラック番号を表示するか否か(0:非表示、1:表示)
#define DISP_NOTEON  0   //  NoteOnを表示するか否か(0:非表示、1:表示)
#define DISP_NOTEOFF 0   // NoteOffを表示するか否か(0:非表示、1:表示)

#define CHECK_VELO_ZERO 1   // NoteOnのベロシティーが0の時に、それを計算に含めるかどうか
                            // (0:含めない、1:含める)

//====== 以降はプログラム ======
#define  ON 1   // NoteOn状態
#define OFF 0   // NoteOff状態

typedef struct st_noteonlist {
    // ノートオン情報のリスト
    int note;   // ノート番号
    int velo;   // ベロシティー
    struct st_noteonlist *next;   // 次のリストを指す
} NoteOnList;

typedef struct st_notelist {
    // ノート情報のリスト
    int tick;       // 発音の長さ
    struct st_notelist *next; // 次のリストを指す
} NoteList;

typedef struct {
    // ノートの状態を格納する構造体
    int state;   // 状態(ON/OFF)
    int time;   // NoteOnになったときの累計デルタタイム
} NoteInfo;

typedef struct {
    // トラックチャンクのデータを格納する構造体
    char type[4]; // チャンクタイプを示す文字列を格納。「MTrk」が入るはず。[4byte]
    int size;     // トラックチャンクデータのサイズ [4byte]
    char *data;   // トラックデータ(イベントの羅列)へのポインタ
} TrackChunk;

short mergeChar7bit(char x, char y){
    // charの下7bitずつを結合してshort型で出力
    // 【引数】:結合対象となる2つのchar型変数x, y
    short s;
    s = (unsigned char)x; // 上位バイトを先に入れておく
    s <<= 7;              // 7bit左シフト
    s = (s | (unsigned char)(y & 0x7f));   // 下位バイトの下7bitを合成。。。
    return s;
}

int convertEndian(void *input, size_t s){
    // エンディアン変換をおこなう関数
    // stdlib.hをインクルードしてください。
    // 【引数】: void *input...エンディアン変換対象へのポインタ
    // 【引数】: size_t    s...変換対象のバイト数

    int i;   // カウンタ
    char *temp;   // 変換時に用いる一時的配列

    if((temp = (char *)calloc(s, sizeof(char))) == NULL){
        perror("Error: Cannot get memory for temp.");
        return 0;   // 領域確保できず(失敗)
    }

    for(i=0; i<s; i++){   // inputデータをtempに一時保管
        temp[i] = ((char *)input)[i];
    }

    for(i=1; i<=s; i++){   // tempデータを逆方向にしてinputへ代入
        ((char *)input)[i-1] = temp[s-i];
    }

    free(temp);   // 確保した領域を解放

    return 1;   // 正常終了
}

int main(){
    int i, j, k, cnt;   // カウンタ
    FILE *fp;         // ファイルポインタ生成
    int endian; //   エンディアン判定にいろいろ使用(0:BigEndian, 1:LittleEndian)

    // 音階・ベロシティーに関する平均値・分散値を求めるための変数
    double sum_on_note = 0.0;   // NoteOnのノート番号の総和
    double sum_on_velo = 0.0;   // NoteOnの累計ベロシティー
    unsigned int cnt_on = 0;    // ノートオンの回数
    NoteOnList *on_list_top = NULL; // 先頭のリストを指すポインタ
    NoteOnList *on_list_pt;     // リスト作成などに使う一時的な変数
    NoteOnList *on_list_temp;   // リスト操作に使う一時的な変数
    double note_average  = 0;   // 音階の平均値
    double note_variance = 0;   // 音階の分散値
    double velo_average  = 0;   // ベロシティーの平均値
    double velo_variance = 0;   // ベロシティーの分散値

    // NoteOn-NoteOff間の時間差に関する平均値・分散値を求めるための変数
    unsigned int note_num = 0;   // 処理中のノート番号
    unsigned int velo_num = 0;   // 処理中のノートのベロシティー
    NoteInfo note[128];          // ノート状態(On/Off)を格納する配列
    unsigned int sum_length = 0; // ノートオンの累計時間(累計発音時間)
                                 //    (分散値算出にもこの変数を利用)
    unsigned int cnt_length = 0; // ノートオン(発音)の回数カウンタ
    NoteList *list_top = NULL;   // 先頭のリストを指すポインタ
    NoteList *list_pt;           // リスト作成などに使う一時的な変数
    double average;              // 平均値
    double variance;             // 分散値

    // ヘッダチャンク情報
    char  header_chunk_type[4]; // チャンクタイプを示す文字列を格納。「MThd」が入るはず。[4byte]
    int   header_chunk_size;    // ヘッダチャンクデータのサイズ [4byte]
    short smf_format;     // SMFのフォーマットタイプ(0か1か2) [2byte]
    short tracks;         // トラックチャンク総数 [2byte]
    short division;       // 四分音符あたりの分解能(ここではデルタタイム) [2byte]

    // トラックチャンク情報
    TrackChunk *track_chunks;   // トラックチャンク情報を格納する配列のためのポインタ
    unsigned char c;   // イベント解析の際に使用する一時保存用変数
    unsigned char status;   // ステータスバイト用の一時変数
    unsigned int delta;   // デルタタイム
    unsigned int sum_delta;   // 累計時間

    // 配列初期化
    for(i=0; i<128; i++){
        note[i].state = OFF;   // 最初は全てノートオフ状態
    }

    // エンディアン判定
    endian = 1;
    if(*(char *)&endian){   // リトルエンディアンなら...
        endian = 1;   // Little Endian
    } else {   // ビッグエンディアンなら...
        endian = 0;   // Big Endian
    }

    // MIDIファイルを開く
    if((fp = fopen("./sample.mid", "rb")) == NULL){   // バイナリ読み取りモードでファイルを開く
        perror("Error: Cannot open the file.
");   // 失敗したらエラーを吐く
        return 0;
    }

    // ヘッダチャンク取得
    fread(header_chunk_type, 1, 4, fp);   // チャンクタイプ
    fread(&header_chunk_size, 4, 1, fp);  // チャンクデータサイズ
    fread(&smf_format, 2, 1, fp);   // SMFフォーマットタイプ
    fread(&tracks, 2, 1, fp);       // トラックチャンク総数
    fread(&division, 2, 1, fp);     // 分解能(デルタタイム)

    if(endian){   // リトルエンディアンならビッグエンディアンに変換
        convertEndian(&tracks, sizeof(tracks));
    }

    // トラックチャンク取得
    if((track_chunks = (TrackChunk *)calloc(tracks, sizeof(TrackChunk))) == NULL){   // トラック数に応じて領域確保
        perror("Error: Cannot get memory for track_chunks.");
        return 0;   // 領域確保できず(失敗)
    }
    for(i=0; i<tracks; i++){   // トラック数だけ繰返し
        fread(track_chunks[i].type, 1, 4, fp);   // チャンクタイプ
        fread(&track_chunks[i].size, 4, 1, fp);   // チャンクデータサイズ
        if(endian){   // リトルエンディアンなら要変換
            convertEndian(&track_chunks[i].size, sizeof(track_chunks[i].size));
        }
        if((track_chunks[i].data = (char *)calloc(track_chunks[i].size, sizeof(char))) == NULL){   // データサイズに応じて領域確保
            perror("Error: Cannot get memory for track_chunks[i].data .");
            return 0;   // 領域確保できず(失敗)
        }
        fread(track_chunks[i].data, track_chunks[i].size, sizeof(char), fp);   // データ(イベントの羅列)
    }

    // 読み取ったトラックチャンク情報を出力
    for(i=0; i<tracks; i++){   // トラック数だけ繰返し
        sum_delta = 0;   // トラックごとに累計時間を算出
        if(DISP_TRACK) printf("# Track[%02d] =====================
", i+1);
        for(j=0; j<track_chunks[i].size; j++){   // データ分だけ繰返し

            delta = 0;   // 初期化
            while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){
                delta = delta | (c & 0x7F);   // 合成
                delta <<= 7;   // 7bit左シフト
            }
            delta = delta | c;    // 合成
            sum_delta += delta;   // 累計時間に加算

            // ランニングステータスルールに対する処理
            if((track_chunks[i].data[j] & 0x80) == 0x80){
                // ランニングステータスルールが適用されない場合は、ステータスバイト用変数を更新。
                status = (unsigned char)track_chunks[i].data[j];   // ステータスバイトを保持しておく
            } else {
                //printf("@");   // ランニングステータスルール適用のしるし...は出さない
                j--;   // データバイトの直前のバイト(デルタタイムかな?)を指すようにしておく。
                       // 次の処理でj++するはずなので、そうすればデータバイトにアクセスできる。
            }

            // データ判別
            if((status & 0xf0) == 0x80){
                // ノート・オフ【ボイスメッセージ】
                j++;
                note_num = (unsigned char)track_chunks[i].data[j++];  // ノート番号取得
                velo_num = (unsigned char)track_chunks[i].data[j];    // ベロシティー取得
                if(DISP_NOTEOFF){
                    printf("%7d [+%4d]:: ", sum_delta, delta);   // デルタタイム出力
                    printf("Note Off [%02dch] : ", (status & 0x0f));
                    printf("Note%d", note_num);
                    printf("[0x%02x] ", note_num);
                    printf("Velocity=%d
", velo_num);
                }
                if(note[note_num].state == ON){   // 状態がONなら処理する
                    list_pt = (NoteList *)calloc(1, sizeof(NoteList)); // リスト要素の領域確保
                    list_pt->next = list_top;                       // 直前まで"先頭要素"だったものを"次の要素"にする
                    list_top = list_pt;                             // 今作った要素を先頭要素とする
                    list_pt->tick = sum_delta - note[note_num].time;   // 発音時間を計算
                    sum_length += list_pt->tick;   // 累計発音時間を更新
                    cnt_length++;
                }
                note[note_num].state = OFF;   // ノートの状態をOFFに更新
            } else if((status & 0xf0) == 0x90){
                // ノート・オン【ボイスメッセージ】
                j++;
                note_num = (unsigned char)track_chunks[i].data[j++];  // ノート番号取得
                velo_num = (unsigned char)track_chunks[i].data[j];    // ベロシティー取得
                if(DISP_NOTEON){
                    printf("%7d [+%4d]:: ", sum_delta, delta);   // デルタタイム出力
                    printf("Note On  [%02dch] : ", (status & 0x0f));
                    printf("Note%d", note_num);
                    printf("[0x%02x] ", note_num);
                    printf("Velocity=%d
", velo_num);
                }
                if(velo_num == 0){   // ベロシティーが0ならノートオフ扱い
                    if(note[note_num].state == ON){   // 状態がONなら処理する
                        list_pt = (NoteList *)calloc(1, sizeof(NoteList)); // リスト要素の領域確保
                        list_pt->next = list_top;                       // 直前まで"先頭要素"だったものを"次の要素"にする
                        list_top = list_pt;                             // 今作った要素を先頭要素とする
                        list_pt->tick = sum_delta - note[note_num].time;   // 発音時間を計算
                        sum_length += list_pt->tick;   // 累計発音時間を更新
                        cnt_length++;
                    }
                    note[note_num].state = OFF;  // ノートの状態をOFFに更新
                } else {
                    note[note_num].state = ON;   // ノートの状態をONに更新
                    note[note_num].time  = sum_delta;   // ONになった時点での累積デルタタイム保管
                }
                if(velo_num!=0 || CHECK_VELO_ZERO){   // ベロシティーが0の時は、事前指定(CHECK_VELO_ZERO)によって処理したりしなかったり
                    on_list_pt = (NoteOnList *)calloc(1, sizeof(NoteOnList));   // リスト要素の領域確保
                    on_list_pt->next = on_list_top;
                    on_list_top = on_list_pt;
                    on_list_pt->note = note_num;   // ノート番号を記録
                    on_list_pt->velo = velo_num;   // ベロシティーを記録
                    sum_on_note += note_num;   // ノート番号の総和を更新
                    sum_on_velo += velo_num;   // 累計ベロシティーを更新
                    cnt_on++;   // NoteOnの回数をカウント
                }
            } else if((status & 0xf0) == 0xa0){   // ポリフォニック・キー・プレッシャー【ボイスメッセージ】
                j += 2;
            } else if((status & 0xf0) == 0xb0){   // コントロールチェンジ【ボイスメッセージ】、【モードメッセージ】
                j += 2;
            } else if((status & 0xf0) == 0xc0){   // プログラム・チェンジ【ボイスメッセージ】
                j += 1;
            } else if((status & 0xf0) == 0xd0){   // チャンネル・プレッシャー【ボイスメッセージ】
                j += 1;
            } else if((status & 0xf0) == 0xe0){   // ピッチ・ベンド・チェンジ【ボイスメッセージ】
                j += 2;
            } else if((status & 0xf0) == 0xf0){
                // 【システム・メッセージ】
                switch(status & 0x0f){
                    case 0x00:   // エクスクルーシブ・メッセージ
                        j++;
                        // SysExのデータ長を取得
                        cnt = 0;   // 初期化
                        while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){   // フラグビットが1の間はループ
                            cnt = cnt | c;   // 合成
                            cnt <<= 7;   // 7bit左シフト
                        }
                        cnt = cnt | c;   // 合成
                        for(k=1; k<=cnt; k++){   // 長さの分だけデータ取得
                            j++;
                        }
                        j--;   // 行き過ぎた分をデクリメント
                        break;
                    case 0x01:   // MIDIタイムコード
                        j += 1;
                        break;
                    case 0x02:   // ソング・ポジション・ポインタ
                        j += 2;
                        break;
                    case 0x03:   // ソング・セレクト
                        j += 1;
                        break;
                    case 0x06:   // チューン・リクエスト
                        break;
                    case 0x07:   // エクスクルーシブ・メッセージ(?)
                        j++;
                        // SysExのデータ長を取得
                        cnt = 0;   // 初期化
                        while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){   // フラグビットが1の間はループ
                            cnt = cnt | c;   // 合成
                            cnt <<= 7;   // 7bit左シフト
                        }
                        cnt = cnt | c;   // 合成
                        for(k=1; k<=cnt; k++){   // 長さの分だけデータ取得
                            j++;
                        }
                        j--;   // 行き過ぎた分をデクリメント
                        break;
                    case 0x08:   // タイミング・クロック
                    case 0x0A:   // スタート
                    case 0x0B:   // コンティニュー
                    case 0x0C:   // ストップ
                    case 0x0E:   // アクティブ・センシング
                        break;
                    //case 0x0F:   // システムリセット(何か間違っている気がする。。。)
                    //    printf("System Reset");
                    case 0x0F:   // メタイベント
                        j++;
                        switch((unsigned char)track_chunks[i].data[j]){
                            case 0x00:   // シーケンス番号
                                j += 3;  // データ長の分を通り越す
                                break;
                            case 0x01:   // テキスト[可変長]
                            case 0x02:   // 著作権表示[可変長]
                            case 0x03:   // シーケンス名(曲タイトル)・トラック名[可変長]
                            case 0x04:   // 楽器名[可変長]
                            case 0x05:   // 歌詞[可変長]
                            case 0x06:   // マーカー[可変長]
                            case 0x07:   // キューポイント[可変長]
                            case 0x08:   // プログラム名(音色名)[可変長]
                            case 0x09:   // デバイス名(音源名)[可変長]
                                j += 1;
                                cnt = 0;
                                while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){
                                    cnt = cnt | (c & 0x7F);   // 合成
                                    cnt <<= 7;   // 7bit左シフト
                                }
                                cnt = cnt | (c & 0x7F);   // 合成
                                for(k=1; k<=cnt; k++){
                                    j++;
                                }
                                j--;   // 行き過ぎた分をデクリメント
                                break;
                            case 0x20:   // MIDIチャンネルプリフィックス[1byte]
                            case 0x21:   // ポート指定[1byte]
                                j += 2;  // データ長の分を通り越す
                                break;
                            case 0x2F:   // トラック終端[0byte]
                                j += 1;  // データ長の分を通り越す
                                break;
                            case 0x51:   // テンポ設定[3byte]
                                j += 4;  // データ長の分を通り越す
                                break;
                            case 0x54:   // SMPTEオフセット[5byte]
                                j += 6;  // データ長の分を通り越す
                                break;
                            case 0x58:   // 拍子設定[4byte]
                                j += 5;  // データ長の分を通り越す
                                break;
                            case 0x59:   // 調設定[2byte]
                                j += 3;  // データ長の分を通り越す
                                break;
                            case 0x7F:   // シーケンサ特定メタイベント
                                j += 1;
                                cnt = 0;
                                while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){
                                    cnt = cnt | (c & 0x7F);   // 合成
                                    cnt <<= 7;   // 7bit左シフト
                                }
                                cnt = cnt | (c & 0x7F);   // 合成
                                for(k=1; k<=cnt; k++){
                                    j++;
                                }
                                j--;   // 行き過ぎた分をデクリメント
                                break;
                            default :
                                ;
                        }
                        break;
                    default:
                        ;   // 見知らぬステータスなら(知らぬフリ...)
                }
            } else {
                ;   // 見知らぬステータスなら(知らぬフリ...)
            }
        }
    }

    printf("## 統計結果 =====================
");
    
    printf("[音階]
");
    if(cnt_on){   // 1つでもNoteOnがあれば
        // まずは音階の平均値算出
        note_average = sum_on_note / (double)cnt_on;   // 平均値の計算
        printf(" @平均値  : %f
", note_average);   // 平均値を出力
        // 次に、音階の分散値算出
        sum_on_note = 0;
        on_list_temp = on_list_top;   // 一時的な変数に先頭要素アドレスをコピー
        while(on_list_pt = on_list_temp){   // NULL(末端)になるまでリスト要素にアクセス
            sum_on_note += (on_list_pt->note - note_average) * (on_list_pt->note - note_average);
            on_list_temp = on_list_pt->next;
        }
        note_variance = sum_on_note / (double)cnt_on;   // 分散値の計算
        printf(" @分散値  : %f
", note_variance);   // 分散値を出力
        printf(" @標準偏差 : %f
", sqrt(note_variance));   // 標準偏差を出力
    } else {   // NoteOnが1つもなかったら
        printf(" @NoteOnが存在しませんでした。");
    }

    printf("[ベロシティー]
");
    if(cnt_on){   // 1つでもNoteOnがあれば
        // まずはベロシティーの平均値算出
        velo_average = sum_on_velo / (double)cnt_on;   // 平均値の計算
        printf(" @平均値  : %f
", velo_average);   // 平均値を出力
        // 次に、ベロシティーの分散値算出
        sum_on_velo = 0;
        on_list_temp = on_list_top;   // 一時的な変数に先頭要素アドレスをコピー
        while(on_list_pt = on_list_temp){   // NULL(末端)になるまでリスト要素にアクセス
            sum_on_velo += (on_list_pt->velo - velo_average) * (on_list_pt->velo - velo_average);
            on_list_temp = on_list_pt->next;
        }
        velo_variance = sum_on_velo / (double)cnt_on;   // 分散値の計算
        printf(" @分散値  : %f
", velo_variance);   // 分散値を出力
        printf(" @標準偏差 : %f
", sqrt(velo_variance));   // 標準偏差を出力
    } else {   // NoteOnが1つもなかったら
        printf(" @NoteOnが存在しませんでした。");
    }

    while(on_list_pt = on_list_top){   // 確保していた領域を解放
        on_list_top = on_list_pt->next;
        free(on_list_pt);   // 確保いていた領域を解放
    }

    printf("[NoteOn-NoteOff間の時間差]
");
    if(cnt_length){   // 1つでもNoteOffがあれば
        // NoteOn-NoteOff間の時間差の平均値算出
        average = sum_length / (double)cnt_length;   // 平均値の計算
        printf(" @平均値  : %f
", average);   // 平均値を出力
        // 次に、その分散値を算出
        sum_length = 0;
        while(list_pt = list_top){   // NULL(末端)になるまでリスト要素にアクセス
            sum_length += (list_pt->tick - average) * (list_pt->tick - average);
            //printf("list_pt->tick : %d, average : %d, sum_length : %d
", list_pt->tick, (int)average, sum_length);
            list_top = list_pt->next;
            free(list_pt);   // 確保していた領域を解放
        }
        variance = sum_length / (double)cnt_length;   // 分散値の計算
        printf(" @分散値  : %f
", variance);   // 分散値を出力
        printf(" @標準偏差 : %f
", sqrt(variance));   // 標準偏差を出力
    } else {   // NoteOffが1つもなかったら
        printf(" @NoteOffが存在しませんでした。");
    }

    // track_chunks,track_chunks[i].dataはcalloc()で領域確保しているので解放し忘れないように!

    return 1;
}


以下に実行結果を示します。(以下、sample.midの解析結果)

$ gcc -o program ./program.c 
$ ./program 
## 統計結果 =====================
[音階]
 @平均値  : 65.153846
 @分散値  : 13.822485
 @標準偏差 : 3.717860
[ベロシティー]
 @平均値  : 100.000000
 @分散値  : 0.000000
 @標準偏差 : 0.000000
[NoteOn-NoteOff間の時間差]
 @平均値  : 480.000000
 @分散値  : 0.000000
 @標準偏差 : 0.000000

全トラックまとめて平均値・分散値・標準偏差値を求めます。
しかしこのテストでは、長さが480の音しかないので、分散値と標準偏差値は0になります。
それではテストになっていないので、もう一つMIDIファイルを作ってテストしてみます。
(以下、sample2.midの解析結果)

$ gcc -o program ./program.c 
$ ./program 
## 統計結果 =====================
[音階]
 @平均値  : 64.062500
 @分散値  : 14.308594
 @標準偏差 : 3.782670
[ベロシティー]
 @平均値  : 100.000000
 @分散値  : 0.000000
 @標準偏差 : 0.000000
[NoteOn-NoteOff間の時間差]
 @平均値  : 630.000000
 @分散値  : 164700.000000
 @標準偏差 : 405.832478

しまった...ベロシティーの値を変えてなかった...orz

他のMIDIファイルでも解析してみましたが、値はどれも正常なようです。
下に紹介するサイトで検算してみました。
JavaScriptによる統計計算など:http://aoki2.si.gunma-u.ac.jp/JavaScript/

(紹介したサイトでは分散ではなく不偏分散を求めています。「wikipedia:分散」における、標本分散の式と不偏分散の式を見比べてください。除数が「n」なのか「n-1」なのかの違いです。そこで今回のプログラムの除数を1小さくしてから計算させてみました。つまり不偏分散を求めるようにしたのです。すると、先程のJavaScriptによる計算結果と一致しました。なのできっと標本分散の値もあっているはず...ということです。テストになっていない気もしますが...^^;)


例によってテストに使用したMIDIファイルを置いておきます。
sample.mid【普通のMIDIファイル】
run_sample.mid【sample.midにランニングステータスルールを適用したMIDIファイル】
sample2.mid【異なる長さの音が含まれるMIDIファイル】


プログラムの冒頭部分には#defineによる設定項目を付けたので、必要な変更を適宜加えてください。
バグがあったらスミマセン...m(__;)m
以後の更新内容の改善のために、是非ともご評価のほどよろしくお願いします!→

テーマ : サウンド・プログラミング /  ジャンル : コンピュータ

【C言語】MIDI(SMF)のNoteOn-NoteOffの時間差に関する統計処理(平均値・分散値・標準偏差値の算出)

"統計処理"というと何やらおこがましいような気もしますが、
MIDI(SMF)のNoteOn-NoteOffの時間差に関する統計処理をしてみました。
(平均値・分散値・標準偏差値を算出するものです。)

この記事に掲載するプログラムは次の記事を継承したものですので、必要であれば合わせて御覧ください。
・「C言語でMIDI(SMF)データを読んでみる!
・「C言語でMIDI(SMF)データのNoteOn/NoteOffだけ表示する!

今回のプログラムもそんなにテストをしていないので、参考になさる場合は十分に注意しつつご利用ください。(free()を忘れてるとか、MIDIの解析が間違ってるとか。)

ちなみに、コメントに「【改】」と入っている部分が今回の追加・編集部分です。(抜けてたらゴメンナサイ...)

//
//  Reading MIDI(SMF) Data
//                2010.08.07 - 2010.08.16
//        NoteOn/Off Version : 2010.12.09
//            平均値・分散値 : 2011.01.02

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define  ON 1   // 【改】NoteOn状態
#define OFF 0   // 【改】NoteOff状態

#define DISP_NOTEON  1   // 【改】 NoteOnを表示するか否か(0:非表示、1:表示)
#define DISP_NOTEOFF 1   // 【改】NoteOffを表示するか否か(0:非表示、1:表示)

typedef struct st_notelist {   // 【改】
    // ノート情報のリスト
    int tick;       // 発音の長さ
    struct st_notelist *next; // 次のリストを指す
} NoteList;

typedef struct {   // 【改】
    // ノートの状態を格納する構造体
    int state;   // 状態(ON/OFF)
    int time;   // NoteOnになったときの累計デルタタイム
} NoteInfo;

typedef struct {
    // トラックチャンクのデータを格納する構造体
    char type[4]; // チャンクタイプを示す文字列を格納。「MTrk」が入るはず。[4byte]
    int size;     // トラックチャンクデータのサイズ [4byte]
    char *data;   // トラックデータ(イベントの羅列)へのポインタ
} TrackChunk;

short mergeChar7bit(char x, char y){
    // charの下7bitずつを結合してshort型で出力
    // 【引数】:結合対象となる2つのchar型変数x, y
    short s;
    s = (unsigned char)x; // 上位バイトを先に入れておく
    s <<= 7;              // 7bit左シフト
    s = (s | (unsigned char)(y & 0x7f));   // 下位バイトの下7bitを合成。。。
    return s;
}

int convertEndian(void *input, size_t s){
    // エンディアン変換をおこなう関数
    // stdlib.hをインクルードしてください。
    // 【引数】: void *input...エンディアン変換対象へのポインタ
    // 【引数】: size_t    s...変換対象のバイト数

    int i;   // カウンタ
    char *temp;   // 変換時に用いる一時的配列

    if((temp = (char *)calloc(s, sizeof(char))) == NULL){
        perror("Error: Cannot get memory for temp.");
        return 0;   // 領域確保できず(失敗)
    }

    for(i=0; i<s; i++){   // inputデータをtempに一時保管
        temp[i] = ((char *)input)[i];
    }

    for(i=1; i<=s; i++){   // tempデータを逆方向にしてinputへ代入
        ((char *)input)[i-1] = temp[s-i];
    }

    free(temp);   // 確保した領域を解放

    return 1;   // 正常終了
}

int main(){
    int i, j, k, cnt;   // カウンタ
    FILE *fp;         // ファイルポインタ生成
    int endian; //   エンディアン判定にいろいろ使用(0:BigEndian, 1:LittleEndian)

    // 【改】平均値・分散値のための変数
    unsigned int note_num;   // 【改】処理中のノート番号
    unsigned int velo_num;   // 【改】処理中のノートのベロシティー
    NoteInfo note[128];      // 【改】ノート状態(On/Off)を格納する配列
    unsigned int sum_length; // 【改】ノートオンの累計時間(累計発音時間)
                             //    (分散値算出にもこの変数を利用)
    unsigned int cnt_length; // 【改】ノートオン(発音)の回数カウンタ
    NoteList *list_top; // 【改】先頭のリストを指すポインタ
    NoteList *list_pt;  // 【改】リスト作成に使う一時的な変数
    double average;     // 【改】平均値
    double variance;    // 【改】分散値

    // ヘッダチャンク情報
    char  header_chunk_type[4]; // チャンクタイプを示す文字列を格納。「MThd」が入るはず。[4byte]
    int   header_chunk_size;    // ヘッダチャンクデータのサイズ [4byte]
    short smf_format;     // SMFのフォーマットタイプ(0か1か2) [2byte]
    short tracks;         // トラックチャンク総数 [2byte]
    short division;       // 四分音符あたりの分解能(ここではデルタタイム) [2byte]

    // トラックチャンク情報
    TrackChunk *track_chunks;   // トラックチャンク情報を格納する配列のためのポインタ
    unsigned char c;   // イベント解析の際に使用する一時保存用変数
    unsigned char status;   // ステータスバイト用の一時変数
    unsigned int delta;   // デルタタイム
    unsigned int sum_delta;   // 累計時間

    // 【改】配列初期化
    for(i=0; i<128; i++){
        note[i].state = OFF;   // 最初は全てノートオフ状態
    }

    // エンディアン判定
    endian = 1;
    if(*(char *)&endian){   // リトルエンディアンなら...
        endian = 1;   // Little Endian
    } else {   // ビッグエンディアンなら...
        endian = 0;   // Big Endian
    }

    // MIDIファイルを開く
    if((fp = fopen("./nt.mid", "rb")) == NULL){   // バイナリ読み取りモードでファイルを開く
        perror("Error: Cannot open the file.
");   // 失敗したらエラーを吐く
        return 0;
    }

    // ヘッダチャンク取得
    fread(header_chunk_type, 1, 4, fp);   // チャンクタイプ
    fread(&header_chunk_size, 4, 1, fp);  // チャンクデータサイズ
    fread(&smf_format, 2, 1, fp);   // SMFフォーマットタイプ
    fread(&tracks, 2, 1, fp);       // トラックチャンク総数
    fread(&division, 2, 1, fp);     // 分解能(デルタタイム)

    if(endian){   // リトルエンディアンならビッグエンディアンに変換
        convertEndian(&tracks, sizeof(tracks));
    }

    // トラックチャンク取得
    if((track_chunks = (TrackChunk *)calloc(tracks, sizeof(TrackChunk))) == NULL){   // トラック数に応じて領域確保
        perror("Error: Cannot get memory for track_chunks.");
        return 0;   // 領域確保できず(失敗)
    }
    for(i=0; i<tracks; i++){   // トラック数だけ繰返し
        fread(track_chunks[i].type, 1, 4, fp);   // チャンクタイプ
        fread(&track_chunks[i].size, 4, 1, fp);   // チャンクデータサイズ
        if(endian){   // リトルエンディアンなら要変換
            convertEndian(&track_chunks[i].size, sizeof(track_chunks[i].size));
        }
        if((track_chunks[i].data = (char *)calloc(track_chunks[i].size, sizeof(char))) == NULL){   // データサイズに応じて領域確保
            perror("Error: Cannot get memory for track_chunks[i].data .");
            return 0;   // 領域確保できず(失敗)
        }
        fread(track_chunks[i].data, track_chunks[i].size, sizeof(char), fp);   // データ(イベントの羅列)
    }

    // 読み取ったトラックチャンク情報を出力
    for(i=0; i<tracks; i++){   // トラック数だけ繰返し
        sum_length = 0;   // 【改】
        cnt_length = 0;   // 【改】
        sum_delta = 0;   // トラックごとに累計時間を算出
        printf("# Track[%02d] =====================
", i+1);
        for(j=0; j<track_chunks[i].size; j++){   // データ分だけ繰返し

            delta = 0;   // 初期化
            while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){
                delta = delta | (c & 0x7F);   // 合成
                delta <<= 7;   // 7bit左シフト
            }
            delta = delta | c;    // 合成
            sum_delta += delta;   // 累計時間に加算

            // ランニングステータスルールに対する処理
            if((track_chunks[i].data[j] & 0x80) == 0x80){
                // ランニングステータスルールが適用されない場合は、ステータスバイト用変数を更新。
                status = (unsigned char)track_chunks[i].data[j];   // ステータスバイトを保持しておく
            } else {
                //printf("@");   // ランニングステータスルール適用のしるし...は出さない
                j--;   // データバイトの直前のバイト(デルタタイムかな?)を指すようにしておく。
                       // 次の処理でj++するはずなので、そうすればデータバイトにアクセスできる。
            }

            // データ判別
            if((status & 0xf0) == 0x80){
                // ノート・オフ【ボイスメッセージ】
                j++;
                note_num = (unsigned char)track_chunks[i].data[j++];  // 【改】ノート番号取得
                velo_num = (unsigned char)track_chunks[i].data[j];    // 【改】ベロシティー取得
                if(DISP_NOTEOFF){   // 【改】
                    printf("%7d [+%4d]:: ", sum_delta, delta);   // デルタタイム出力
                    printf("Note Off [%02dch] : ", (status & 0x0f));
                    printf("Note%d", note_num);        // 【改】
                    printf("[0x%02x] ", note_num);     // 【改】
                    printf("Velocity=%d
", velo_num); // 【改】
                }
                if(note[note_num].state == ON){   // 【改】状態がONなら処理する
                    list_pt = (NoteList *)calloc(1, sizeof(NoteList)); // 【改】リスト要素の領域確保
                    list_pt->next = list_top;                       // 【改】直前まで"先頭要素"だったものを"次の要素"にする
                    list_top = list_pt;                             // 【改】今作った要素を先頭要素とする
                    list_pt->tick = sum_delta - note[note_num].time;   // 【改】発音時間を計算
                    sum_length += list_pt->tick;   // 【改】累計発音時間を更新
                    cnt_length++;
                }
                note[note_num].state = OFF;   // 【改】ノートの状態をOFFに更新
            } else if((status & 0xf0) == 0x90){
                // ノート・オン【ボイスメッセージ】
                j++;
                note_num = (unsigned char)track_chunks[i].data[j++];  // 【改】ノート番号取得
                velo_num = (unsigned char)track_chunks[i].data[j];    // 【改】ベロシティー取得
                if(DISP_NOTEON){   // 【改】
                    printf("%7d [+%4d]:: ", sum_delta, delta);   // デルタタイム出力
                    printf("Note On  [%02dch] : ", (status & 0x0f));
                    printf("Note%d", note_num);        // 【改】
                    printf("[0x%02x] ", note_num);     // 【改】
                    printf("Velocity=%d
", velo_num); // 【改】
                }
                if(velo_num == 0){   // ベロシティーが0ならノートオフ扱い
                    if(note[note_num].state == ON){   // 【改】状態がONなら処理する
                        list_pt = (NoteList *)calloc(1, sizeof(NoteList)); // 【改】リスト要素の領域確保
                        list_pt->next = list_top;                       // 【改】直前まで"先頭要素"だったものを"次の要素"にする
                        list_top = list_pt;                             // 【改】今作った要素を先頭要素とする
                        list_pt->tick = sum_delta - note[note_num].time;   // 【改】発音時間を計算
                        sum_length += list_pt->tick;   // 【改】累計発音時間を更新
                        cnt_length++;
                    }
                    note[note_num].state = OFF;  // 【改】ノートの状態をOFFに更新
                } else {
                    note[note_num].state = ON;   // 【改】ノートの状態をONに更新
                    note[note_num].time  = sum_delta;   // ONになった時点での累積デルタタイム保管
                }
            } else if((status & 0xf0) == 0xa0){   // ポリフォニック・キー・プレッシャー【ボイスメッセージ】
                j += 2;
            } else if((status & 0xf0) == 0xb0){   // コントロールチェンジ【ボイスメッセージ】、【モードメッセージ】
                j += 2;
            } else if((status & 0xf0) == 0xc0){   // プログラム・チェンジ【ボイスメッセージ】
                j += 1;
            } else if((status & 0xf0) == 0xd0){   // チャンネル・プレッシャー【ボイスメッセージ】
                j += 1;
            } else if((status & 0xf0) == 0xe0){   // ピッチ・ベンド・チェンジ【ボイスメッセージ】
                j += 2;
            } else if((status & 0xf0) == 0xf0){
                // 【システム・メッセージ】
                switch(status & 0x0f){
                    case 0x00:   // エクスクルーシブ・メッセージ
                        j++;
                        // SysExのデータ長を取得
                        cnt = 0;   // 初期化
                        while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){   // フラグビットが1の間はループ
                            cnt = cnt | c;   // 合成
                            cnt <<= 7;   // 7bit左シフト
                        }
                        cnt = cnt | c;   // 合成
                        for(k=1; k<=cnt; k++){   // 長さの分だけデータ取得
                            j++;
                        }
                        j--;   // 行き過ぎた分をデクリメント
                        break;
                    case 0x01:   // MIDIタイムコード
                        j += 1;
                        break;
                    case 0x02:   // ソング・ポジション・ポインタ
                        j += 2;
                        break;
                    case 0x03:   // ソング・セレクト
                        j += 1;
                        break;
                    case 0x06:   // チューン・リクエスト
                        break;
                    case 0x07:   // エクスクルーシブ・メッセージ(?)
                        j++;
                        // SysExのデータ長を取得
                        cnt = 0;   // 初期化
                        while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){   // フラグビットが1の間はループ
                            cnt = cnt | c;   // 合成
                            cnt <<= 7;   // 7bit左シフト
                        }
                        cnt = cnt | c;   // 合成
                        for(k=1; k<=cnt; k++){   // 長さの分だけデータ取得
                            j++;
                        }
                        j--;   // 行き過ぎた分をデクリメント
                        break;
                    case 0x08:   // タイミング・クロック
                    case 0x0A:   // スタート
                    case 0x0B:   // コンティニュー
                    case 0x0C:   // ストップ
                    case 0x0E:   // アクティブ・センシング
                        break;
                    //case 0x0F:   // システムリセット(何か間違っている気がする。。。)
                    //    printf("System Reset");
                    case 0x0F:   // メタイベント
                        j++;
                        switch((unsigned char)track_chunks[i].data[j]){
                            case 0x00:   // シーケンス番号
                                j += 3;  // データ長の分を通り越す
                                break;
                            case 0x01:   // テキスト[可変長]
                            case 0x02:   // 著作権表示[可変長]
                            case 0x03:   // シーケンス名(曲タイトル)・トラック名[可変長]
                            case 0x04:   // 楽器名[可変長]
                            case 0x05:   // 歌詞[可変長]
                            case 0x06:   // マーカー[可変長]
                            case 0x07:   // キューポイント[可変長]
                            case 0x08:   // プログラム名(音色名)[可変長]
                            case 0x09:   // デバイス名(音源名)[可変長]
                                j += 1;
                                cnt = 0;
                                while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){
                                    cnt = cnt | (c & 0x7F);   // 合成
                                    cnt <<= 7;   // 7bit左シフト
                                }
                                cnt = cnt | (c & 0x7F);   // 合成
                                for(k=1; k<=cnt; k++){
                                    j++;
                                }
                                j--;   // 行き過ぎた分をデクリメント
                                break;
                            case 0x20:   // MIDIチャンネルプリフィックス[1byte]
                            case 0x21:   // ポート指定[1byte]
                                j += 2;  // データ長の分を通り越す
                                break;
                            case 0x2F:   // トラック終端[0byte]
                                j += 1;  // データ長の分を通り越す
                                break;
                            case 0x51:   // テンポ設定[3byte]
                                j += 4;  // データ長の分を通り越す
                                break;
                            case 0x54:   // SMPTEオフセット[5byte]
                                j += 6;  // データ長の分を通り越す
                                break;
                            case 0x58:   // 拍子設定[4byte]
                                j += 5;  // データ長の分を通り越す
                                break;
                            case 0x59:   // 調設定[2byte]
                                j += 3;  // データ長の分を通り越す
                                break;
                            case 0x7F:   // シーケンサ特定メタイベント
                                j += 1;
                                cnt = 0;
                                while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){
                                    cnt = cnt | (c & 0x7F);   // 合成
                                    cnt <<= 7;   // 7bit左シフト
                                }
                                cnt = cnt | (c & 0x7F);   // 合成
                                for(k=1; k<=cnt; k++){
                                    j++;
                                }
                                j--;   // 行き過ぎた分をデクリメント
                                break;
                            default :
                                ;
                        }
                        break;
                    default:
                        ;   // 見知らぬステータスなら(知らぬフリ...)
                }
            } else {
                ;   // 見知らぬステータスなら(知らぬフリ...)
            }
        }
        
        if(cnt_length){   // 【改】1つでもNoteOffがあれば
            // 1トラックの処理が終わる前に...
            // まずは、そのトラックの平均値算出
            average = sum_length / (double)cnt_length;   // 【改】平均値の計算
            printf(" @平均値 : %f
", average);   // 【改】平均値を出力
            // 次に、そのトラックの分散値算出
            sum_length = 0;   // 【改】
            while(list_pt = list_top){   // 【改】NULL(末端)になるまでリスト要素にアクセス
                sum_length += (list_pt->tick - average) * (list_pt->tick - average);   // 【改】
                list_top = list_pt->next;   // 【改】
                free(list_pt);   // 【改】確保していた領域を解放
            }
            variance = sum_length / (double)cnt_length;   // 【改】分散値の計算
            printf(" @分散値 : %f
", variance);   // 【改】分散値を出力
            printf(" @標準偏差 : %f
", sqrt(variance));   // 【改】標準偏差を出力
        }
    }

    // track_chunks,track_chunks[i].dataはcalloc()で領域確保しているので解放し忘れないように!

    return 1;
}


以下に実行結果を示します。

$ gcc -o program ./program.c 
$ ./program 
# Track[01] =====================
# Track[02] =====================
# Track[03] =====================
    480 [+ 146]:: Note On  [00ch] : Note60[0x3c] Velocity=100
    960 [+ 480]:: Note Off [00ch] : Note60[0x3c] Velocity=0
    960 [+   0]:: Note On  [00ch] : Note62[0x3e] Velocity=100
   1440 [+ 480]:: Note Off [00ch] : Note62[0x3e] Velocity=0
   1440 [+   0]:: Note On  [00ch] : Note64[0x40] Velocity=100
   1920 [+ 480]:: Note Off [00ch] : Note64[0x40] Velocity=0
   1920 [+   0]:: Note On  [00ch] : Note65[0x41] Velocity=100
   2400 [+ 480]:: Note Off [00ch] : Note65[0x41] Velocity=0
   2400 [+   0]:: Note On  [00ch] : Note67[0x43] Velocity=100
   2400 [+   0]:: Note On  [00ch] : Note64[0x40] Velocity=100
   2880 [+ 480]:: Note Off [00ch] : Note67[0x43] Velocity=0
   2880 [+   0]:: Note Off [00ch] : Note64[0x40] Velocity=0
   2880 [+   0]:: Note On  [00ch] : Note69[0x45] Velocity=100
   2880 [+   0]:: Note On  [00ch] : Note62[0x3e] Velocity=100
   3360 [+ 480]:: Note Off [00ch] : Note69[0x45] Velocity=0
   3360 [+   0]:: Note Off [00ch] : Note62[0x3e] Velocity=0
   3360 [+   0]:: Note On  [00ch] : Note71[0x47] Velocity=100
   3360 [+   0]:: Note On  [00ch] : Note67[0x43] Velocity=100
   3840 [+ 480]:: Note Off [00ch] : Note71[0x47] Velocity=0
   3840 [+   0]:: Note Off [00ch] : Note67[0x43] Velocity=0
   3840 [+   0]:: Note On  [00ch] : Note72[0x48] Velocity=100
   3840 [+   0]:: Note On  [00ch] : Note60[0x3c] Velocity=100
   3840 [+   0]:: Note On  [00ch] : Note64[0x40] Velocity=100
   4320 [+ 480]:: Note Off [00ch] : Note72[0x48] Velocity=0
   4320 [+   0]:: Note Off [00ch] : Note60[0x3c] Velocity=0
   4320 [+   0]:: Note Off [00ch] : Note64[0x40] Velocity=0
 @平均値 : 480.000000
 @分散値 : 0.000000
 @標準偏差 : 0.000000
# Track[04] =====================
# Track[05] =====================
# Track[06] =====================
# Track[07] =====================
# Track[08] =====================
# Track[09] =====================
# Track[10] =====================
# Track[11] =====================
# Track[12] =====================
# Track[13] =====================
# Track[14] =====================
# Track[15] =====================
# Track[16] =====================
# Track[17] =====================
# Track[18] =====================

各トラックごとに平均値・分散値・標準偏差値を求めます。
...とは言いつつ、テストに使ったMIDIファイルは長さが480の音しかないので、
分散値と標準偏差値は0になります...(テストになっとらん...orz)

手持ちに他のMIDIファイルがあったので解析してみましたが、(確信はあまり持てませんが...)まあそれらしい値が出ているようです。
なお、著作権の関係でそのMIDIファイルは置けませんのでご自分のデータで試してみてください!


前回同様、テストに使用した(役に立たない)MIDIファイルを置いておきます。
(かなり短いのと、ティック数が全て480で分散値・標準偏差値が出ないという...orz)
sample.mid【普通のMIDIファイル】
run_sample.mid【ランニングステータスルールを適用したMIDIファイル】

ななし さん、コメントありがとうございました!!m(__)m

【追記】2010.01.03 : プログラムを若干修正しました(#defineの値を変更、コメントの変更、出力部の変更、if条件式の変更)
以後の更新内容の改善のために、是非ともご評価のほどよろしくお願いします!→

テーマ : サウンド・プログラミング /  ジャンル : コンピュータ

C言語でMIDI(SMF)データのNoteOn/NoteOffだけ表示する!

先日(といっても4ヶ月前)掲載した記事「C言語でMIDI(SMF)データを読んでみる!」では、
MIDI(SMF)ファイルをC言語で読み込んでデータを解析する...ということをやってみましたが、
その記事において、
どのようにすればノートオンとノートオフだけ表示できるのか
...というコメントをいただきました。

そうなんです、、、
ホントは私も「ノートオンとノートオフを表示」ができさえすれば良かったのです。^_^;
(ただ、この解析プログラムを書いてるうちに、気がついたら本質からズレちゃってました...orz)

...ってなわけで、ノートオンとノートオフだけを表示するプログラムに修正してみました!


//
//  Reading MIDI(SMF) Data
//                2010.08.07 - 2010.08.16
//        NoteOn/Off Version : 2010.12.09

#include 
#include 

typedef struct {
    // トラックチャンクのデータを格納する構造体
    char type[4]; // チャンクタイプを示す文字列を格納。「MTrk」が入るはず。[4byte]
    int size;     // トラックチャンクデータのサイズ [4byte]
    char *data;   // トラックデータ(イベントの羅列)へのポインタ
} TrackChunk;

short mergeChar7bit(char x, char y){
    // charの下7bitずつを結合してshort型で出力
    // 【引数】:結合対象となる2つのchar型変数x, y
    short s;
    s = (unsigned char)x; // 上位バイトを先に入れておく
    s <<= 7;              // 7bit左シフト
    s = (s | (unsigned char)(y & 0x7f));   // 下位バイトの下7bitを合成。。。
    return s;
}

int convertEndian(void *input, size_t s){
    // エンディアン変換をおこなう関数
    // stdlib.hをインクルードしてください。
    // 【引数】: void *input...エンディアン変換対象へのポインタ
    // 【引数】: size_t    s...変換対象のバイト数

    int i;   // カウンタ
    char *temp;   // 変換時に用いる一時的配列

    if((temp = (char *)calloc(s, sizeof(char))) == NULL){
        perror("Error: Cannot get memory for temp.");
        return 0;   // 領域確保できず(失敗)
    }

    for(i=0; i<s; i++){   // inputデータをtempに一時保管
        temp[i] = ((char *)input)[i];
    }

    for(i=1; i<=s; i++){   // tempデータを逆方向にしてinputへ代入
        ((char *)input)[i-1] = temp[s-i];
    }

    free(temp);   // 確保した領域を解放

    return 1;   // 正常終了
}

int main(){
    int i, j, k, cnt;   // カウンタ
    FILE *fp;         // ファイルポインタ生成
    int endian; //   エンディアン判定にいろいろ使用(0:BigEndian, 1:LittleEndian)

    // ヘッダチャンク情報
    char  header_chunk_type[4]; // チャンクタイプを示す文字列を格納。「MThd」が入るはず。[4byte]
    int   header_chunk_size;    // ヘッダチャンクデータのサイズ [4byte]
    short smf_format;     // SMFのフォーマットタイプ(0か1か2) [2byte]
    short tracks;         // トラックチャンク総数 [2byte]
    short division;       // 四分音符あたりの分解能(ここではデルタタイム) [2byte]

    // トラックチャンク情報
    TrackChunk *track_chunks;   // トラックチャンク情報を格納する配列のためのポインタ
    unsigned char c;   // イベント解析の際に使用する一時保存用変数
    unsigned char status;   // ステータスバイト用の一時変数
    unsigned int delta;   // デルタタイム
    unsigned int sum_delta;   // 累計時間

    // エンディアン判定
    endian = 1;
    if(*(char *)&endian){   // リトルエンディアンなら...
        endian = 1;   // Little Endian
    } else {   // ビッグエンディアンなら...
        endian = 0;   // Big Endian
    }

    // MIDIファイルを開く
    if((fp = fopen("./sample.mid", "rb")) == NULL){   // バイナリ読み取りモードでファイルを開く
        perror("Error: Cannot open the file.\n");   // 失敗したらエラーを吐く
        return 0;
    }

    // ヘッダチャンク取得
    fread(header_chunk_type, 1, 4, fp);   // チャンクタイプ
    fread(&header_chunk_size, 4, 1, fp);  // チャンクデータサイズ
    fread(&smf_format, 2, 1, fp);   // SMFフォーマットタイプ
    fread(&tracks, 2, 1, fp);       // トラックチャンク総数
    fread(&division, 2, 1, fp);     // 分解能(デルタタイム)

    if(endian){   // リトルエンディアンならビッグエンディアンに変換
        convertEndian(&tracks, sizeof(tracks));
    }

    // トラックチャンク取得
    if((track_chunks = (TrackChunk *)calloc(tracks, sizeof(TrackChunk))) == NULL){
        // トラック数に応じて領域確保
        perror("Error: Cannot get memory for track_chunks.");
        return 0;   // 領域確保できず(失敗)
    }
    for(i=0; i<tracks; i++){   // トラック数だけ繰返し
        fread(track_chunks[i].type, 1, 4, fp);   // チャンクタイプ
        fread(&track_chunks[i].size, 4, 1, fp);   // チャンクデータサイズ
        if(endian){   // リトルエンディアンなら要変換
            convertEndian(&track_chunks[i].size, sizeof(track_chunks[i].size));
        }
        if((track_chunks[i].data = (char *)calloc(track_chunks[i].size, sizeof(char))) == NULL){   // データサイズに応じて領域確保
            perror("Error: Cannot get memory for track_chunks[i].data .");
            return 0;   // 領域確保できず(失敗)
        }
        fread(track_chunks[i].data, track_chunks[i].size, sizeof(char), fp);   // データ(イベントの羅列)
    }

    // 読み取ったトラックチャンク情報を出力
    for(i=0; i<tracks; i++){   // トラック数だけ繰返し
        sum_delta = 0;   // トラックごとに累計時間を算出
        printf("# Track[%02d] =====================\n", i+1);
        for(j=0; j<track_chunks[i].size; j++){   // データ分だけ繰返し

            delta = 0;   // 初期化
            while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){
                delta = delta | (c & 0x7F);   // 合成
                delta <<= 7;   // 7bit左シフト
            }
            delta = delta | c;    // 合成
            sum_delta += delta;   // 累計時間に加算

            // ランニングステータスルールに対する処理
            if((track_chunks[i].data[j] & 0x80) == 0x80){
                // ランニングステータスルールが適用されない場合は、ステータスバイト用変数を更新。
                status = (unsigned char)track_chunks[i].data[j];   // ステータスバイトを保持しておく
            } else {
                //printf("\b@");   // ランニングステータスルール適用のしるし...は出さない
                j--;   // データバイトの直前のバイト(デルタタイムかな?)を指すようにしておく。
                       // 次の処理でj++するはずなので、そうすればデータバイトにアクセスできる。
            }

            // データ判別
            if((status & 0xf0) == 0x80){
                // ノート・オフ【ボイスメッセージ】
                printf("%7d [+%4d]:: ", sum_delta, delta);   // デルタタイム出力
                printf("Note Off [%02dch] : ", (status & 0x0f));
                j++;
                printf("Note%d", (unsigned char)track_chunks[i].data[j]);
                printf("[0x%02x] ", (unsigned char)track_chunks[i].data[j++]);
                printf("Velocity=%d\n", (unsigned char)track_chunks[i].data[j]);
            } else if((status & 0xf0) == 0x90){
                // ノート・オン【ボイスメッセージ】
                printf("%7d [+%4d]:: ", sum_delta, delta);   // デルタタイム出力
                printf("Note On  [%02dch] : ", (status & 0x0f));
                j++;
                printf("Note%d", (unsigned char)track_chunks[i].data[j]);
                printf("[0x%02x] ", (unsigned char)track_chunks[i].data[j++]);
                printf("Velocity=%d\n", (unsigned char)track_chunks[i].data[j]);
            } else if((status & 0xf0) == 0xa0){   // ポリフォニック・キー・プレッシャー【ボイスメッセージ】
                j += 2;
            } else if((status & 0xf0) == 0xb0){   // コントロールチェンジ【ボイスメッセージ】、【モードメッセージ】
                j += 2;
            } else if((status & 0xf0) == 0xc0){   // プログラム・チェンジ【ボイスメッセージ】
                j += 1;
            } else if((status & 0xf0) == 0xd0){   // チャンネル・プレッシャー【ボイスメッセージ】
                j += 1;
            } else if((status & 0xf0) == 0xe0){   // ピッチ・ベンド・チェンジ【ボイスメッセージ】
                j += 2;
            } else if((status & 0xf0) == 0xf0){
                // 【システム・メッセージ】
                switch(status & 0x0f){
                    case 0x00:   // エクスクルーシブ・メッセージ
                        j++;
                        // SysExのデータ長を取得
                        cnt = 0;   // 初期化
                        while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){   // フラグビットが1の間はループ
                            cnt = cnt | c;   // 合成
                            cnt <<= 7;   // 7bit左シフト
                        }
                        cnt = cnt | c;   // 合成
                        for(k=1; k<=cnt; k++){   // 長さの分だけデータ取得
                            j++;
                        }
                        j--;   // 行き過ぎた分をデクリメント
                        break;
                    case 0x01:   // MIDIタイムコード
                        j += 1;
                        break;
                    case 0x02:   // ソング・ポジション・ポインタ
                        j += 2;
                        break;
                    case 0x03:   // ソング・セレクト
                        j += 1;
                        break;
                    case 0x06:   // チューン・リクエスト
                        break;
                    case 0x07:   // エクスクルーシブ・メッセージ(?)
                        j++;
                        // SysExのデータ長を取得
                        cnt = 0;   // 初期化
                        while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){   // フラグビットが1の間はループ
                            cnt = cnt | c;   // 合成
                            cnt <<= 7;   // 7bit左シフト
                        }
                        cnt = cnt | c;   // 合成
                        for(k=1; k<=cnt; k++){   // 長さの分だけデータ取得
                            j++;
                        }
                        j--;   // 行き過ぎた分をデクリメント
                        break;
                    case 0x08:   // タイミング・クロック
                    case 0x0A:   // スタート
                    case 0x0B:   // コンティニュー
                    case 0x0C:   // ストップ
                    case 0x0E:   // アクティブ・センシング
                        break;
                    //case 0x0F:   // システムリセット(何か間違っている気がする。。。)
                    //    printf("System Reset");
                    case 0x0F:   // メタイベント
                        j++;
                        switch((unsigned char)track_chunks[i].data[j]){
                            case 0x00:   // シーケンス番号
                                j += 3;  // データ長の分を通り越す
                                break;
                            case 0x01:   // テキスト[可変長]
                            case 0x02:   // 著作権表示[可変長]
                            case 0x03:   // シーケンス名(曲タイトル)・トラック名[可変長]
                            case 0x04:   // 楽器名[可変長]
                            case 0x05:   // 歌詞[可変長]
                            case 0x06:   // マーカー[可変長]
                            case 0x07:   // キューポイント[可変長]
                            case 0x08:   // プログラム名(音色名)[可変長]
                            case 0x09:   // デバイス名(音源名)[可変長]
                                j += 1;
                                cnt = 0;
                                while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){
                                    cnt = cnt | (c & 0x7F);   // 合成
                                    cnt <<= 7;   // 7bit左シフト
                                }
                                cnt = cnt | (c & 0x7F);   // 合成
                                for(k=1; k<=cnt; k++){
                                    j++;
                                }
                                j--;   // 行き過ぎた分をデクリメント
                                break;
                            case 0x20:   // MIDIチャンネルプリフィックス[1byte]
                            case 0x21:   // ポート指定[1byte]
                                j += 2;  // データ長の分を通り越す
                                break;
                            case 0x2F:   // トラック終端[0byte]
                                j += 1;  // データ長の分を通り越す
                                break;
                            case 0x51:   // テンポ設定[3byte]
                                j += 4;  // データ長の分を通り越す
                                break;
                            case 0x54:   // SMPTEオフセット[5byte]
                                j += 6;  // データ長の分を通り越す
                                break;
                            case 0x58:   // 拍子設定[4byte]
                                j += 5;  // データ長の分を通り越す
                                break;
                            case 0x59:   // 調設定[2byte]
                                j += 3;  // データ長の分を通り越す
                                break;
                            case 0x7F:   // シーケンサ特定メタイベント
                                j += 1;
                                cnt = 0;
                                while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){
                                    cnt = cnt | (c & 0x7F);   // 合成
                                    cnt <<= 7;   // 7bit左シフト
                                }
                                cnt = cnt | (c & 0x7F);   // 合成
                                for(k=1; k<=cnt; k++){
                                    j++;
                                }
                                j--;   // 行き過ぎた分をデクリメント
                                break;
                            default :
                                ;
                        }
                        break;
                    default:
                        ;   // 見知らぬステータスなら(知らぬフリ...)
                }
            } else {
                ;   // 見知らぬステータスなら(知らぬフリ...)
            }
        }
    }
    // track_chunks,track_chunks[i].dataはcalloc()で領域確保しているので解放し忘れないように!
    return 1;
}


ということで、4ヶ月前に書いたプログラムと久々に対面したわけです(笑)
それにしてもコードの書き方が雑だなぁ...orz
コメントをくださった方に申し訳ないくらいに汚いコードです、スミマセン...(__;

さて、プログラムについてですが、もう少し簡単化できるかもしれませんが、
あまりイジると動かなくなりそうで怖い...のでやめておきました。。。
(一から書き直すほうが安全ですよね^^;)

このプログラムと同じ場所にsample.midというファイルを置いておいてください。
あとはプログラムを実行すればノートオン/ノートオフの抽出を行います。

以下がその結果です。

$ gcc -o program ./program.c 
$ ./program 
# Track[01] =====================
# Track[02] =====================
# Track[03] =====================
    480 [+ 146]:: Note On  [00ch] : Note60[0x3c] Velocity=100
    960 [+ 480]:: Note Off [00ch] : Note60[0x3c] Velocity=0
    960 [+   0]:: Note On  [00ch] : Note62[0x3e] Velocity=100
   1440 [+ 480]:: Note Off [00ch] : Note62[0x3e] Velocity=0
   1440 [+   0]:: Note On  [00ch] : Note64[0x40] Velocity=100
   1920 [+ 480]:: Note Off [00ch] : Note64[0x40] Velocity=0
   1920 [+   0]:: Note On  [00ch] : Note65[0x41] Velocity=100
   2400 [+ 480]:: Note Off [00ch] : Note65[0x41] Velocity=0
   2400 [+   0]:: Note On  [00ch] : Note67[0x43] Velocity=100
   2400 [+   0]:: Note On  [00ch] : Note64[0x40] Velocity=100
   2880 [+ 480]:: Note Off [00ch] : Note67[0x43] Velocity=0
   2880 [+   0]:: Note Off [00ch] : Note64[0x40] Velocity=0
   2880 [+   0]:: Note On  [00ch] : Note69[0x45] Velocity=100
   2880 [+   0]:: Note On  [00ch] : Note62[0x3e] Velocity=100
   3360 [+ 480]:: Note Off [00ch] : Note69[0x45] Velocity=0
   3360 [+   0]:: Note Off [00ch] : Note62[0x3e] Velocity=0
   3360 [+   0]:: Note On  [00ch] : Note71[0x47] Velocity=100
   3360 [+   0]:: Note On  [00ch] : Note67[0x43] Velocity=100
   3840 [+ 480]:: Note Off [00ch] : Note71[0x47] Velocity=0
   3840 [+   0]:: Note Off [00ch] : Note67[0x43] Velocity=0
   3840 [+   0]:: Note On  [00ch] : Note72[0x48] Velocity=100
   3840 [+   0]:: Note On  [00ch] : Note60[0x3c] Velocity=100
   3840 [+   0]:: Note On  [00ch] : Note64[0x40] Velocity=100
   4320 [+ 480]:: Note Off [00ch] : Note72[0x48] Velocity=0
   4320 [+   0]:: Note Off [00ch] : Note60[0x3c] Velocity=0
   4320 [+   0]:: Note Off [00ch] : Note64[0x40] Velocity=0
# Track[04] =====================
# Track[05] =====================
# Track[06] =====================
# Track[07] =====================
# Track[08] =====================
# Track[09] =====================
# Track[10] =====================
# Track[11] =====================
# Track[12] =====================
# Track[13] =====================
# Track[14] =====================
# Track[15] =====================
# Track[16] =====================
# Track[17] =====================
# Track[18] =====================

 480 [+ 146]:: Note On [00ch] : Note60[0x3c] Velocity=100 

「480」は累計時間。デルタタイムの総和です。
「146」はデルタタイム。つまり、直前のイベントとの時間差です。
「Note On」は、このイベントがノートオンであることを表します。
「00ch」はチャンネル番号です。
「Note60」の「60」は音階を表します。これは『』です。(鍵盤の中央の...)
「0x3c」は、上の「Note60」の「60」を16進表記にしただけです。
「Velocity=100」は音の強さ(打鍵の強さ?)のようなものです。ベロシティ。

 累計時間 デルタタイム NoteOn/Off チャンネル番号 音階(10進) 音階(16進) ベロシティ 

ただ、この出力フォーマットではさすがに扱いにくいですよね^^;
なので、タブ区切りでデータを出力するようにします。
(追加で「トラック番号」も入れるので、仕切り(# Track[??] ===...)は不要ですね。)
また、NoteOn/Offの項目は、Onを「1」で、Offを「0」で表します。

 トラック番号 累計時間 デルタタイム NoteOn/Off チャンネル番号 音階(10進) ベロシティ 

この修正をおこなったプログラムを下に掲載します。
(変更したのは「ノート・オフ【ボイスメッセージ】」と「ノート・オン【ボイスメッセージ】」の部分だけです。ソースコード上で検索にかけると場所を特定できるはずです。)


//
//  Reading MIDI(SMF) Data   (TAB Format)
//                2010.08.07 - 2010.08.16
//        NoteOn/Off Version : 2010.12.09

#include 
#include 

typedef struct {
    // トラックチャンクのデータを格納する構造体
    char type[4]; // チャンクタイプを示す文字列を格納。「MTrk」が入るはず。[4byte]
    int size;     // トラックチャンクデータのサイズ [4byte]
    char *data;   // トラックデータ(イベントの羅列)へのポインタ
} TrackChunk;

short mergeChar7bit(char x, char y){
    // charの下7bitずつを結合してshort型で出力
    // 【引数】:結合対象となる2つのchar型変数x, y
    short s;
    s = (unsigned char)x; // 上位バイトを先に入れておく
    s <<= 7;              // 7bit左シフト
    s = (s | (unsigned char)(y & 0x7f));   // 下位バイトの下7bitを合成。。。
    return s;
}

int convertEndian(void *input, size_t s){
    // エンディアン変換をおこなう関数
    // stdlib.hをインクルードしてください。
    // 【引数】: void *input...エンディアン変換対象へのポインタ
    // 【引数】: size_t    s...変換対象のバイト数

    int i;   // カウンタ
    char *temp;   // 変換時に用いる一時的配列

    if((temp = (char *)calloc(s, sizeof(char))) == NULL){
        perror("Error: Cannot get memory for temp.");
        return 0;   // 領域確保できず(失敗)
    }

    for(i=0; i<s; i++){   // inputデータをtempに一時保管
        temp[i] = ((char *)input)[i];
    }

    for(i=1; i<=s; i++){   // tempデータを逆方向にしてinputへ代入
        ((char *)input)[i-1] = temp[s-i];
    }

    free(temp);   // 確保した領域を解放

    return 1;   // 正常終了
}

int main(){
    int i, j, k, cnt;   // カウンタ
    FILE *fp;         // ファイルポインタ生成
    int endian; //   エンディアン判定にいろいろ使用(0:BigEndian, 1:LittleEndian)

    // ヘッダチャンク情報
    char  header_chunk_type[4]; // チャンクタイプを示す文字列を格納。「MThd」が入るはず。[4byte]
    int   header_chunk_size;    // ヘッダチャンクデータのサイズ [4byte]
    short smf_format;     // SMFのフォーマットタイプ(0か1か2) [2byte]
    short tracks;         // トラックチャンク総数 [2byte]
    short division;       // 四分音符あたりの分解能(ここではデルタタイム) [2byte]

    // トラックチャンク情報
    TrackChunk *track_chunks;   // トラックチャンク情報を格納する配列のためのポインタ
    unsigned char c;   // イベント解析の際に使用する一時保存用変数
    unsigned char status;   // ステータスバイト用の一時変数
    unsigned int delta;   // デルタタイム
    unsigned int sum_delta;   // 累計時間

    // エンディアン判定
    endian = 1;
    if(*(char *)&endian){   // リトルエンディアンなら...
        endian = 1;   // Little Endian
    } else {   // ビッグエンディアンなら...
        endian = 0;   // Big Endian
    }

    // MIDIファイルを開く
    if((fp = fopen("./sample.mid", "rb")) == NULL){   // バイナリ読み取りモードでファイルを開く
        perror("Error: Cannot open the file.\n");   // 失敗したらエラーを吐く
        return 0;
    }

    // ヘッダチャンク取得
    fread(header_chunk_type, 1, 4, fp);   // チャンクタイプ
    fread(&header_chunk_size, 4, 1, fp);  // チャンクデータサイズ
    fread(&smf_format, 2, 1, fp);   // SMFフォーマットタイプ
    fread(&tracks, 2, 1, fp);       // トラックチャンク総数
    fread(&division, 2, 1, fp);     // 分解能(デルタタイム)

    if(endian){   // リトルエンディアンならビッグエンディアンに変換
        convertEndian(&tracks, sizeof(tracks));
    }

    // トラックチャンク取得
    if((track_chunks = (TrackChunk *)calloc(tracks, sizeof(TrackChunk))) == NULL){   // トラック数に応じて領域確保
        perror("Error: Cannot get memory for track_chunks.");
        return 0;   // 領域確保できず(失敗)
    }
    for(i=0; i<tracks; i++){   // トラック数だけ繰返し
        fread(track_chunks[i].type, 1, 4, fp);   // チャンクタイプ
        fread(&track_chunks[i].size, 4, 1, fp);   // チャンクデータサイズ
        if(endian){   // リトルエンディアンなら要変換
            convertEndian(&track_chunks[i].size, sizeof(track_chunks[i].size));
        }
        if((track_chunks[i].data = (char *)calloc(track_chunks[i].size, sizeof(char))) == NULL){   // データサイズに応じて領域確保
            perror("Error: Cannot get memory for track_chunks[i].data .");
            return 0;   // 領域確保できず(失敗)
        }
        fread(track_chunks[i].data, track_chunks[i].size, sizeof(char), fp);   // データ(イベントの羅列)
    }

    // 読み取ったトラックチャンク情報を出力
    for(i=0; i<tracks; i++){   // トラック数だけ繰返し
        sum_delta = 0;   // トラックごとに累計時間を算出
        //printf("# Track[%02d] =====================\n", i+1);   // もはや仕切りは不要。
        for(j=0; j<track_chunks[i].size; j++){   // データ分だけ繰返し

            delta = 0;   // 初期化
            while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){
                delta = delta | (c & 0x7F);   // 合成
                delta <<= 7;   // 7bit左シフト
            }
            delta = delta | c;    // 合成
            sum_delta += delta;   // 累計時間に加算

            // ランニングステータスルールに対する処理
            if((track_chunks[i].data[j] & 0x80) == 0x80){
                // ランニングステータスルールが適用されない場合は、ステータスバイト用変数を更新。
                status = (unsigned char)track_chunks[i].data[j];   // ステータスバイトを保持しておく
            } else {
                //printf("\b@");   // ランニングステータスルール適用のしるし...は出さない
                j--;   // データバイトの直前のバイト(デルタタイムかな?)を指すようにしておく。
                       // 次の処理でj++するはずなので、そうすればデータバイトにアクセスできる。
            }

            // データ判別
            if((status & 0xf0) == 0x80){
                // ノート・オフ【ボイスメッセージ】
                // 【タブ区切りのフォーマット】
                printf("%d\t", i+1);       // トラック番号
                printf("%d\t", sum_delta); // 累計時間(経過時間)
                printf("%d\t", delta);     // デルタタイム出力
                printf("0\t");           // 0 = Note Off
                printf("%d\t", (status & 0x0f)); // チャンネル番号
                j++;
                printf("%d\t", (unsigned char)track_chunks[i].data[j++]); // 音階
                printf("%d", (unsigned char)track_chunks[i].data[j]);    // ベロシティ
                printf("\n");
            } else if((status & 0xf0) == 0x90){
                // ノート・オン【ボイスメッセージ】
                // 【タブ区切りのフォーマット】
                printf("%d\t", i+1);       // トラック番号
                printf("%d\t", sum_delta); // 累計時間(経過時間)
                printf("%d\t", delta);     // デルタタイム出力
                printf("1\t");            // 1 = Note On
                printf("%d\t", (status & 0x0f)); // チャンネル番号
                j++;
                printf("%d\t", (unsigned char)track_chunks[i].data[j++]); // 音階
                printf("%d", (unsigned char)track_chunks[i].data[j]);    // ベロシティ
                printf("\n");
            } else if((status & 0xf0) == 0xa0){   // ポリフォニック・キー・プレッシャー【ボイスメッセージ】
                j += 2;
            } else if((status & 0xf0) == 0xb0){   // コントロールチェンジ【ボイスメッセージ】、【モードメッセージ】
                j += 2;
            } else if((status & 0xf0) == 0xc0){   // プログラム・チェンジ【ボイスメッセージ】
                j += 1;
            } else if((status & 0xf0) == 0xd0){   // チャンネル・プレッシャー【ボイスメッセージ】
                j += 1;
            } else if((status & 0xf0) == 0xe0){   // ピッチ・ベンド・チェンジ【ボイスメッセージ】
                j += 2;
            } else if((status & 0xf0) == 0xf0){
                // 【システム・メッセージ】
                switch(status & 0x0f){
                    case 0x00:   // エクスクルーシブ・メッセージ
                        j++;
                        // SysExのデータ長を取得
                        cnt = 0;   // 初期化
                        while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){   // フラグビットが1の間はループ
                            cnt = cnt | c;   // 合成
                            cnt <<= 7;   // 7bit左シフト
                        }
                        cnt = cnt | c;   // 合成
                        for(k=1; k<=cnt; k++){   // 長さの分だけデータ取得
                            j++;
                        }
                        j--;   // 行き過ぎた分をデクリメント
                        break;
                    case 0x01:   // MIDIタイムコード
                        j += 1;
                        break;
                    case 0x02:   // ソング・ポジション・ポインタ
                        j += 2;
                        break;
                    case 0x03:   // ソング・セレクト
                        j += 1;
                        break;
                    case 0x06:   // チューン・リクエスト
                        break;
                    case 0x07:   // エクスクルーシブ・メッセージ(?)
                        j++;
                        // SysExのデータ長を取得
                        cnt = 0;   // 初期化
                        while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){   // フラグビットが1の間はループ
                            cnt = cnt | c;   // 合成
                            cnt <<= 7;   // 7bit左シフト
                        }
                        cnt = cnt | c;   // 合成
                        for(k=1; k<=cnt; k++){   // 長さの分だけデータ取得
                            j++;
                        }
                        j--;   // 行き過ぎた分をデクリメント
                        break;
                    case 0x08:   // タイミング・クロック
                    case 0x0A:   // スタート
                    case 0x0B:   // コンティニュー
                    case 0x0C:   // ストップ
                    case 0x0E:   // アクティブ・センシング
                        break;
                    //case 0x0F:   // システムリセット(何か間違っている気がする。。。)
                    //    printf("System Reset");
                    case 0x0F:   // メタイベント
                        j++;
                        switch((unsigned char)track_chunks[i].data[j]){
                            case 0x00:   // シーケンス番号
                                j += 3;  // データ長の分を通り越す
                                break;
                            case 0x01:   // テキスト[可変長]
                            case 0x02:   // 著作権表示[可変長]
                            case 0x03:   // シーケンス名(曲タイトル)・トラック名[可変長]
                            case 0x04:   // 楽器名[可変長]
                            case 0x05:   // 歌詞[可変長]
                            case 0x06:   // マーカー[可変長]
                            case 0x07:   // キューポイント[可変長]
                            case 0x08:   // プログラム名(音色名)[可変長]
                            case 0x09:   // デバイス名(音源名)[可変長]
                                j += 1;
                                cnt = 0;
                                while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){
                                    cnt = cnt | (c & 0x7F);   // 合成
                                    cnt <<= 7;   // 7bit左シフト
                                }
                                cnt = cnt | (c & 0x7F);   // 合成
                                for(k=1; k<=cnt; k++){
                                    j++;
                                }
                                j--;   // 行き過ぎた分をデクリメント
                                break;
                            case 0x20:   // MIDIチャンネルプリフィックス[1byte]
                            case 0x21:   // ポート指定[1byte]
                                j += 2;  // データ長の分を通り越す
                                break;
                            case 0x2F:   // トラック終端[0byte]
                                j += 1;  // データ長の分を通り越す
                                break;
                            case 0x51:   // テンポ設定[3byte]
                                j += 4;  // データ長の分を通り越す
                                break;
                            case 0x54:   // SMPTEオフセット[5byte]
                                j += 6;  // データ長の分を通り越す
                                break;
                            case 0x58:   // 拍子設定[4byte]
                                j += 5;  // データ長の分を通り越す
                                break;
                            case 0x59:   // 調設定[2byte]
                                j += 3;  // データ長の分を通り越す
                                break;
                            case 0x7F:   // シーケンサ特定メタイベント
                                j += 1;
                                cnt = 0;
                                while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){
                                    cnt = cnt | (c & 0x7F);   // 合成
                                    cnt <<= 7;   // 7bit左シフト
                                }
                                cnt = cnt | (c & 0x7F);   // 合成
                                for(k=1; k<=cnt; k++){
                                    j++;
                                }
                                j--;   // 行き過ぎた分をデクリメント
                                break;
                            default :
                                ;
                        }
                        break;
                    default:
                        ;   // 見知らぬステータスなら(知らぬフリ...)
                }
            } else {
                ;   // 見知らぬステータスなら(知らぬフリ...)
            }
        }
    }
    // track_chunks,track_chunks[i].dataはcalloc()で領域確保しているので解放し忘れないように!
    return 1;
}

さて、このプログラムの実行結果が下です。読み込むMIDIファイルは同じものです。

$ gcc -o program ./program2.c
$ ./program2
3	480	146	1	0	60	100
3	960	480	0	0	60	0
3	960	0	1	0	62	100
3	1440	480	0	0	62	0
3	1440	0	1	0	64	100
3	1920	480	0	0	64	0
3	1920	0	1	0	65	100
3	2400	480	0	0	65	0
3	2400	0	1	0	67	100
3	2400	0	1	0	64	100
3	2880	480	0	0	67	0
3	2880	0	0	0	64	0
3	2880	0	1	0	69	100
3	2880	0	1	0	62	100
3	3360	480	0	0	69	0
3	3360	0	0	0	62	0
3	3360	0	1	0	71	100
3	3360	0	1	0	67	100
3	3840	480	0	0	71	0
3	3840	0	0	0	67	0
3	3840	0	1	0	72	100
3	3840	0	1	0	60	100
3	3840	0	1	0	64	100
3	4320	480	0	0	72	0
3	4320	0	0	0	60	0
3	4320	0	0	0	64	0


いかがでしょうか!^^

ちなみに、上のプログラムで解析したサンプルのMIDIファイルも置いておきます。
sample.mid【普通のMIDIファイル】
run_sample.mid【ランニングステータスルールを適用したMIDIファイル】


最後になりましたが、ななし さん、コメントありがとうございました!(^_^)/


余談:
あと、この“タブ区切りフォーマット”を読み込むプログラムも書いてみました。
エラー対策は何もやってませんが、動くことには動きます。
参考までに!

//
//  Tab Data Reader & Printer
//                    2010.12.09

#include 
#include 

int main(){

    FILE *fp;
    int track, sum_delta, delta, on, channel, note, velocity;

    fp = fopen("./tab.dat", "r");   // ファイルを開く

    while(fscanf(fp, "%d\t%d\t%d\t%d\t%d\t%d\t%d\n", &track,
                 &sum_delta, &delta, &on, &channel, ¬e, &velocity) != EOF){
        printf("Track[%d]: ", track);
        printf("累計時間=%d ", sum_delta);
        printf("デルタタイム=%d ", delta);
        printf("%s ", (on ? "NoteOn" : "NoteOff"));
        printf("チャンネル=%d ", channel);
        printf("音階=%d ", note);
        printf("ベロシティ=%d", velocity);
        printf("\n");
    }

    fclose(fp);   // ファイルを閉じる

    return 0;
}

下はその結果

$ gcc -o tab_data_reader ./tab_data_reader.c 
$ ./tab_data_reader
Track[3]: 累計時間=480 デルタタイム=146 NoteOn チャンネル=0 音階=60 ベロシティ=100
Track[3]: 累計時間=960 デルタタイム=480 NoteOff チャンネル=0 音階=60 ベロシティ=0
Track[3]: 累計時間=960 デルタタイム=0 NoteOn チャンネル=0 音階=62 ベロシティ=100
Track[3]: 累計時間=1440 デルタタイム=480 NoteOff チャンネル=0 音階=62 ベロシティ=0
Track[3]: 累計時間=1440 デルタタイム=0 NoteOn チャンネル=0 音階=64 ベロシティ=100
Track[3]: 累計時間=1920 デルタタイム=480 NoteOff チャンネル=0 音階=64 ベロシティ=0
Track[3]: 累計時間=1920 デルタタイム=0 NoteOn チャンネル=0 音階=65 ベロシティ=100
Track[3]: 累計時間=2400 デルタタイム=480 NoteOff チャンネル=0 音階=65 ベロシティ=0
Track[3]: 累計時間=2400 デルタタイム=0 NoteOn チャンネル=0 音階=67 ベロシティ=100
Track[3]: 累計時間=2400 デルタタイム=0 NoteOn チャンネル=0 音階=64 ベロシティ=100
Track[3]: 累計時間=2880 デルタタイム=480 NoteOff チャンネル=0 音階=67 ベロシティ=0
Track[3]: 累計時間=2880 デルタタイム=0 NoteOff チャンネル=0 音階=64 ベロシティ=0
Track[3]: 累計時間=2880 デルタタイム=0 NoteOn チャンネル=0 音階=69 ベロシティ=100
Track[3]: 累計時間=2880 デルタタイム=0 NoteOn チャンネル=0 音階=62 ベロシティ=100
Track[3]: 累計時間=3360 デルタタイム=480 NoteOff チャンネル=0 音階=69 ベロシティ=0
Track[3]: 累計時間=3360 デルタタイム=0 NoteOff チャンネル=0 音階=62 ベロシティ=0
Track[3]: 累計時間=3360 デルタタイム=0 NoteOn チャンネル=0 音階=71 ベロシティ=100
Track[3]: 累計時間=3360 デルタタイム=0 NoteOn チャンネル=0 音階=67 ベロシティ=100
Track[3]: 累計時間=3840 デルタタイム=480 NoteOff チャンネル=0 音階=71 ベロシティ=0
Track[3]: 累計時間=3840 デルタタイム=0 NoteOff チャンネル=0 音階=67 ベロシティ=0
Track[3]: 累計時間=3840 デルタタイム=0 NoteOn チャンネル=0 音階=72 ベロシティ=100
Track[3]: 累計時間=3840 デルタタイム=0 NoteOn チャンネル=0 音階=60 ベロシティ=100
Track[3]: 累計時間=3840 デルタタイム=0 NoteOn チャンネル=0 音階=64 ベロシティ=100
Track[3]: 累計時間=4320 デルタタイム=480 NoteOff チャンネル=0 音階=72 ベロシティ=0
Track[3]: 累計時間=4320 デルタタイム=0 NoteOff チャンネル=0 音階=60 ベロシティ=0
Track[3]: 累計時間=4320 デルタタイム=0 NoteOff チャンネル=0 音階=64 ベロシティ=0

【追記】2010.01.03 : プログラムを若干修正しました(出力部の変更)
以後の更新内容の改善のために、是非ともご評価のほどよろしくお願いします!→

テーマ : サウンド・プログラミング /  ジャンル :

C言語でMIDI(SMF)データを読んでみる!

今回は、MIDI(SMF)データをC言語で解析しましょー!という、ちょい長めのお話です。

※1:当サイトの別記事「ファミコンやらゲームボーイやらのピコピコ音を作るぞ!(with C言語)」のために用意した記事です。)
(MIDIとかSMFについては、[ Wikipedia: MIDI ]が少しだけ参考になると思います。)

MIDI(SMF)のデータ構造については上の※1の記事内に参考サイトを掲載しているのでそちらをご覧下さい。この記事の中では説明を割愛します。^^;


この過程の結果(すなわち、解析プログラム)を知りたい方は記事の下の方を見てくださいね。
(この記事の内容にはミスがあるかもしれませんが、その時はご容赦ください。)

1. とりあえず、ヘッダチャンクを読み取ってみよう!

MIDIはバイナリデータなので少々扱いが面倒な気がしますが、恐れずにやってみましょう!


プログラムはちょっと長くなるので、表示/非表示を選択できるようにしました。
下のボタンを操作して、出したり消したりしてください。(えっ...?)

#include <stdio.h>

int main(){
    // ヘッダチャンク情報を格納する変数群
    char  header_chunk_type[4]; // チャンクタイプを示す文字列を格納。「MThd」が入るはず。[4byte]
    int   header_chunk_size;    // ヘッダチャンクデータのサイズ [4byte]
    short smf_format;     // SMFのフォーマットタイプ(0か1か2) [2byte]
    short tracks;         // トラックチャンク総数 [2byte]
    short division;       // 四分音符あたりの分解能(ここではデルタタイム) [2byte]

    // データ読み取り開始!
    FILE *fp;   // ファイルポインタ生成
    if((fp = fopen("./sample.mid", "rb")) === NULL){   // バイナリ読み取りモードでファイルを開く
        printf("Error: Cannot open the file.
");   // 失敗したらエラーを吐く
        return -1;
    }

    // ヘッダチャンク取得
    fread(header_chunk_type, 1, 4, fp);  // チャンクタイプ【※ア】
    fread(&header_chunk_size, 4, 1, fp); // チャンクデータサイズ【※イ】
    fread(&smf_format, 2, 1, fp);        // SMFフォーマットタイプ
    fread(&tracks, 2, 1, fp);            // トラックチャンク総数
    fread(&division, 2, 1, fp);          // 分解能(デルタタイム)
    
    // 読み取った情報を出力してみよう!
    printf("header_chunk_type : %c%c%c%c
", header_chunk_type[0], 
        header_chunk_type[1], header_chunk_type[2], header_chunk_type[3]);   // 【※ウ】
    printf("header_chunk_size : %d
", header_chunk_size);
    printf("smf_format : %hd
", smf_format);
    printf("tracks     : %hd
", tracks);
    printf("division   : %hd
", division);

    return 0;
}

このプログラムの概説です。
プログラムと同じディレクトリにある./sample.midというファイルを解析します。

念のために書いておきます。。。
#【※ア】文字配列header_chunk_typeの場所に、1byteデータを4つ取り込む...ということをしている。
#   配列の場合は、配列名自体がポインタ(先頭要素のアドレス)になっているので、「&」は不要!
#【※イ】int型変数header_chunk_sizeの場所に、4byteデータを1つ取り込む...ということをしている。
#   配列ではないので、変数名に「&」をつけることでそのアドレスを取得!
#【※ウ】header_chunk_typeの出力時に「%s」ではなく「%c%c%c%c」としているのは、
#   文字列header_chunk_typeには終端文字()が含まれていないからです。


ちなみに、解析するMIDIデータの先頭部分はこんな感じです。

$ hexdump ./sample.mid
00000000  4d 54 68 64 00 00 00 06  00 01 00 12 01 e0 4d 54
00000010  72 6b 00 00 00 5d 00 ff  03 00 00 ff 02 13 43 6f
...(省略)...

4d 54 68 64 : 「MThd」...チャンクタイプ(「MThd」なのでヘッダ!)
00 00 00 06 : 「0x00000006」=「6」...チャンクデータ(smf_format, tracks, division)のサイズは6byte
00 01 : 「0x0001」=「1」...SMFのFormat1
00 12 : 「0x0012」=「18」...18Tracks
01 e0 : 「0x01e0」=「480」...分解能480
(「0x?????」というのは、?????が16進数であることを表します。)

さあ、これで読めるだろうとか思ってやってみたら、見事につまずいたわけです。^^;;

$ ./program 
header_chunk_type : MThd
header_chunk_size : 100663296
smf_format : 256
tracks     : 4608
division   : -8191

なんじゃこりゃ...?(汗)
とんでもない嘘つきの解析プログラムでした。。。(^^;;
あっているのはheader_chunk_typeの「MThd」だけ。

一体、何故このようなことになったのでしょうか...
...にしてもsmf_formatの「256」というのは、あまりに綺麗な数字ですね。(2の8乗)
これは2進数で表すと「100000000」の9桁(=9ビット)ですよね。
データの最小単位はバイト(=8ビット)なので9ビットの情報は16ビットで表します。
すなわち、「00000001 00000000」です。
これを4桁ずつ区切って16進表記にすると「01 00」となります。

あれっ?MIDIのデータには「00 01」と書いてあるのに。。。...もしや!!(ひらめきっ!

そうなんです。おそらく、エンディアンの問題です。
MIDIのデータとして格納されている数値はビッグエンディアン形式ですが、
私はリトルエンディアンの環境(IntelプロセッサのMac)を使っていたのです。

正直、「そんなややこしい問題とか、勘弁してー!!!」と思いました。。。^^;

しかし、そんなことで嘆いてどうするんだ学生よ!と言われそうなので頑張ります。


とりあえず、エンディアンの問題さえ解決すればよいわけです。

2. エンディアンの問題を片付ける!

...これについては、また込み入りそうなので、別の記事として書いておきました。
詳しく知りたい場合は是非エンディアン判定(別記事)を参考に!

先ほど取得した値はエンディアンが逆になっていたようなので、
エンディアンを変換してやればよいのです。
これもエンディアン変換(別記事)を参考に!

ということで、この2つの記事の内容を参考にしながら実装してみました!
...第3節へ?^^;

3. 読み込めたぞ、ヘッダチャンク!

とりあえずプログラムを書いてみました。

#include <stdio.h>
#include <stdlib.h>

int convertEndian(void *input, size_t s){
    // エンディアン変換をおこなう関数
    // stdlib.hをインクルードしてください。
    // 【引数】: void *input...エンディアン変換対象へのポインタ
    // 【引数】: size_t    s...変換対象のバイト数

    int i;   // カウンタ
    char *temp;   // 変換時に用いる一時的配列

    if((temp = (char *)calloc( s, sizeof(char))) == NULL){
        return 0;   // 領域確保できず(失敗)
    }

    for(i=0; i<s; i++){   // inputデータをtempに一時保管
        temp[i] = ((char *)input)[i];
    }

    for(i=1; i<=s; i++){   // tempデータを逆方向にしてinputへ代入
        ((char *)input)[i-1] = temp[s-i];
    }

    free(temp);   // 確保した領域を解放

    return 1;   // 正常終了
}

int main(){
    // ヘッダチャンク情報
    char  header_chunk_type[4]; // チャンクタイプを示す文字列を格納。「MThd」が入るはず。[4byte]
    int   header_chunk_size;    // ヘッダチャンクデータのサイズ [4byte]
    short smf_format;     // SMFのフォーマットタイプ(0か1か2) [2byte]
    short tracks;         // トラックチャンク総数 [2byte]
    short division;       // 四分音符あたりの分解能(ここではデルタタイム) [2byte]

    // MIDI(SMF)データ読み取り
    FILE *fp;         // ファイルポインタ生成
    int endian_check; //   エンディアン判定に使用

    if((fp = fopen("./sample.mid", "rb")) == NULL){   // バイナリ読み取りモードでファイルを開く
        printf("Error: Cannot open the file.
");   // 失敗したらエラーを吐く
        return -1;
    }

    // ヘッダチャンク取得
    fread(header_chunk_type, 1, 4, fp);   // チャンクタイプ
    fread(&header_chunk_size, 4, 1, fp);  // チャンクデータサイズ
    fread(&smf_format, 2, 1, fp);   // SMFフォーマットタイプ
    fread(&tracks, 2, 1, fp);       // トラックチャンク総数
    fread(&division, 2, 1, fp);     // 分解能(デルタタイム)

    // エンディアン判定&変換
    endian_check = 1;
    if(*(char *)&endian_check){   // リトルエンディアンなら...
        // エンディアン変換処理
        convertEndian(&header_chunk_size, sizeof(header_chunk_size));
        convertEndian(&smf_format, sizeof(smf_format));
        convertEndian(&tracks, sizeof(tracks));
        convertEndian(&division, sizeof(division));
    } else {   // ビッグエンディアンなら...
        // 何も処理しない
    }

    // 読み取った情報を出力してみよう!
    printf("header_chunk_type : %c%c%c%c
", header_chunk_type[0], 
        header_chunk_type[1], header_chunk_type[2], header_chunk_type[3]);
    printf("header_chunk_size : %d
", header_chunk_size);
    printf("smf_format : %hd
", smf_format);
    printf("tracks     : %hd
", tracks);
    printf("division   : %hd
", division);

    return 0;
}

...というプログラムでしたが、さあ実行結果を見てみましょう。

$ ./program 
header_chunk_type : MThd
header_chunk_size : 6
smf_format : 1
tracks     : 18
division   : 480


おぉっ、なんだかんだでヘッダチャンクは読み取れましたね!!
次はトラックチャンクの読取に挑戦です!

4. トラックチャンクも読み込もう!(ただし、MIDIイベントはスルー)

先ほどと同様にMIDIデータを眺めてみましょう。

$ hexdump ./sample.mid
00000000  4d 54 68 64 00 00 00 06  00 01 00 12 01 e0 4d 54
00000010  72 6b 00 00 00 5d 00 ff  03 00 00 ff 02 13 43 6f
00000020  70 79 72 69 67 68 74 20  28 43 29 20 32 30 31 30
00000030  20 00 ff 01 10 81 79 83  54 83 43 83 67 96 bc 81
00000040  7a 20 55 52 4c 00 ff 51  03 07 a1 20 00 ff 06 05
00000050  53 65 74 75 70 00 ff 58  04 04 02 18 08 83 60 ff
00000060  51 03 07 a1 20 8b 20 ff  06 05 53 74 61 72 74 00
00000070  ff 2f 00
                   4d 54 72 6b 00  00 01 09 00 ff 03 0c 53
00000080  79 73 74 65 6d 20 53 65  74 75 70 00 ff 21 01 00
00000090  00 f0 05 7e 7f 09 01 f7  3c f0 0a 41 10 42 12 40
...(省略)...

4d 54 72 6b : 「MTrk」...チャンクタイプ(「MTrk」なのでトラック!)
00 00 00 5d : 「0x0000005d」=「93」...チャンクのサイズは93byte
00 ff ...(中略)... 2f 00 ...93byte分のデータ(MIDIイベントの羅列

こんな感じの『 MTrk チャンクサイズ イベントの羅列 』が、1つのトラックとして扱われます。
ヘッダチャンクを読み取ったとき、トラック数は18でしたね。
なので、このMIDIファイルはトラックが18個並んでいるということです。(下の表を参考に^^)

MIDI(SMF)データ
 MThd  サイズは 6byte  Format 1  18Tracks  分解能480 
←ヘッダチャンク
 MTrk  サイズは 93byte  [ 93byte分のMIDIイベント] 
←1個目のトラックチャンク
 MTrk  サイズは 265byte  [265byte分のMIDIイベント] 
←2個目のトラックチャンク
 MTrk  サイズは ?? byte  [?? byte分のMIDIイベント] 
←3個目のトラ...
 MTrk  サイズは ?? byte  [?? byte分のMIDIイベント] 
←4個目
 ・・・ 
(5...16個目)
 MTrk  サイズは ?? byte  [?? byte分のMIDIイベント] 
←17個目
 MTrk  サイズは ?? byte  [?? byte分のMIDIイベント] 
←18個目


さて、実際にトラックチャンクを解析してみましょう。
ただし、まだMIDIイベントについては解析をおこないません。あとでやります。


#include <stdio.h>
#include <stdlib.h>

typedef struct {
    // トラックチャンクのデータを格納する構造体
    char type[4]; // チャンクタイプを示す文字列を格納。「MTrk」が入るはず。[4byte]
    int size;     // トラックチャンクデータのサイズ [4byte]
    char *data;   // トラックデータ(MIDIイベントの羅列)へのポインタ
} TrackChunk;

int convertEndian(void *input, size_t s){
    // エンディアン変換をおこなう関数
    // stdlib.hをインクルードしてください。
    // 【引数】: void *input...エンディアン変換対象へのポインタ
    // 【引数】: size_t    s...変換対象のバイト数

    int i;   // カウンタ
    char *temp;   // 変換時に用いる一時的配列

    if((temp = (char *)calloc(s, sizeof(char))) == NULL){
        perror("Error: Cannot get memory for temp.");
        return 0;   // 領域確保できず(失敗)
    }

    for(i=0; i<s; i++){   // inputデータをtempに一時保管
        temp[i] = ((char *)input)[i];
    }

    for(i=1; i<=s; i++){   // tempデータを逆方向にしてinputへ代入
        ((char *)input)[i-1] = temp[s-i];
    }

    free(temp);   // 確保した領域を解放

    return 1;   // 正常終了
}

int main(){
    int i, j;   // カウンタ
    FILE *fp;         // ファイルポインタ生成
    int endian; //   エンディアン判定にいろいろ使用(0:BigEndian, 1:LittleEndian)

    // ヘッダチャンク情報
    char  header_chunk_type[4]; // チャンクタイプを示す文字列を格納。「MThd」が入るはず。[4byte]
    int   header_chunk_size;    // ヘッダチャンクデータのサイズ [4byte]
    short smf_format;     // SMFのフォーマットタイプ(0か1か2) [2byte]
    short tracks;         // トラックチャンク総数 [2byte]
    short division;       // 四分音符あたりの分解能(ここではデルタタイム) [2byte]

    // トラックチャンク情報
    TrackChunk *track_chunks;   // トラックチャンク情報を格納する配列のためのポインタ


    // エンディアン判定
    endian = 1;
    if(*(char *)&endian){   // リトルエンディアンなら...
        endian = 1;   // Little Endian
    } else {   // ビッグエンディアンなら...
        endian = 0;   // Big Endian
    }

    // MIDIファイルを開く
    if((fp = fopen("./sample.mid", "rb")) == NULL){   // バイナリ読み取りモードでファイルを開く
        perror("Error: Cannot open the file.
");   // 失敗したらエラーを吐く
        return 0;
    }

    // ヘッダチャンク取得
    fread(header_chunk_type, 1, 4, fp);   // チャンクタイプ
    fread(&header_chunk_size, 4, 1, fp);  // チャンクデータサイズ
    fread(&smf_format, 2, 1, fp);   // SMFフォーマットタイプ
    fread(&tracks, 2, 1, fp);       // トラックチャンク総数
    fread(&division, 2, 1, fp);     // 分解能(デルタタイム)

    // 必要ならエンディアン変換
    if(endian){   // リトルエンディアンなら要変換
        // エンディアン変換処理
        convertEndian(&header_chunk_size, sizeof(header_chunk_size));
        convertEndian(&smf_format, sizeof(smf_format));
        convertEndian(&tracks, sizeof(tracks));
        convertEndian(&division, sizeof(division));
    }

    // 読み取ったヘッダチャンク情報を出力
    printf("# Header ========================
"); 
    printf("header_chunk_type : %c%c%c%c
", header_chunk_type[0], 
        header_chunk_type[1], header_chunk_type[2], header_chunk_type[3]);
    printf("header_chunk_size : %d
", header_chunk_size);
    printf("smf_format : %hd
", smf_format);
    printf("tracks     : %hd
", tracks);
    printf("division   : %hd
", division);


    // トラックチャンク取得
    if((track_chunks = (TrackChunk *)calloc(tracks, sizeof(TrackChunk))) == NULL){   // トラック数に応じて領域確保
        perror("Error: Cannot get memory for track_chunks.");
        return 0;   // 領域確保できず(失敗)
    }
    for(i=0; i<tracks; i++){   // トラック数だけ繰返し
        fread(track_chunks[i].type, 1, 4, fp);   // チャンクタイプ
        fread(&track_chunks[i].size, 4, 1, fp);   // チャンクデータサイズ
        if(endian){   // リトルエンディアンなら要変換
            convertEndian(&track_chunks[i].size, sizeof(track_chunks[i].size));
        }
        if((track_chunks[i].data = (char *)calloc(track_chunks[i].size, sizeof(char))) == NULL){   // データサイズに応じて領域確保
            perror("Error: Cannot get memory for track_chunks[i].data .");
            return 0;   // 領域確保できず(失敗)
        }
        fread(track_chunks[i].data, track_chunks[i].size, sizeof(char), fp);   // データ(MIDIイベントの羅列)
    }

    // 読み取ったトラックチャンク情報を出力
    for(i=0; i<tracks; i++){   // トラック数だけ繰返し
        printf("# Track[%02d] =====================
", i);
        printf("track_chunks[%d].type : %c%c%c%c
", i, track_chunks[i].type[0], 
            track_chunks[i].type[1], track_chunks[i].type[2], track_chunks[i].type[3]);
        printf("track_chunks[%d].size : %d
", i, track_chunks[i].size);
        printf("track_chunks[%d].data
 : ", i);
        for(j=0; j<track_chunks[i].size; j++){
            printf("%02x ", (unsigned char)track_chunks[i].data[j]);   // 16進表記で出力
            if(!((j+1)%10)) printf("
 : ");   // 10バイト出力するたびに改行
        }
        printf("
");
    }

    return 1;
}

いろいろと問題(例えばこんなこと)にぶつかったりして困りましたが、
無事にトラックチャンクを読み取ることができました。

さて、次こそ本題ですね。MIDIイベントの解析をしようと思います!

5. いよいよ、本体データ(MIDIイベント)を読んでいこう!

記事がここまで長いと、最初に紹介したサイトへのリンクまで行くのが面倒ですね...(^^;;
...なので改めて紹介します。ここからは以下のサイトを参考にします。


about Standard MIDI format
[ http://www2s.biglobe.ne.jp/~yyagi/material/smfspec.html ]
MIDI規格一覧表
[ http://www2.odn.ne.jp/~cbu69490/MIDI/MIDIlect2/MIDIlect_end.html ]
【資料】SMFの解説
[ http://members.jcom.home.ne.jp/0127953801/bubu/jukusui.htm ]
ファイル形式、MIDIシーケンスデータ
[ http://altered.s201.xrea.com/documentsdaw/m20070615fileformatmidi.html ]
小泉プロジェクト MIDIミニ知識
[ http://homepage3.nifty.com/koizumipro/miditowa.htm ]
StandardMIDI_document10
[ http://www.ne.jp/asahi/10/40/onptank/doc_smf10.html ]

結局、いろいろ妥協するはめになりました。。。
(メタイベント開始を示す「FF」とシステムリセット「FF」の判別がよくわからなかったので、システムリセットは見つけることができません。(システムリセットはほとんど使われないそうですが...^^;;))
(それから、日本語に対応していない...とか。ほかもいろいろ。)

...という感じで問題が多々あるような気がしますが、とりあえず重要そうなデータは取り出すことが出来るようになったので良しとします。(良しとさせてください...m(_ _;)m)


//
//  Reading MIDI(SMF) Data
//                2010.08.07 - 2010.08.16

#include <stdio.h>
#include <stdlib.h>

typedef struct {
    // トラックチャンクのデータを格納する構造体
    char type[4]; // チャンクタイプを示す文字列を格納。「MTrk」が入るはず。[4byte]
    int size;     // トラックチャンクデータのサイズ [4byte]
    char *data;   // トラックデータ(イベントの羅列)へのポインタ
} TrackChunk;

short mergeChar7bit(char x, char y){
    // charの下7bitずつを結合してshort型で出力
    // 【引数】:結合対象となる2つのchar型変数x, y
    short s;
    s = (unsigned char)x; // 上位バイトを先に入れておく
    s <<= 7;              // 7bit左シフト
    s = (s | (unsigned char)(y & 0x7f));   // 下位バイトの下7bitを合成。。。
    return s;
}

int convertEndian(void *input, size_t s){
    // エンディアン変換をおこなう関数
    // stdlib.hをインクルードしてください。
    // 【引数】: void *input...エンディアン変換対象へのポインタ
    // 【引数】: size_t    s...変換対象のバイト数

    int i;   // カウンタ
    char *temp;   // 変換時に用いる一時的配列

    if((temp = (char *)calloc(s, sizeof(char))) == NULL){
        perror("Error: Cannot get memory for temp.");
        return 0;   // 領域確保できず(失敗)
    }

    for(i=0; i<s; i++){   // inputデータをtempに一時保管
        temp[i] = ((char *)input)[i];
    }

    for(i=1; i<=s; i++){   // tempデータを逆方向にしてinputへ代入
        ((char *)input)[i-1] = temp[s-i];
    }

    free(temp);   // 確保した領域を解放

    return 1;   // 正常終了
}

int main(){
    int i, j, k, cnt;   // カウンタ
    FILE *fp;         // ファイルポインタ生成
    int endian; //   エンディアン判定にいろいろ使用(0:BigEndian, 1:LittleEndian)

    // ヘッダチャンク情報
    char  header_chunk_type[4]; // チャンクタイプを示す文字列を格納。「MThd」が入るはず。[4byte]
    int   header_chunk_size;    // ヘッダチャンクデータのサイズ [4byte]
    short smf_format;     // SMFのフォーマットタイプ(0か1か2) [2byte]
    short tracks;         // トラックチャンク総数 [2byte]
    short division;       // 四分音符あたりの分解能(ここではデルタタイム) [2byte]

    // トラックチャンク情報
    TrackChunk *track_chunks;   // トラックチャンク情報を格納する配列のためのポインタ
    unsigned char c;   // イベント解析の際に使用する一時保存用変数
    unsigned char status;   // ステータスバイト用の一時変数
    unsigned int delta;   // デルタタイム


    // エンディアン判定
    endian = 1;
    if(*(char *)&endian){   // リトルエンディアンなら...
        endian = 1;   // Little Endian
    } else {   // ビッグエンディアンなら...
        endian = 0;   // Big Endian
    }

    // MIDIファイルを開く
    if((fp = fopen("./sample.mid", "rb")) == NULL){   // バイナリ読み取りモードでファイルを開く
        perror("Error: Cannot open the file.
");   // 失敗したらエラーを吐く
        return 0;
    }

    // ヘッダチャンク取得
    fread(header_chunk_type, 1, 4, fp);   // チャンクタイプ
    fread(&header_chunk_size, 4, 1, fp);  // チャンクデータサイズ
    fread(&smf_format, 2, 1, fp);   // SMFフォーマットタイプ
    fread(&tracks, 2, 1, fp);       // トラックチャンク総数
    fread(&division, 2, 1, fp);     // 分解能(デルタタイム)

    // 必要ならエンディアン変換
    if(endian){   // リトルエンディアンなら要変換
        // エンディアン変換処理
        convertEndian(&header_chunk_size, sizeof(header_chunk_size));
        convertEndian(&smf_format, sizeof(smf_format));
        convertEndian(&tracks, sizeof(tracks));
        convertEndian(&division, sizeof(division));
    }

    // 読み取ったヘッダチャンク情報を出力
    printf("# Header ========================
"); 
    printf("header_chunk_type : %c%c%c%c
", header_chunk_type[0], 
        header_chunk_type[1], header_chunk_type[2], header_chunk_type[3]);
    printf("header_chunk_size : %d
", header_chunk_size);
    printf("smf_format : %hd
", smf_format);
    printf("tracks     : %hd
", tracks);
    printf("division   : %hd
", division);


    // トラックチャンク取得
    if((track_chunks = (TrackChunk *)calloc(tracks, sizeof(TrackChunk))) == NULL){   // トラック数に応じて領域確保
        perror("Error: Cannot get memory for track_chunks.");
        return 0;   // 領域確保できず(失敗)
    }
    for(i=0; i<tracks; i++){   // トラック数だけ繰返し
        fread(track_chunks[i].type, 1, 4, fp);   // チャンクタイプ
        fread(&track_chunks[i].size, 4, 1, fp);   // チャンクデータサイズ
        if(endian){   // リトルエンディアンなら要変換
            convertEndian(&track_chunks[i].size, sizeof(track_chunks[i].size));
        }
        if((track_chunks[i].data = (char *)calloc(track_chunks[i].size, sizeof(char))) == NULL){   // データサイズに応じて領域確保
            perror("Error: Cannot get memory for track_chunks[i].data .");
            return 0;   // 領域確保できず(失敗)
        }
        fread(track_chunks[i].data, track_chunks[i].size, sizeof(char), fp);   // データ(イベントの羅列)
    }

    // 読み取ったトラックチャンク情報を出力
    for(i=0; i<tracks; i++){   // トラック数だけ繰返し
        printf("# Track[%02d] =====================
", i+1);
        printf("track_chunks[%d].type : %c%c%c%c
", i, track_chunks[i].type[0], 
            track_chunks[i].type[1], track_chunks[i].type[2], track_chunks[i].type[3]);
        printf("track_chunks[%d].size : %d
", i, track_chunks[i].size);
        printf("track_chunks[%d].data
", i);
        for(j=0; j<track_chunks[i].size; j++){   // データ分だけ繰返し

            delta = 0;   // 初期化
            while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){
                delta = delta | (c & 0x7F);   // 合成
                delta <<= 7;   // 7bit左シフト
            }
            delta = delta | c;   // 合成
            printf("%7d:: ", delta);   // デルタタイム出力
            
            // ランニングステータスルールに対する処理
            if((track_chunks[i].data[j] & 0x80) == 0x80){
                // ランニングステータスルールが適用されない場合は、ステータスバイト用変数を更新。
                status = (unsigned char)track_chunks[i].data[j];   // ステータスバイトを保持しておく
            } else {
                printf("@");   // ランニングステータスルール適用のしるし
                j--;   // データバイトの直前のバイト(デルタタイムかな?)を指すようにしておく。
                       // 次の処理でj++するはずなので、そうすればデータバイトにアクセスできる。
            }

            // データ判別
            if((status & 0xf0) == 0x80){
                // ノート・オフ【ボイスメッセージ】
                printf("Note Off [%02dch] : ", (status & 0x0f));
                j++;
                printf("Note%d", (unsigned char)track_chunks[i].data[j]);
                printf("[0x%02x] ", (unsigned char)track_chunks[i].data[j++]);
                printf("Velocity=%d
", (unsigned char)track_chunks[i].data[j]);
            } else if((status & 0xf0) == 0x90){
                // ノート・オン【ボイスメッセージ】
                printf("Note On  [%02dch] : ", (status & 0x0f));
                j++;
                printf("Note%d", (unsigned char)track_chunks[i].data[j]);
                printf("[0x%02x] ", (unsigned char)track_chunks[i].data[j++]);
                printf("Velocity=%d
", (unsigned char)track_chunks[i].data[j]);
            } else if((status & 0xf0) == 0xa0){
                // ポリフォニック・キー・プレッシャー【ボイスメッセージ】
                printf("Polyphonic Key Pressure [%02dch] : ", (status & 0x0f));
                j++;
                printf("Note%d", (unsigned char)track_chunks[i].data[j]);
                printf("[0x%02x] ", (unsigned char)track_chunks[i].data[j++]);
                printf("Pressure=%d
", (unsigned char)track_chunks[i].data[j]);
            } else if((status & 0xf0) == 0xb0){
                // コントロールチェンジ【ボイスメッセージ】、【モードメッセージ】
                printf("Control Change [%02dch] : ", (status & 0x0f));
                j++;
                c = (unsigned char)track_chunks[i].data[j++];
                if(c<=63){   // 連続可変タイプのエフェクトに関するコントロール情報(MSBもLSBも)
                    // (ホントは「0<=c && c<=63」と書きたいけど、warningが出るので「c<=63」にする)
                    printf("VariableEffect(");
                    switch(c){
                        case 0:    // 上位バイト[MSB]
                        case 32:   // 下位バイト[LSB]
                            printf("BankSelect[%s]", (c==0)?"MSB":"LSB");   // バンク・セレクト
                            break;
                        case 1:
                        case 33:
                            printf("ModulationDepth[%s]", (c==1)?"MSB":"LSB");   // モジュレーション・デプス
                            break;
                        case 2:
                        case 34:
                            printf("BreathType[%s]", (c==2)?"MSB":"LSB");   // ブレス・タイプ
                            break;
                        case 4:
                        case 36:
                            printf("FootType[%s]", (c==4)?"MSB":"LSB");   // フット・タイプ
                            break;
                        case 5:
                        case 37:
                            printf("PortamentoTime[%s]", (c==5)?"MSB":"LSB");   // ポルタメント・タイム
                            break;
                        case 6:
                        case 38:
                            printf("DataEntry[%s]", (c==6)?"MSB":"LSB");   // データ・エントリー
                            break;
                        case 7:
                        case 39:
                            printf("MainVolume[%s]", (c==7)?"MSB":"LSB");   // メイン・ボリューム
                            break;
                        case 8:
                        case 40:
                            printf("BalanceControl[%s]", (c==8)?"MSB":"LSB");   // バランス・コントロール
                            break;
                        case 10:
                        case 42:
                            printf("Panpot[%s]", (c==10)?"MSB":"LSB");   // パンポット
                            break;
                        case 11:
                        case 43:
                            printf("Expression[%s]", (c==11)?"MSB":"LSB");   // エクスプレッション
                            break;
                        case 16:
                        case 17:
                        case 18:
                        case 19:
                        case 48:
                        case 49:
                        case 50:
                        case 51:
                            printf("SomethingElse_No.%d[%s]", c, (c==16)?"MSB":"LSB");   // 汎用操作子
                            break;
                        default:
                            printf("##UndefinedType_No.%d[%s]", (c<32)?c:c-32, (c<32)?"MSB":"LSB");   // よくわからないコントロール
                    }

                    printf(")=%d", (unsigned char)track_chunks[i].data[j]);
                } else if(64<=c && c<=95){   // 連続可変でないタイプのエフェクトに関するコントロール情報
                    printf("InvariableEffect(");
                    switch(c){
                        case 64:
                            printf("Hold1(Damper)");   // ホールド1(ダンパー)
                            break;
                        case 65:
                            printf("Portamento");   // ポルタメント
                            break;
                        case 66:
                            printf("Sostenuto");   // ソステヌート
                            break;
                        case 67:
                            printf("SoftPedal");   // ソフト・ペダル
                            break;
                        case 69:
                            printf("Hold2(Freeze)");   // ホールド2(フリーズ)
                            break;
                        case 71:
                            printf("HarmonicIntensity");   // ハーモニック・インテンシティ
                            break;
                        case 72:
                            printf("ReleaseTime");   // リリース・タイム
                            break;
                        case 73:
                            printf("AttackTime");   // アタック・タイム
                            break;
                        case 74:
                            printf("Brightness");   // ブライトネス
                            break;
                        case 80:
                        case 81:
                        case 82:
                        case 83:
                            printf("SomethingElse_No.%d", c);   // 汎用操作子
                            break;
                        case 91:
                            printf("ExternalEffect");   // 外部エフェクト
                            break;
                        case 92:
                            printf("Tremolo");   // トレモロ
                            break;
                        case 93:
                            printf("Chorus");   // コーラス
                            break;
                        case 94:
                            printf("Celeste");   // セレステ
                            break;
                        case 95:
                            printf("Phaser");   // フェイザー
                            break;
                        default:
                            printf("##UndefinedType_No.%d", c);   // よくわからないコントロール
                    }
                    printf(")=%d", (unsigned char)track_chunks[i].data[j]);
                } else if(96<=c && c<=119){   // 特殊な情報
                    printf("SpecialPurpose(");
                    switch(c){
                        case 96:
                            printf("DataIncrement");   // データ・インクリメント
                            break;
                        case 97:
                            printf("DataDecrement");   // デクリメント
                            break;
                        case 98:
                            printf("NRPN[LSB]");   // NRPNのLSB
                            break;
                        case 99:
                            printf("NRPN[MSB]");   // NRPNのMSB
                            break;
                        case 100:
                            printf("RPN[LSB]");   // RPNのLSB
                            break;
                        case 101:
                            printf("RPN[MSB]");   // RPNのMSB
                            break;
                        default:
                            printf("##UndefinedType_No.%d", c);   // よくわからないコントロール
                    }
                    printf(")=%d", (unsigned char)track_chunks[i].data[j]);
                } else if(120<=c && c<=127){   // モード・メッセージ
                    printf("ModeMessage(");
                    switch(c){
                        case 120:
                            printf("AllSoundOff");   // オール・サウンド・オフ
                            break;
                        case 121:
                            printf("ResetAllController");   // リセット・オール・コントローラー
                            break;
                        case 122:
                            printf("LocalControl");   // ローカル・コントロール
                            break;
                        case 123:
                            printf("AllNoteOff");   // オール・ノート・オフ
                            break;
                        case 124:
                            printf("OmniOn");   // オムニ・オン
                            break;
                        case 125:
                            printf("OmniOff");   // オムニ・オフ
                            break;
                        case 126:
                            printf("MonoModeOn");   // モノモード・オン(ポリモード・オフ)
                            break;
                        case 127:
                            printf("PolyModeOn");   // ポリモード・オン(モノモード・オフ)
                            break;
                        default:
                            printf("##UndefinedType_No.%d", c);   // よくわからないコントロール
                    }
                    printf(")=%d", c, (unsigned char)track_chunks[i].data[j]);
                }
                printf("
");
            } else if((status & 0xf0) == 0xc0){
                // プログラム・チェンジ【ボイスメッセージ】
                printf("Program Change [%02dch] : ", (status & 0x0f));
                j++;
                printf("ToneNo=%d
", (unsigned char)track_chunks[i].data[j]);
            } else if((status & 0xf0) == 0xd0){
                // チャンネル・プレッシャー【ボイスメッセージ】
                printf("Channel Pressure [%02dch] : ", (status & 0x0f));
                j++;
                printf("Pressure=%d
", (unsigned char)track_chunks[i].data[j]);
            } else if((status & 0xf0) == 0xe0){
                // ピッチ・ベンド・チェンジ【ボイスメッセージ】
                printf("Pitch Bend Change [%02dch] : ", (status & 0x0f));
                j++;
                printf("Bend=%hd", mergeChar7bit(track_chunks[i].data[j+1], track_chunks[i].data[j]) - 8192);
                printf(" (LSB:%d", (unsigned char)track_chunks[i].data[j++]);
                printf(", MSB:%d)
", (unsigned char)track_chunks[i].data[j]);
            } else if((status & 0xf0) == 0xf0){
                // 【システム・メッセージ】
                switch(status & 0x0f){
                    case 0x00:   // エクスクルーシブ・メッセージ
                        printf("F0 Exclusive Message : ");
                        j++;

                        // SysExのデータ長を取得
                        cnt = 0;   // 初期化
                        while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){   // フラグビットが1の間はループ
                            cnt = cnt | c;   // 合成
                            cnt <<= 7;   // 7bit左シフト
                        }
                        cnt = cnt | c;   // 合成
                        printf(" Length=%u", (unsigned int)cnt);   // SysExのデータ長を取得

                        for(k=1; k<=cnt; k++){   // 長さの分だけデータ取得
                            printf("[%02x]", (unsigned char)track_chunks[i].data[j++]);
                        }
                        j--;   // 行き過ぎた分をデクリメント

                        break;
                    case 0x01:   // MIDIタイムコード
                        printf("MIDI Time Code : ");
                        j++;
                        printf("(frame/sec/min/hour)=%d", (unsigned char)track_chunks[i].data[j]);
                        break;
                    case 0x02:   // ソング・ポジション・ポインタ
                        printf("Song Position Pointer : ");
                        j++;
                        printf("Position=%hd[MIDI beat]", mergeChar7bit(track_chunks[i].data[j+1], track_chunks[i].data[j]));
                        printf(" (LSB:%d", (unsigned char)track_chunks[i].data[j++]);
                        printf(", MSB:%d)", (unsigned char)track_chunks[i].data[j]);
                        break;
                    case 0x03:   // ソング・セレクト
                        printf("Song Select : ");
                        j++;
                        printf("SelectNo=%d", (unsigned char)track_chunks[i].data[j]);
                        break;
                    case 0x06:   // チューン・リクエスト
                        printf("Tune Request");
                        break;
                    case 0x07:   // エンド・オブ・エクスクルーシブでもあるけども...
                                 // F7ステータスの場合のエクスクルーシブ・メッセージ
                        //printf("@End of Exclusive");
                        printf("F7 Exclusive Message : ");
                        j++;

                        // SysExのデータ長を取得
                        cnt = 0;   // 初期化
                        while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){   // フラグビットが1の間はループ
                            cnt = cnt | c;   // 合成
                            cnt <<= 7;   // 7bit左シフト
                        }
                        cnt = cnt | c;   // 合成
                        printf(" Length=%u", (unsigned int)cnt);   // SysExのデータ長を取得

                        for(k=1; k<=cnt; k++){   // 長さの分だけデータ取得
                            printf("[%02x]", (unsigned char)track_chunks[i].data[j++]);
                        }
                        j--;   // 行き過ぎた分をデクリメント

                        break;
                    case 0x08:   // タイミング・クロック
                        printf("Timing Clock");
                        break;
                    case 0x0A:   // スタート
                        printf("Start");
                        break;
                    case 0x0B:   // コンティニュー
                        printf("Continue");
                        break;
                    case 0x0C:   // ストップ
                        printf("Stop");
                        break;
                    case 0x0E:   // アクティブ・センシング
                        printf("Active Sensing");
                        break;
                    //case 0x0F:   // システムリセット(何か間違っている気がする。。。)
                    //    printf("System Reset");
                    case 0x0F:   // メタイベント
                        printf("Meta Ivent : ");
                        j++;
                        switch((unsigned char)track_chunks[i].data[j]){
                            case 0x00:   // シーケンス番号
                                printf("Sequence Number=");
                                j += 2;   // データ長の分を通り越す
                                cnt = (unsigned char)track_chunks[i].data[j++];
                                cnt <<= 8;   // 8bit左シフト
                                cnt = cnt | (unsigned char)track_chunks[i].data[j];
                                printf("%d", cnt);
                                break;
                            case 0x01:   // テキスト[可変長]
                                printf("Text=");
                                cnt = 0;
                                j += 1;
                                while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){
                                    cnt = cnt | (c & 0x7F);   // 合成
                                    cnt <<= 7;   // 7bit左シフト
                                }
                                cnt = cnt | (c & 0x7F);   // 合成
                                for(k=1; k<=cnt; k++){
                                    printf("%c", track_chunks[i].data[j++]);
                                }
                                j--;   // 行き過ぎた分をデクリメント
                                break;
                            case 0x02:   // 著作権表示[可変長]
                                printf("Copyright=");
                                cnt = 0;
                                j += 1;
                                while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){
                                    cnt = cnt | (c & 0x7F);   // 合成
                                    cnt <<= 7;   // 7bit左シフト
                                }
                                cnt = cnt | (c & 0x7F);   // 合成
                                for(k=1; k<=cnt; k++){
                                    printf("%c", track_chunks[i].data[j++]);
                                }
                                j--;   // 行き過ぎた分をデクリメント
                                break;
                            case 0x03:   // シーケンス名(曲タイトル)・トラック名[可変長]
                                printf("Title=");
                                cnt = 0;
                                j += 1;
                                while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){
                                    cnt = cnt | (c & 0x7F);   // 合成
                                    cnt <<= 7;   // 7bit左シフト
                                }
                                cnt = cnt | (c & 0x7F);   // 合成
                                for(k=1; k<=cnt; k++){
                                    printf("%c", track_chunks[i].data[j++]);
                                }
                                j--;   // 行き過ぎた分をデクリメント
                                break;
                            case 0x04:   // 楽器名[可変長]
                                printf("InstName=");
                                cnt = 0;
                                j += 1;
                                while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){
                                    cnt = cnt | (c & 0x7F);   // 合成
                                    cnt <<= 7;   // 7bit左シフト
                                }
                                cnt = cnt | (c & 0x7F);   // 合成
                                for(k=1; k<=cnt; k++){
                                    printf("%c", track_chunks[i].data[j++]);
                                }
                                j--;   // 行き過ぎた分をデクリメント
                                break;
                            case 0x05:   // 歌詞[可変長]
                                printf("Words=");
                                cnt = 0;
                                j += 1;
                                while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){
                                    cnt = cnt | (c & 0x7F);   // 合成
                                    cnt <<= 7;   // 7bit左シフト
                                }
                                cnt = cnt | (c & 0x7F);   // 合成
                                for(k=1; k<=cnt; k++){
                                    printf("%c", track_chunks[i].data[j++]);
                                }
                                j--;   // 行き過ぎた分をデクリメント
                                break;
                            case 0x06:   // マーカー[可変長]
                                printf("Marker=");
                                cnt = 0;
                                j += 1;
                                while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){
                                    cnt = cnt | (c & 0x7F);   // 合成
                                    cnt <<= 7;   // 7bit左シフト
                                }
                                cnt = cnt | (c & 0x7F);   // 合成
                                for(k=1; k<=cnt; k++){
                                    printf("%c", track_chunks[i].data[j++]);
                                }
                                j--;   // 行き過ぎた分をデクリメント
                                break;
                            case 0x07:   // キューポイント[可変長]
                                printf("CuePoint=");
                                cnt = 0;
                                j += 1;
                                while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){
                                    cnt = cnt | (c & 0x7F);   // 合成
                                    cnt <<= 7;   // 7bit左シフト
                                }
                                cnt = cnt | (c & 0x7F);   // 合成
                                for(k=1; k<=cnt; k++){
                                    printf("%c", track_chunks[i].data[j++]);
                                }
                                j--;   // 行き過ぎた分をデクリメント
                                break;
                            case 0x08:   // プログラム名(音色名)[可変長]
                                printf("ProgramName=");
                                cnt = 0;
                                j += 1;
                                while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){
                                    cnt = cnt | (c & 0x7F);   // 合成
                                    cnt <<= 7;   // 7bit左シフト
                                }
                                cnt = cnt | (c & 0x7F);   // 合成
                                for(k=1; k<=cnt; k++){
                                    printf("%c", track_chunks[i].data[j++]);
                                }
                                j--;   // 行き過ぎた分をデクリメント
                                break;
                            case 0x09:   // デバイス名(音源名)[可変長]
                                printf("DeviceName=");
                                cnt = 0;
                                j += 1;
                                while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){
                                    cnt = cnt | (c & 0x7F);   // 合成
                                    cnt <<= 7;   // 7bit左シフト
                                }
                                cnt = cnt | (c & 0x7F);   // 合成
                                for(k=1; k<=cnt; k++){
                                    printf("%c", track_chunks[i].data[j++]);
                                }
                                j--;   // 行き過ぎた分をデクリメント
                                break;
                            case 0x20:   // MIDIチャンネルプリフィックス[1byte]
                                printf("MidiChannelPrefix=");
                                j += 2;   // データ長の分を通り越す
                                cnt = (unsigned char)track_chunks[i].data[j];
                                printf("%d", cnt);
                                break;
                            case 0x21:   // ポート指定[1byte]
                                printf("Port=");
                                j += 2;   // データ長の分を通り越す
                                cnt = (unsigned char)track_chunks[i].data[j];
                                printf("%d", cnt);
                                break;
                            case 0x2F:   // トラック終端[0byte]
                                printf("End of Track");
                                j += 1;   // データ長の分を通り越す
                                break;
                            case 0x51:   // テンポ設定[3byte]
                                printf("Temp=");
                                j += 2;   // データ長の分を通り越す
                                cnt = (unsigned char)track_chunks[i].data[j++];
                                cnt <<= 8;   // 8bit左シフト
                                cnt = cnt | (unsigned char)track_chunks[i].data[j++];
                                cnt <<= 8;   // 8bit左シフト
                                cnt = cnt | (unsigned char)track_chunks[i].data[j];
                                printf("%d", cnt);
                                break;
                            case 0x54:   // SMPTEオフセット[5byte]
                                printf("SMPTE_Offset=");
                                j += 2;   // データ長の分を通り越す
                                cnt = (unsigned char)track_chunks[i].data[j++];
                                switch(cnt & 0xC0){   // フレーム速度
                                    case 0x00:
                                        printf("24fps");
                                        break;
                                    case 0x01:
                                        printf("25fps");
                                        break;
                                    case 0x10:
                                        printf("30fps(DropFrame)");
                                        break;
                                    case 0x11:
                                        printf("30fps");
                                        break;
                                }
                                printf(",Hour:%d", (cnt & 0x3F));   // 時間
                                printf(",Min:%d", (unsigned char)track_chunks[i].data[j++]);   // 分
                                printf(",Sec:%d", (unsigned char)track_chunks[i].data[j++]);   // 秒
                                printf(",Frame:%d", (unsigned char)track_chunks[i].data[j++]);   // フレーム
                                printf(",SubFrame:%d", (unsigned char)track_chunks[i].data[j]);   // サブフレーム
                                break;
                            case 0x58:   // 拍子設定[4byte]
                                printf("Rhythm=");
                                j += 2;   // データ長の分を通り越す
                                printf("%d", (unsigned char)track_chunks[i].data[j++]);
                                cnt = 1;
                                for(k=0; k<=(int)track_chunks[i].data[j]; k++){   // 拍子の分母を算出する
                                    cnt *= 2;   // 2の累乗
                                }
                                printf("/%d", cnt);
                                j++;
                                printf(" ClockPerBeat=%d", (unsigned char)track_chunks[i].data[j++]);
                                printf(" 32NotePer4Note=%d", (unsigned char)track_chunks[i].data[j]);
                                break;
                            case 0x59:   // 調設定[2byte]
                                printf("Key=");
                                j += 2;   // データ長の分を通り越す
                                cnt = (int)track_chunks[i].data[j++];
                                if(c > 0){
                                    printf("Sharp[%d]", c);
                                } else if(c == 0){
                                    printf("C");
                                } else {
                                    printf("Flat[%d]", c);
                                }
                                cnt = (int)track_chunks[i].data[j];
                                if(c == 0){
                                    printf("_Major");
                                } else {
                                    printf("_Minor");
                                }

                                break;
                            case 0x7F:   // シーケンサ特定メタイベント
                                printf("SpecialIvent=");
                                cnt = 0;
                                j += 1;
                                while((c = (unsigned char)track_chunks[i].data[j++]) & 0x80){
                                    cnt = cnt | (c & 0x7F);   // 合成
                                    cnt <<= 7;   // 7bit左シフト
                                }
                                cnt = cnt | (c & 0x7F);   // 合成
                                for(k=1; k<=cnt; k++){
                                    printf("[%02x]", (unsigned char)track_chunks[i].data[j++]);
                                }
                                j--;   // 行き過ぎた分をデクリメント
                                break;
                            default :
                                ;
                        }
                        break;
                    default:
                        printf("# SysEx (Something else...[Status:%02x])", status);   // 見知らぬステータスなら
                }
                printf("
");
            } else {
                printf("## Something else...[Status:%02x]
", status);   // 見知らぬステータスなら
            }
        }
        printf("
");
    }

    // track_chunks,track_chunks[i].dataはcalloc()で領域確保しているので解放し忘れないように!

    return 1;
}


関数にしてまとめる...なんてこともしないグダグダなプログラムですが...(汗)

このプログラムをコンパイルして実行すると、同一ディレクトリ内に存在する「sample.mid」というMIDI(SMF)ファイルを解析してくれます。
(ちなみにgccでコンパイルしました。)
(MacOS Xで、Intel Macです。きっとリトルエンディアンなのです。)


ちなみに、ランニングステータスルールにも対応しています。(実験済み)


実際に「sample.mid」を解析した結果出力を以下に示します。(長いので一部省略)
「結果出力を表示する」のボタンを押すと表示されます。


$ gcc -o program ./program.c 
$ ./program 
# Header ========================
header_chunk_type : MThd
header_chunk_size : 6
smf_format : 1
tracks     : 18
division   : 480
# Track[01] =====================
track_chunks[0].type : MTrk
track_chunks[0].size : 93
track_chunks[0].data
      0:: Meta Ivent : Title=
      0:: Meta Ivent : Copyright=Copyright (C) 2010 
      0:: Meta Ivent : Text=?y?T?C?g???z URL
      0:: Meta Ivent : Temp=500000
      0:: Meta Ivent : Marker=Setup
      0:: Meta Ivent : Rhythm=4/8 ClockPerBeat=24 32NotePer4Note=8
    480:: Meta Ivent : Temp=500000
   1440:: Meta Ivent : Marker=Start
      0:: Meta Ivent : End of Track

# Track[02] =====================
track_chunks[1].type : MTrk
track_chunks[1].size : 265
track_chunks[1].data
      0:: Meta Ivent : Title=System Setup
      0:: Meta Ivent : Port=0
      0:: F0 Exclusive Message :  Length=5[7e][7f][09][01][f7]
     60:: F0 Exclusive Message :  Length=10[41][10][42][12][40][00][7f][00][41][f7]
     60:: F0 Exclusive Message :  Length=7[7f][7f][04][01][00][7f][f7]
     10:: F0 Exclusive Message :  Length=10[41][10][42][12][40][11][15][00][1a][f7]
     10:: F0 Exclusive Message :  Length=10[41][10][42][12][40][12][15][00][19][f7]
     10:: F0 Exclusive Message :  Length=10[41][10][42][12][40][13][15][00][18][f7]
     10:: F0 Exclusive Message :  Length=10[41][10][42][12][40][14][15][00][17][f7]
     10:: F0 Exclusive Message :  Length=10[41][10][42][12][40][15][15][00][16][f7]
     10:: F0 Exclusive Message :  Length=10[41][10][42][12][40][16][15][00][15][f7]
     10:: F0 Exclusive Message :  Length=10[41][10][42][12][40][17][15][00][14][f7]
     10:: F0 Exclusive Message :  Length=10[41][10][42][12][40][18][15][00][13][f7]
     10:: F0 Exclusive Message :  Length=10[41][10][42][12][40][19][15][00][12][f7]
     10:: F0 Exclusive Message :  Length=10[41][10][42][12][40][10][15][01][1a][f7]
     10:: F0 Exclusive Message :  Length=10[41][10][42][12][40][1a][15][00][11][f7]
     10:: F0 Exclusive Message :  Length=10[41][10][42][12][40][1b][15][00][10][f7]
     10:: F0 Exclusive Message :  Length=10[41][10][42][12][40][1c][15][00][0f][f7]
     10:: F0 Exclusive Message :  Length=10[41][10][42][12][40][1d][15][00][0e][f7]
     10:: F0 Exclusive Message :  Length=10[41][10][42][12][40][1e][15][00][0d][f7]
     10:: F0 Exclusive Message :  Length=10[41][10][42][12][40][1f][15][00][0c][f7]
    200:: Meta Ivent : End of Track

# Track[03] =====================
track_chunks[2].type : MTrk
track_chunks[2].size : 219
track_chunks[2].data
      0:: Meta Ivent : Title=CH01
      0:: Meta Ivent : Port=0
    290:: Control Change [00ch] : ModeMessage(ResetAllController)=121
      4:: Control Change [00ch] : VariableEffect(BankSelect[MSB])=0
      0:: Control Change [00ch] : VariableEffect(BankSelect[LSB])=0
      0:: Program Change [00ch] : ToneNo=0
      4:: Control Change [00ch] : VariableEffect(Panpot[MSB])=64
      4:: Control Change [00ch] : VariableEffect(MainVolume[MSB])=100
      4:: Control Change [00ch] : InvariableEffect(ExternalEffect)=0
      4:: Control Change [00ch] : VariableEffect(Expression[MSB])=127
      4:: Control Change [00ch] : VariableEffect(ModulationDepth[MSB])=0
      4:: Control Change [00ch] : SpecialPurpose(RPN[MSB])=0
      0:: Control Change [00ch] : SpecialPurpose(RPN[LSB])=1
      0:: Control Change [00ch] : VariableEffect(DataEntry[MSB])=64
      0:: Control Change [00ch] : VariableEffect(DataEntry[LSB])=0
      4:: Control Change [00ch] : SpecialPurpose(RPN[MSB])=0
      0:: Control Change [00ch] : SpecialPurpose(RPN[LSB])=2
      0:: Control Change [00ch] : VariableEffect(DataEntry[MSB])=64
      4:: Control Change [00ch] : SpecialPurpose(RPN[MSB])=0
      0:: Control Change [00ch] : SpecialPurpose(RPN[LSB])=0
      0:: Control Change [00ch] : VariableEffect(DataEntry[MSB])=12
      4:: Pitch Bend Change [00ch] : Bend=0 (LSB:0, MSB:64)
      4:: Control Change [00ch] : SpecialPurpose(RPN[MSB])=127
      0:: Control Change [00ch] : SpecialPurpose(RPN[LSB])=127
    146:: Note On  [00ch] : Note60[0x3c] Velocity=100
    480:: Note Off [00ch] : Note60[0x3c] Velocity=0
      0:: Note On  [00ch] : Note62[0x3e] Velocity=100
    480:: Note Off [00ch] : Note62[0x3e] Velocity=0
      0:: Note On  [00ch] : Note64[0x40] Velocity=100
    480:: Note Off [00ch] : Note64[0x40] Velocity=0
      0:: Note On  [00ch] : Note65[0x41] Velocity=100
    480:: Note Off [00ch] : Note65[0x41] Velocity=0
      0:: Note On  [00ch] : Note67[0x43] Velocity=100
      0:: Note On  [00ch] : Note64[0x40] Velocity=100
    480:: Note Off [00ch] : Note67[0x43] Velocity=0
      0:: Note Off [00ch] : Note64[0x40] Velocity=0
      0:: Note On  [00ch] : Note69[0x45] Velocity=100
      0:: Note On  [00ch] : Note62[0x3e] Velocity=100
    480:: Note Off [00ch] : Note69[0x45] Velocity=0
      0:: Note Off [00ch] : Note62[0x3e] Velocity=0
      0:: Note On  [00ch] : Note71[0x47] Velocity=100
      0:: Note On  [00ch] : Note67[0x43] Velocity=100
    480:: Note Off [00ch] : Note71[0x47] Velocity=0
      0:: Note Off [00ch] : Note67[0x43] Velocity=0
      0:: Note On  [00ch] : Note72[0x48] Velocity=100
      0:: Note On  [00ch] : Note60[0x3c] Velocity=100
      0:: Note On  [00ch] : Note64[0x40] Velocity=100
    480:: Note Off [00ch] : Note72[0x48] Velocity=0
      0:: Note Off [00ch] : Note60[0x3c] Velocity=0
      0:: Note Off [00ch] : Note64[0x40] Velocity=0
    480:: Meta Ivent : End of Track

# Track[04] =====================
track_chunks[3].type : MTrk
track_chunks[3].size : 106
track_chunks[3].data
      0:: Meta Ivent : Title=CH02
      0:: Meta Ivent : Port=0
    290:: Control Change [01ch] : ModeMessage(ResetAllController)=121
      4:: Control Change [01ch] : VariableEffect(BankSelect[MSB])=0
      0:: Control Change [01ch] : VariableEffect(BankSelect[LSB])=0
      0:: Program Change [01ch] : ToneNo=0
      4:: Control Change [01ch] : VariableEffect(Panpot[MSB])=64
      4:: Control Change [01ch] : VariableEffect(MainVolume[MSB])=100
      4:: Control Change [01ch] : InvariableEffect(ExternalEffect)=0
      4:: Control Change [01ch] : VariableEffect(Expression[MSB])=127
      4:: Control Change [01ch] : VariableEffect(ModulationDepth[MSB])=0
      4:: Control Change [01ch] : SpecialPurpose(RPN[MSB])=0
      0:: Control Change [01ch] : SpecialPurpose(RPN[LSB])=1
      0:: Control Change [01ch] : VariableEffect(DataEntry[MSB])=64
      0:: Control Change [01ch] : VariableEffect(DataEntry[LSB])=0
      4:: Control Change [01ch] : SpecialPurpose(RPN[MSB])=0
      0:: Control Change [01ch] : SpecialPurpose(RPN[LSB])=2
      0:: Control Change [01ch] : VariableEffect(DataEntry[MSB])=64
      4:: Control Change [01ch] : SpecialPurpose(RPN[MSB])=0
      0:: Control Change [01ch] : SpecialPurpose(RPN[LSB])=0
      0:: Control Change [01ch] : VariableEffect(DataEntry[MSB])=12
      4:: Pitch Bend Change [01ch] : Bend=0 (LSB:0, MSB:64)
      4:: Control Change [01ch] : SpecialPurpose(RPN[MSB])=127
      0:: Control Change [01ch] : SpecialPurpose(RPN[LSB])=127
    146:: Meta Ivent : End of Track

# Track[05] =====================
track_chunks[4].type : MTrk
track_chunks[4].size : 106
track_chunks[4].data
      0:: Meta Ivent : Title=CH03
      0:: Meta Ivent : Port=0
    290:: Control Change [02ch] : ModeMessage(ResetAllController)=121
      4:: Control Change [02ch] : VariableEffect(BankSelect[MSB])=0
      0:: Control Change [02ch] : VariableEffect(BankSelect[LSB])=0
      0:: Program Change [02ch] : ToneNo=0
      4:: Control Change [02ch] : VariableEffect(Panpot[MSB])=64



【...中略...】



      0:: Control Change [14ch] : SpecialPurpose(RPN[LSB])=0
      0:: Control Change [14ch] : VariableEffect(DataEntry[MSB])=12
      4:: Pitch Bend Change [14ch] : Bend=0 (LSB:0, MSB:64)
      4:: Control Change [14ch] : SpecialPurpose(RPN[MSB])=127
      0:: Control Change [14ch] : SpecialPurpose(RPN[LSB])=127
    146:: Meta Ivent : End of Track

# Track[18] =====================
track_chunks[17].type : MTrk
track_chunks[17].size : 106
track_chunks[17].data
      0:: Meta Ivent : Title=CH16
      0:: Meta Ivent : Port=0
    290:: Control Change [15ch] : ModeMessage(ResetAllController)=121
      4:: Control Change [15ch] : VariableEffect(BankSelect[MSB])=0
      0:: Control Change [15ch] : VariableEffect(BankSelect[LSB])=0
      0:: Program Change [15ch] : ToneNo=0
      4:: Control Change [15ch] : VariableEffect(Panpot[MSB])=64
      4:: Control Change [15ch] : VariableEffect(MainVolume[MSB])=100
      4:: Control Change [15ch] : InvariableEffect(ExternalEffect)=0
      4:: Control Change [15ch] : VariableEffect(Expression[MSB])=127
      4:: Control Change [15ch] : VariableEffect(ModulationDepth[MSB])=0
      4:: Control Change [15ch] : SpecialPurpose(RPN[MSB])=0
      0:: Control Change [15ch] : SpecialPurpose(RPN[LSB])=1
      0:: Control Change [15ch] : VariableEffect(DataEntry[MSB])=64
      0:: Control Change [15ch] : VariableEffect(DataEntry[LSB])=0
      4:: Control Change [15ch] : SpecialPurpose(RPN[MSB])=0
      0:: Control Change [15ch] : SpecialPurpose(RPN[LSB])=2
      0:: Control Change [15ch] : VariableEffect(DataEntry[MSB])=64
      4:: Control Change [15ch] : SpecialPurpose(RPN[MSB])=0
      0:: Control Change [15ch] : SpecialPurpose(RPN[LSB])=0
      0:: Control Change [15ch] : VariableEffect(DataEntry[MSB])=12
      4:: Pitch Bend Change [15ch] : Bend=0 (LSB:0, MSB:64)
      4:: Control Change [15ch] : SpecialPurpose(RPN[MSB])=127
      0:: Control Change [15ch] : SpecialPurpose(RPN[LSB])=127
    146:: Meta Ivent : End of Track

ちなみに「sample.mid」はWindows環境で作られました。
MIDI音楽編集ソフトDomino[サイトはコチラ]を利用しました。
あと、ランニングステータスルールに対応するために、まずルールを適用したMIDIファイルを生成するため、SMFランナー 1.0[サイトはコチラ]を利用しました。
また、解析の正しさを確認するために、SMFView[Vectorにてダウンロード可能]にて解析を行い、比較・検証しました。
(これらの環境及びこれまで紹介したサイトの存在無しに、この解析プログラムは存在し得ません。勝手ではありますが、この場で御礼申し上げます。)



一応、そのsample.midもアップロードしておきます。



あぁ、長かった。。。
個人的にはNoteOnとNoteOffさえ取り出せれば良かったんですが。。。
「やるなら全部読みたいやん!」という衝動に駆られて作ってみました。
(が、結局中途半端ですけどね^^;;)

もし活用できる方がいらっしゃれば是非使ってくださいまし。嬉しい限りです。
(使えるものとは到底思えないけど。。。orz)



(もう一度...この記事の内容にはミスがあるかもしれませんが、その時はどうぞご容赦ください。^^;)

# 2010.10.16 : 語句修正
以後の更新内容の改善のために、是非ともご評価のほどよろしくお願いします!→

テーマ : サウンド・プログラミング /  ジャンル : コンピュータ

カテゴリー
ようこそ!
Author: Torasuke
Profile: 地元大学の情報系学部に息をひそめる二回生。
   SA SW


ブログ内検索
最近のコメント
オススメ
京都の大学生のラボブログ
Python,Java,Objective-C,GAE,Macなど
Python独習中の大学生のブログ


ltzz.info
ここの管理者さんには謁見済み!(えっ

 Use OpenOffice.org
無補償でも良いなら、MSOfficeよりOpenOfficeで十分です。

Mozilla Firefox ブラウザ無料ダウンロード
当サイトは、Firefoxというブラウザで動作確認しています。私は以前、IE派でしたが、一度乗り換えて慣れてしまうと、Firefoxのほうが便利だということを実感しました。

是非よろしくお願いします。
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。