/* * MidiFile.cs * Copyright (c) 2009 kbinani * * This file is part of Boare.Lib.Vsq. * * Boare.Lib.Vsq is free software; you can redistribute it and/or * modify it under the terms of the BSD License. * * Boare.Lib.Vsq is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ using System; using System.IO; using System.Collections.Generic; namespace Boare.Lib.Vsq { /// /// midiイベント。メタイベントは、メタイベントのデータ長をData[1]に格納せず、生のデータをDataに格納するので、注意が必要 /// public struct MidiEvent : IComparable { public long clock; public byte firstByte; public byte[] data; public long Clock { get { return clock; } set { clock = value; } } public byte FirstByte { get { return firstByte; } set { firstByte = value; } } public byte[] Data { get { return data; } set { data = value; } } private static void writeDeltaClock( Stream stream, long number ) { bool[] bits = new bool[64]; long val = 0x1; bits[0] = (number & val) == val; for ( int i = 1; i < 64; i++ ) { val = val << 1; bits[i] = (number & val) == val; } int first = 0; for ( int i = 63; i >= 0; i-- ) { if ( bits[i] ) { first = i; break; } } // 何バイト必要か? int bytes = first / 7 + 1; for ( int i = 1; i <= bytes; i++ ) { uint num = 0; uint count = 0x80; for ( int j = (bytes - i + 1) * 7 - 1; j >= (bytes - i + 1) * 7 - 6 - 1; j-- ) { count = count >> 1; if ( bits[j] ) { num += count; } } if ( i != bytes ) { num += 0x80; } stream.WriteByte( (byte)num ); } } private static long readDeltaClock( Stream stream ) { long ret = 0; while ( true ) { int i = stream.ReadByte(); if ( i < 0 ) { break; } byte d = (byte)i; ret = (ret << 7) | ((long)d & 0x7f); if ( (d & 0x80) == 0x00 ) { break; } } return ret; } public static MidiEvent read( Stream stream, ref long last_clock, ref byte last_status_byte ) { long delta_clock = readDeltaClock( stream ); last_clock += delta_clock; byte first_byte = (byte)stream.ReadByte(); if ( first_byte < 0x80 ) { // ランニングステータスが適用される stream.Seek( -1, SeekOrigin.Current ); first_byte = last_status_byte; } else { last_status_byte = first_byte; } byte ctrl = (byte)(first_byte & (byte)0xf0); if ( ctrl == 0x80 || ctrl == 0x90 || ctrl == 0xA0 || ctrl == 0xB0 || ctrl == 0xE0 || first_byte == 0xF2 ) { // 3byte使用するチャンネルメッセージ: // 0x8*: ノートオフ // 0x9*: ノートオン // 0xA*: ポリフォニック・キープレッシャ // 0xB*: コントロールチェンジ // 0xE*: ピッチベンドチェンジ // 3byte使用するシステムメッセージ // 0xF2: ソングポジション・ポインタ MidiEvent me = new MidiEvent(); me.clock = last_clock; me.firstByte = first_byte; me.data = new byte[2]; stream.Read( me.data, 0, 2 ); return me; } else if ( ctrl == 0xC0 || ctrl == 0xD0 || first_byte == 0xF1 || first_byte == 0xF2 ) { // 2byte使用するチャンネルメッセージ // 0xC*: プログラムチェンジ // 0xD*: チャンネルプレッシャ // 2byte使用するシステムメッセージ // 0xF1: クォータフレーム // 0xF3: ソングセレクト MidiEvent me = new MidiEvent(); me.clock = last_clock; me.firstByte = first_byte; me.data = new byte[1]; stream.Read( me.data, 0, 1 ); return me; } else if ( first_byte == 0xF6 ) { // 1byte使用するシステムメッセージ // 0xF6: チューンリクエスト // 0xF7: エンドオブエクスクルーシブ(このクラスではF0ステータスのSysExの一部として取り扱う) // 0xF8: タイミングクロック // 0xFA: スタート // 0xFB: コンティニュー // 0xFC: ストップ // 0xFE: アクティブセンシング // 0xFF: システムリセット MidiEvent me = new MidiEvent(); me.clock = last_clock; me.firstByte = first_byte; me.data = new byte[0]; return me; } else if ( first_byte == 0xff ) { // メタイベント byte meta_event_type = (byte)stream.ReadByte(); long meta_event_length = readDeltaClock( stream ); MidiEvent me = new MidiEvent(); me.clock = last_clock; me.firstByte = first_byte; me.data = new byte[meta_event_length + 1]; me.data[0] = meta_event_type; stream.Read( me.data, 1, (int)meta_event_length ); return me; } else if ( first_byte == 0xf0 ) { // f0ステータスのSysEx MidiEvent me = new MidiEvent(); me.clock = last_clock; me.firstByte = first_byte; long sysex_length = readDeltaClock( stream ); me.data = new byte[sysex_length + 1]; stream.Read( me.data, 0, (int)(sysex_length + 1) ); return me; } else if ( first_byte == 0xf7 ) { // f7ステータスのSysEx MidiEvent me = new MidiEvent(); me.clock = last_clock; me.firstByte = first_byte; long sysex_length = readDeltaClock( stream ); me.data = new byte[sysex_length]; stream.Read( me.data, 0, (int)sysex_length ); return me; } else { throw new ApplicationException( "don't know how to process first_byte: 0x" + Convert.ToString( first_byte, 16 ) ); } } public void writeData( Stream stream ) { stream.WriteByte( firstByte ); if ( firstByte == 0xff ) { stream.WriteByte( data[0] ); writeDeltaClock( stream, data.Length - 1 ); //stream.WriteByte( (byte)(Data.Length - 1) ); stream.Write( data, 1, data.Length - 1 ); } else { stream.Write( data, 0, data.Length ); } } public int CompareTo( MidiEvent item ) { return (int)(clock - item.clock); } public static MidiEvent generateTimeSigEvent( int clock, int numerator, int denominator ) { MidiEvent ret = new MidiEvent(); ret.clock = clock; ret.firstByte = 0xff; byte b_numer = (byte)(Math.Log( denominator, 2 ) + 0.1); #if DEBUG Console.WriteLine( "VsqEvent.generateTimeSigEvent; b_number=" + b_numer + "; denominator=" + denominator ); #endif ret.data = new byte[5] { 0x58, (byte)numerator, b_numer, 0x18, 0x08 }; return ret; } public static MidiEvent generateTempoChangeEvent( int clock, int tempo ) { MidiEvent ret = new MidiEvent(); ret.clock = clock; ret.firstByte = 0xff; byte b1 = (byte)(tempo & 0xff); tempo = tempo >> 8; byte b2 = (byte)(tempo & 0xff); tempo = tempo >> 8; byte b3 = (byte)(tempo & 0xff); ret.data = new byte[4] { 0x51, b3, b2, b1 }; return ret; } } public class MidiFile : IDisposable { private List> m_events; private ushort m_format; private ushort m_time_format; public MidiFile( string path ){ Stream stream = new FileStream( path, FileMode.Open, FileAccess.Read ); // ヘッダ byte[] byte4 = new byte[4]; stream.Read( byte4, 0, 4 ); if ( makeUInt32( byte4 ) != 0x4d546864 ) { throw new ApplicationException( "header error: MThd" ); } // データ長 stream.Read( byte4, 0, 4 ); uint length = makeUInt32( byte4 ); // フォーマット stream.Read( byte4, 0, 2 ); m_format = makeUint16( byte4 ); // トラック数 int tracks = 0; stream.Read( byte4, 0, 2 ); tracks = (int)makeUint16( byte4 ); // 時間分解能 stream.Read( byte4, 0, 2 ); m_time_format = makeUint16( byte4 ); // 各トラックを読込み m_events = new List>(); for ( int track = 0; track < tracks; track++ ) { // ヘッダー stream.Read( byte4, 0, 4 ); if ( makeUInt32( byte4 ) != 0x4d54726b ) { throw new ApplicationException( "header error; MTrk" ); } m_events.Add( new List() ); // チャンクサイズ stream.Read( byte4, 0, 4 ); long size = (long)makeUInt32( byte4 ); long startpos = stream.Position; // チャンクの終わりまで読込み long clock = 0; byte last_status_byte = 0x00; while ( stream.Position < startpos + size ) { MidiEvent mi = MidiEvent.read( stream, ref clock, ref last_status_byte ); m_events[track].Add( mi ); } if ( m_time_format != 480 ) { int count = m_events[track].Count; for ( int i = 0; i < count; i++ ) { MidiEvent mi = m_events[track][i]; mi.clock = mi.clock * 480 / m_time_format; m_events[track][i] = mi; } } } m_time_format = 480; #if DEBUG string dbg = Path.Combine( Path.GetDirectoryName( path ), Path.GetFileNameWithoutExtension( path ) + ".txt" ); using ( StreamWriter sw = new StreamWriter( dbg ) ) { const string format = " {0,8} 0x{1:X4} {2,-35} 0x{3:X2} 0x{4:X2}"; const string format0 = " {0,8} 0x{1:X4} {2,-35} 0x{3:X2}"; for ( int track = 1; track < m_events.Count; track++ ) { sw.WriteLine( "MidiFile..ctor; track=" + track ); byte msb, lsb, data_msb, data_lsb; msb = lsb = data_msb = data_lsb = 0x0; for ( int i = 0; i < m_events[track].Count; i++ ) { if ( m_events[track][i].firstByte == 0xb0 ) { switch ( m_events[track][i].data[0] ) { case 0x63: msb = m_events[track][i].data[1]; lsb = 0x0; break; case 0x62: lsb = m_events[track][i].data[1]; break; case 0x06: data_msb = m_events[track][i].data[1]; ushort nrpn = (ushort)(msb << 8 | lsb); string name = NRPN.getName( nrpn ); if ( name == "" ) { name = "* * UNKNOWN * *"; sw.WriteLine( string.Format( format0, m_events[track][i].clock, nrpn, name, data_msb ) ); } else { //if ( !NRPN.is_require_data_lsb( nrpn ) ) { sw.WriteLine( string.Format( format0, m_events[track][i].clock, nrpn, name, data_msb ) ); //} } break; case 0x26: data_lsb = m_events[track][i].data[1]; ushort nrpn2 = (ushort)(msb << 8 | lsb); string name2 = NRPN.getName( nrpn2 ); if ( name2 == "" ) { name2 = "* * UNKNOWN * *"; } sw.WriteLine( string.Format( format, m_events[track][i].clock, nrpn2, name2, data_msb, data_lsb ) ); break; } } } } } #endif stream.Close(); } /*public void Write( string path ) { }*/ public List getMidiEventList( int track ) { if ( m_events == null ) { return new List(); } else if ( 0 <= track && track < m_events.Count ) { return m_events[track]; } else { return new List(); } } public int getTrackCount() { if ( m_events == null ) { return 0; } else { return m_events.Count; } } public void Dispose() { Close(); } public void Close() { if ( m_events != null ) { for ( int i = 0; i < m_events.Count; i++ ) { m_events[i].Clear(); } m_events.Clear(); } } private static UInt32 makeUInt32( byte[] value ) { return (uint)((uint)((uint)((uint)(value[0] << 8) | value[1]) << 8 | value[2]) << 8 | value[3]); } private static UInt16 makeUint16( byte[] value ) { return (ushort)((ushort)(value[0] << 8) | value[1]); } private static long readDeltaClock( Stream stream ) { byte[] b; long ret = 0; while ( true ) { byte d = (byte)stream.ReadByte(); ret = (ret << 7) | ((long)d & 0x7f); if ( (d & 0x80) == 0x00 ) { break; } } return ret; } } }