2016年12月30日金曜日

C#でMIDI その7

 C#でMIDIの続きです。前回のソースコードは、行数が多くて分かりにくいので、クラスに分けてみました。ついでに、Channel Event以外にも対応できるようにしてあります。

 主なクラスは以下の通り。

  • MidiEvent
    • その名の通り、MIDI Eventのクラス
  • MidiFile
    • Standard Midi Fileの読み書きの関数のクラス
    • Byte配列を読み込むだけのStatic関数
  • MidiByte
    • Byte配列とMIDI Eventの配列を変換するためのStatic関数
    • MIDIの仕様 (Running Statusとか) のために、分岐がたくさん
  • MidiFormat
    • SMF0 (Trackはひとつ) とSMF1 (Trackは複数) を変換するためのStatic関数
    • 適当にMIDI Eventの配列を並び替える


 個人的な趣味で、三項演算子を多用しています。三項演算子も使い慣れると便利です。また、インクリメント演算子をわざわざ別行にしているのもただの趣味です。

 コメントは少ないです。理由は趣味だから。と、コメントをメンテナンスするのが面倒だから。普通のソフトウェアメーカーなら怒られるでしょう。

 プログラムの使い方は察してください。一応、MIDIファイルをドラッグ&ドロップすると動作するはずです。

 例によって、動作保証はしませんが、ご利用はご自由にどうぞ。



using System;
using System.IO;
using System.Threading;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Windows.Forms;

