2017年5月4日木曜日

C#でMIDI その4

 C#でMIDIの続きです。

 今回は寄り道で、MIDIファイルの読み込みをしてみます。
 MIDIファイルの仕様は、なかなか面倒です。Big EndianとかRunning StatusとかVariable Lengthとかとか。そこら辺を知りたい方は、MIDIの仕様を調べてみてください。

 今回のプログラムのポイントは2つです。
 1つは、3種類のMIDIイベントをオブジェクト指向っぽくクラス化しています。
 もう1つは、個数が分からないMIDIイベントをメモリに格納するため、自動的にメモリを増やしてくれる配列を使っています。C#のCollectionを使うという手もあるのですが、わざわざ自分で書いているのは気分です。

 おまけで、テスト用にSampleMidi.midというのを作っています。

 いろいろ適当ですが、趣味のプログラムなので、ご了承ください。



using System;
using System.IO;
using System.Drawing;
using System.Windows.Forms;

class Program
{
[STAThread]

static void Main()
{
Application.Run( new FormMidi());
}
}

/*--------------------------------------------------*/
/* FormMidi Class */
/*--------------------------------------------------*/
class FormMidi : Form
{
private Label lbl;
private Button btn;

/*--------------------------------------------------*/
/* FormMidi */
/*--------------------------------------------------*/
public FormMidi()
{
this.Text = "Midi";
this.ClientSize = new Size( 240, 120);
this.FormBorderStyle = FormBorderStyle.FixedSingle;

this.Load += new EventHandler( this.FormMidi_Load);
this.Closed += new EventHandler( this.FormMidi_Closed);

/*--------------------------------------------------*/
this.AllowDrop = true;
this.DragEnter += new DragEventHandler( this.FormMidi_DragEnter);
this.DragDrop += new DragEventHandler( this.FormMidi_DragDrop);

/*--------------------------------------------------*/
this.lbl = new Label();
this.lbl.SetBounds( 20, 10, 200, 60);
    this.lbl.Font = new Font( "Arial", 10);
this.lbl.Text = "File : " + "\r\n";
this.lbl.Text += "Track : " + "\r\n";
this.lbl.Text += "Event : " + "\r\n";
this.lbl.Text += "TimeDivision : " + "\r\n";
//this.lbl.BorderStyle = BorderStyle.FixedSingle;
this.Controls.Add( this.lbl);

/*--------------------------------------------------*/
this.btn = new Button();
this.btn.SetBounds( 40, 80, 160, 30);
    this.btn.Font = new Font( "Arial", 10);
this.btn.Text = "Open";
this.btn.Click += new EventHandler( this.btn_Click);
this.Controls.Add( this.btn);
}

/*--------------------------------------------------*/
/* FormMidi_Load */
/*--------------------------------------------------*/
private void FormMidi_Load( object sender, EventArgs e)
{
this.SampleMidi();

string[] args = Environment.GetCommandLineArgs();

if( 1 < args.Length)
{
this.OpenFile( args[1]);
}
}

/*--------------------------------------------------*/
/* FormMidi_Closed */
/*--------------------------------------------------*/
private void FormMidi_Closed( object sender, EventArgs e)
{
}

/*--------------------------------------------------*/
/* DragEnter */
/*--------------------------------------------------*/
private void FormMidi_DragEnter( object sender, DragEventArgs e)
{
if( e.Data.GetDataPresent( DataFormats.FileDrop))
{
e.Effect = DragDropEffects.Move;
}
}

/*--------------------------------------------------*/
/* DragDrop */
/*--------------------------------------------------*/
private void FormMidi_DragDrop( object sender, DragEventArgs e)
{
string[] filename = (string[]) e.Data.GetData( DataFormats.FileDrop, false);
this.OpenFile( filename[0]);
}

/*--------------------------------------------------*/
/* btn_Click */
/*--------------------------------------------------*/
private void btn_Click( object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "midi file|*.mid|*|*.*";

if( ofd.ShowDialog() == DialogResult.OK)
{
this.OpenFile( ofd.FileName);
}
}

/*--------------------------------------------------*/
/* OpenFile */
/*--------------------------------------------------*/
private void OpenFile( string filename)
{
if( Path.GetExtension( filename) != ".mid")
{
return;
}

Console.WriteLine( "----- OpenFile -----");

int temp;
MidiEventArray[] Track;
uint TimeDivision;

/*--------------------------------------------------*/
FileStream ifs = new FileStream( filename, FileMode.Open, FileAccess.Read);

byte[] hc = new byte[14];
ifs.Read( hc, 0, hc.Length);

temp = ( hc[10] << 8) + hc[11];
Track = new MidiEventArray[temp];

temp = ( hc[12] << 8) + hc[13];
TimeDivision = (uint) temp;

for( int i = 0; i < Track.Length; i++)
{
byte[] tc = new byte[8];
ifs.Read( tc, 0, tc.Length);

temp = ( tc[4] << 24) + ( tc[5] << 16) + (tc[6] << 8) + tc[7];
byte[] data = new byte[temp];
ifs.Read( data, 0, data.Length);

Track[i] = new MidiEventArray();
Track[i].FromByte( data);
}

ifs.Close();

/*--------------------------------------------------*/
temp = 0;

for( int i = 0; i < Track.Length; i++)
{
temp += Track[i].Count;
}

this.lbl.Text = "File : " + Path.GetFileName( filename) + "\r\n";
this.lbl.Text += "Track : " + Track.Length + "\r\n";
this.lbl.Text += "Event : " + temp + "\r\n";
this.lbl.Text += "TimeDivision : " + TimeDivision + "\r\n";

for( int i = 0; i < Track.Length; i++)
{
for( int j = 0; j < Track[i].Count; j++)
{
Console.Write( i.ToString() + "\t");
Console.Write( j.ToString() + "\t");
Console.Write( Track[i].Event[j].Time.ToString() + "\t");
Console.Write( Track[i].Event[j].Event.ToString() + "\t");
Console.WriteLine();
}
}
}

/*--------------------------------------------------*/
/* SampleMidi */
/*--------------------------------------------------*/
private void SampleMidi()
{
string path = Application.ExecutablePath;
path = Path.GetDirectoryName( path);

FileStream ofs = new FileStream( path + @"\SampleMidi.mid", FileMode.Create, FileAccess.Write);

/*--------------------------------------------------*/
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;
hc[8] = (byte) 0;
hc[9] = (byte) 0;
hc[10] = (byte) 0;
hc[11] = (byte) 1;
hc[12] = (byte) 0;
hc[13] = (byte) 96;

/*--------------------------------------------------*/
byte[] tc = new byte[8];

tc[0] = (byte) 'M';
tc[1] = (byte) 'T';
tc[2] = (byte) 'r';
tc[3] = (byte) 'k';

byte[] data = new byte[68];
byte[] keys = new byte[8];

keys[0] = (byte) 60;
keys[1] = (byte) 62;
keys[2] = (byte) 64;
keys[3] = (byte) 65;
keys[4] = (byte) 67;
keys[5] = (byte) 69;
keys[6] = (byte) 71;
keys[7] = (byte) 72;

for( int i = 0; i < 8; i++)
{
data[8 * i + 0] = (byte) 0;
data[8 * i + 1] = (byte) 0x90;
data[8 * i + 2] = (byte) keys[i];
data[8 * i + 3] = (byte) 127;

data[8 * i + 4] = (byte) 96;
data[8 * i + 5] = (byte) 0x80;
data[8 * i + 6] = (byte) keys[i];
data[8 * i + 7] = (byte) 127;
}

data[64] = (byte) 0;
data[65] = (byte) 0xff;
data[66] = (byte) 0x2f;
data[67] = (byte) 0;

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);

/*--------------------------------------------------*/
ofs.Write( hc, 0, hc.Length);
ofs.Write( tc, 0, tc.Length);
ofs.Write( data, 0, data.Length);
ofs.Close();
}
}



