2017年5月4日木曜日

C#でMIDI その5

 C#でMIDIの続きです。

 今回はMIDI Playerを作ります。前回までで、MIDIファイルを読み込めて、MIDIで音を出せるのだから、後はその組み合わせです。・・・でも、簡単ではないです。

 MIDIファイルにはMIDIイベントが時系列に並んでいるわけではありません。なので、MIDIイベントの順序を入れ替えるSortが必要です。
 また実際作ってみると、MIDIイベント間の時間待ちにSleep関数を使うと、演奏にずれが生じることが分かりました。というわけで、Sleep関数を使わない方式に変えました。

 ソースコードはご自由にご利用ください。ただし、バグがあっても知りません。
 趣味のプログラムということで、ご了承ください。



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

/*--------------------------------------------------*/
/* FormMidi Class */
/*--------------------------------------------------*/
class FormMidi : Form
{
private Label lbl;
private Button btn_open;
private Button btn_play;
private Button btn_stop;

private MidiPlayer Player;

/*--------------------------------------------------*/
/* 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_open = new Button();
this.btn_open.SetBounds( 15, 80, 70, 30);
    this.btn_open.Font = new Font( "Arial", 10);
this.btn_open.Text = "Open";
this.btn_open.Click += new EventHandler( this.btn_open_Click);
this.Controls.Add( this.btn_open);

/*--------------------------------------------------*/
this.btn_play = new Button();
this.btn_play.SetBounds( 85, 80, 70, 30);
    this.btn_play.Font = new Font( "Arial", 10);
this.btn_play.Text = "Play";
this.btn_play.Click += new EventHandler( this.btn_play_Click);
this.Controls.Add( this.btn_play);

/*--------------------------------------------------*/
this.btn_stop = new Button();
this.btn_stop.SetBounds( 155, 80, 70, 30);
    this.btn_stop.Font = new Font( "Arial", 10);
this.btn_stop.Text = "Stop";
this.btn_stop.Click += new EventHandler( this.btn_stop_Click);
this.Controls.Add( this.btn_stop);
}

/*--------------------------------------------------*/
/* FormMidi_Load */
/*--------------------------------------------------*/
private void FormMidi_Load( object sender, EventArgs e)
{
this.Player = new MidiPlayer();

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

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

/*--------------------------------------------------*/
/* FormMidi_Closed */
/*--------------------------------------------------*/
private void FormMidi_Closed( object sender, EventArgs e)
{
if( this.Player.IsPlaying() == true)
{
this.Player.PlayStop();
}
}

/*--------------------------------------------------*/
/* 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_open_Click */
/*--------------------------------------------------*/
private void btn_open_Click( object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "midi file|*.mid|*|*.*";

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

/*--------------------------------------------------*/
/* btn_play_Click */
/*--------------------------------------------------*/
private void btn_play_Click( object sender, EventArgs e)
{
if( this.Player.IsPlaying() == true)
{
this.Player.PlayStop();
}

this.Player.PlayStart();
}

/*--------------------------------------------------*/
/* btn_stop_Click */
/*--------------------------------------------------*/
private void btn_stop_Click( object sender, EventArgs e)
{
if( this.Player.IsPlaying() == true)
{
this.Player.PlayStop();
}
}

/*--------------------------------------------------*/
/* OpenFile */
/*--------------------------------------------------*/
private void OpenFile( string filename)
{
if( this.Player.IsPlaying() == true)
{
this.Player.PlayStop();
}

if( Path.GetExtension( filename) == ".mid")
{
this.Player.Load( filename);

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

this.Player.PlayStart();
}
}



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



/*--------------------------------------------------*/
/* MidiPlayer Class */
/*--------------------------------------------------*/
public class MidiPlayer
{
/*--------------------------------------------------*/
/* API */
/*--------------------------------------------------*/
[DllImport( "Winmm.dll")]
extern static uint midiOutGetNumDevs();

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

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

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

[DllImport( "Winmm.dll")]
extern static uint midiOutReset( long 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 long hMidi;
private Thread PlayThread;

public uint TimeDivision;
public MidiEventArray[] Track;

/*--------------------------------------------------*/
/* MidiPlayer */
/*--------------------------------------------------*/
public MidiPlayer()
{
this.hMidi = 0;
ThreadStart ts = new ThreadStart( this.Play);
this.PlayThread = new Thread( ts);
this.Track = new MidiEventArray[0];
this.TimeDivision = 96;
}

/*--------------------------------------------------*/
/* Load */
/*--------------------------------------------------*/
public void Load( string filename)
{
int temp;

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];
this.Track = new MidiEventArray[temp];

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

for( int i = 0; i < this.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);

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

ifs.Close();
}

/*--------------------------------------------------*/
/* GetEventCount */
/*--------------------------------------------------*/
public int GetEventCount()
{
int ret = 0;

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

return ret;
}

/*--------------------------------------------------*/
/* IsPlaying */
/*--------------------------------------------------*/
public bool IsPlaying()
{
bool ret = true;

if( this.PlayThread.IsAlive == false)
{
ret = false;
}

return ret;
}

/*--------------------------------------------------*/
/* PlayStart */
/*--------------------------------------------------*/
public void PlayStart()
{
if( this.Track.Length == 0)
{
return;
}

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

ThreadStart ts = new ThreadStart( this.Play);
this.PlayThread = new Thread( ts);
this.PlayThread.IsBackground = true;
this.PlayThread.Start();
}

/*--------------------------------------------------*/
/* Play */
/*--------------------------------------------------*/
public void Play()
{
MidiEventArray mea = new MidiEventArray();

for( int i = 0; i < this.Track.Length; i++)
{
for( int j = 0; j < this.Track[i].Count; j++)
{
mea.Add( this.Track[i].Event[j]);
}
}

mea.Sort();

/*--------------------------------------------------*/
Console.WriteLine( "----- Play Start -----");

long t0 = DateTime.Now.Ticks / 10000; // [msec]
long t1 = 0; // [msec]
uint td = this.TimeDivision;
uint msg;

for( int i = 0; i < mea.Count; i++)
{
while( t1 * td / 500 < mea.Event[i].Time)
{
t1 = DateTime.Now.Ticks / 10000 - t0;
}

if( mea.Event[i].Event < 0xf0)
{
ChannelEvent ce = (ChannelEvent) mea.Event[i];
msg = (uint) ( ( ce.Param2 << 16) + ( ce.Param1 << 8) + ce.Event);
midiOutShortMsg( this.hMidi, msg);

Console.Write( i.ToString() + "\t");
Console.Write( ce.Time.ToString() + "\t");
Console.Write( ce.Event.ToString( "X") + "\t");
Console.Write( ce.Param1.ToString() + "\t");
Console.Write( ce.Param2.ToString() + "\t");
Console.WriteLine();
}
}

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

/*--------------------------------------------------*/
midiOutReset( this.hMidi);
midiOutClose( this.hMidi);
}

/*--------------------------------------------------*/
/* PlayStop */
/*--------------------------------------------------*/
public void PlayStop()
{
this.PlayThread.Abort();

midiOutReset( this.hMidi);
midiOutClose( this.hMidi);
}
}

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

/*--------------------------------------------------*/
/* Sort */
/*--------------------------------------------------*/
public void Sort()
{
MidiEvent temp;

for( int i = 1; i < this.Count; i++)
{
temp = this.Event[i];

int j;

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

this.Event[j] = 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 件のコメント:

コメントを投稿