class Program
{
[STAThread]

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

class FormMidi : Form
{
/*--------------------------------------------------*/
/* API */
/*--------------------------------------------------*/
[DllImport( "Winmm.dll")]
extern static uint midiOutGetNumDevs();

[DllImport( "Winmm.dll")]
extern static uint midiOutOpen( ref uint lphmo, uint uDeviceID, uint dwCallback, uint dwCallbackInstance, uint dwFlags);

[DllImport( "Winmm.dll")]
extern static uint midiOutClose( uint hmo);

[DllImport( "Winmm.dll")]
extern static uint midiOutShortMsg( uint hmo, uint dwMsg);

[DllImport( "Winmm.dll")]
extern static uint midiOutReset( uint hmo);

private const uint MMSYSERR_NOERROR = 0;

private const uint MMSYSERR_BADDEVICEID = 2;
private const uint MMSYSERR_ALLOCATED = 4;
private const uint MMSYSERR_NOMEM = 7;
private const uint MMSYSERR_INVALPARAM = 11;
private const uint MMSYSERR_NODEVICE = 68;

private const uint MMSYSERR_INVALHANDLE = 5;
private const uint MIDIERR_STILLPLAYING = 65;

private const uint MIDI_MAPPER = 0xffffffff;

/*--------------------------------------------------*/
/* Variables */
/*--------------------------------------------------*/
private uint hMidi;

private MenuStrip ms;
private ToolStripMenuItem[][] tsmi;
private Label lbl;
private TextBox tbx;
private Button btn;

private Thread thread;

private uint div; //Midi Time Division [tick]
private MidiEvent[][] midi; //Midi Event

/*--------------------------------------------------*/
/* FormMidi */
/*--------------------------------------------------*/
public FormMidi()
{
this.Text = "Midi";
this.ClientSize = new Size( 360, 180);
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.ms = new MenuStrip();

this.tsmi = new ToolStripMenuItem[2][];
this.tsmi[0] = new ToolStripMenuItem[3];
this.tsmi[1] = new ToolStripMenuItem[2];

for( int i = 0; i < this.tsmi.Length; i++)
{
for( int j = 0; j < this.tsmi[i].Length; j++)
{
this.tsmi[i][j] = new ToolStripMenuItem();
}
}

for( int i = 0; i < this.tsmi.Length; i++)
{
this.ms.Items.Add( this.tsmi[i][0]);

for( int j = 1; j < this.tsmi[i].Length; j++)
{
this.tsmi[i][0].DropDownItems.Add( this.tsmi[i][j]);
this.tsmi[i][j].Click += new EventHandler( this.Menu_Click);
}
}

this.tsmi[0][0].Text = "File (&F)";
this.tsmi[0][1].Text = "Open (&O)";
this.tsmi[0][2].Text = "Exit (&X)";
this.tsmi[1][0].Text = "Command (&C)";
this.tsmi[1][1].Text = "Play (&P)";

this.Controls.Add( this.ms);
this.MainMenuStrip = this.ms;

/*--------------------------------------------------*/
this.lbl = new Label();
    this.lbl.Font = new Font( "Arial", 10);
this.lbl.SetBounds( 20, this.ms.Bottom + 4, 320, 60);
this.Controls.Add( this.lbl);

/*--------------------------------------------------*/
this.tbx = new TextBox();
    this.tbx.Font = new Font( "Arial", 10);
this.tbx.Multiline = true;
this.tbx.ReadOnly = true;
this.tbx.BorderStyle = BorderStyle.None;
this.tbx.SetBounds( 20, this.lbl.Bottom + 4, 320, 40);
this.Controls.Add( this.tbx);

/*--------------------------------------------------*/
this.btn = new Button();
    this.btn.Font = new Font( "Arial", 10);
this.btn.Text = "Play";
this.btn.SetBounds( 120, this.tbx.Bottom + 4, 120, 40);
this.btn.Click += new EventHandler( this.btn_Click);
this.Controls.Add( this.btn);
}

/*--------------------------------------------------*/
/* Load */
/*--------------------------------------------------*/
private void FormMidi_Load( object sender, EventArgs e)
{
Console.WriteLine( "MIDI Open");

uint ret = midiOutGetNumDevs();
Console.WriteLine( "Number of Midi Device : " + ret);

if( midiOutOpen( ref this.hMidi, MIDI_MAPPER, 0, 0, 0) != MMSYSERR_NOERROR)
{
MessageBox.Show( "midiOutOpen error");
Application.Exit();
}

/*--------------------------------------------------*/
this.SampleMidi();

/*--------------------------------------------------*/
string path = Application.ExecutablePath;
path = Path.GetDirectoryName( path);

if( File.Exists( path + @"\midi.txt") == false)
{
this.SaveText( path + @"\midi.txt");
}

/*--------------------------------------------------*/
string[] args = Environment.GetCommandLineArgs();

if( 1 < args.Length)
{
this.OpenFile( args[1]);
}
else
{
this.OpenFile( path + @"\midi.txt");
}
}

/*--------------------------------------------------*/
/* Closed */
/*--------------------------------------------------*/
private void FormMidi_Closed( object sender, EventArgs e)
{
midiOutReset( this.hMidi);

Console.WriteLine( "MIDI Close");
midiOutClose( this.hMidi);
}

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

/*--------------------------------------------------*/
/* Menu_Click */
/*--------------------------------------------------*/
private void Menu_Click( object sender, EventArgs e)
{
if( sender.ToString() == this.tsmi[0][1].Text)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "*|*.*|txt file|*.txt|midi file|*.mid";

if( ofd.ShowDialog() == DialogResult.OK)
{
this.OpenFile( ofd.FileName);
}
}
else if( sender.ToString() == this.tsmi[0][2].Text)
{
this.Close();
}
else if( sender.ToString() == this.tsmi[1][1].Text)
{
this.StartThread();
}
}

/*--------------------------------------------------*/
/* btn_Click */
/*--------------------------------------------------*/
private void btn_Click( object sender, EventArgs e)
{
this.StartThread();
}

/*--------------------------------------------------*/
/* OpenFile */
/*--------------------------------------------------*/
private void OpenFile( string filename)
{
try
{
if( filename.Substring( filename.Length - 3) == "txt")
{
this.LoadText( filename);
}
else if( filename.Substring( filename.Length - 3) == "mid")
{
this.LoadSmf( filename);
}

string path = Application.ExecutablePath;
path = Path.GetDirectoryName( path);

this.SaveText( path + @"\midi.txt");
this.SaveSmf0( path + @"\smf0.mid");
this.SaveSmf1( path + @"\smf1.mid");

this.lbl.Text = "File Name : " + Path.GetFileName( filename) + "\r\n";
this.lbl.Text += "Midi Channel Event : " + this.CountChannelEvent().ToString() + "\r\n";
this.lbl.Text += "Time Division : " + this.div.ToString() + "\r\n";

this.tbx.Text = "No.\ttime\tevent\tparam1\tparam2\t\r\n";
this.tbx.Select( 0, 0);
}
catch( Exception e)
{
MessageBox.Show( e.Message);
}
}

/*--------------------------------------------------*/
/* StartThread */
/*--------------------------------------------------*/
private void StartThread()
{
if( this.thread == null || this.thread.IsAlive == false)
{
this.btn.Text = "Stop";
this.tsmi[1][1].Text = "Stop (&S)";

ThreadStart ts = new ThreadStart( this.play);
this.thread = new Thread( ts);
this.thread.IsBackground = true;
this.thread.Start();
}
else
{
this.btn.Text = "Play";
this.tsmi[1][1].Text = "Play (&P)";

midiOutReset( this.hMidi);
this.thread.Abort();
}
}

/*--------------------------------------------------*/
/* play */
/*--------------------------------------------------*/
private void play()
{
Console.WriteLine( "----- Play Start -----");

MidiEvent[] me = MidiFormat.MergeMidiEvent( this.midi);

/*--------------------------------------------------*/
long t0 = DateTime.Now.Ticks / 10000; // [msec]
long t1 = 0; // [msec]
long t2 = 0; // [msec]

for( int i = 0; i < me.Length; i++)
{
Console.WriteLine( i.ToString() + "\t" + me[i].Time.ToString() + "\t" + me[i].Event.ToString( "X") + "\t");

while( t1 * this.div / 500 < me[i].Time)
{
t1 = DateTime.Now.Ticks / 10000 - t0;
}

if( me[i].Event < 0xf0)
{
// Channel Eventのみ
ChannelEvent ce = (ChannelEvent) me[i];
midiOutShortMsg( this.hMidi, (uint) ( ( ce.Param2 << 16) + ( ce.Param1 << 8) + ce.Event));

if( t2 < t1)
{
this.tbx.Text = "No.\ttime\tevent\tparam1\tparam2\t\r\n";
this.tbx.Text += i.ToString() + "\t";
this.tbx.Text += ce.Time.ToString() + "\t";
this.tbx.Text += ce.Event.ToString( "X") + "\t";
this.tbx.Text += ce.Param1.ToString() + "\t";
this.tbx.Text += ce.Param1.ToString() + "\t";
t2 += 10; //10[msec]毎に表示を更新
}
}
}

/*--------------------------------------------------*/
this.btn.Text = "Play";
this.tsmi[1][1].Text = "Play (&P)";

midiOutReset( this.hMidi);

Console.WriteLine( "----- Play Stop -----");
}

/*--------------------------------------------------*/
/* SampleMidi */
/*--------------------------------------------------*/
private void SampleMidi()
{
this.div = 96;

this.midi = new MidiEvent[2][];
this.midi[0] = new MidiEvent[1];
this.midi[1] = new MidiEvent[17];

this.midi[0][0] = new MetaEvent( 0, 0xff, 0x2f, new byte[0]);

this.midi[1][0] = new ChannelEvent( 0, 0x90, 60, 127);
this.midi[1][1] = new ChannelEvent( 96, 0x80, 60, 127);
this.midi[1][2] = new ChannelEvent( 96, 0x90, 62, 127);
this.midi[1][3] = new ChannelEvent( 192, 0x80, 62, 127);
this.midi[1][4] = new ChannelEvent( 192, 0x90, 64, 127);
this.midi[1][5] = new ChannelEvent( 288, 0x80, 64, 127);
this.midi[1][6] = new ChannelEvent( 288, 0x90, 65, 127);
this.midi[1][7] = new ChannelEvent( 384, 0x80, 65, 127);
this.midi[1][8] = new ChannelEvent( 384, 0x90, 67, 127);
this.midi[1][9] = new ChannelEvent( 480, 0x80, 67, 127);
this.midi[1][10] = new ChannelEvent( 480, 0x90, 69, 127);
this.midi[1][11] = new ChannelEvent( 576, 0x80, 69, 127);
this.midi[1][12] = new ChannelEvent( 576, 0x90, 71, 127);
this.midi[1][13] = new ChannelEvent( 672, 0x80, 71, 127);
this.midi[1][14] = new ChannelEvent( 672, 0x90, 72, 127);
this.midi[1][15] = new ChannelEvent( 768, 0x80, 72, 127);
this.midi[1][16] = new MetaEvent( 768, 0xff, 0x2f, new byte[0]);
}

/*--------------------------------------------------*/
/* CountChannelEvent */
/*--------------------------------------------------*/
private int CountChannelEvent()
{
int count = 0;

for( int i = 0; i < this.midi.Length; i++)
{
for( int j = 0; j < this.midi[i].Length; j++)
{
count += ( this.midi[i][j].Event < 0xf0 ? 1 : 0);
}
}

return count;
}

/*--------------------------------------------------*/
/* SaveText */
/*--------------------------------------------------*/
private void SaveText( string filename)
{
StreamWriter sw = new StreamWriter( filename, false);

sw.WriteLine( "MidiText");
sw.WriteLine( "TimeDivision"  + "\t" + this.div);
sw.WriteLine( "\ttime\tevent\tchannel\tparam1\tparam2");

for( int i = 0; i < this.midi.Length; i++)
{
for( int j = 0; j < this.midi[i].Length; j++)
{
// Channel Eventのみ
if( this.midi[i][j].Event < 0xf0)
{
ChannelEvent ce = (ChannelEvent) this.midi[i][j];

sw.Write( ":" + "\t");
sw.Write( ce.Time + "\t");
sw.Write( "0x" + ( ( ce.Event & 0xf0) >> 4).ToString( "X") + "\t");
sw.Write( ( ce.Event & 0x0f).ToString() + "\t");
sw.Write( ce.Param1 + "\t");
sw.Write( ce.Param2 + "\t");
sw.WriteLine();
}
}
}

sw.Close();
}

/*--------------------------------------------------*/
/* LoadText */
/*--------------------------------------------------*/
private void LoadText( string filename)
{
StreamReader sr = new StreamReader( filename);

string txt = sr.ReadToEnd();
txt.Replace( "\r\n", "\n");
string[] lines = txt.Split( '\n');

sr.Close();

/*--------------------------------------------------*/
string[] str = lines[0].Split( '\t');

if( str[0].Substring( 0, 8) != "MidiText")
{
throw new Exception();
}

str = lines[1].Split( '\t');
this.div = (uint) Convert.ToInt32( str[1]);

/*--------------------------------------------------*/
int count = 0;

for( int i = 0; i < lines.Length; i++)
{
if( 0 < lines[i].Length && lines[i].Substring( 0, 1) == ":")
{
count++;
}
}

MidiEvent[] me = new MidiEvent[count + 1];

/*--------------------------------------------------*/
count = 0;

for( int i = 0; i < lines.Length; i++)
{
if( 0 < lines[i].Length && lines[i].Substring( 0, 1) == ":")
{
str = lines[i].Split( '\t');

uint tm = (uint) Convert.ToInt32( str[1]);
byte evt = (byte) Convert.ToInt32( str[2], 16);
evt = (byte) ( ( evt << 4) + Convert.ToInt32( str[3]));
byte p1 = (byte) Convert.ToInt32( str[4]);
byte p2 = (byte) Convert.ToInt32( str[5]);

me[count] = new ChannelEvent( tm, evt, p1, p2);
count++;
}
}

/*--------------------------------------------------*/
MidiFormat.SetTrackEnd( me);
MidiFormat.SortTime( me);
this.midi = MidiFormat.SplitMidiEvent( me);

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

/*--------------------------------------------------*/
/* SaveSmf0 */
/*--------------------------------------------------*/
private void SaveSmf0( string filename)
{

MidiEvent[] me = MidiFormat.MergeMidiEvent( this.midi);
byte[][] data = new byte[1][];
data[0] = MidiByte.EventToByte( me);

MidiFile.Save( filename, data, this.div);
}

/*--------------------------------------------------*/
/* SaveSmf1 */
/*--------------------------------------------------*/
private void SaveSmf1( string filename)
{
byte[][] data = new byte[this.midi.Length][];

for( int i = 0; i < data.Length; i++)
{
data[i] = MidiByte.EventToByte( this.midi[i]);
}

MidiFile.Save( filename, data, this.div);
}

/*--------------------------------------------------*/
/* LoadSmf */
/*--------------------------------------------------*/
private void LoadSmf( string filename)
{
byte[][] data = MidiFile.Load( filename, ref this.div);

this.midi = new MidiEvent[data.Length][];

for( int i = 0; i < this.midi.Length; i++)
{
this.midi[i] = MidiByte.ByteToEvent( data[i]);
}

if( this.midi.Length == 1)
{
this.midi = MidiFormat.SplitMidiEvent( this.midi[0]); // SMF0
}
}
}

/*--------------------------------------------------*/
/* MIDI Event Class */
/*--------------------------------------------------*/
public class MidiEvent
{
public uint Time; // tick (absolute time)
public byte Event; // 0x80 - 0xff
}

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

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

public class SystemEvent : MidiEvent
{
public byte[] Data;

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

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

/*--------------------------------------------------*/
/* MidiFormat Class */
/*--------------------------------------------------*/
public class MidiFormat
{
/*--------------------------------------------------*/
/* MergeMidiEvent */
/*--------------------------------------------------*/
public static MidiEvent[] MergeMidiEvent( MidiEvent[][] me)
{
int count = 0;

for( int i = 0; i < me.Length; i++)
{
count += me[i].Length - 1;
}

MidiEvent[] ret = new MidiEvent[count + 1];

count = 0;

for( int i = 0; i < me.Length; i++)
{
for( int j = 0; j < me[i].Length - 1; j++)
{
ret[count] = me[i][j];
count++;
}
}

MidiFormat.SetTrackEnd( ret);
MidiFormat.SortTime( ret);

return ret;
}

/*--------------------------------------------------*/
/* SplitMidiEvent */
/*--------------------------------------------------*/
public static MidiEvent[][] SplitMidiEvent( MidiEvent[] me)
{
MidiEvent[][] ret = new MidiEvent[17][];
int[] n = new int[17];

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

for( int i = 0; i < me.Length - 1; i++)
{
if( me[i].Event < 0xf0)
{
int ch = me[i].Event & 0x0f;
n[ch + 1]++;
}
else if( me[i].Event == 0xf0 || me[i].Event == 0xf7 || me[i].Event == 0xff)
{
n[0]++;
}
}

for( int i = 0; i < ret.Length; i++)
{
ret[i] = new MidiEvent[n[i] + 1];
n[i] = 0;
}

for( int i = 0; i < me.Length - 1; i++)
{
if( me[i].Event < 0xf0)
{
int ch = me[i].Event & 0x0f;
ret[ch + 1][n[ch + 1]] = me[i];
n[ch + 1]++;
}
else if( me[i].Event == 0xf0 || me[i].Event == 0xf7 || me[i].Event == 0xff)
{
ret[0][n[0]] = me[i];
n[0]++;
}
}

for( int i = 0; i < ret.Length; i++)
{
MidiFormat.SetTrackEnd( ret[i]);
}

return ret;
}

/*--------------------------------------------------*/
/* SortTime */
/*--------------------------------------------------*/
public static void SortTime( MidiEvent[] me)
{
MidiEvent temp;

for( int i = 1; i < me.Length; i++)
{
temp = me[i];

int j;

for( j = i; 0 < j; j--)
{
if( temp.Time < me[j - 1].Time)
{
me[j] = me[j - 1];
}
else
{
break;
}
}

me[j] = temp;
}
}

/*--------------------------------------------------*/
/* SetTrackEnd */
/*--------------------------------------------------*/
public static void SetTrackEnd( MidiEvent[] me)
{
uint tm = 0;

for( int i = 0; i < me.Length - 1; i++)
{
tm = ( tm < me[i].Time ? me[i].Time : tm);
}

me[me.Length - 1] = new MetaEvent( tm, 0xff, 0x2f, new byte[0]);
}
}

/*--------------------------------------------------*/
/* MidiByte Class */
/*--------------------------------------------------*/
public class MidiByte
{
/*--------------------------------------------------*/
/* WriteVariableLength */
/*--------------------------------------------------*/
private static void WriteVariableLength( byte[] data, ref int pos, uint val)
{
int temp = ( 0 < val ? (int) Math.Log( val, 0x80) : 0);

while( 0 < temp)
{
data[pos] = (byte) ( ( ( val >> ( 7 * temp)) & 0x7f) + 0x80);
pos++;
temp--;
}

data[pos] = (byte) ( val & 0x7f);
pos++;
}

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

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

/*--------------------------------------------------*/
/* CountByteLength */
/*--------------------------------------------------*/
private static int CountByteLength( MidiEvent[] me)
{
int m_pos = 0;
int d_pos = 0;

while( m_pos < me.Length)
{
/*--------------------------------------------------*/
uint tm = me[m_pos].Time;
tm -= ( 0 < m_pos ? me[m_pos - 1].Time : 0);
d_pos += ( 0 < tm ? (int) Math.Log( tm, 0x80) : 0) + 1;

/*--------------------------------------------------*/
if( m_pos == 0 || 0xf0 <= me[m_pos].Event || me[m_pos].Event != me[m_pos - 1].Event)
{
d_pos++;
}

/*--------------------------------------------------*/
if( me[m_pos].Event < 0xf0)
{
d_pos += ( ( me[m_pos].Event < 0xc0 || 0xe0 <= me[m_pos].Event) ? 2 : 1);
}
else if( me[m_pos].Event == 0xf0 || me[m_pos].Event == 0xf7)
{
byte[] buf = ( (SystemEvent) me[m_pos]).Data;
d_pos += ( 0 < buf.Length ? (int) Math.Log( buf.Length, 0x80) : 0) + 1;
d_pos += buf.Length;
}
else if( me[m_pos].Event == 0xff)
{
d_pos++;
byte[] buf = ( (MetaEvent) me[m_pos]).Data;
d_pos += ( 0 < buf.Length ? (int) Math.Log( buf.Length, 0x80) : 0) + 1;
d_pos += buf.Length;
}

/*--------------------------------------------------*/
m_pos++;
}

return d_pos;
}

/*--------------------------------------------------*/
/* EventToByte */
/*--------------------------------------------------*/
public static byte[] EventToByte( MidiEvent[] me)
{
int m_pos = 0;
int d_pos = 0;

byte[] data = new byte[MidiByte.CountByteLength( me)];

while( m_pos < me.Length)
{
/*--------------------------------------------------*/
uint tm = me[m_pos].Time;
tm -= ( 0 < m_pos ? me[m_pos - 1].Time : 0);
MidiByte.WriteVariableLength( data, ref d_pos, tm);

/*--------------------------------------------------*/
if( m_pos == 0 || 0xf0 <= me[m_pos].Event || me[m_pos].Event != me[m_pos - 1].Event)
{
data[d_pos] = me[m_pos].Event;
d_pos++;
}

/*--------------------------------------------------*/
if( me[m_pos].Event < 0xf0)
{
data[d_pos] = ( (ChannelEvent) me[m_pos]).Param1;
data[d_pos + 1] = ( (ChannelEvent) me[m_pos]).Param2;
d_pos += ( ( me[m_pos].Event < 0xc0 || 0xe0 <= me[m_pos].Event) ? 2 : 1);
}
else if( me[m_pos].Event == 0xf0 || me[m_pos].Event == 0xf7)
{
byte[] buf = ( (SystemEvent) me[m_pos]).Data;
MidiByte.WriteVariableLength( data, ref d_pos, (uint) buf.Length);

for( int i = 0; i < buf.Length; i++)
{
data[d_pos] = buf[i];
d_pos++;
}
}
else if( me[m_pos].Event == 0xff)
{
data[d_pos] = ( (MetaEvent) me[m_pos]).Type;
d_pos++;

byte[] buf = ( (MetaEvent) me[m_pos]).Data;
MidiByte.WriteVariableLength( data, ref d_pos, (uint) buf.Length);

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

/*--------------------------------------------------*/
m_pos++;
}

return data;
}

/*--------------------------------------------------*/
/* CountEventLength */
/*--------------------------------------------------*/
private static int CountEventLength( byte[] data)
{
int m_pos = 0;
int d_pos = 0;

byte evt = 0;
uint temp = 0;

while( d_pos < data.Length)
{
/*--------------------------------------------------*/
MidiByte.ReadVariableLength( data, ref d_pos, ref temp);

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

/*--------------------------------------------------*/
if( evt < 0xf0)
{
d_pos += ( ( evt < 0xc0 || 0xe0 <= evt) ? 2 : 1);
}
else if( evt == 0xf0 || evt == 0xf7)
{
MidiByte.ReadVariableLength( data, ref d_pos, ref temp);
d_pos += (int) temp;
}
else if( evt == 0xff)
{
d_pos++;
MidiByte.ReadVariableLength( data, ref d_pos, ref temp);
d_pos += (int) temp;
}

/*--------------------------------------------------*/
m_pos++;
}

return m_pos;
}

/*--------------------------------------------------*/
/* ByteToEvent */
/*--------------------------------------------------*/
public static MidiEvent[] ByteToEvent( byte[] data)
{
MidiEvent[] me = new MidiEvent[MidiByte.CountEventLength( data)];

int m_pos = 0;
int d_pos = 0;

uint tm = 0;
byte evt = 0;
uint temp = 0;

while( d_pos < data.Length)
{
/*--------------------------------------------------*/
MidiByte.ReadVariableLength( data, ref d_pos, ref tm);

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

/*--------------------------------------------------*/
if( evt < 0xf0)
{
byte p1 = data[d_pos];
byte p2 = ( ( evt < 0xc0 || 0xe0 <= evt) ? data[d_pos + 1] : (byte) 0);
d_pos += ( ( evt < 0xc0 || 0xe0 <= evt) ? 2 : 1);

me[m_pos] = new ChannelEvent( tm, evt, p1, p2);
}
else if( evt == 0xf0 || evt == 0xf7)
{
MidiByte.ReadVariableLength( data, ref d_pos, ref temp);
byte[] buf = new byte[temp];

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

me[m_pos] = new SystemEvent( tm, evt, buf);
}
else if( evt == 0xff)
{
byte et = data[d_pos];
d_pos++;

MidiByte.ReadVariableLength( data, ref d_pos, ref temp);
byte[] buf = new byte[temp];

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

me[m_pos] = new MetaEvent( tm, evt, et, buf);
}

/*--------------------------------------------------*/
m_pos++;
}

for( int i = 1; i < m_pos; i++)
{
me[i].Time += me[i - 1].Time;
}

return me;
}
}

/*--------------------------------------------------*/
/* MidiFile Class */
/*--------------------------------------------------*/
public class MidiFile
{
/*--------------------------------------------------*/
/* Save */
/* ( data.Length == 1) => save as SMF0 */
/* ( data.Length != 1) => save as SMF1 */
/*--------------------------------------------------*/
public static void Save( string filename, byte[][] data, uint div)
{
FileStream ofs = new FileStream( filename, FileMode.Create, FileAccess.Write);

byte[] hc = MidiFile.HeaderChunk( ( data.Length == 1 ? 0 : 1), data.Length, div);
ofs.Write( hc, 0, hc.Length);

for( int i = 0; i < data.Length; i++)
{
byte[] tc = MidiFile.TrackChunk( data[i].Length);

ofs.Write( tc, 0, tc.Length);
ofs.Write( data[i], 0, data[i].Length);
}

ofs.Close();
}

/*--------------------------------------------------*/
/* Load */
/*--------------------------------------------------*/
public static byte[][] Load( string filename, ref uint div)
{
FileStream ifs = new FileStream( filename, FileMode.Open, FileAccess.Read);

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

int FormatType = ( hc[8] << 8) + hc[9];
int NumberOfTracks = ( hc[10] << 8) + hc[11];
div = (uint) ( hc[12] << 8) + hc[13];
byte[][] data = new byte[NumberOfTracks][];

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

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

ifs.Close();

return data;
}

/*--------------------------------------------------*/
/* HeaderChunk */
/*--------------------------------------------------*/
private static byte[] HeaderChunk( int FormatType, int NumberOfTracks, uint TimeDivision)
{
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) FormatType;
hc[10] = (byte) ( ( NumberOfTracks >> 8) & 0xff);
hc[11] = (byte) ( NumberOfTracks & 0xff);
hc[12] = (byte) (( TimeDivision >> 8) & 0xff);
hc[13] = (byte) ( TimeDivision & 0xff);

return hc;
}

/*--------------------------------------------------*/
/* TrackChunk */
/*--------------------------------------------------*/
private static byte[] TrackChunk( int ChunkSize)
{
byte[] tc = new byte[8];

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

tc[4] = (byte) ( ( ChunkSize >> 24) & 0xff);
tc[5] = (byte) ( ( ChunkSize >> 16) & 0xff);
tc[6] = (byte) ( ( ChunkSize >> 8) & 0xff);
tc[7] = (byte) ( ChunkSize & 0xff);

return tc;
}
}


2016年12月24日土曜日

C#でDump