/*--------------------------------------------------*/
/* MidiEvent Class */
/*--------------------------------------------------*/
public class MidiEvent
{
public uint Time; // DeltaTime [tick]
public byte Event; // 0x80 - 0xff
}

/*--------------------------------------------------*/
/* ChannelEvent Class */
/*--------------------------------------------------*/
public class ChannelEvent : MidiEvent
{
public byte Param1; // 0x00 - 0x7f
public byte Param2; // 0x00 - 0x7f

public ChannelEvent( uint tm, byte evt, byte p1, byte p2)
{
this.Time = tm;
this.Event = evt;
this.Param1 = p1;
this.Param2 = p2;
}
}

/*--------------------------------------------------*/
/* SystemEvent Class */
/*--------------------------------------------------*/
public class SystemEvent : MidiEvent
{
public byte[] Data;

public SystemEvent( uint tm, byte evt, byte[] dt)
{
this.Time = tm;
this.Event = evt;
this.Data = dt;
}
}

/*--------------------------------------------------*/
/* MetaEvent Class */
/*--------------------------------------------------*/
public class MetaEvent : MidiEvent
{
public byte Type;
public byte[] Data;

public MetaEvent( uint tm, byte evt, byte tp, byte[] dt)
{
this.Time = tm;
this.Event = evt;
this.Type = tp;
this.Data = dt;
}
}



