今回は、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();
}
}
------------------------------
------------------------------
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 件のコメント:
コメントを投稿