 C#でDumpしてみました。MIDIとかのプログラミングをするときに必要だったからという動機です。

 以前、VBAでも作りましたが、こちらは、ドラッグ&ドロップに対応しているので、少し使いやすいかもしれません。

 ご自由にご利用ください。例によって、動作保証はありませんが。



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

public class Program
{
    [STAThread]

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

public class FormDump : Form
{
private const int VIEW_SIZE = 256;

private byte[] data;

private MenuStrip ms;
private ToolStripMenuItem[][] tsmi;

private Label lbl1;
private TrackBar tbr;
private Label lbl2;
private ComboBox cmb;

private DataGridView dgv;

public FormDump()
{
this.ClientSize = new System.Drawing.Size( 480, 360);

/*--------------------------------------------------*/
this.ms = new MenuStrip();

this.tsmi = new ToolStripMenuItem[1][];
this.tsmi[0] = new ToolStripMenuItem[3];

for( int i = 0; i < this.tsmi.Length; i++)
{
this.tsmi[i][0] = new ToolStripMenuItem();
this.ms.Items.Add( this.tsmi[i][0]);

for( int j = 1; j < this.tsmi[i].Length; j++)
{
this.tsmi[i][j] = new ToolStripMenuItem();
this.tsmi[i][0].DropDownItems.Add( this.tsmi[i][j]);
this.tsmi[i][j].Click += new EventHandler( this.Menu_Click);
}
}

this.tsmi[0][0].Text = "File (&F)";
this.tsmi[0][1].Text = "Open (&O)";
this.tsmi[0][2].Text = "Exit (&X)";

this.Controls.Add( this.ms);
this.MainMenuStrip = this.ms;

/*--------------------------------------------------*/
this.lbl1 = new Label();
this.lbl1.Anchor = AnchorStyles.Left | AnchorStyles.Top;
this.lbl1.Font = new Font( "Arial", 10, FontStyle.Regular);
this.lbl1.TextAlign = ContentAlignment.MiddleRight;
this.lbl1.Text = "Format";
this.lbl1.SetBounds( 0, this.ms.Height, 80, 20);
this.Controls.Add( this.lbl1);

this.cmb = new ComboBox();
this.cmb.Anchor = AnchorStyles.Left | AnchorStyles.Top;
this.cmb.Font = new Font( "Arial", 10, FontStyle.Regular);
this.cmb.DropDownStyle = ComboBoxStyle.DropDownList;

this.cmb.Items.Add( "Hex");
this.cmb.Items.Add( "Dec");
this.cmb.Items.Add( "Asc");
this.cmb.SelectedIndex = 0;

this.cmb.SelectedIndexChanged += new EventHandler( this.cmb_SelectedIndexChanged);
this.cmb.SetBounds( 80, this.ms.Height, 80, 20);
this.Controls.Add( this.cmb);

/*--------------------------------------------------*/
this.lbl2 = new Label();
this.lbl2.Anchor = AnchorStyles.Left | AnchorStyles.Top;
this.lbl2.Font = new Font( "Arial", 10, FontStyle.Regular);
this.lbl2.TextAlign = ContentAlignment.MiddleRight;
this.lbl2.Text = "Offset";
this.lbl2.SetBounds( 160, this.ms.Height, 80, 20);
this.Controls.Add( this.lbl2);

this.tbr = new TrackBar();
this.tbr.Anchor = AnchorStyles.Left | AnchorStyles.Top;
this.tbr.Font = new Font( "Arial", 10, FontStyle.Regular);
this.tbr.AutoSize = false;
this.tbr.ValueChanged += new EventHandler( this.tbr_ValueChanged);
this.tbr.Minimum = 0;
this.tbr.Maximum = 0;
this.tbr.Value = 0;
this.tbr.SmallChange = 1;
this.tbr.LargeChange = 10;
this.tbr.SetBounds( 240, this.ms.Height, 160, 20);
this.Controls.Add( this.tbr);

/*--------------------------------------------------*/
this.dgv = new DataGridView();
this.dgv.SetBounds( 0, this.ms.Height + this.cmb.Height, 480, 360 - this.ms.Height - this.cmb.Height);
this.dgv.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Bottom;
this.dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
this.dgv.RowHeadersWidthSizeMode = DataGridViewRowHeadersWidthSizeMode.AutoSizeToAllHeaders;
this.dgv.AllowDrop = true;
this.dgv.DragEnter += new DragEventHandler( this.dgv_DragEnter);
this.dgv.DragDrop += new DragEventHandler( this.dgv_DragDrop);
this.Controls.Add( this.dgv);

for( int i = 0; i < 16; i++)
{
this.dgv.Columns.Add( i.ToString( "x"), i.ToString( "x"));
this.dgv.Rows.Add();
}

/*--------------------------------------------------*/
this.data = new byte[0];
this.UpdateView();

/*--------------------------------------------------*/
string[] args = Environment.GetCommandLineArgs();

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

private void Menu_Click( object sender, EventArgs e)
{
if( sender.ToString() == this.tsmi[0][1].Text)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "file|*.*";

if( ofd.ShowDialog() == DialogResult.OK)
{
dump( ofd.FileName);
}
}
else if( sender.ToString() == this.tsmi[0][2].Text)
{
this.Close();
}
}

private void cmb_SelectedIndexChanged( object sender, EventArgs e)
{
this.UpdateView();
}

private void tbr_ValueChanged( object sender, EventArgs e)
{
this.UpdateView();
}

private void dgv_DragEnter( object sender, DragEventArgs e)
{
if( e.Data.GetDataPresent( DataFormats.FileDrop))
{
e.Effect = DragDropEffects.Move;
}
}

private void dgv_DragDrop( object sender, DragEventArgs e)
{
string[] filename = (string[]) e.Data.GetData( DataFormats.FileDrop, false);

dump( filename[0]);
}

private void dump( string filename)
{
try
{
this.Text = Path.GetFileName( filename);

FileInfo fi = new FileInfo( filename);
this.data = new byte[fi.Length];

this.tbr.Maximum = (int) fi.Length;
this.tbr.LargeChange = (int) fi.Length / 10;

FileStream fs = new FileStream( filename, FileMode.Open, FileAccess.Read);
            fs.Seek( 0, SeekOrigin.Begin);
fs.Read( this.data, 0, this.data.Length);
fs.Close();

this.UpdateView();
}
catch( System.Exception err)
{
MessageBox.Show( err.Message);
}
}

private void UpdateView()
{
int offset = this.tbr.Value;

for( int i = 0; i < FormDump.VIEW_SIZE / 16; i++)
{
for( int j = 0; j < 16; j++)
{
if( i * 16 + j + offset < this.data.Length)
{
if( this.cmb.SelectedIndex == 0)
{
this.dgv[j,i].Value = this.data[i * 16 + j + offset].ToString( "x");
}
else if( this.cmb.SelectedIndex == 1)
{
this.dgv[j,i].Value = this.data[i * 16 + j + offset].ToString();
}
else if( this.cmb.SelectedIndex == 2)
{
this.dgv[j,i].Value = ( (char) this.data[i * 16 + j + offset]).ToString();
}
}
else
{
this.dgv[j,i].Value = null;
}
}

this.dgv.Rows[i].HeaderCell.Value = ( i * 16 + offset).ToString();
}
}
}