/*--------------------------------------------------*/
/* MidiEventArray Class */
/*--------------------------------------------------*/
public class MidiEventArray
{
public MidiEvent[] Event;
public int Count;

/*--------------------------------------------------*/
/* MidiEventArray */
/*--------------------------------------------------*/
public MidiEventArray()
{
this.Event = new MidiEvent[256];
this.Count = 0;
}

/*--------------------------------------------------*/
/* Add */
/*--------------------------------------------------*/
public void Add( MidiEvent evt)
{
if( this.Count == this.Event.Length - 1)
{
this.MemoryAllocate();
}

this.Event[this.Count] = evt;
this.Count++;
}

/*--------------------------------------------------*/
/* MemoryAllocate */
/*--------------------------------------------------*/
public void MemoryAllocate()
{
MidiEvent[] temp = new MidiEvent[this.Event.Length * 2];

for( int i = 0; i < this.Event.Length; i++)
{
temp[i] = this.Event[i];
}

this.Event = temp;
}

/*--------------------------------------------------*/
/* ReadVariableLength */
/*--------------------------------------------------*/
private static uint ReadVariableLength( byte[] data, ref int pos)
{
uint ret = (uint) ( data[pos] & 0x7f);
pos++;

while( 0x80 <= data[pos - 1])
{
ret = ( ret << 7) + (uint) ( data[pos] & 0x7f);
pos++;
}

return ret;
}

/*--------------------------------------------------*/
/* FromByte */
/*--------------------------------------------------*/
public void FromByte( byte[] data)
{
uint tm = 0;
byte evt = 0;
byte p1 = 0;
byte p2 = 0;
byte tp = 0;
uint len = 0;
byte[] dt;

int pos = 0;

while( pos < data.Length)
{
/*--------------------------------------------------*/
tm += MidiEventArray.ReadVariableLength( data, ref pos);

/*--------------------------------------------------*/
if( 0x80 <= data[pos])
{
evt = data[pos];
pos++;
}

/*--------------------------------------------------*/
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;
}

this.Add( new ChannelEvent( tm, evt, p1, p2));
}
else if( evt == 0xf0 || evt == 0xf7)
{
len = MidiEventArray.ReadVariableLength( data, ref pos);
dt = new byte[len];

for( int i = 0; i < dt.Length; i++)
{
dt[i] = data[pos];
pos++;
}

this.Add( new SystemEvent( tm, evt, dt));
}
else if( evt == 0xff)
{
tp = data[pos];
pos++;

len = MidiEventArray.ReadVariableLength( data, ref pos);
dt = new byte[len];

for( int i = 0; i < dt.Length; i++)
{
dt[i] = data[pos];
pos++;
}

this.Add( new MetaEvent( tm, evt, tp, dt));
}

/*--------------------------------------------------*/
}
}
}

0 件のコメント:

コメントを投稿