MIDI in C# (Next MIDI Architecture)

MIDI入力ポートを開く

ポートを列挙したら、次にポートを開きます。

但し、本気で仕掛け・挙動ともに複雑です。
このコードでも時々マーシャリングエラーが発生します。

  1. ■  実装
  2. ■  API仕様
  3. ■  ポートを開く
  4. ■  ポートを閉じる
  5. ■  ソースコード全文

■  実装  ■

// MIDI入力ポート
public class MidiInPortHandle : IDisposable
{
    public MidiInPortHandle(int portNum)

	// IDisposable 実装
    public void Dispose()

	// ポートを閉じる
    public void Close()

    // MIDI受信
    public event EventHandler<MidiInNativeEventArgs> MidiReceived;

	// ポートの名前
    public string Name
    {
	    get {}
	}
}

MidiInPortHandle.cs

using System;

namespace NextMIDI.MidiInPort
{
    public class MidiInPortHandle : IDisposable
    {
        public MidiInPortHandle(int portNum)
        {
            name = MidiInApi.GetPortInformation(portNum).szPname;
            receiver = new MidiReceiver(portNum);

            receiver.MidiReceived += new EventHandler<MidiInNativeEventArgs>(MidiReceiver_MidiReceived);
        }

        string name = "";
        MidiReceiver receiver;

        public event EventHandler<MidiInNativeEventArgs> MidiReceived;
        protected void OnMidiReceived(MidiInNativeEventArgs e)
        {
            if (MidiReceived != null)
            {
                MidiReceived(this, e);
            }
        }

        public void Dispose()
        {
            Close();
        }

        public void Close()
        {
            if (receiver != null)
            {
                receiver.ClosePort();
                receiver = null;
            }
        }

        private void MidiReceiver_MidiReceived(object sender, MidiInNativeEventArgs e)
        {
            OnMidiReceived(e);
        }

        public string Name
        {
            get
            {
                return name;
            }
        }
    }
}

MidiReceiver.cs

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;

namespace NextMIDI.MidiInPort
{
    internal class MidiReceiver : Control, IDisposable
    {
        internal MidiReceiver(int portNum)
        {
            isOpen = true;

			//----------// ポートハンドル作成

            midiInOpen(ref systemHandle, portNum, this.Handle, 0, MidiInCallBackFlag.callBack_Window);

			//----------// バッファ作成

			buffer = new MidiInBuffer();

			buffer.SystemHandle = systemHandle;
			buffer.ResetHeader();

			//----------// MIDI入力ポート起動

            midiInStart(systemHandle);

			//----------//
        }

        public event EventHandler<MidiInNativeEventArgs> MidiReceived;
        protected void OnMidiReceived(MidiInNativeEventArgs e)
        {
            if (MidiReceived != null)
            {
                MidiReceived(this, e);
            }
        }

        uint systemHandle = 0;
        bool isOpen = false;
		MidiInBuffer buffer;

        void IDisposable.Dispose()
        {
            ClosePort();
            base.Dispose();
        }

        public void ClosePort()
        {
			InternalClose internalClose = new InternalClose(ClosePortInternal);
			this.Invoke(internalClose);
        }

		delegate void InternalClose();

		private void ClosePortInternal()
		{
			if (isOpen)
			{
				isOpen = false;

				//----------// 入力を停止する

				midiInStop(systemHandle);

				//----------// 未処理のバッファをコールバック関数に返す

				/*

				while ((dataHeader.dwFlags & MidiHdrFlag.MHDR_DONE) == 0)
				{
					Thread.Sleep(1);
				}

				*/

				midiInReset(systemHandle);

				buffer.UnprepareHeader();

				//----------//

				midiInClose(systemHandle);
			}
		}

        protected override void WndProc(ref Message m)
        {
			if (isOpen)
			{
				switch (m.Msg)
				{
					case 0x3C1:	//MM_MIM_OPEN
						return;
					case 0x3C2:	//MM_MIM_CLOSE
						return;
					case 0x3C3:	//MM_MIM_DATA
						int receiveData = m.LParam.ToInt32();
						Thread.CurrentThread.Priority = ThreadPriority.AboveNormal;
						switch (receiveData & 0xF0)
						{
							case 0x80:
							case 0x90:
							case 0xA0:
							case 0xB0:
							case 0xE0:
								OnMidiReceived(new MidiInNativeEventArgs(new byte[3] { Convert.ToByte(receiveData & 255), Convert.ToByte((receiveData & 65535) >> 8), Convert.ToByte((receiveData & ((2 << 24) - 1)) >> 16) }));
								break;
							case 0xC0:
							case 0xD0:
								OnMidiReceived(new MidiInNativeEventArgs(new byte[2] { Convert.ToByte(receiveData & 255), Convert.ToByte((receiveData & 65535) >> 8) }));
								break;
						}
						Thread.CurrentThread.Priority = ThreadPriority.Normal;
						return;
					case 0x3C4:	//MM_MIM_LONGDATA
						Thread.CurrentThread.Priority = ThreadPriority.AboveNormal;

						byte[] receiveLongData = buffer.GetData();
						buffer.ResetHeader();
						OnMidiReceived(new MidiInNativeEventArgs(receiveLongData));
						Thread.CurrentThread.Priority = ThreadPriority.Normal;
						return;
					case 0x3C5:	//MM_MIM_ERROR
						return;
					case 0x3C6:	//MM_MIM_LONGERROR
						return;
					case 0x3CC: //MOREDATA
						return;
					default:
						base.WndProc(ref m);
						return;
				}
			}
			else
			{
				base.WndProc(ref m);
				return;
			}
        }

