/*
* 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;
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 ) {
byte d = (byte)stream.ReadByte();
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( numerator, 2 ) + 0.1);
ret.Data = new byte[5] { 0x58, (byte)denominator, 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 )
: this( new FileStream( path, FileMode.Open ) ) {
}
public MidiFile( Stream stream ) {
// ヘッダ
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 ) {
m_events[track].Add( MidiEvent.read( stream, ref clock, ref last_status_byte ) );
}
}
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;
}
}
}