2020年9月6日日曜日

C#で、もう1回、MIDI その7

  今回は、MIDIの演奏をしてみます。


 以前、MIDIの演奏プログラムを作ったときは、すべてのMIDI Eventを時系列に並べ直してから演奏しました。今回は各Trackは分けたままで演奏しています。「だから何」って話ですが、前処理がない分、ソースコードは簡単になった気がしてます。大差ないですが。

 今回のプログラムは、別スレッドで無限ループを使って演奏しています。たぶん、本職のプログラマーの方々であれば、別のタイマーの使い方をしそうな気がします。まぁ、趣味なので、動けばいいんです。

 おまけで、少し小細工をして、途中からの再生にも対応させています。


 以下が今回のC#のソースコードです。ソースコードはメイン関数の部分"Main.cs"と、MIDI演奏の"MidiPlayer.cs"に分かれています。"MidiEvent.cs"と"MidiAPI.cs"も使うので、その4つを合わせてコンパイルしてください。


 ソースコードはご自由にご利用ください。ただし、趣味のプログラムなので保証はありません。コメントとかも適当です。プログラミングを勉強する方のご参考にでもなれば。


------------------------------

ここから"Main.cs"

------------------------------

using System;

using System.IO;

using System.Threading;


public class Program

{

public static void Main()

{

/*------------------------------*/

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


/*------------------------------*/

MidiPlayer player = new MidiPlayer();


player.Stop(); //再生していないときの停止無効


player.Play( TimeDivision, Track, 1000);


player.Play( TimeDivision, Track); //再生中の再生は無効


Thread.Sleep( 3000);


player.Stop();


player.Play( TimeDivision, Track, 500); //停止後に再生


Thread.Sleep( 3000);


Console.WriteLine( player.Stop());

/*------------------------------*/

Console.WriteLine( "Press Enter Key");

Console.ReadLine();

}

}


------------------------------

ここから"MidiPlayer.cs"

------------------------------

using System;

using System.Threading;


/*--------------------------------------------------*/

/* MidiPlayer Class */

/*--------------------------------------------------*/

public class MidiPlayer

{

/*--------------------------------------------------*/

/* Variables */

/*--------------------------------------------------*/

private IntPtr hMidi;

private Thread PlayThread;


private int TimeDivision;

private MidiEvent[][] Track;


private int Tempo;


private long PlayOffset; // [msec]

public long PlayTime; // [msec]


/*--------------------------------------------------*/

/* MidiPlayer */

/*--------------------------------------------------*/

public MidiPlayer()

{

this.Tempo = 500; // 1 TimeDivision = 500 [msec] のみ対応

}


~MidiPlayer()

{

this.Stop();

}


/*--------------------------------------------------*/

/* Play */

/*--------------------------------------------------*/

public void Play( int tm, MidiEvent[][] trk)

{

this.Play( tm, trk, 0);

}


public void Play( int tm, MidiEvent[][] trk, long offset)

{

if( this.PlayThread != null)

{

return;

}


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


this.TimeDivision = tm;

this.Track = trk;

this.PlayOffset = offset;


ThreadStart ts = new ThreadStart( this.play_func);

this.PlayThread = new Thread( ts);

this.PlayThread.IsBackground = true;

this.PlayThread.Start();

}


/*--------------------------------------------------*/

/* play_func */

/*--------------------------------------------------*/

private void play_func()

{

Console.WriteLine( "----- play_func start -----");


/*--------------------------------------------------*/

// Midi DeviceはThread内でOpenしないといけないみたい

if( MidiAPI.midiOutOpen( ref this.hMidi, MidiAPI.MIDI_MAPPER, 0, 0, 0) != MidiAPI.MMSYSERR_NOERROR )

{

Console.WriteLine( "MidiAPI.midiOutOpen error");

throw new Exception( "MidiAPI.midiOutOpen error");

}


/*--------------------------------------------------*/

int[] event_count = new int[Track.Length];


for( int i = 0; i < Track.Length; i++)

{

event_count[i] = 0;

}


long time_start = DateTime.Now.Ticks / 10000 - this.PlayOffset; // [msec]


this.PlayTime = 0;


bool flag = true;


while( flag)

{

if( this.PlayTime < this.PlayOffset)

{

// offsetまでは、1msec間隔でMidiEventを実行する (本当は音を鳴らす必要はない)

// MidiEventには、Program Changeなどもあるので、途中からの再生でも最初からMidiEventを処理する

this.PlayTime++; // [msec]

}

else

{

this.PlayTime = DateTime.Now.Ticks / 10000 - time_start; // [msec]

}


flag = false;


for( int i = 0; i < Track.Length; i++)

{

/*--------------------------------------------------*/

if( event_count[i] == Track[i].Length)

{

continue;

}


flag = true;


/*--------------------------------------------------*/

MidiEvent me = Track[i][event_count[i]];


if( this.PlayTime < me.Time * this.Tempo / this.TimeDivision)

{

continue;

}


/*--------------------------------------------------*/

if( me.Event < 0xf0)

{

// ChannelEventのみ対応 (本当はMetaEventのTempoとかも対応したほうがいい)

ChannelEvent ce = (ChannelEvent) me;

uint msg = (uint) ( ( ce.Param2 << 16) + ( ce.Param1 << 8) + ce.Event);

MidiAPI.midiOutShortMsg( this.hMidi, msg);

}


event_count[i]++;

}

}


this.PlayTime = 0;


/*--------------------------------------------------*/

Console.WriteLine( "----- play_func stop -----");


this.PlayThread = null;


MidiAPI.midiOutReset( this.hMidi);

MidiAPI.midiOutClose( this.hMidi);

}


/*--------------------------------------------------*/

/* Stop */

/*--------------------------------------------------*/

public long Stop()

{

if( this.PlayThread == null)

{

return 0;

}


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


this.PlayThread.Abort();


this.PlayThread = null;


MidiAPI.midiOutReset( this.hMidi);

MidiAPI.midiOutClose( this.hMidi);


return this.PlayTime;

}


/*--------------------------------------------------*/

public bool IsPlaying()

{

if( this.PlayThread == null)

{

return false;

}


return true;

}

}


0 件のコメント:

コメントを投稿