#はじめに
MIDIファイルを解析したいとふと思い、達成目標をMIDIファイルを独自の構造の中に取り込める所までをゴールとし制作を行いました。
MIDIファイルを読み取るために、以下のサイトを参考にさせていただきました。
SMF(Standard MIDI File)フォーマット解説
SMF (Standard MIDI Files) の構造
#MIDIファイルについて
SMF(Standard MIDI File)のフォーマットは、ファイル全体のデータを示すヘッダチャンクと、具体的なデータを格納するトラックチャンクで成り立っています。
ヘッダファイルはファイルの先頭に一つのみ存在し、以下のようなバイト列をしています。(場所は、先頭バイトから見て何バイト目かを記している)
場所 | 名前 | 大きさ | 値の内容 |
---|---|---|---|
0 | チャンクタイプ | 4byte | ”MThd”という文字列が入ってる |
4 | データ長 | 4byte | 0x00000006が入っている |
8 | フォーマット | 2byte | 0か1か2が入る |
10 | トラック数 | 2byte | トラックチャンクの数が入っている |
12 | 時間単位 | 2byte | 分解能のデータが入っている |
また、トラックチャンクは以下のようなバイト列になっています。(場所は、トラックチャンクの開始地点から何バイト目かを示している)
場所 | 名前 | 大きさ | 値の内容 |
---|---|---|---|
0 | チャンクタイプ | 4byte | ”MTrk”という文字列が入ってる |
4 | データ長 | 4byte | トラックチャンクのサイズが入ってる |
8 | データ本体 | 可変長 | MIDIデータの本体が入ってる |
大まかにはこのようなバイト列になっていますが、それぞれのデータの意味等の詳細は参考にしたサイトに記述されているので、細かいことを知りたい人はそこを参考にすればよいと思います。
MIDI解析
ここから今回の記事の本編です。C++で書いたコードなので、少し分かり辛い書き方をしているかもしれませんが、許してください。
と言ってもMIDIファイルのデータをC++上で読み込みするだけなので、そこまで複雑な事をするつもりもないです。
まず初めにMIDIファイルを解析する際に、MIDIクラスを作り、そのクラス内で解析とデータの格納を行う様にしました。ヘッダ内のデータはファイル全体に関わるデータなので、クラスのメンバ変数として特別に値を格納する領域を持たせるようにし、トラック内のデータは可変長で複雑化しているので、このデータは動的かつ構造的なデータで構成するべきだと考えてデータ領域を作りました。
具体的には
short mMaxTrackNum; //トラックの数
short mResolution; //分解能の値
Track* mTrack; //トラック構造体を示すポインタ
というようなメンバを宣言しました。
ここで、Track型は以下のような構造を取りました。
typedef struct Track {
Event* event;
size_t eventnum;
size_t tracksize;
} Track;
この様な構造を取ったのは、SMFのフォーマットでは、トラックのデータはデータ長とデータ本体のみの構造であり、トラックデータの大きさと、データ本体の構造(Event構造体)と、その構造がいくつあるかを示す変数の領域があれば十分だと考えたからです。
次に、データ本体のEvent構造については、以下のような構造になっています。
typedef struct Event {
EVENT type; //何のイベントかを格納する
size_t datasize; //それぞれのイベントのデータサイズを格納する
const unsigned char* data; //イベントのデータを格納する
size_t time; //デルタタイムを格納する
} Event;
EVENTはイベントのタイプを示す列挙子で、実体化した構造がどのイベントを示すのかが分かる様に入れていいます。datasizeには、具体的なデータが何バイトあるのかを示すデータが入り、dataには具体的なデータの先頭バイトのポインタが入るようにしています。最後のtimeはEventがどの時間に発生するかを示したデルタタイムが入るようにしました。
EVENTは具体的には以下のような形で宣言してあります。
enum class EVENT : unsigned char {
//メタイベント //FF|イベントタイプ|データ長|データ
//SEQUENCE_NUMBER, //FF 00 02 シーケンス番号、フォーマット0と1
COMMENT, //FF 01 len data テキストを書き込むためのイベント
COPY_RIGHT, //FF 02 len data 著作権が書いてある
SEQUENCE_TRACK_NAME, //FF 03 len data シーケンス名(曲名)・トラック名
INSTRUMENT_NAME, //FF 04 len data 楽器名
LYRIC, //FF 05 len data lyricは歌詞の事
MARKER, //FF 06 len data リハーサル記号やセクション名のような、シーケンスのその時点の名称を記述する
QUEUE_POINT, //FF 07 len data 曲データ中、このイベントが挿入されている位置で、曲以外の進行を記述する。(画面のフェードアウトのような動画等に対する進行)
PROGRAM_NAME, //FF 08 len data プログラムチェンジとバンクチェンジで表している音色名を記載する(General MIDIが制定される前の遺物)
DEVICE_NAME, //FF 09 len data このイベントがあるトラックがどのデバイスに配置されるのかということを示す。このイベントは、1トラックにつき1度だけ、最初に呼ばれるべきもの
CHANNEL_PREFIX, //FF 20 01 cc MIDIイベントじゃないのに(SysEx or メタイベント)MIDIチャンネルを指定する際に用いる
PORT_SPECIFIED, //FF 21 01 pp 出力ポートの指定 0~3がそれぞれポートの1~4に対応する(例外はあるが、データのみでは判別不可)
END_OF_TRACK, //FF 2F 00 トラックの最後であることを表す
SET_TEMPO, //FF 51 03 tt tt tt tttttt(3byte)には、4分音符の長さをμ秒で表したものを格納する。BPM = 120の時、4分音符の長さは 60 x 10^6 / 120 = 500000[μs]
TIME_SIGNATURE, //FF 58 04 nn dd cc bb | 拍子 nn = 分子 dd = 分子 cc = メトロノーム間隔[四分音符間隔なら18H] bb = 四分音符当たりの三十二分音符の数
KEY_SIGNATURE, //FF 59 02 sf ml キー(調)を示す sf = 正:シャープの数 or 負:フラットの数 ml = 0:長調(メジャー) 1:短調(マイナー)
//SPECIAL_META_EVENT, //FF 7F len data 最初の1バイトはメーカIDで、その後に独自フォーマットのデータが続く
FF_NONE, //何も見つからなかった時、これを入れる
//システムエクスクルシーブメッセージを格納するイベント。個々の楽器メーカが独自に作成したメッセージを格納する。MIDIデバイスに何かしら設定するのに利用される
SYSEX, //F0 データ長 エクスクルシーブメッセージ F7
F7_SYSEX, //F7 データ長 エクスクルシーブメッセージ
//MIDIイベント
POLYPHONIC_KEY_PRESSURE, //An kk vv nのチャンネルのkk番目のノートにベロシティvvのプレッシャー情報を与える
CONTROL, //Bn cc vv コントローラ設定。チャンネルnでコントローラナンバーccにvvの値を送る
PROGRAM_CHANGE, //Cn pp プログラムチェンジ。チャンネルnで、音色をppに変更する
CHANNEL_PRESSURE, //Dn vv チャンネルnに対してプレッシャー情報vvを送信する。
PITCH_BENT, //En mm ll ピッチベント。なぜかリトルエンディアンらしい
NOTE_ON, //9n ノート・オン
NOTE_OFF //8n ノート・オフ
};
この様なデータ構造の元、実際のデータの格納の処理は以下のコードで実現しました。
//ファイルの実体がなければここで落とす
if (mSize == 0) {
return;
}
//ファイルヘッダ確認(4バイト)
if (fileData[0] == 'M' && fileData[1] == 'T' && fileData[2] == 'h' && fileData[3] == 'd') {
//何もしない
}
else {
return;
}
//ヘッダサイズが6になっているかを確認する(なぜか4バイト)
if (fileData[4] == 0 && fileData[5] == 0 && fileData[6] == 0 && fileData[7] == 0x06) {
//何もしない
}
else {
return;
}
//フォーマット確認(フォーマットには0、1、2がある)(2バイト)
//フォーマット0は全てのチャンネルのデータを一つのトラックにまとめたもの
//フォーマット1は複数のチャンネルを用いてデータをまとめたもの
//フォーマット2はシーケンスパターンをそのまま保存する方式、おそらくかなり複雑だと思った(小並感)
//フォーマットが0の時
if (fileData[8] == 0 && fileData[9] == 0) {
//トラック数(2バイト)
//トラック数は1でないとフォーマット情報と合わない
if (fileData[10] != 0 || fileData[11] != 1) {
return;
}
mMaxTrackNum = 1;
}
//フォーマットが1の時
if (fileData[8] == 0 && fileData[9] == 1) {
//トラック数を獲得(2バイト)
mMaxTrackNum = (fileData[10] << 8) + fileData[11];
//mMaxTrackNumが0だと色々まずいので落とす
if (mMaxTrackNum == 0) {
return;
}
}
//時間単位の獲得(分解能のこと)
//最初のビットが0ならば、何小節の何拍目のデータといった方式でトラックチャンクが作られる
//最初のビットが1ならば、何分何秒といったデータといった方式で保存する
//大抵は前者
if (fileData[12] & 0b10000000) {
//何分何秒のやつ(今回は考えないのでスルーする)
}
else {
//何小節何拍目のやつ
mResolution = (fileData[12] << 8) + fileData[13];
}
//トラック分のTrackを用意する
mTrack = new Track[mMaxTrackNum];
int TrackPoint = 0;
for (int i = 14; i < mSize;) {
int eventNum = 0;
if (fileData[i] == 'M' && fileData[i + 1] == 'T' && fileData[i + 2] == 'r' && fileData[i + 3] == 'k') {
//MTrkの文字分のバイトを進める
i += 4;
//トラックのデータのサイズ
size_t trackSize = (fileData[i] << 24) + (fileData[i + 1] << 16) + (fileData[i + 2] << 8) + fileData[i + 3];
//トラックサイズ分のバイトを進める
i += 4;
//イベントの数をカウントする
size_t count = 0;
//最初はデルタタイムのデータが来るのでtrue
bool delta = true;
for (int j = i; j < (trackSize + i);) {
if (delta) {
//最大4バイトだから実質O(1)
for (int k = 0; k < 4; ++k) {
if (fileData[j + k] & 0x80) {
//何もしない
}
else {
//デルタタイム分のバイト数進める
j += k + 1;
break;
}
}
}
else { //イベント処理に関する処理
if (fileData[j] == 0xff) {
//FFから始まるデータはlenバイトでバイトの長さを指定しているので、その分のバイトを飛ばす
unsigned char len = fileData[j + 2];
j += 3 + len;
count++;
}
else if (fileData[j] == 0xf0 || fileData[j] == 0xf7) {
//0xf0分のデータバイトを飛ばす
j += 1;
//lenは可変長である事を考慮する
int k, len = 0;
for (k = 0; k < 4; ++k) {
len <<= 7;
len += fileData[j + k] & 0x7f;
if (fileData[j + k] & 0x80) { //最上位ビットが1なら、次のビットも確認する
//何もしない
}
else {
break;
}
}
//元々1バイト分のデータはある
j += k + 1;
//データの長さ分のバイトをぶち飛ばす
j += len;
count++;
}
else if ((fileData[j] & 0xf0) == 0x90 || (fileData[j] & 0xf0) == 0x80 || (fileData[j] & 0xf0) == 0xA0 || (fileData[j] & 0xf0) == 0xB0 || (fileData[j] & 0xf0) == 0xE0) {
j += 3;
count++;
}
else if ((fileData[j] & 0xf0) == 0xC0 || (fileData[j] & 0xf0) == 0xD0) {
j += 2;
count++;
}
else {
printf("0x%x is not defined", fileData[j]);
destroy();
return;
}
}
delta = !delta;
}
mTrack[TrackPoint].tracksize = trackSize;
mTrack[TrackPoint].eventnum = count;
mTrack[TrackPoint].event = new Event[count];
//要素を具体的に格納していく(2週目)
//最初はデルタタイムのデータ
delta = true;
int eventPoint = 0;
for (int j = i; j < (trackSize + i);) {
if (delta) {
unsigned int time = 0;
int k;
//最大4バイトだから実質O(1)
for (k = 0; k < 4; ++k) {
time <<= 7;
time += fileData[j + k] & 0x7f;
if (fileData[j + k] & 0x80) {
continue;
}
else {
break;
}
}
//デルタタイム分のバイト数進める
j += k + 1;
mTrack[TrackPoint].event[eventPoint].time = time;
}
else { //イベントとそれに基づくデータの格納処理
if (fileData[j] == 0xff) {
//データのイベントタイプを判別する値
unsigned char eventType = fileData[j + 1];
//FFから始まるデータはlenバイトでバイトの長さを指定しているので、その分のバイトを飛ばす
unsigned char len = fileData[j + 2];
//長さのデータ格納
mTrack[TrackPoint].event[eventPoint].datasize = len;
if (len == 0) {
//サイズが0ならデータを獲得しない
mTrack[TrackPoint].event[eventPoint].data = nullptr;
}
else {
//データの開始地点のポインタをいただく
mTrack[TrackPoint].event[eventPoint].data = &fileData[j + 3];
}
//イベントタイプをそれぞれ格納する
switch (eventType)
{
//case 0:
// mTrack[TrackPoint].event[eventPoint].type = EVENT::SEQUENCE_NUMBER; break;
case 1:
mTrack[TrackPoint].event[eventPoint].type = EVENT::COMMENT; break;
case 2:
mTrack[TrackPoint].event[eventPoint].type = EVENT::COPY_RIGHT; break;
case 3:
mTrack[TrackPoint].event[eventPoint].type = EVENT::SEQUENCE_TRACK_NAME; break;
case 4:
mTrack[TrackPoint].event[eventPoint].type = EVENT::INSTRUMENT_NAME; break;
case 5:
mTrack[TrackPoint].event[eventPoint].type = EVENT::LYRIC; break;
case 6:
mTrack[TrackPoint].event[eventPoint].type = EVENT::MARKER; break;
case 7:
mTrack[TrackPoint].event[eventPoint].type = EVENT::QUEUE_POINT; break;
case 8:
mTrack[TrackPoint].event[eventPoint].type = EVENT::PROGRAM_NAME; break;
case 9:
mTrack[TrackPoint].event[eventPoint].type = EVENT::DEVICE_NAME; break;
case 0x20:
mTrack[TrackPoint].event[eventPoint].type = EVENT::CHANNEL_PREFIX; break;
case 0x21:
mTrack[TrackPoint].event[eventPoint].type = EVENT::PORT_SPECIFIED; break;
case 0x2f:
mTrack[TrackPoint].event[eventPoint].type = EVENT::END_OF_TRACK; break;
case 0x51:
mTrack[TrackPoint].event[eventPoint].type = EVENT::SET_TEMPO; break;
case 0x58:
mTrack[TrackPoint].event[eventPoint].type = EVENT::TIME_SIGNATURE; break;
case 0x59:
mTrack[TrackPoint].event[eventPoint].type = EVENT::KEY_SIGNATURE; break;
//case 0x7f:
// mTrack[TrackPoint].event[eventPoint].type = EVENT::SPECIAL_META_EVENT; break;
default:
mTrack[TrackPoint].event[eventPoint].type = EVENT::FF_NONE; break;
}
j += 3 + len;
}
else if (fileData[j] == 0xf0 || fileData[j] == 0xf7) {
//f0(f7)識別子分のビットを飛ばす
j += 1;
//lenは可変長である事を考慮する
int len = 0;
for (int k = 0; k < 4; ++k) {
len <<= 7;
len += fileData[j + k] & 0x7f;
if (fileData[j + k] & 0x80) { //最上位ビットが1なら、次のビットも確認する
//何もしない
}
else {
j += k + 1;
break;
}
}
//イベントの格納
mTrack[TrackPoint].event[eventPoint].type = EVENT::SYSEX;
//長さを入れてやる
mTrack[TrackPoint].event[eventPoint].datasize = len;
//長さを獲得してしまえば後はこっちのもの
mTrack[TrackPoint].event[eventPoint].data = &fileData[j];
//長さ分のバイトをぶち飛ばす
j += len;
}
//MIDIイベントは、識別子にデータが入ってる特殊な形なのでイベント情報が格納されているバイトごとデータを獲得する
//ノートオン・オフ処理
else if ((fileData[j] & 0xf0) == 0x90 || (fileData[j] & 0xf0) == 0x80) {
mTrack[TrackPoint].event[eventPoint].datasize = 3;
mTrack[TrackPoint].event[eventPoint].data = &fileData[j];
if ((fileData[j] & 0xf0) == 0x90) {
mTrack[TrackPoint].event[eventPoint].type = EVENT::NOTE_ON;
}
else if ((fileData[j] & 0xf0) == 0x80) {
mTrack[TrackPoint].event[eventPoint].type = EVENT::NOTE_OFF;
}
j += 3;
}
//3バイト使うイベントをぶち込んでる
else if ((fileData[j] & 0xf0) == 0xA0 || (fileData[j] & 0xf0) == 0xB0 || (fileData[j] & 0xf0) == 0xE0) {
mTrack[TrackPoint].event[eventPoint].datasize = 3;
mTrack[TrackPoint].event[eventPoint].data = &fileData[j];
if ((fileData[j] & 0xf0) == 0xA0) {
mTrack[TrackPoint].event[eventPoint].type = EVENT::POLYPHONIC_KEY_PRESSURE;
}
else if ((fileData[j] & 0xf0) == 0xB0) {
mTrack[TrackPoint].event[eventPoint].type = EVENT::CONTROL;
}
else if ((fileData[j] & 0xf0) == 0xE0) {
mTrack[TrackPoint].event[eventPoint].type = EVENT::PITCH_BENT;
}
j += 3;
}
//2バイト使うイベントをぶち込む
else if ((fileData[j] & 0xf0) == 0xC0 || (fileData[j] & 0xf0) == 0xD0) {
mTrack[TrackPoint].event[eventPoint].datasize = 2;
mTrack[TrackPoint].event[eventPoint].data = &fileData[j];
if ((fileData[j] & 0xf0) == 0xC0) {
mTrack[TrackPoint].event[eventPoint].type = EVENT::PROGRAM_CHANGE;
}
else if ((fileData[j] & 0xf0) == 0xD0) {
mTrack[TrackPoint].event[eventPoint].type = EVENT::CHANNEL_PRESSURE;
}
j += 2;
}
else {
printf("0x%x is not defined\n", fileData[j]);
return;
}
eventPoint += 1;
}
delta = !delta;
}
i += trackSize;
TrackPoint += 1;
}
else {
//MTrkが来ないのはおかしいので落とす
destroy();
return;
}
}
めんどくさいので細かな解説はしませんが、コメントを見れば大体分かると思います。やってる事は規則に従ってデータの格納を行ってるだけなので、大変ではありますがSMFのリファレンスと見比べてみると納得できると思います。
MIDIファイル書き込み
今回作ったMIDIクラスが正常かどうかを確認するために、このクラスを用いて、MIDIファイルの書き込みができるような関数を作りました。
関数は以下のようなコードになっています。
int MIDIWrite(const char* filename, MIDI& midi) {
FILE* midiFile = nullptr;
if((midiFile = fopen(filename, "wb")) == NULL) {
fprintf(stderr, "%sの読み込みに失敗しました。", filename);
return 1;
}
unsigned short sbuff = 0;
unsigned int ibuff = 0;
fwrite("MThd", 1, 4, midiFile); //チャンクタイプ
ibuff = 6; //ヘッダのデータサイズである6をいれりゅ
reverceByte((void*)&ibuff, 4);
fwrite(&ibuff, 4, 1, midiFile); //ヘッダのデータサイズ
if (midi.getMaxTrackNum() > 1) { //トラックの数が1以上なら
sbuff = 1;
reverceByte((void*)&sbuff, 2);
fwrite(&sbuff, 1, 2, midiFile); //フォーマット1
}
else if (midi.getMaxTrackNum() == 1) { //トラックの数が1なら
sbuff = 0;
reverceByte((void*)&sbuff, 2); //バイトを逆転させる
fwrite(&sbuff, 1, 2, midiFile); //フォーマット0
}
short maxTrackNum = midi.getMaxTrackNum(); //トラックが何個あるかを獲得
reverceByte((void*)&maxTrackNum, 2);
fwrite(&maxTrackNum, 1, 2, midiFile); //トラック数をファイルに書き込み
short resolution = midi.getResolution(); //分解能を獲得
reverceByte((void*)&resolution, 2);
fwrite(&resolution, 1, 2, midiFile); //分解能を書き込み
for (int i = 0; i < midi.getMaxTrackNum(); ++i) { //トラックの数だけループを回す
fwrite("MTrk", 1, 4, midiFile); //トラックチャンクの始まりの識別子を記述
MIDI::Track* track = midi.getTrack(i); //ある一つのトラックのトラック構造をMIDIクラスから獲得
size_t uibuff = track->tracksize;
reverceByte((void*)&uibuff, 4);
fwrite(&uibuff, 1, 4, midiFile); //トラックチャンクのサイズを記述
for (int j = 0; j < track->eventnum; ++j) { //トラック内に存在するイベント分のループを回す
if (track->event[j].type != MIDI::EVENT::FF_NONE) { //もしイベントがないのならスルーする(激うまギャグ)
DeltaTimeWrite(midiFile, track->event[j].time); //デルタタイムを書き込む
EventWrite(midiFile, track->event[j]); //イベントを識別する要素を書き込む関数
fwrite(track->event[j].data, 1, track->event[j].datasize, midiFile); //データ本体を書き込む
}
}
}
return 0;
}
void reverceByte(void* data, unsigned char datasize) {
unsigned char *reverceData = new unsigned char[datasize];
for (int i = 0; i < datasize; ++i) {
reverceData[i] = reinterpret_cast<char*>(data)[datasize - i - 1];
}
for (int i = 0; i < datasize; ++i) {
reinterpret_cast<char*>(data)[i] = reverceData[i];
}
delete[] reverceData;
}
void DeltaTimeWrite(FILE* midiFile, size_t time) {
unsigned char cbuff = 0;
if (time >= (1 << 23)) { //1バイトにつき7ビットしか使えない事を考慮し、その上で最大サイズである4バイト分を考慮する
fwrite(&(cbuff = (((time & 0x0fe00000) | 0x10000000) >> 24)), 1, 1, midiFile); //「1111 1110 0000 0000 0000 0000 0000」(全28ビット)のマスクを考える
}
if (time >= (1 << 15)) {
fwrite(&(cbuff = (((time & 0x001fc000) | 0x00200000) >> 14)), 1, 1, midiFile); //「0000 0001 1111 1100 0000 0000 0000 」
}
if (time >= (1 << 7)) {
fwrite(&(cbuff = (((time & 0x00003f80) | 0x00004000) >> 7)), 1, 1, midiFile); //「0000 0000 0000 0011 1111 1000 0000」
}
fwrite(&(cbuff = (time & 0x0000007f)), 1, 1, midiFile); //「0000 0000 0000 0000 0000 0111 1111」
}
void EventWrite(FILE* midiFile, MIDI::Event& eve) {
unsigned short metaEventBuff = 0;
unsigned char cbuff = 0;
switch (eve.type) {
case MIDI::EVENT::COMMENT:
metaEventBuff = (0xff << 8) | 0x01; break;
case MIDI::EVENT::COPY_RIGHT:
metaEventBuff = (0xff << 8) | 0x02; break;
case MIDI::EVENT::SEQUENCE_TRACK_NAME:
metaEventBuff = (0xff << 8) | 0x03; break;
case MIDI::EVENT::INSTRUMENT_NAME:
metaEventBuff = (0xff << 8) | 0x04; break;
case MIDI::EVENT::LYRIC:
metaEventBuff = (0xff << 8) | 0x05; break;
case MIDI::EVENT::MARKER:
metaEventBuff = (0xff << 8) | 0x06; break;
case MIDI::EVENT::QUEUE_POINT:
metaEventBuff = (0xff << 8) | 0x07; break;
case MIDI::EVENT::PROGRAM_NAME:
metaEventBuff = (0xff << 8) | 0x08; break;
case MIDI::EVENT::DEVICE_NAME:
metaEventBuff = (0xff << 8) | 0x09; break;
case MIDI::EVENT::CHANNEL_PREFIX:
metaEventBuff = (0xff << 8) | 0x20; break;
case MIDI::EVENT::PORT_SPECIFIED:
metaEventBuff = (0xff << 8) | 0x21; break;
case MIDI::EVENT::END_OF_TRACK:
metaEventBuff = (0xff << 8) | 0x2f; break;
case MIDI::EVENT::SET_TEMPO:
metaEventBuff = (0xff << 8) | 0x51; break;
case MIDI::EVENT::TIME_SIGNATURE:
metaEventBuff = (0xff << 8) | 0x58; break;
case MIDI::EVENT::KEY_SIGNATURE:
metaEventBuff = (0xff << 8) | 0x59; break;
default:
if (eve.type == MIDI::EVENT::SYSEX || eve.type == MIDI::EVENT::F7_SYSEX) {
if (eve.type == MIDI::EVENT::SYSEX) {
fwrite(&(cbuff = 0xf0), 1, 1, midiFile);
}
else {
fwrite(&(cbuff = 0xf7), 1, 1, midiFile);
}
size_t len = eve.datasize;
if (len >= (1 << 23)) { //1バイトにつき7ビットしか使えない事を考慮し、その上で最大サイズである4バイト分を考慮する
fwrite(&(cbuff = (((len & 0x0fe00000) | 0x10000000) >> 24)), 1, 1, midiFile); //「1111 1110 0000 0000 0000 0000 0000」(全28ビット)のマスクを考える
}
if (len >= (1 << 15)) {
fwrite(&(cbuff = (((len & 0x001fc000) | 0x00200000) >> 14)), 1, 1, midiFile); //「0000 0001 1111 1100 0000 0000 0000 」
}
if (len >= (1 << 7)) {
fwrite(&(cbuff = (((len & 0x00003f80) | 0x00004000) >> 7)), 1, 1, midiFile); //「0000 0000 0000 0011 1111 1000 0000」
}
fwrite(&(cbuff = (len & 0x0000007f)), 1, 1, midiFile); //「0000 0000 0000 0000 0000 0111 1111」
}
return;
}
reverceByte((void*)&metaEventBuff, 2);
fwrite(&metaEventBuff, 1, 2, midiFile);
fwrite(&(cbuff = (unsigned char)eve.datasize), 1, 1, midiFile);
}
これも解説が面倒なので詳細は省きますが、MIDIクラス内のデータを用いてファイル書き込みをしています。
以下の画像の様にデータが一致している事が確認できたので、ちゃんとファイルが読み込めていることが実感できました。
おわりに
MIDIファイルは今までに解析したことがないビックエンディアン方式のフォーマットだったりして、躓いた点も多かったですが何とかここまで開発出来てよかったです。
今回が初めての長めの記事の投稿になるので、至らない点がいくつもあったと思います。何か誤字や脱字や言葉がおかしい箇所があればコメントしていただけるとありがたいです。