        [DllImport("winmm.dll")]
        private static extern int midiInOpen(ref uint lphMidiIn, int uDeviceID, System.IntPtr dwCallback, int dwCallbackInstance, int dwFlags);
        [DllImport("winmm.dll")]
		private static extern int midiInClose(uint hMidiIn);
        [DllImport("winmm.dll")]
		private static extern int midiInStart(uint hMidiIn);
        [DllImport("winmm.dll")]
		private static extern int midiInStop(uint hMidiIn);
        [DllImport("winmm.dll")]
		private static extern int midiInReset(uint hMidiIn);
    }
}

MidiInBuffer.cs

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using NextMIDI.MidiPort;

namespace NextMIDI.MidiInPort
{
	internal class MidiInBuffer : IDisposable
	{
		internal MidiInBuffer()
		{
			dataHandle = GCHandle.Alloc(new byte[128], GCHandleType.Pinned);
			dataHeader.lpData = dataHandle.AddrOfPinnedObject();
			dataHeader.dwBufferLength = 128;
		}

		GCHandle dataHandle = new GCHandle();
		MidiHdr dataHeader = new MidiHdr();
		uint systemHandle = 0;
		object lockTarget = new object();

		internal void Clear()
		{
			lock (lockTarget)
			{
				dataHandle.Free();
			}
		}

		public void Dispose()
		{
			Clear();
		}

		internal void ResetHeader()
		{
			lock (lockTarget)
			{
				midiInPrepareHeader(systemHandle, ref dataHeader, Marshal.SizeOf(typeof(MidiHdr)));
				while ((dataHeader.dwFlags & MidiHdrFlag.MHDR_PREPARED) == 0)
				{
					Thread.Sleep(1);
				}
				midiInAddBuffer(systemHandle, ref dataHeader, Marshal.SizeOf(typeof(MidiHdr)));
			}
		}

		internal void UnprepareHeader()
		{
			lock (lockTarget)
			{
				if ((dataHeader.dwFlags & MidiHdrFlag.MHDR_PREPARED) == MidiHdrFlag.MHDR_PREPARED)
				{
					midiInUnprepareHeader(systemHandle, ref dataHeader, Marshal.SizeOf(typeof(MidiHdr)));
				}
			}
		}

		internal byte[] GetData()
		{
			lock (lockTarget)
			{
				if (!dataHandle.IsAllocated)
				{
					return null;
				}
				if (dataHeader.dwBufferLength < dataHeader.dwBytesRecorded)
				{
					throw new InvalidOperationException();
				}
				byte[] data = (byte[])dataHandle.Target;

				int dataLength = (int)dataHeader.dwBytesRecorded;
				byte[] receiveBytes = new byte[dataLength];
				for (int i = 0; i < dataLength; i++)
				{
					receiveBytes[i] = data[i];
				}
				return receiveBytes;
			}
		}

		internal uint SystemHandle
		{
			get
			{
				return systemHandle;
			}
			set
			{
				lock (lockTarget)
				{
					systemHandle = value;
				}
			}
		}

		[DllImport("winmm.dll")]
		private static extern int midiInPrepareHeader(uint hMidiIn, ref MidiHdr lpMidiInHdr, int uSize);
		[DllImport("winmm.dll")]
		private static extern int midiInUnprepareHeader(uint hMidiIn, ref MidiHdr lpMidiInHdr, int uSize);
		[DllImport("winmm.dll")]
		private static extern int midiInAddBuffer(uint hMidiIn, ref MidiHdr lpMidiInHdr, int uSize);
	}
}

MidiInCallBackFlag.cs

using System;

namespace NextMIDI.MidiInPort
{
	public class MidiInCallBackFlag
	{
		/// <summary>
		/// 第三引数がコントロールハンドルであることを示します。
		/// </summary>
		public const int callBack_Window = 0x10000;
	}
}

MidiInNativeEventArgs.cs

using System;

namespace NextMIDI.MidiInPort
{
    public class MidiInNativeEventArgs : EventArgs
    {
        public MidiInNativeEventArgs(byte[] data)
        {
            this.data = data;
        }

        byte[] data;

        public byte[] ReceiveData
        {
            get
            {
                return data;
            }
        }
    }
}
Top