今回は、MIDIファイルの読み込みと書き出しをしてみます。以前にExcel VBAでも作っているので、内容は大体同じです。
今回のプログラムで何が面倒かというと、MIDIファイルのフォーマットがBig EndianでVariable Lengthなんてのを使っていることです。MidiEventの個数が分かっていれば、メモリの確保が簡単なのに。。。Excelのときと同様、C#にもCollectionとか便利なクラスはあるんですが。。。リスト構造を自作するという手もあるんですが。。。結局、ただの配列が好きなので、配列を使っています。今回作ったStoreなんたらという関数は、配列のサイズが足りなくなったときに自動でメモリを再確保する関数です。
以下が今回のC#のソースコードです。ソースコードはメイン関数の部分"Main.cs"と、MIDIファイルの読み書きの部分"MidiFile.cs"に分かれています。前回のその5で作った"MidiEvent.cs"も使うので、その3つを合わせてコンパイルしてください。
ソースコードはご自由にご利用ください。ただし、趣味のプログラムなので保証はありません。コメントとかも適当です。プログラミングを勉強する方のご参考にでもなれば。
------------------------------
ここから"Main.cs"
------------------------------
using System;
using System.IO;
public class Program
{
public static void Main()
{
string filename;
/*------------------------------*/
/* 適当なMidi Fileの作成 */
filename = System.Environment.CurrentDirectory + @"\smf0.mid";
sample_smf0( filename);
filename = System.Environment.CurrentDirectory + @"\smf1.mid";
sample_smf1( filename);
/*------------------------------*/
/* Midi Fileの読み込み */
filename = System.Environment.CurrentDirectory + @"\smf1.mid";
int TimeDivision = 0;
MidiEvent[][] Track = null;
try
{
MidiFileReader.Read( filename, ref TimeDivision, ref Track);
}
catch( Exception e)
{
Console.WriteLine( e);
}
SaveCSV( TimeDivision, Track);
/*------------------------------*/
Console.WriteLine( "Press Enter Key");
Console.ReadLine();
}
private static void sample_smf0( string filename)
{
int TimeDivision = 960;
MidiEvent[][] Track = new MidiEvent[1][];
byte[] data = new byte[0];
Track[0] = new MidiEvent[17];
Track[0][0] = new ChannelEvent( 0, 0x90, 60, 127);
Track[0][1] = new ChannelEvent( TimeDivision * 1, 0x80, 60, 127);
Track[0][2] = new ChannelEvent( TimeDivision * 1, 0x90, 62, 127);
Track[0][3] = new ChannelEvent( TimeDivision * 2, 0x80, 62, 127);
Track[0][4] = new ChannelEvent( TimeDivision * 2, 0x90, 64, 127);
Track[0][5] = new ChannelEvent( TimeDivision * 3, 0x80, 64, 127);
Track[0][6] = new ChannelEvent( TimeDivision * 3, 0x90, 65, 127);
Track[0][7] = new ChannelEvent( TimeDivision * 4, 0x80, 65, 127);
Track[0][8] = new ChannelEvent( TimeDivision * 4, 0x90, 67, 127);
Track[0][9] = new ChannelEvent( TimeDivision * 5, 0x80, 67, 127);
Track[0][16] = new ChannelEvent( TimeDivision * 5, 0x90, 69, 127);
Track[0][15] = new ChannelEvent( TimeDivision * 6, 0x80, 69, 127);
Track[0][14] = new ChannelEvent( TimeDivision * 6, 0x90, 71, 127);
Track[0][13] = new ChannelEvent( TimeDivision * 7, 0x80, 71, 127);
Track[0][12] = new ChannelEvent( TimeDivision * 7, 0x90, 72, 127);
Track[0][11] = new ChannelEvent( TimeDivision * 8, 0x80, 72, 127);
Track[0][10] = new MetaEvent( TimeDivision * 9, 0xff, 0x2f, data);
/* Sortのテスト */
MidiEvent.Sort( Track[0]);
try
{
MidiFileWriter.Write( filename, TimeDivision, Track);
}
catch( Exception e)
{
Console.WriteLine( e);
}
}
private static void sample_smf1( string filename)
{
int TimeDivision = 960;
MidiEvent[][] Track = new MidiEvent[2][];
byte[] data = new byte[0];
Track[0] = new MidiEvent[1];
Track[0][0] = new MetaEvent( 0, 0xff, 0x2f, data);
Track[1] = new MidiEvent[17];
Track[1][0] = new ChannelEvent( 0, 0x90, 60, 127);
Track[1][1] = new ChannelEvent( TimeDivision * 1, 0x80, 60, 127);
Track[1][2] = new ChannelEvent( TimeDivision * 1, 0x90, 62, 127);
Track[1][3] = new ChannelEvent( TimeDivision * 2, 0x80, 62, 127);
Track[1][4] = new ChannelEvent( TimeDivision * 2, 0x90, 64, 127);
Track[1][5] = new ChannelEvent( TimeDivision * 3, 0x80, 64, 127);
Track[1][6] = new ChannelEvent( TimeDivision * 3, 0x90, 65, 127);
Track[1][7] = new ChannelEvent( TimeDivision * 4, 0x80, 65, 127);
Track[1][8] = new ChannelEvent( TimeDivision * 4, 0x90, 67, 127);
Track[1][9] = new ChannelEvent( TimeDivision * 5, 0x80, 67, 127);
Track[1][10] = new ChannelEvent( TimeDivision * 5, 0x90, 69, 127);
Track[1][11] = new ChannelEvent( TimeDivision * 6, 0x80, 69, 127);
Track[1][12] = new ChannelEvent( TimeDivision * 6, 0x90, 71, 127);
Track[1][13] = new ChannelEvent( TimeDivision * 7, 0x80, 71, 127);
Track[1][14] = new ChannelEvent( TimeDivision * 7, 0x90, 72, 127);
Track[1][15] = new ChannelEvent( TimeDivision * 8, 0x80, 72, 127);
Track[1][16] = new MetaEvent( TimeDivision * 8, 0xff, 0x2f, data);
try
{
MidiFileWriter.Write( filename, TimeDivision, Track);
}
catch( Exception e)
{
Console.WriteLine( e);
}
}
private static void SaveCSV( int TimeDivision, MidiEvent[][] Track)
{
string filename = System.Environment.CurrentDirectory + @"\MidiFile.txt";
try
{
StreamWriter sw = new StreamWriter( filename, false);
sw.WriteLine( "Track, Index, Time, Event, Param1, Param2, Type, Length");
for( int i = 0; i < Track.Length; i++)
{
for( int j = 0; j < Track[i].Length; j++)
{
sw.Write( i.ToString() + ", ");
sw.Write( j.ToString() + ", ");
byte evt = Track[i][j].Event;
if( evt < 0xf0)
{
ChannelEvent ce = (ChannelEvent) Track[i][j];
sw.Write( ce.Time.ToString() + ", ");
sw.Write( ce.Event.ToString( "x") + ", ");
sw.Write( ce.Param1.ToString( "x") + ", ");
sw.Write( ce.Param2.ToString( "x") + ", ");
sw.Write( ", ");
sw.Write( ", ");
sw.WriteLine();
}
else if( evt == 0xf0 || evt == 0xf7)
{
SystemEvent se = (SystemEvent) Track[i][j];
sw.Write( se.Time.ToString() + ", ");
sw.Write( se.Event.ToString( "x") + ", ");
sw.Write( ", ");
sw.Write( ", ");
sw.Write( ", ");
sw.Write( se.Data.Length + ", ");
sw.WriteLine();
}
else if( evt == 0xff)
{
MetaEvent me = (MetaEvent) Track[i][j];
sw.Write( me.Time.ToString() + ", ");
sw.Write( me.Event.ToString( "x") + ", ");
sw.Write( ", ");
sw.Write( ", ");
sw.Write( me.Type.ToString( "x") + ", ");
sw.Write( me.Data.Length.ToString() + ", ");
sw.WriteLine();
}
}
}
sw.Close();
}
catch( Exception e)
{
Console.WriteLine( e);
}
}
}
------------------------------
ここから"MidiFile.cs"
------------------------------
using System;
using System.IO;
/*--------------------------------------------------*/
/* MidiFileWriter */
/*--------------------------------------------------*/
public class MidiFileWriter
{
/*--------------------------------------------------*/
public static void Write( string filename, int TimeDivision, MidiEvent[][] Track)
{
Console.WriteLine( "----- MidiFileWriter.Write -----");
FileStream fs = new FileStream( filename, FileMode.Create, FileAccess.Write);
/*------------------------------*/
/* Header Chunk */
/*------------------------------*/
byte[] hc = new byte[14];
hc[0] = (byte) 'M';
hc[1] = (byte) 'T';
hc[2] = (byte) 'h';
hc[3] = (byte) 'd';
hc[4] = (byte) 0;
hc[5] = (byte) 0;
hc[6] = (byte) 0;
hc[7] = (byte) 6;
int NumberOfTracks = Track.Length;
hc[8] = (byte) 0;
hc[9] = (byte) ( NumberOfTracks == 1 ? 0 : 1);
hc[10] = (byte) ( ( NumberOfTracks >> 8) & 0xff);
hc[11] = (byte) ( NumberOfTracks & 0xff);
hc[12] = (byte) ( ( TimeDivision >> 8) & 0xff);
hc[13] = (byte) ( TimeDivision & 0xff);
fs.Write( hc, 0, hc.Length);
/*------------------------------*/
/* Track Chunk */
/*------------------------------*/
for( int i = 0; i < Track.Length; i++)
{
byte[] tc = new byte[8];
tc[0] = (byte) 'M';
tc[1] = (byte) 'T';
tc[2] = (byte) 'r';
tc[3] = (byte) 'k';
byte[] data = null;
MidiFileWriter.SetMidiEvent( ref data, Track[i]);
tc[4] = (byte) ( ( data.Length >> 24) & 0xff);
tc[5] = (byte) ( ( data.Length >> 16) & 0xff);
tc[6] = (byte) ( ( data.Length >> 8) & 0xff);
tc[7] = (byte) ( data.Length & 0xff);
fs.Write( tc, 0, tc.Length);
fs.Write( data, 0, data.Length);
Console.WriteLine( "Track " + i.ToString() + " : " + data.Length.ToString() + " bytes");
}
fs.Close();
}
/*--------------------------------------------------*/
private static void SetMidiEvent( ref byte[] data, MidiEvent[] Events)
{
int tm = 0;
byte evt = 0;
int pos = 0;
byte[] buf = new byte[1024];
for( int i = 0; i < Events.Length; i++)
{
/*------------------------------*/
/* Delta Time */
/*------------------------------*/
tm = Events[i].Time - ( ( 0 < i) ? Events[i - 1].Time : 0);
MidiFileWriter.SetVariableLength( tm, ref buf, ref pos);
/*------------------------------*/
/* MIDI Event */
/*------------------------------*/
if( Events[i].Event < 0xf0)
{
if( Events[i].Event != evt)
{
evt = Events[i].Event;
MidiFileWriter.StoreData( Events[i].Event, ref buf, ref pos);
}
if( evt < 0xc0 || 0xe0 <= evt)
{
MidiFileWriter.StoreData( ( (ChannelEvent) Events[i]).Param1, ref buf, ref pos);
MidiFileWriter.StoreData( ( (ChannelEvent) Events[i]).Param2, ref buf, ref pos);
}
else
{
MidiFileWriter.StoreData( ( (ChannelEvent) Events[i]).Param1, ref buf, ref pos);
}
}
else if( Events[i].Event == 0xf0 || Events[i].Event == 0xf7)
{
evt = Events[i].Event;
MidiFileWriter.StoreData( Events[i].Event, ref buf, ref pos);
MidiFileWriter.SetVariableLength( ( (SystemEvent) Events[i]).Data.Length, ref buf, ref pos);
for( int j = 0; j < ( (SystemEvent) Events[i]).Data.Length; j++)
{
MidiFileWriter.StoreData( ( (SystemEvent) Events[i]).Data[j], ref buf, ref pos);
}
}
else if( Events[i].Event == 0xff)
{
evt = Events[i].Event;
MidiFileWriter.StoreData( Events[i].Event, ref buf, ref pos);
MidiFileWriter.StoreData( ( (MetaEvent) Events[i]).Type, ref buf, ref pos);
MidiFileWriter.SetVariableLength( ( (MetaEvent) Events[i]).Data.Length, ref buf, ref pos);
for( int j = 0; j < ( (MetaEvent) Events[i]).Data.Length; j++)
{
MidiFileWriter.StoreData( ( (MetaEvent) Events[i]).Data[j], ref buf, ref pos);
}
}
}
/*--------------------------------------------------*/
data = new byte[pos];
for( int i = 0; i < data.Length; i++)
{
data[i] = buf[i];
}
}
/*--------------------------------------------------*/
private static void SetVariableLength( int val, ref byte[] data, ref int pos)
{
int temp = 1;
while( temp <= val)
{
temp = temp << 7;
}
temp = temp >> 7;
while( 1 < temp)
{
MidiFileWriter.StoreData( (byte) ( ( ( val / temp) & 0x7f) + 0x80), ref data, ref pos);
temp = temp >> 7;
}
MidiFileWriter.StoreData( (byte) ( val & 0x7f), ref data, ref pos);
}
/*--------------------------------------------------*/
private static void StoreData( byte val, ref byte[] data, ref int pos)
{
if( data.Length < pos + 1)
{
byte[] buf = new byte[2 * data.Length];
for( int i = 0; i < data.Length; i++)
{
buf[i] = data[i];
}
data = buf;
}
data[pos] = val;
pos++;
}
}
/*--------------------------------------------------*/
/* MidiFileReader */
/*--------------------------------------------------*/
public class MidiFileReader
{
/*--------------------------------------------------*/
public static void Read( string filename, ref int TimeDivision, ref MidiEvent[][] Track)
{
Console.WriteLine( "----- MidiFileReader.Read -----");
FileStream fs = new FileStream( filename, FileMode.Open, FileAccess.Read);
/*------------------------------*/
/* Header Chunk */
/*------------------------------*/
byte[] hc = new byte[14];
fs.Read( hc, 0, hc.Length);
if( hc[0] != (byte)77 || hc[1] != (byte)84 || hc[2] != (byte)104 || hc[3] != (byte)100)
{
throw new Exception( "It's not a MIDI File.");
}
int NumberOfTracks = ( hc[10] << 8) + hc[11];
TimeDivision = ( ( hc[12] << 8) + hc[13]);
Track = new MidiEvent[NumberOfTracks][];
/*------------------------------*/
Console.Write( "ChunkID : ");
Console.Write( (char)hc[0]);
Console.Write( (char)hc[1]);
Console.Write( (char)hc[2]);
Console.Write( (char)hc[3]);
Console.WriteLine();
Console.WriteLine( "ChunkSize : " + ( ( hc[4] << 24) + ( hc[5] << 16) + ( hc[6] << 8) + hc[7]).ToString());
Console.WriteLine( "FormatType : " + ( ( hc[8] << 8) + hc[9]).ToString());
Console.WriteLine( "NumberOfTracks : " + NumberOfTracks.ToString());
Console.WriteLine( "TimeDivision : " + TimeDivision.ToString());
Console.WriteLine();
/*------------------------------*/
/* Track Chunk */
/*------------------------------*/
for( int i = 0; i < Track.Length; i++)
{
byte[] tc = new byte[8];
fs.Read( tc, 0, tc.Length);
int temp = ( tc[4] << 24) + ( tc[5] << 16) + (tc[6] << 8) + tc[7];
byte[] data = new byte[temp];
fs.Read( data, 0, data.Length);
MidiFileReader.GetMidiEvent( data, ref Track[i]);
/*------------------------------*/
Console.Write( (char)tc[0]);
Console.Write( (char)tc[1]);
Console.Write( (char)tc[2]);
Console.Write( (char)tc[3]);
Console.WriteLine( " " + i.ToString() + " : " + data.Length.ToString() + " bytes");
}
fs.Close();
}
/*--------------------------------------------------*/
private static void GetMidiEvent( byte[] data, ref MidiEvent[] Events)
{
int tm = 0;
byte evt = 0;
byte p1 = 0;
byte p2 = 0;
byte tp = 0;
byte[] dt;
int pos = 0;
int count = 0;
MidiEvent[] buf = new MidiEvent[1024];
while( pos < data.Length)
{
/*------------------------------*/
/* Delta Time */
/*------------------------------*/
tm = MidiFileReader.GetVariableLength( data, ref pos) + ( ( 0 < count) ? buf[count - 1].Time : 0);
/*------------------------------*/
/* Event Type */
/*------------------------------*/
if( 0x80 <= data[pos])
{
evt = data[pos];
pos++;
}
/*------------------------------*/
/* MIDI Event */
/*------------------------------*/
if( evt < 0xf0)
{
if( evt < 0xc0 || 0xe0 <= evt)
{
p1 = data[pos];
p2 = data[pos + 1];
pos += 2;
}
else
{
p1 = data[pos];
p2 = 0;
pos += 1;
}
MidiFileReader.StoreMidiEvent( new ChannelEvent( tm, evt, p1, p2), ref buf, ref count);
}
else if( evt == 0xf0 || evt == 0xf7)
{
dt = new byte[MidiFileReader.GetVariableLength( data, ref pos)];
for( int i = 0; i < dt.Length; i++)
{
dt[i] = data[pos];
pos++;
}
MidiFileReader.StoreMidiEvent( new SystemEvent( tm, evt, dt), ref buf, ref count);
}
else if( evt == 0xff)
{
tp = data[pos];
pos++;
dt = new byte[MidiFileReader.GetVariableLength( data, ref pos)];
for( int i = 0; i < dt.Length; i++)
{
dt[i] = data[pos];
pos++;
}
MidiFileReader.StoreMidiEvent( new MetaEvent( tm, evt, tp, dt), ref buf, ref count);
}
}
/*--------------------------------------------------*/
Events = new MidiEvent[count];
for( int i = 0; i < Events.Length; i++)
{
Events[i] = buf[i];
}
}
/*--------------------------------------------------*/
private static int GetVariableLength( byte[] data, ref int pos)
{
int ret = ( data[pos] & 0x7f);
pos++;
while( 0x80 <= data[pos - 1])
{
ret = ( ret << 7) + ( data[pos] & 0x7f);
pos++;
}
return ret;
}
/*--------------------------------------------------*/
private static void StoreMidiEvent( MidiEvent Event, ref MidiEvent[] Events, ref int count)
{
if( Events.Length < count + 1)
{
MidiEvent[] buf = new MidiEvent[2 * Events.Length];
for( int i = 0; i < Events.Length; i++)
{
buf[i] = Events[i];
}
Events = buf;
}
Events[count] = Event;
count++;
}
}