//テストログ static void DataTestLog(HeaderChunkData h) { Console.WriteLine( "チャンクID:" + (char)h.chunkID[0] + (char)h.chunkID[1] + (char)h.chunkID[2] + (char)h.chunkID[3] + "\n" + "データ長:" + h.dataLength + "\n" + "フォーマット:" + h.format + "\n" + "トラック数:" + h.tracks + "\n" + "分解能:" + h.timeBase + "\n"); }
//main-------------------- public static void ReadMidi(string filePath, int _baseScale, float magniSpeed /*速度倍率*/) { //リスト生成 int baseScale = _baseScale; //四分音符の長さ var headerData = new HeaderChunkData(); TrackChunkData[] trackChunks; var b_noteDataList = new List <Bfr_NoteData>(); var b_tempDataList = new List <Bfr_TempData>(); //ファイル読み込み 読み込み終わるまで出ない! using (var file = new FileStream(filePath, FileMode.Open, FileAccess.Read)) using (var reader = new BinaryReader(file)) { //-------- ヘッダ解析 ------- HeaderDataAnaly(ref headerData, reader); //-------- トラック解析 ------- trackChunks = new TrackChunkData[headerData.tracks]; //ヘッダからトラック数を参照 for (int i = 0; i < trackChunks.Length; i++) //トラック数分回す { TrackDataAnaly(ref trackChunks, reader, i); //演奏データ解析へ TrackMusicAnaly(trackChunks[i].data, headerData, ref b_noteDataList, ref b_tempDataList); } } MstimeFix(ref b_noteDataList, ref b_tempDataList); //曲中のBPM変更に対応 //欲しいデータに変換 a_noteDataList = new List <Aftr_NoteData>(); AftrNoteCreate(b_noteDataList, headerData.timeBase, baseScale); a_tempDataList = new List <Aftr_TempData>(); AftrTempCreate(b_tempDataList, baseScale, magniSpeed); //以下ログ DataTestLog(headerData); DataTestLog(trackChunks); DataTestLog(b_tempDataList); DataTestLog(b_noteDataList); DataTestLog(a_noteDataList); DataTestLog(a_tempDataList); }
//ヘッダー解析 static void HeaderDataAnaly(ref HeaderChunkData header, BinaryReader reader) { //チャンクID header.chunkID = reader.ReadBytes(4); //リトルエンディアンなら逆に if (BitConverter.IsLittleEndian) { //データ長 var bytePick = reader.ReadBytes(4); Array.Reverse(bytePick); header.dataLength = BitConverter.ToInt32(bytePick, 0); //フォーマット bytePick = reader.ReadBytes(2); Array.Reverse(bytePick); header.format = BitConverter.ToInt16(bytePick, 0); //トラック数 bytePick = reader.ReadBytes(2); Array.Reverse(bytePick); header.tracks = BitConverter.ToInt16(bytePick, 0); //分解能 bytePick = reader.ReadBytes(2); Array.Reverse(bytePick); header.timeBase = BitConverter.ToInt16(bytePick, 0); } else { //データ長 header.dataLength = BitConverter.ToInt32(reader.ReadBytes(4), 0); //フォーマット header.format = BitConverter.ToInt16(reader.ReadBytes(2), 0); //トラック数 header.tracks = BitConverter.ToInt16(reader.ReadBytes(2), 0); //分解能 header.timeBase = BitConverter.ToInt16(reader.ReadBytes(2), 0); } }
//トラック演奏データ解析 static void TrackMusicAnaly(byte[] data, HeaderChunkData header, ref List <Bfr_NoteData> b_noteL, ref List <Bfr_TempData> b_tmpL) { //トラック内で引き継ぎたいもの uint tickTime = 0; //開始からのTick数 byte statusByte = 0; //FFとか入る uint Instrument = 0; //楽器 //データ分 for (int i = 0; i < data.Length;) { //---デルタタイム--- tickTime += DeltaMath(data, ref i); //---ランニングステータス--- if (data[i] < 0x80) { //*** } else { statusByte = data[i++]; } //---ステータスバイト--- //ステバ分岐 この辺はもう筋肉 //--Midiイベント-- if (statusByte >= 0x80 & statusByte <= 0xef) { switch (statusByte & 0xf0) { case 0x90: //ノートオン { byte leanNum = data[i++]; //音階 byte velocity = data[i++]; //音の強さ //ノート情報まとめる Bfr_NoteData noteData = new Bfr_NoteData(); noteData.tickTime = (int)tickTime; noteData.msTime = 0; //後でやる noteData.leanNum = (int)leanNum; noteData.ch = statusByte & 0x0f; //下4を取得 //ベロ値でオンオフを送ってくる奴に対応 if (velocity > 0) //音が鳴っていたらオン { noteData.type = NoteType.ON; } else { noteData.type = NoteType.OFF; } b_noteL.Add(noteData); } break; case 0x80: //ノートオフ { byte leanNum = data[i++]; byte velocity = data[i++]; Bfr_NoteData noteData = new Bfr_NoteData(); noteData.tickTime = (int)tickTime; noteData.msTime = 0; noteData.leanNum = (int)leanNum; noteData.ch = statusByte & 0x0f; //下4を取得 noteData.type = NoteType.OFF; //オフしか来ない b_noteL.Add(noteData); } break; case 0xc0: //プログラムチェンジ 音色 楽器を変える Instrument = data[i++]; break; case 0xa0: //キープッシャー i += 2; break; case 0xb0: //コンチェ i += 2; break; case 0xd0: //チェンネルプレッシャー i += 1; break; case 0xe0: //ピッチベンド i += 2; break; } } //--システムエクスクルーシブイベント-- else if (statusByte == 0x70 || statusByte == 0x7f) { byte dataLen = data[i++]; i += dataLen; } //--メタイベ-- else if (statusByte == 0xff) { byte eveNum = data[i++]; byte dataLen = (byte)DeltaMath(data, ref i); //可変長 switch (eveNum) { case 0x51: { Bfr_TempData tempData = new Bfr_TempData(); tempData.tickTime = (int)tickTime; tempData.msTime = 0; //後でやる //3byte固定 4分音符の長さをマイクロ秒で uint temp = 0; temp |= data[i++]; temp <<= 8; temp |= data[i++]; temp <<= 8; temp |= data[i++]; //BPM計算 = 60秒のマクロ秒/4分音符のマイクロ秒 tempData.bpm = SECOND_BASE * 1000000 / (float)temp; //小数点第1位切り捨て tempData.bpm = (float)Math.Floor(tempData.bpm * 10) / 10; //tick値=60/分解能*1000 tempData.tick = (SECOND_BASE / tempData.bpm / header.timeBase * 1000); b_tmpL.Add(tempData); } break; default: i += dataLen; //メタはデータ長で全てとばせる 書くの面倒だった break; } } } }
/// <summary> /// MIDIファイルをロードし必要な情報を返す /// </summary> /// <returns>ロードに成功したらそのデータ、失敗したらnull</returns> public MIDILoadData Load(string fileName) { var ret = new MIDILoadData(); try { using (var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read)) using (var reader = new BinaryReader(stream)) { /* ヘッダチャンク侵入 */ var headerChunk = new HeaderChunkData(); // チャンクID読み込み headerChunk.ChunkType = reader.ReadBytes(4); // お前は本当にヘッダチャンクか? if ( headerChunk.ChunkType[0] != 'M' || headerChunk.ChunkType[1] != 'T' || headerChunk.ChunkType[2] != 'h' || headerChunk.ChunkType[3] != 'd') { throw new FormatException("head chunk != MThd."); } // 自分のPCがリトルエンディアンなら変換する if (BitConverter.IsLittleEndian) { // ヘッダ部のデータ長(値は6固定) var byteArray = reader.ReadBytes(4); Array.Reverse(byteArray); headerChunk.DataLength = BitConverter.ToInt32(byteArray, 0); // フォーマット(2byte) byteArray = reader.ReadBytes(2); Array.Reverse(byteArray); headerChunk.Format = BitConverter.ToInt16(byteArray, 0); // トラック数(2byte) byteArray = reader.ReadBytes(2); Array.Reverse(byteArray); headerChunk.Tracks = BitConverter.ToInt16(byteArray, 0); // タイムベース(2byte) byteArray = reader.ReadBytes(2); Array.Reverse(byteArray); headerChunk.Division = BitConverter.ToInt16(byteArray, 0); } else { // ヘッダ部のデータ長(値は6固定) headerChunk.DataLength = BitConverter.ToInt32(reader.ReadBytes(4), 0); // フォーマット(2byte) headerChunk.Format = BitConverter.ToInt16(reader.ReadBytes(2), 0); // トラック数(2byte) headerChunk.Tracks = BitConverter.ToInt16(reader.ReadBytes(2), 0); // タイムベース(2byte) headerChunk.Division = BitConverter.ToInt16(reader.ReadBytes(2), 0); } // 分能値保存 ret.Division = headerChunk.Division; // トラックが何もなかったら出ていけぇ! if (headerChunk.Tracks <= 0) { throw new Exception("not exsist tracks."); } /* トラックチャンク侵入 */ var trackChunks = new TrackChunkData[headerChunk.Tracks]; // トラック数ぶん回す for (int i = 0; i < headerChunk.Tracks; i++) { // チャンクID読み込み trackChunks[i].ChunkType = reader.ReadBytes(4); // お前は本当にトラックチャンクか? if ( trackChunks[i].ChunkType[0] != 'M' || trackChunks[i].ChunkType[1] != 'T' || trackChunks[i].ChunkType[2] != 'r' || trackChunks[i].ChunkType[3] != 'k') { throw new FormatException("track chunk != MTrk."); } // 自分のPCがリトルエンディアンなら変換する if (BitConverter.IsLittleEndian) { // トラックのデータ長読み込み(値は6固定) var byteArray = reader.ReadBytes(4); Array.Reverse(byteArray); trackChunks[i].DataLength = BitConverter.ToInt32(byteArray, 0); } else { trackChunks[i].DataLength = BitConverter.ToInt32(reader.ReadBytes(4), 0); } // データ部読み込み trackChunks[i].Data = reader.ReadBytes(trackChunks[i].DataLength); // データ部解析 TrackDataAnalysis( trackChunks[i].Data, headerChunk.Division, (n, s, b) => { ret.NoteArray = n; ret.SoflanArray = s; ret.BeatArray = b; }, () => { throw new Exception("track data analysis failure."); }); } } } catch (Exception e) { // エラーメッセージ処理 Debug.LogError("LoadMIDI Error: " + e); return(null); } return(ret); }
public void LoadMSF(byte[] file) { noteList.Clear(); tempoList.Clear(); // using(var stream = new FileStream(fileAddres, FileMode.Open, FileAccess.Read)) // using(var reader = new BinaryReader.ReadBytes(file)){ var reader = file; var headerCH = new HeaderChunkData(); // チャンクID headerCH.chunkID = SubArraybyte(file, 4); // 自分のPCがリトルエンディアンならバイト順を逆に if (BitConverter.IsLittleEndian) { // ヘッダ部のデータ長(値は6固定) var byteArray = SubArraybyte(file, 4); Array.Reverse(byteArray); headerCH.dataLength = BitConverter.ToInt32(byteArray, 0); // フォーマット(2byte) byteArray = SubArraybyte(file, 2); Array.Reverse(byteArray); headerCH.format = BitConverter.ToInt16(byteArray, 0); // トラック数(2byte) byteArray = SubArraybyte(file, 2); Array.Reverse(byteArray); headerCH.tracks = BitConverter.ToInt16(byteArray, 0); // タイムベース(2byte) byteArray = SubArraybyte(file, 2); Array.Reverse(byteArray); headerCH.division = BitConverter.ToInt16(byteArray, 0); } else { // ヘッダ部のデータ長(値は6固定) headerCH.dataLength = BitConverter.ToInt32(SubArraybyte(file, 4), 0); // フォーマット(2byte) headerCH.format = BitConverter.ToInt16(SubArraybyte(file, 2), 0); // トラック数(2byte) headerCH.tracks = BitConverter.ToInt16(SubArraybyte(file, 2), 0); // タイムベース(2byte) headerCH.division = BitConverter.ToInt16(SubArraybyte(file, 2), 0); } // トラックチャンク侵入 var trackCH = new TrackChunkData[headerCH.tracks]; // トラック数ぶん for (int i = 0; i < headerCH.tracks; i++) { // チャンクID trackCH[i].chunkID = SubArraybyte(file, 4); // 自分のPCがリトルエンディアンなら変換する if (BitConverter.IsLittleEndian) { // トラックのデータ長読み込み(値は6固定) var byteArray = SubArraybyte(file, 4); Array.Reverse(byteArray); trackCH[i].dataLength = BitConverter.ToInt32(byteArray, 0); } else { trackCH[i].dataLength = BitConverter.ToInt32(SubArraybyte(file, 4), 0); } // データ部読み込み trackCH[i].data = SubArraybyte(file, trackCH[i].dataLength); // トラックデータ解析に回す TrackDataAnalys(trackCH[i].data, headerCH); } // using(var stream = new FileStream(fileAddres, FileMode.Open, FileAccess.Read)) // using(var reader = new BinaryReader.ReadBytes(file)){ // var reader=file; // var headerCH =new HeaderChunkData(); // // チャンクID // headerCH.chunkID = SubArraybyte(4); // // 自分のPCがリトルエンディアンならバイト順を逆に // if (BitConverter.IsLittleEndian) // { // // ヘッダ部のデータ長(値は6固定) // var byteArray = reader.ReadBytes(4); // Array.Reverse(byteArray); // headerCH.dataLength = BitConverter.ToInt32(byteArray, 0); // // フォーマット(2byte) // byteArray = reader.ReadBytes(2); // Array.Reverse(byteArray); // headerCH.format = BitConverter.ToInt16(byteArray, 0); // // トラック数(2byte) // byteArray = reader.ReadBytes(2); // Array.Reverse(byteArray); // headerCH.tracks = BitConverter.ToInt16(byteArray, 0); // // タイムベース(2byte) // byteArray = reader.ReadBytes(2); // Array.Reverse(byteArray); // headerCH.division = BitConverter.ToInt16(byteArray, 0); // } // else // { // // ヘッダ部のデータ長(値は6固定) // headerCH.dataLength = BitConverter.ToInt32(reader.ReadBytes(4), 0); // // フォーマット(2byte) // headerCH.format = BitConverter.ToInt16(reader.ReadBytes(2), 0); // // トラック数(2byte) // headerCH.tracks = BitConverter.ToInt16(reader.ReadBytes(2), 0); // // タイムベース(2byte) // headerCH.division = BitConverter.ToInt16(reader.ReadBytes(2), 0); // } // // トラックチャンク侵入 // var trackCH = new TrackChunkData[headerCH.tracks]; // // トラック数ぶん // for(int i=0;i<headerCH.tracks;i++){ // // チャンクID // trackCH[i].chunkID = reader.ReadBytes(4); // // 自分のPCがリトルエンディアンなら変換する // if (BitConverter.IsLittleEndian) // { // // トラックのデータ長読み込み(値は6固定) // var byteArray = reader.ReadBytes(4); // Array.Reverse(byteArray); // trackCH[i].dataLength = BitConverter.ToInt32(byteArray, 0); // } // else // { // trackCH[i].dataLength = BitConverter.ToInt32(reader.ReadBytes(4), 0); // } // // データ部読み込み // trackCH[i].data=reader.ReadBytes(trackCH[i].dataLength); // // トラックデータ解析に回す // TrackDataAnalys(trackCH[i].data,headerCH); // } // } // } }
void TrackDataAnalys(byte[] data, HeaderChunkData headerCH) { // Debug.Log(data.Length); uint CrTime = 0; // 現在の時間 [ ms ] byte statusByte = 0; // ステータスバイト bool[] longFlags = new bool[128]; // ロングノーツ用フラグ for (int i = 0; i < data.Length;) { // Debug.Log(i); bool Fintrack = false; uint deltaTime = 0; while (true) { var tmp = data[i++]; // 下位7bitを格納 deltaTime |= (tmp & (uint)0x7f); // 最上位1bitが0ならデータ終了 if ((tmp & 0x80) == 0) { break; } // 次の下位7bit用にビット移動 deltaTime = deltaTime << 7; } // 現在の時間にデルタタイムを足す CrTime += deltaTime; /* ランニングステータスチェック */ if (data[i] >= 0x80) { statusByte = data[i++]; // ステータスバイト保存 } //else:ランニングステータス適応(前回のステータスバイトを使いまわす) // ステータスバイト後のデータ保存用 byte dataByte0, dataByte1; // byte dataByte2, dataByte3; if (statusByte >= 0x80 && statusByte <= 0xef) { switch (statusByte & 0xf0) { /* チャンネルメッセージ */ // ノートオフ case 0x80: dataByte0 = data[i++]; // どのキーが離されたか // ベロシティ値 dataByte1 = data[i++]; // 前のレーンがロングノーツなら if (longFlags[dataByte0]) { // ロング終点ノート情報生成 var note = new NoteData(); note.eventTime = (int)CrTime; note.laneIndex = (int)dataByte0; note.type = NoteType.LongEnd; // リストにつっこむ noteList.Add(note); // ロングノーツフラグ解除 longFlags[note.laneIndex] = false; } break; case 0x90: // ノートオン(ノートオフが呼ばれるまでは押しっぱなし扱い) // どのキーが押されたか dataByte0 = data[i++]; // ベロシティ値という名の音の強さ。ノートオフメッセージの代わりにここで0を送ってくるタイプもある dataByte1 = data[i++]; { // ノート情報生成 var note = new NoteData(); note.eventTime = (int)CrTime; note.laneIndex = (int)dataByte0; note.type = NoteType.Normal; // 独自でやっている。ベロシティ値が最大のときのみロングの始点とする if (dataByte1 == 127) { note.type = NoteType.LongStart; // ロングノーツフラグセット longFlags[note.laneIndex] = true; } // ノートオフイベントではなく、ベロシティ値0をノートオフとして保存する形式もあるので対応 if (dataByte1 == 0) { // 同じレーンで前回がロングノーツ始点なら if (longFlags[note.laneIndex]) { note.type = NoteType.LongEnd; // ロングノーツフラグ解除 longFlags[note.laneIndex] = false; } } // リストにつっこむ noteList.Add(note); } break; case 0xa0: // ポリフォニック キープレッシャー(鍵盤楽器で、キーを押した状態でさらに押し込んだ際に、その圧力に応じて送信される) i += 2; // 使わないのでスルー break; case 0xb0: // コントロールチェンジ(音量、音質など様々な要素を制御するための命令) // コントロールする番号 dataByte0 = data[i++]; // 設定する値 dataByte1 = data[i++]; // ※0x00-0x77までがコントロールチェンジで、それ以上はチャンネルモードメッセージとして処理する if (dataByte0 < 0x78) { // コントロールチェンジ } else { // チャンネルモードメッセージは一律データバイトを2つ使用している // チャンネルモードメッセージ switch (dataByte0) { case 0x78: // オールサウンドオフ // 該当するチャンネルの発音中の音を直ちに消音する。後述のオールノートオフより強制力が強い。 break; case 0x79: // リセットオールコントローラ // 該当するチャンネルの全種類のコントロール値を初期化する。 break; case 0x7a: // ローカルコントロール // オフ:鍵盤を弾くとMIDIメッセージは送信されるがピアノ自体から音は出ない // オン:鍵盤を弾くと音源から音が出る(基本こっち) break; case 0x7b: // オールノートオフ // 該当するチャンネルの発音中の音すべてに対してノートオフ命令を出す break; /* MIDIモード設定 */ // オムニのオン・オフとモノ・ポリモードを組み合わせて4種類のモードがある case 0x7c: // オムニモードオフ break; case 0x7d: // オムニモードオン break; case 0x7e: // モノモードオン break; case 0x7f: // モノモードオン break; } } break; case 0xc0: // プログラムチェンジ(音色を変える命令) i += 1; break; case 0xd0: // チャンネルプレッシャー(概ねポリフォニック キープレッシャーと同じだが、違いはそのチャンネルの全ノートナンバーに対して有効となる) i += 1; break; case 0xe0: // ピッチベンド(ウォェーンウェューンの表現で使う) i += 2; // ボルテのつまみみたいなのを実装する場合、ここの値が役立つかも break; } } /* システムエクスクルーシブ (SysEx) イベント*/ else if (statusByte == 0x70 || statusByte == 0x7f) { byte dataLength = data[i++]; i += dataLength; } /* メタイベント*/ else if (statusByte == 0xff) { // メタイベントの番号 byte metaEventID = data[i++]; // データ長 byte dataLength = data[i++]; switch (metaEventID) { case 0x00: // シーケンスメッセージ i += dataLength; break; case 0x01: // テキストイベント i += dataLength; break; case 0x02: // 著作権表示 i += dataLength; break; case 0x03: // シーケンス/トラック名 i += dataLength; break; case 0x04: // 楽器名 i += dataLength; break; case 0x05: // 歌詞 i += dataLength; break; case 0x06: // マーカー i += dataLength; break; case 0x07: // キューポイント i += dataLength; break; case 0x20: // MIDIチャンネルプリフィクス i += dataLength; break; case 0x21: // MIDIポートプリフィックス i += dataLength; break; case 0x2f: // トラック終了 i += dataLength; Fintrack = true; // ここでループを抜けても良い break; case 0x51: // テンポ変更 { // テンポ変更情報リストに格納する var tempoData = new TempoData(); tempoData.eventTime = (int)CrTime; // 4分音符の長さをマイクロ秒単位で格納されている uint tempo = 0; tempo |= data[i++]; tempo <<= 8; tempo |= data[i++]; tempo <<= 8; tempo |= data[i++]; // BPM割り出し tempoData.bpm = 60000000 / (float)tempo; // 小数点第1で切り捨て処理(10にすると第一位、100にすると第2位まで切り捨てられる) tempoData.bpm = Mathf.Floor(tempoData.bpm * 10) / 10; // tick値割り出し tempoData.tick = (60 / tempoData.bpm / headerCH.division * 1000); // リストにつっこむ tempoList.Add(tempoData); } break; case 0x54: // SMTPEオフセット i += dataLength; break; case 0x58: // 拍子 i += dataLength; // 小節線を表示させるなら使えるかも break; case 0x59: // 調号 i += dataLength; break; case 0x7f: // シーケンサ固有メタイベント i += dataLength; break; } } if (Fintrack) { break; } } // Debug.Log("FinAnalyz"); }
public void LoadMSF(string fileAddres) { noteList.Clear(); tempoList.Clear(); using (var stream = new FileStream(fileAddres, FileMode.Open, FileAccess.Read)) using (var reader = new BinaryReader(stream)){ var headerCH = new HeaderChunkData(); // チャンクID headerCH.chunkID = reader.ReadBytes(4); // 自分のPCがリトルエンディアンならバイト順を逆に if (BitConverter.IsLittleEndian) { // ヘッダ部のデータ長 var byteArray = reader.ReadBytes(4); Array.Reverse(byteArray); headerCH.dataLength = BitConverter.ToInt32(byteArray, 0); // フォーマット(2byte) byteArray = reader.ReadBytes(2); Array.Reverse(byteArray); headerCH.format = BitConverter.ToInt16(byteArray, 0); // トラック数(2byte) byteArray = reader.ReadBytes(2); Array.Reverse(byteArray); headerCH.tracks = BitConverter.ToInt16(byteArray, 0); // タイムベース(2byte) byteArray = reader.ReadBytes(2); Array.Reverse(byteArray); headerCH.division = BitConverter.ToInt16(byteArray, 0); } else { // ヘッダ部のデータ長 headerCH.dataLength = BitConverter.ToInt32(reader.ReadBytes(4), 0); // フォーマット(2byte) headerCH.format = BitConverter.ToInt16(reader.ReadBytes(2), 0); // トラック数(2byte) headerCH.tracks = BitConverter.ToInt16(reader.ReadBytes(2), 0); // タイムベース(2byte) headerCH.division = BitConverter.ToInt16(reader.ReadBytes(2), 0); } // トラックチャンク var trackCH = new TrackChunkData[headerCH.tracks]; // トラック数 for (int i = 0; i < headerCH.tracks; i++) { // チャンクID trackCH[i].chunkID = reader.ReadBytes(4); // 自分のPCがリトルエンディアンなら変換する if (BitConverter.IsLittleEndian) { // トラックのデータ長読み込み var byteArray = reader.ReadBytes(4); Array.Reverse(byteArray); trackCH[i].dataLength = BitConverter.ToInt32(byteArray, 0); } else { trackCH[i].dataLength = BitConverter.ToInt32(reader.ReadBytes(4), 0); } // データ部読み込み trackCH[i].data = reader.ReadBytes(trackCH[i].dataLength); // トラックデータ解析へ TrackDataAnalys(trackCH[i].data, headerCH); } } }
void TrackDataAnalys(byte[] data, HeaderChunkData headerCH) { // Debug.Log(data.Length); uint CrTime = 0; byte statusByte = 0; bool[] longFlags = new bool[128]; for (int i = 0; i < data.Length;) { // Debug.Log(i); bool Fintrack = false; uint deltaTime = 0; while (true) { var tmp = data[i++]; deltaTime |= (tmp & (uint)0x7f); if ((tmp & 0x80) == 0) { break; } deltaTime = deltaTime << 7; } // 現在の時間にデルタタイムを足す CrTime += deltaTime; if (data[i] >= 0x80) { statusByte = data[i++]; } //else:ランニングステータス適応(前回のステータスバイトを使いまわす) // ステータスバイト後のデータ保存用 byte dataByte0, dataByte1; // byte dataByte2, dataByte3; if (statusByte >= 0x80 && statusByte <= 0xef) { switch (statusByte & 0xf0) { /* チャンネルメッセージ */ // ノートオフ case 0x80: dataByte0 = data[i++]; dataByte1 = data[i++]; if (longFlags[dataByte0]) { var note = new NoteData(); note.eventTime = (int)CrTime; note.laneIndex = (int)dataByte0; note.type = NoteType.LongEnd; // リストにつっこむ noteList.Add(note); longFlags[note.laneIndex] = false; } break; // ノートオン(ノートオフが呼ばれるまでは押しっぱなし扱い) case 0x90: // どのキーが押されたか dataByte0 = data[i++]; dataByte1 = data[i++]; { // ノート情報生成 var note = new NoteData(); note.eventTime = (int)CrTime; note.laneIndex = (int)dataByte0; note.type = NoteType.Normal; if (dataByte1 == 127) { note.type = NoteType.LongStart; longFlags[note.laneIndex] = true; } // ノートオフイベントではなく、ベロシティ値0をノートオフとして保存する形式もあるので対応 if (dataByte1 == 0) { // 同じレーンで前回がロングノーツ始点なら if (longFlags[note.laneIndex]) { note.type = NoteType.LongEnd; longFlags[note.laneIndex] = false; } } // リストにつっこむ noteList.Add(note); } break; case 0xa0: i += 2; // 使わないのでスルー break; case 0xb0: dataByte0 = data[i++]; dataByte1 = data[i++]; // ※0x00-0x77までがコントロールチェンジで、それ以上はチャンネルモードメッセージとして処理する if (dataByte0 < 0x78) { // コントロールチェンジ } else { switch (dataByte0) { case 0x78: break; case 0x79: break; case 0x7a: break; case 0x7b: break; // オムニモードオフ case 0x7c: break; // オムニモードオン case 0x7d: break; // モノモードオン case 0x7e: break; // モノモードオン case 0x7f: break; } } break; case 0xc0: i += 1; break; case 0xd0: i += 1; break; case 0xe0: i += 2; break; } } else if (statusByte == 0x70 || statusByte == 0x7f) { byte dataLength = data[i++]; i += dataLength; } else if (statusByte == 0xff) { // メタイベント byte metaEventID = data[i++]; byte dataLength = data[i++]; switch (metaEventID) { // シーケンスメッセージ case 0x00: i += dataLength; break; // テキストイベント case 0x01: i += dataLength; break; // 著作権表示 case 0x02: i += dataLength; break; // シーケンス/トラック名 case 0x03: i += dataLength; break; // 楽器名 case 0x04: i += dataLength; break; // 歌詞 case 0x05: i += dataLength; break; // マーカー case 0x06: i += dataLength; break; // キューポイント case 0x07: i += dataLength; break; // MIDIチャンネルプリフィクス case 0x20: i += dataLength; break; // MIDIポートプリフィックス case 0x21: i += dataLength; break; // トラック終了 case 0x2f: i += dataLength; Fintrack = true; // ここでループを抜ける break; // テンポ変更 case 0x51: { // テンポ変更情報リストに格納する var tempoData = new TempoData(); tempoData.eventTime = (int)CrTime; // 4分音符の長さをマイクロ秒単位で格納されている uint tempo = 0; tempo |= data[i++]; tempo <<= 8; tempo |= data[i++]; tempo <<= 8; tempo |= data[i++]; // BPM割り出し tempoData.bpm = 60000000 / (float)tempo; // 小数点第1で切り捨て処理(10にすると第一位、100にすると第2位まで切り捨てられる) tempoData.bpm = Mathf.Floor(tempoData.bpm * 10) / 10; // tick値割り出し tempoData.tick = (60 / tempoData.bpm / headerCH.division * 1000); // リストにつっこむ tempoList.Add(tempoData); } break; // SMTPEオフセット case 0x54: i += dataLength; break; // 拍子 case 0x58: // 小節線を表示させるなら使えるかも i += dataLength; break; // 調号 case 0x59: i += dataLength; break; // シーケンサ固有メタイベント case 0x7f: i += dataLength; break; } } if (Fintrack) { break; } } // Debug.Log("FinAnalyz"); }