スポンサーサイト

上記の広告は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条件式の変更)
以後の更新内容の改善のために、是非ともご評価のほどよろしくお願いします!→

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

カテゴリー
ようこそ!
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ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。