/// <summary> /// テンポを元にイベント時刻を補正 /// </summary> void ModificationEventTimes(List <NoteData> noteList, List <SoflanData> soflanList, List <BeatData> beatList) { var tempNoteList = new List <NoteData>(noteList); var tempSoflanList = new List <SoflanData>(soflanList); var tempBeatList = new List <BeatData>(beatList); // ソフラン座標調整 for (int i = 1; i < soflanList.Count; i++) { SoflanData soflan = soflanList[i]; int timeDifference = tempSoflanList[i].EventTime - tempSoflanList[i - 1].EventTime; soflan.EventTime = (int)(timeDifference * soflanList[i - 1].Tick) + soflanList[i - 1].EventTime; soflanList[i] = soflan; } // ノーツ座標調整 for (int i = 0; i < noteList.Count; i++) { for (int j = soflanList.Count - 1; j >= 0; j--) { if (tempNoteList[i].EventTime >= tempSoflanList[j].EventTime) // >ではなく、>=にしないとソフランがずれる { NoteData note = noteList[i]; int timeDifference = noteList[i].EventTime - tempSoflanList[j].EventTime; note.EventTime = (int)(timeDifference * tempSoflanList[j].Tick) + soflanList[j].EventTime; // ソフランのイベント時間+そっからの自分の時間的な何か note.SoflanNo = j; noteList[i] = note; break; } } } // 拍子座標調整 for (int i = 0; i < beatList.Count; i++) { for (int j = soflanList.Count - 1; j >= 0; j--) { if (tempBeatList[i].EventTime >= tempSoflanList[j].EventTime) // >ではなく、>=にしないとソフランがずれる { BeatData beat = beatList[i]; int timeDifference = beatList[i].EventTime - tempSoflanList[j].EventTime; beat.EventTime = (int)(timeDifference * tempSoflanList[j].Tick) + soflanList[j].EventTime; // ソフランのイベント時間+そっからの自分の時間的な何か beatList[i] = beat; break; } } } }
/// <summary> /// トラックデータ解析 /// </summary> public void TrackDataAnalysis(byte[] data, short division, Action <NoteData[], SoflanData[], BeatData[]> success, Action failure) { // リスト初期化 var noteList = new List <NoteData>(); var soflanList = new List <SoflanData>(); var beatList = new List <BeatData>(); try { uint deltaTime = 0; // デルタタイム格納用 uint currentTime = 0; // デルタタイムを足していく、つまり現在の時間(ノーツやソフランのイベントタイムはこれを使う) byte tmp = 0; // 値保存用 byte statusByte = 0; // ステータスバイト byte dataByte0, dataByte1, dataByte2, dataByte3; // メッセージの後に付いてるデータ // データぶん回す for (int i = 0; i < data.Length;) { // メモ データ部は<デルタタイム><イベント>の繰り返しで構成されている /* デルタタイム(可変長数値表現) */ deltaTime = 0; // 可変長数値表現をint型に戻す while (true) { // 無限ループは怖いので例外を書いておく if (i >= data.Length) { throw new Exception("delta time infinity loop."); } tmp = data[i++]; // 下位7bitを格納 deltaTime |= tmp & (uint)0x7f; // 最上位1bitが0ならデータ終了 if ((tmp & 0x80) == 0) { break; } // 次の下位7bit用に移動 deltaTime = deltaTime << 7; } // 現在の時間にデルタタイムを足す currentTime += deltaTime; System.Diagnostics.Debug.WriteLine("delta_time:" + deltaTime); System.Diagnostics.Debug.WriteLine("current_time:" + currentTime); /* ランニングステータス(前回のステータスバイトを使いまわす) */ if (data[i] < 0x80) { // ランニングステータス適応 //throw new Exception("not status byte. must be statusByte < 0x80 || statusByte > 0xff" + statusByte); System.Diagnostics.Debug.WriteLine("Running Status."); } else { // ステータスバイト保存 statusByte = data[i++]; } /* イベント */ /* MIDIイベント(ステータスバイト0x80-0xEF) */ if (statusByte >= 0x80 && statusByte <= 0xef) { // MIDIチャンネル番号 System.Diagnostics.Debug.WriteLine("channel:" + (statusByte & 0x0f)); switch (statusByte & 0xf0) { /* チャンネルメッセージ */ case 0x80: // ノートオフ // どのキーが離されたか dataByte0 = data[i++]; // ベロシティ値 dataByte1 = data[i++]; // ノート情報生成 { var note = new NoteData(); note.EventTime = (int)currentTime; note.LaneIndex = dataByte0; noteList.Add(note); } System.Diagnostics.Debug.WriteLine("NoteOFF. kk:" + dataByte0 + " vv:" + dataByte1); break; case 0x90: // ノートオン(ノートオフが呼ばれるまでは押しっぱなし扱い) // どのキーが押されたか dataByte0 = data[i++]; // ベロシティ値(鍵盤押す強さ機能がないキーボードは0x40固定) キーボードによってはノートオフメッセージの代わりにここで0を送ってくる dataByte1 = data[i++]; // ノート情報生成 { var note = new NoteData(); note.EventTime = (int)currentTime; note.LaneIndex = dataByte0; note.IsPress = dataByte1 != 0; noteList.Add(note); } System.Diagnostics.Debug.WriteLine("NoteON. kk:" + dataByte0 + " vv:" + dataByte1); break; case 0xa0: // ポリフォニック キープレッシャー(鍵盤楽器で、キーを押した状態でさらに押し込んだ際に、その圧力に応じて送信される) // どのキーが押されたか dataByte0 = data[i++]; // 押されている圧力 dataByte1 = data[i++]; System.Diagnostics.Debug.WriteLine("Polyphonic Key Pressure. kk:" + dataByte0 + " vv:" + dataByte1); break; case 0xb0: // コントロールチェンジ(音量、音質など様々な要素を制御するための命令) // コントロールする番号 dataByte0 = data[i++]; // 設定する値 dataByte1 = data[i++]; // ※0x00-0x77までがコントロールチェンジで、それ以上はチャンネルモードメッセージとして処理する if (dataByte0 < 0x78) { // コントロールチェンジ System.Diagnostics.Debug.WriteLine("Controller Change. cc:" + dataByte0 + " nn:" + dataByte1); } else { // チャンネルモードメッセージは一律データバイトを2つ使用している // チャンネルモードメッセージ switch (dataByte0) { case 0x78: // オールサウンドオフ // 該当するチャンネルの発音中の音を直ちに消音する。後述のオールノートオフより強制力が強い。 System.Diagnostics.Debug.WriteLine("All Sound Off."); break; case 0x79: // リセットオールコントローラ // 該当するチャンネルの全種類のコントロール値を初期化する。 System.Diagnostics.Debug.WriteLine("Reset All Controllers."); break; case 0x7a: // ローカルコントロール(ピアノ音ゲーをやる時に電子ピアノに設定するやつ) // オフ:鍵盤を弾くとMIDIメッセージは送信されるがピアノ自体から音は出ない // オン:鍵盤を弾くと音源から音が出る(基本こっち) System.Diagnostics.Debug.WriteLine("Local Control. xx:" + (dataByte1 == 0 ? "Local Off" : "Local On")); break; case 0x7b: // オールノートオフ // 該当するチャンネルの発音中の音すべてに対してノートオフ命令を出す System.Diagnostics.Debug.WriteLine("All Notes Off."); break; /* MIDIモード設定 */ // オムニのオン・オフとモノ・ポリモードを組み合わせて4種類のモードがある case 0x7c: // オムニモードオフ System.Diagnostics.Debug.WriteLine("Omni Mode Off."); break; case 0x7d: // オムニモードオン System.Diagnostics.Debug.WriteLine("Omni Mode On."); break; case 0x7e: // モノモードオン /* * dataByte2 * 00 = use n...16 * 01 = use 1 channel * 0x10 = use 16 channels (provided n=0) */ System.Diagnostics.Debug.WriteLine("Mono Mode On."); break; case 0x7f: // モノモードオン System.Diagnostics.Debug.WriteLine("Poly Mode On."); break; default: throw new Exception("channel mode message unintended value."); } } break; case 0xc0: // プログラムチェンジ(音色を変える命令) // 音色の番号 dataByte0 = data[i++]; System.Diagnostics.Debug.WriteLine("Program Change. pp:" + dataByte0); break; case 0xd0: // チャンネルプレッシャー(概ねポリフォニック キープレッシャーと同じだが、違いはそのチャンネルの全ノートナンバーに対して有効となる) // 圧力の値 dataByte0 = data[i++]; System.Diagnostics.Debug.WriteLine("Channel Key Pressure. ww:" + dataByte0); break; case 0xe0: // ピッチベンド(ウォェーンウェューンの表現で使う) // Least Signiflcant Byte dataByte0 = data[i++]; // Most Signiflcant Byte dataByte1 = data[i++]; // メモ 日本語wikiではMSB,LSBの順だと記載されているが、別の英語のサイトではLSB,MSBの順と記載されており正解が分からない。試す機会があったら確認 System.Diagnostics.Debug.WriteLine("Pitch Bend. LSB:" + dataByte0 + " MSB:" + dataByte1); break; default: throw new Exception("channel voice message unintended value."); } } /* システムエクスクルーシブ (SysEx) イベント よくわからんから適当にあしらう */ else if (statusByte == 0x70 || statusByte == 0x7f) { // データ長 byte dataLength = data[i++]; // スルーするー i += dataLength; } /* メタイベント*/ else if (statusByte == 0xff) { tmp = data[i++]; byte[] dataByteArray; // データ長 byte dataLength = data[i++]; switch (tmp) { case 0x00: // シーケンスメッセージ if (dataLength != 0x02) { throw new Exception("Sequence Number not value 0x02."); } // dataByte0 = data[i++]; // dataByte1 = data[i++]; System.Diagnostics.Debug.WriteLine("Sequence. Number. ss:" + dataByte0 + " ss:" + dataByte1); break; case 0x01: // テキストイベント System.Diagnostics.Debug.WriteLine("Text Event."); if (dataLength == 0) { continue; } // テキスト dataByteArray = new byte[dataLength]; Array.Copy(data, i, dataByteArray, 0, dataLength); i += dataLength; System.Diagnostics.Debug.WriteLine("Length:" + dataLength + " Text:" + System.Text.Encoding.ASCII.GetString(dataByteArray)); break; case 0x02: // 著作権表示 System.Diagnostics.Debug.WriteLine("Copyright Notice."); if (dataLength == 0) { continue; } // テキスト dataByteArray = new byte[dataLength]; Array.Copy(data, i, dataByteArray, 0, dataLength); i += dataLength; System.Diagnostics.Debug.WriteLine("Length:" + dataLength + " Text:" + System.Text.Encoding.ASCII.GetString(dataByteArray)); break; case 0x03: // シーケンス/トラック名 System.Diagnostics.Debug.WriteLine("Sequence/Track Name."); if (dataLength == 0) { continue; } // テキスト dataByteArray = new byte[dataLength]; Array.Copy(data, i, dataByteArray, 0, dataLength); i += dataLength; System.Diagnostics.Debug.WriteLine("Length:" + dataLength + " Text:" + System.Text.Encoding.ASCII.GetString(dataByteArray)); break; case 0x04: // 楽器名 System.Diagnostics.Debug.WriteLine("Instrument Name."); if (dataLength == 0) { continue; } // テキスト dataByteArray = new byte[dataLength]; Array.Copy(data, i, dataByteArray, 0, dataLength); i += dataLength; System.Diagnostics.Debug.WriteLine("Length:" + dataLength + " Text:" + System.Text.Encoding.ASCII.GetString(dataByteArray)); break; case 0x05: // 歌詞 System.Diagnostics.Debug.WriteLine("Lyric."); if (dataLength == 0) { continue; } // テキスト dataByteArray = new byte[dataLength]; Array.Copy(data, i, dataByteArray, 0, dataLength); i += dataLength; System.Diagnostics.Debug.WriteLine("Length:" + dataLength + " Text:" + System.Text.Encoding.ASCII.GetString(dataByteArray)); break; case 0x06: // マーカー System.Diagnostics.Debug.WriteLine("Marker."); if (dataLength == 0) { continue; } // テキスト dataByteArray = new byte[dataLength]; Array.Copy(data, i, dataByteArray, 0, dataLength); i += dataLength; System.Diagnostics.Debug.WriteLine(" Length:" + dataLength + " Text:" + System.Text.Encoding.ASCII.GetString(dataByteArray)); break; case 0x07: // キューポイント System.Diagnostics.Debug.WriteLine("Cue Point."); if (dataLength == 0) { continue; } // テキスト dataByteArray = new byte[dataLength]; Array.Copy(data, i, dataByteArray, 0, dataLength); i += dataLength; System.Diagnostics.Debug.WriteLine("Length:" + dataLength + " Text:" + System.Text.Encoding.ASCII.GetString(dataByteArray)); break; case 0x20: // MIDIチャンネルプリフィクス if (dataLength != 0x01) { throw new Exception("MIDI Channel Prefix not value 0x01."); } // dataByte0 = data[i++]; System.Diagnostics.Debug.WriteLine("MIDI Channel Prefix. cc:" + dataByte0); break; case 0x21: // MIDIポートプリフィックス if (dataLength != 0x01) { throw new Exception("MIDI Port Prefix not value 0x01."); } // dataByte0 = data[i++]; System.Diagnostics.Debug.WriteLine("MIDI Port Prefix. cc:" + dataByte0); break; case 0x2f: // トラック終了 if (dataLength != 0x00) { throw new Exception("End of Track not value 0x00."); } System.Diagnostics.Debug.WriteLine("End of Track."); break; case 0x51: // テンポ if (dataLength != 0x03) { throw new Exception("Set Tempo not value 0x03."); } // 4分音符の長さをマイクロ秒単位で格納されている //dataByteArray = new byte[4]; //Array.Copy(data, i, dataByteArray, 0, 3); //i += 3; //// byte配列をintに変換 //var tempo = BitConverter.ToInt32(dataByteArray, 0); var tempo = 0; tempo |= data[i++]; tempo <<= 8; tempo |= data[i++]; tempo <<= 8; tempo |= data[i++]; { // ソフラン情報生成 var soflan = new SoflanData(); soflan.EventTime = (int)currentTime; // BPM割り出し soflan.Bpm = 60000000 / (float)tempo; // 小数点第1で切り捨て処理(10にすると第一位、100にすると第2位まで切り捨てられる) soflan.Bpm = Mathf.Floor(soflan.Bpm * 10) / 10; // tick値割り出し soflan.Tick = (60 / soflan.Bpm / division * 1000); // リストにつっこむ soflanList.Add(soflan); } System.Diagnostics.Debug.WriteLine("Set Tempo. tt:" + tempo); break; case 0x54: // SMTPEオフセット if (dataLength != 0x05) { throw new Exception("SMTPE Offset not value 0x05."); } // hour dataByte0 = data[i++]; // minutes dataByte1 = data[i++]; // seconds dataByte2 = data[i++]; // frames dataByte3 = data[i++]; // fractional frame. tmp = data[i++]; System.Diagnostics.Debug.WriteLine("SMTPE Offset. hh:" + dataByte0 + ", mm:" + dataByte1 + ", ss:" + dataByte2 + ", fr:" + dataByte3 + " , ff:" + tmp); break; case 0x58: // 拍子 if (dataLength != 0x04) { throw new Exception("Time Signature not value 0x04."); } // Time signature numerator dataByte0 = data[i++]; // Time signeture denominator expressed as a power of 2 dataByte1 = data[i++]; // MIDI Clocks per metronome tick dataByte2 = data[i++]; // Number of 1/32 notes per 24 MIDI clocks(8 is standard) dataByte3 = data[i++]; { // 拍子情報生成 var beat = new BeatData(); beat.EventTime = (int)currentTime; // 分子 beat.Numerator = dataByte0; // 分母 beat.Denominator = 1; for (int j = 0; j < dataByte1; j++) { beat.Denominator *= 2; // 2の累乗 } // リストにつっこむ beatList.Add(beat); } System.Diagnostics.Debug.WriteLine("Time Signature. nn:" + dataByte0 + ", dd:" + dataByte1 + ", cc:" + dataByte2 + ", bb:" + dataByte3); break; case 0x59: // 調号 if (dataLength != 0x02) { throw new Exception("Key Signature not value 0x02."); } // number of sharps or flats -7, 0(key of C), +7 dataByte0 = data[i++]; // 0: major key. 1: minor key dataByte1 = data[i++]; System.Diagnostics.Debug.WriteLine("Key Signature. sf:" + dataByte0 + ", mi:" + (dataByte1 == 0 ? "major key" : "minor key")); break; case 0x7f: // シーケンサ固有メタイベント // length of <id>+<data> dataLength = data[i++]; // 1 or 3 bytes repressenting the Manufacture's ID if (dataLength - 8 == 1) { dataByte1 = data[i++]; } else if (dataLength - 8 == 3) { dataByteArray = new byte[3]; Array.Copy(data, i, dataByteArray, 0, 3); i += 3; } else { throw new Exception("Seuencer-Specific Meta-event length not 1 or 3"); } // 8-bit binary data dataByteArray = new byte[8]; Array.Copy(data, i, dataByteArray, 0, 8); i += 8; System.Diagnostics.Debug.WriteLine("Seuencer-Specific Meta-event"); break; default: throw new Exception("meta event unintended value:" + tmp); } } } } catch (Exception e) { // エラーメッセージ処理 Debug.LogWarning("MIDIDataAnalysisError: " + e); failure(); } // テンポを元にイベント時刻を補正 ModificationEventTimes(noteList, soflanList, beatList); success(noteList.ToArray(), soflanList.ToArray(), beatList.ToArray()); }