MIDIポートの名前を取得する
・midiOutGetDevCapsを使います。
/**** Source Code (C#) ****/ /// <summary> /// MIDI出力APIの宣言です。 /// </summary> public static class MidiOutApi { /// <summary> /// MIDI出力ポートの情報を取得します。 /// </summary> [DllImport("winmm.dll", EntryPoint = "midiOutGetDevCapsA", CharSet = CharSet.Ansi)] [return: MarshalAs(UnmanagedType.U4)] public static extern MMResult midiOutGetDevCapsA([MarshalAs(UnmanagedType.U4)] uint uDeviceID, ref MidiOutCapsA pMidiOutCaps, [MarshalAs(UnmanagedType.U4)] uint cbMidiOutCaps); /* ... */ }
省略可能な変数を省略すると以下のようになります。
/**** Source Code (C#) ****/ public static class MidiOutApi { /// <summary> /// MIDI出力ポートの情報を取得します。 /// </summary> [DllImport("winmm.dll")] public static extern MMResult midiOutGetDevCapsA(uint uDeviceID, ref MidiOutCapsA pMidiOutCaps, uint cbMidiOutCaps); }
この関数には3つの引数があり、順に、情報を取得したいポートの番号(C#でいうところのindex)、情報を格納する変数、構造体のメモリ上でのサイズです。
C言語のAPIでは常にメモリバッファのサイズが要求されます。
構造体のマーシャリング後のサイズを取得するには、Marshal.SizeOf関数を使います。
MMResultとMidiOutCapsAは以下のように定義されています。
量的にはちょっと勘弁してださいという感じです。
・MMResult
/**** Source Code (C#) ****/ /// <summary> /// MMRESULTのマネージド実装です。 /// </summary> public enum MMResult : uint { /// <summary> /// 処理に成功しました。 /// </summary> NoError = 0, /// <summary> /// 指定されたIDは無効です。 /// </summary> InvalidDeviceID = 2, /// <summary> /// 指定されたリソースは既に割り当てられています。 /// </summary> Allocated = 4 }
・MidiOutCapsA
/**** Source Code (C#) ****/ /// <summary> /// MIDI出力ポートの情報を表します。 /// </summary> [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct MidiOutCapsA { /// <summary> /// MIDIハードウェアのメーカーIDです。 /// </summary> [MarshalAs(UnmanagedType.U2)] public ushort wMid; /// <summary> /// Product IDです。 /// </summary> [MarshalAs(UnmanagedType.U2)] public ushort wPid; /// <summary> /// ドライバーのバージョンです。 /// </summary> [MarshalAs(UnmanagedType.U4)] public uint vDriverVersion; /// <summary> /// ポートの名前です。 /// </summary> [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MidiPortConst.MaxPNameLen)] public string szPname; /// <summary> /// wTechnology値です。 /// </summary> [MarshalAs(UnmanagedType.U2)] public MidiModuleType wTechnology; /// <summary> /// 最大ボイス数を取得します。 /// </summary> [MarshalAs(UnmanagedType.U2)] public ushort wVoices; /// <summary> /// 最大同時発音数を取得します。 /// </summary> [MarshalAs(UnmanagedType.U2)] public ushort wNotes; /// <summary> /// チャンネルマスクを取得します。 /// </summary> [MarshalAs(UnmanagedType.U2)] public ushort wChannelMask; /// <summary> /// dwSupport値です。 /// </summary> [MarshalAs(UnmanagedType.U4)] public MidiPortCapability dwSupport; /// <summary> /// チャンネルマスクを取得します。 /// </summary> public bool[] GetChannelMask() { bool[] mask = new bool[16]; for (int i = 0; i < 16; i++) { mask[i] = (wChannelMask & (1 << i)) != 0; } return mask; } /// <summary> /// チャンネルマスクを設定します。 /// </summary> public void SetChannelMask(byte ch, bool value) { wChannelMask &= (ushort)(0xFFFF - 1 << ch); wChannelMask |= value ? (ushort)(1 << ch) : (ushort)0; } /// <summary> /// デバイスドライバのメジャーバージョンを取得します。 /// </summary> public byte MajorVersion { get { return (byte)(vDriverVersion >> 8); } } /// <summary> /// デバイスドライバのマイナーバージョンを取得します。 /// </summary> public byte MinorVersion { get { return (byte)(vDriverVersion & 0xFF); } } }
・MidiModuleType (MidiOutCapsA内で使用)
/**** Source Code (C#) ****/ /// <summary> /// MIDIポートの種類を表すフラグです。 /// </summary> public enum MidiModuleType : ushort { /// <summary> /// このポートはハードウェアポートです。 /// </summary> Hardware = 1, /// <summary> /// このポートはソフトウェアシンセサイザです。 /// </summary> Synthesizer = 2, /// <summary> /// このポートは矩形シンセサイザです。 /// </summary> SquareSynth = 3, /// <summary> /// このポートはFMシンセサイザです。 /// </summary> FMSynth = 4, /// <summary> /// このポートはMIDIマッパーです。 /// </summary> MidiMapper = 5, /// <summary> /// このポートはウェーブテーブルシンセサイザです。 /// </summary> Wavetable = 6, /// <summary> /// このポートはソフトウェアシンセサイザです。 /// </summary> SoftwareSynth = 7 }
・MidiPortCapability (MidiOutCapsAで使用)
/**** Source Code (C#) ****/ /// <summary> /// MIDIポートの能力を示すフラグです。 /// </summary> [Flags] public enum MidiPortCapability : uint { /// <summary> /// ポートはボリュームコントロールをサポートします。 /// </summary> Volume = 1, /// <summary> /// ポートは左右独立のボリュームコントロールをサポートします。 /// </summary> LRVolume = 2, /// <summary> /// ポートはキャッシュをサポートします。 /// </summary> Cache = 4, /// <summary> /// ポートはMIDIストリームAPIをネイティブサポートします。 /// </summary> Stream = 8 }
・MidiPortConst (MidiOutApiで使用)
/**** Source Code (C#) ****/ /// <summary> /// MIDI APIで使用する定数です。 /// </summary> public static class MidiPortConst { /// <summary> /// TCHARで数えた時の文字数です。 /// </summary> public const int MaxPNameLen = 32; }
ここでは、おもに2つの特別な技法が使われています。
列挙型へのマーシャリングと固定長文字列です。
列挙型へのマーシャリングについては、特に説明は必要ないと思います。
一般的ではありませんが、通常のキャストの延長として使うことのできる操作です。
.net Frameworkでは一般に固定長文字列を使うことはできません (上記のコードでも、実際に固定長文字列としてメモリが確保されるわけではありません) が、マーシャリング時に文字列を構造体に埋め込むことはできます。
その宣言をしているのが、
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MidiPortConst.MaxPNameLen)]
という一文です。
この例ではTStrを指定しているので、文字列はTCHAR型としてマーシャリングされます。
midiOutGetDevCaps関数を実際に使用するには、以下のようなソースコードを書きます。
/**** Source Code (C#) ****/ static void Main(string[] args) { var caps = new MidiOutCapsA(); int mapper = -1; var result = MidiOutApi.midiOutGetDevCapsA((uint)mapper, ref caps, (uint)Marshal.SizeOf(typeof(MidiOutCapsA))); }
実行結果:
? result
NoError
? caps
{NextMidi.MidiOut.Internal.MidiOutCapsA}
dwSupport: Volume | LRVolume | Stream
MajorVersion: 5
MinorVersion: 0
szPname: "Microsoft MIDI マッパー"
vDriverVersion: 1280
wChannelMask: 65535
wMid: 1
wNotes: 0
wPid: 1
wTechnology: MidiMapper
wVoices: 0
無効な番号を渡すと戻り値として0以外の値が返ります。 (APIの標準的な実装です。)
? MidiOutApi.midiOutGetDevCapsA(100, ref caps, (uint)Marshal.SizeOf(typeof(MidiOutCapsA)));
InvalidDeviceID
戻り値が0以外の場合だった時のために、エラー処理を記述します。
ここで、拡張メソッドを使用すると、処理の局所性が高まり、保守性が良くなります。
・MMResultExtensions
/**** Source Code (C#) ****/ /// <summary> /// MMResultの拡張クラスです。 /// </summary> public static class MMResultExtensions { /// <summary> /// MMResultがNoErrorでない場合にエラーを発生させます。 /// </summary> public static void Throw(this MMResult result) { switch (result) { case MMResult.NoError: return; case MMResult.InvalidDeviceID: throw new ArgumentOutOfRangeException(); case MMResult.Allocated: throw new MMAllocatedException(); default: throw new MMException(); } } }
・MMException
/**** Source Code (C#) ****/ /// <summary> /// winmm.dllの呼び出し時に発生するエラーを表すクラスです。 /// </summary> public class MMException : Exception { /// <summary> /// MMExceptionのインスタンスを作成します。 /// </summary> public MMException() : base("マルチメディアエラーが発生しました。") { } /// <summary> /// MMExceptionのインスタンスを作成します。 /// </summary> public MMException(string message) : base(message) { } }
・MMAllocatedException
/**** Source Code (C#) ****/ /// <summary> /// マルチメディアリソースの割り当てに失敗したことを示すクラスです。 /// </summary> public class MMAllocatedException : MMException { /// <summary> /// MMAllocatedExceptionのインスタンスを作成します。 /// </summary> public MMAllocatedException() : base("指定されたリソースは既に割り当てられています。") { } }
次に、midiOutGetDevCaps関数のラッパーを作成します。
/**** Source Code (C#) ****/ /// <summary> /// MIDI-OUTポートを抽象化するクラスです。 /// </summary> public sealed class MidiOutPortHandle : IDisposable { /// <summary> /// MIDI出力ポートの情報を取得します。 /// </summary> public static MidiOutCapsA GetPortInformation(int portNum) { var caps = new MidiOutCapsA(); MidiOutApi.midiOutGetDevCapsA((uint)portNum, ref caps, (uint)Marshal.SizeOf(typeof(MidiOutCapsA))).Throw(); return caps; } /* ... */ }
さて、長かったですが、いよいよこれまでのコードを組み合わせて、ポート名のリストを書き出してみます。
/**** Source Code (C#) ****/ static void Main(string[] args) { int count = MidiOutPortHandle.GetPortCount(); for (int i = 0; i < count; i++) { var caps = MidiOutPortHandle.GetPortInformation(i); Debug.Print(caps.szPname); } }
実行結果:
1:EDIROL SD-80 PART A
MIDI Yoke NT: 1
MIDI Yoke NT: 2
MIDI Yoke NT: 3
MIDI Yoke NT: 4
MIDI Yoke NT: 5
MIDI Yoke NT: 6
MIDI Yoke NT: 7
MIDI Yoke NT: 8
Yamaha UX16-1
Yamaha MOTIF-R XS-1
Yamaha MOTIF-R XS-2
Yamaha MOTIF-R XS-3
Yamaha MOTIF-R XS-4
Microsoft GS Wavetable SW Synth
E-DSP MIDI Port [1040]
1:EDIROL PC-50 MIDI OUT
1:EDIROL SD-80 PART B
1:EDIROL SD-80 MIDI OUT 1
1:EDIROL SD-80 MIDI OUT 2