GitHubのレポジトリ紹介です。
- やったこと
- なぜやったのか
- 実装について
- やってないけどやれそうなこと
- 補足: MIDI入力読み込みの競合について
1. やったこと
記事タイトルの通り、WindowsとUnityの組み合わせでC#のみ、つまりネイティブプラグイン無しでMIDI入力を読み込めるようにしました。
レポジトリはこちらです。
見てのとおりKeijiroさんのMidiJackのforkです。
2. なぜやったのか
元のMidiJackにないAPIで、「MIDI入力デバイスの占有オン/オフを切り替える」という機能が欲しかったからです。
このAPIが欲しい理由として、以下のような想定要件がありました。
元のMidiJackではMIDI機能をネイティブプラグイン(C++)越しで呼んでいたため、選択肢は2つでした。
この2択に対し、個人的に書きやすいという理由でC#を選びました。
C#化しておくと後から気軽に直せる、というメリットも意識しています。
3. 実装について
MidiJackのWindows向けネイティブ実装であるMidiJackPlugin.cpp
をじーっと眺めます。
ソース中でMIDI関連のAPI呼び出しがあることが読み取れます。以下はその一部です。
midiInOpen
midiInStart
midiInClose
関数名でググってドキュメントを確認します。たとえばmidiInOpen
の場合こういうのです。
関数シグネチャとかライブラリ名(winmm.dll
)が分かるので、それを踏まえながらC#とC++とWindowsの気持ちを汲み取ってP/Invokeコードに置き換えます。
抜粋するとこんな感じ。
using System.Runtime.InteropServices; //... public static class NativeMethods { private const int CALLBACK_FUNCTION = 0x30000; public delegate void MidiInProcDelegate(IntPtr hMidiIn, uint wMsg, IntPtr dwInstance, IntPtr dwParam1, IntPtr dwParam2); [DllImport("winmm.dll")] public static extern uint midiInGetNumDevs(); [DllImport("winmm.dll")] public static extern uint midiInOpen( out IntPtr handle, uint id, MidiInProcDelegate callback, IntPtr hInstance, uint flags ); public static uint midiInOpen(out IntPtr handle, uint id, MidiInProcDelegate callback) => midiInOpen(out handle, id, callback, IntPtr.Zero, CALLBACK_FUNCTION); [DllImport("winmm.dll")] public static extern uint midiInStart(IntPtr hMidiIn); [DllImport("winmm.dll")] public static extern uint midiInClose(IntPtr hMidiIn); public const int MMSYSERR_NOERROR = 0; public const int MIM_CLOSE = 0x3C2; public const int MIM_DATA = 0x3C3; }
それ以外の処理は普通のC++コードなので、素朴に読んでいけばC#に置き換えられます。
置き換えた実装がコチラです。
なお、置き換え後の実装はスレッドセーフじゃないんですが、以下の理由から問題視していません。
- Unity側の呼び出しはどのみちUIスレッドからやる
- ネイティブ側で呼ばれるコールバック関数のアクセス先は
ConcurrentQueue
とConcurrentStack
なのでスレッドセーフ
4. やってないけどやれそうなこと
下記のこともできそうでした。
元レポジトリのネイティブコードにはMIDIデバイス名の取得処理が載っているので、そのラッパーコードを書けば実現できます。ここまでやるとUnity標準のWebCamTexture
に近い手触りが実現できそうに思います。
ただし、今回はそこまでやる理由がなかったため、実装していません。
5. 補足: MIDI入力読み込みの競合について
本記事では素朴にMidiJackのネイティブコードをC#でラップしました。
しかしこの方法では「同じMIDI入力は1アプリからしか読めない」という問題が残ります。
これはWindowsの課題でもあり、スパッと解決する方法は無いんですが、関連の話題として「UWPのAPIを使えば複数アプリから同一のMIDIデバイスにアクセスできる(※ただしアクセスする全ソフトがUWPのAPIを使うことが条件)」というのを紹介して終わりにします。