2/16(PST)にAndroid 15 DP1が公開され、"Virtual MIDI 2.0 Devices" のサポートが追加されました。これについてMIDI 2.0の最新の状況(自分が知りうる公知のもの)を踏まえて解説します。この機能を要望したのは自分なので*1、たぶん自分が説明するのが一番正しいはず…
承前: Android 13のMIDI 2.0サポートからどうなった?
AndroidでMIDI 2.0の機能が初めて追加されたのは2年前のAndroid 13です。正式版がリリースされたとき、このブログでその詳細を解説しました。
内容を箇条書きでまとめるとこんな感じです:
- USB-MIDI 2.0プロトコルの規格が固まったので、Android 13でプラットフォームのレベルで実装された
- MIDI 2.0のサポートは既存のMIDIポートの接続でもできる(UMPストリームをMIDI 1.0メッセージの代わりに流せばいいだけ)
- OSが認識できる((
MidiManager.getDevicesForTransport()
で取得できる))ソフトウェアMIDI 2.0デバイスはまだ作成できない(のでissuetrackerで要望を出した)- 後方互換性の問題で単なる機能追加はできない
- GoogleのAndroidオーディオチームがこの辺の新機能を仕切り直してMIDI-CIサポートも追加してくると思う
ここから2年経って現在に至るわけですが、ソフトウェアMIDI 2.0デバイスのサポートが実際に追加された以外の部分は、半分くらいは「ハズレ」になった感じです。
え、そんなに外れるような予想を出したの??と思われそうですが、もちろん理由があります。2023年6月に、MIDI 2.0仕様は大きく変容したのです。これについては6月に詳しくまとめました。
ここで重要なのは「Protocol Negotiationがなくなった」という部分です。↑の話はProtocol Negotiationを使うことで論理的に1つのMIDIポートをMIDI 1.0 bytestreamとMIDI 2.0 UMPの両方で切り替えて使い回せる前提だったのが*2、そうではなくなってしまったわけです。代わりに導入されたUMP Stream ConfigurationメッセージはUMPとして規定されているので、MIDI 1.0でも2.0でもUniversal SysExであればよかったProtocol Negotiationとは異なり、そのポートが最初からUMPトランスポートであることが前提となりました。
2022年の時点では、MIDI 2.0を包括的にサポートしているプラットフォームAPIはAppleのCoreMIDIとAndroidのUSB限定MIDI 2.0ポートのサポートしかなく、各プラットフォームでどうMIDI 2.0接続をデザインするかは不透明な状態でした。おそらく各社がMMA(MIDI協会)で相談・総合接続の検証を行っていく過程で、「ポートはMIDI 1.0トランスポートかUMPポートか『あらかじめ』規定しておく」という合意が形成されたように(外からは)見えます。
ちなみにもうひとつ「外した」と思われる「MIDI-CIサポートも追加してくると思う」ですが、ktmidiでMIDI-CI実装をゼロから作ったので、将来的には代わりにコレを使ってもらえればと思います。まだAPIが未整備…
MidiUmpDeviceService
: 唯一のAPI変更点
上記のまとめでは「新機能を仕切り直してMIDI-CIサポートも追加してくると思う」と書いたわけですが、MIDI-CIの面倒なProtocol Negotiationがそもそもなくなったので、プラットフォームのAPIでMIDI-CIは不要になりました。結果的に、Android 15で追加されたのは、新しいMidiUmpDeviceService
という、MidiDeviceService
の兄弟分のようなクラス1つのみです。使い方は基本的にMidiDeviceService
とほぼ同じです。MidiUmpDeviceService
から派生した実装クラスを作成し、AndroidManifest.xml
に<service>
をandroid:permission="android.permission.BIND_MIDI_DEVICE_SERVICE"
と<meta-data>
付きで登録し、対応するump_device_info.xml
のようなファイル(リソース名前は任意)をres/xml
に置くだけです。
特に、MidiDeviceService
の中で使われるMidiReceiver
やMidiSender
、MidiInputPort
やMidiOutputPort
などのクラスは全て共通です。なぜなら、(わたしが「外れた」予想の説明で書いた通り)MIDI 1.0のトランスポートの上にUMPを流しても、単なるByteArray
を送受信することにしかならないので、APIを変える必要が無いからです。
ただし、AndroidManifest.xml
で<service>
のMIDIデバイスのメタデータXMLの内容は微妙に異なっているので気をつける必要があります。具体的には、(1) MidiDeviceService
ではmeta-data
属性で指定していましたが、MidiUmpDeviceService
ではproperty
属性で指定し(この属性の存在を知らなかったのですが、API Level 31で追加されていました)、また(2) MidiDeviceService
のときは<input-port>
と<output-port>
をそれぞれ記述していたのに対し、MIDI 2.0のUMPポートは双方向なので、<port>
という要素を使うことになります。Android 15では、ここからinputとoutputのペアが自動的に作成されることになります。
特にproperty
属性を使う点に注意してください。2/21現時点ではMidiUmpDeviceService
のAPIリファレンスにある記述が正しく、android.media.midi
パッケージのOverviewにある記述は間違っています(自分はここでハマりました)。
あとonGetInputPortReceivers()
のreturnが地味にArray<MidiReceiver>
ではなくList<MidiReceiver>
になっています。わたしはこれがArrayだったのは良くないと思っているので、地味に良い変更だと思います。Arrayだとポート数がService生成時に固定になってしまうので、将来的に動的ポート生成をサポートしようとしても整合しなくなる可能性があります。(とはいえまだdeveloper previewなので、単なる実装での見落としとして「元に戻される」かもしれません。)
仮想MIDIデバイスの可能性
MidiUmpDeviceService
を使うと、これまでUSBに限られていたMIDI 2.0デバイスが、理論上はAndroid APIでリーチできる任意のデバイスまで広がったことになります。まあこれは半分は言い方の問題で、これまででも任意のAndroid Serviceを提供することで、android.hardware.usb
のAPIを使ってオレオレUSB MIDI 2.0サポートを実装することもできたでしょうが、公式にAPIを与えられたService
ならおよそ誰が作ったものでも相互接続できるようになるでしょう。
「仮想MIDIデバイスを作れる」と言われても、ソフトウェアMIDIシンセサイザーを作る人以外は関係ないのでは…と思われるかもしれませんが、実際にはもう少し適用領域があります。
- 筆者の事例のひとつとして、ktmidi-ci-toolでは仮想MIDIポートを公開しており、これ自体は他の「MIDI-CIデバイス」に接続して操作するためのアプリケーションですが、MIDI-CIのリファレンス実装として、他のMIDI-CIクライアントアプリケーションがこのアプリケーションに「接続」して使うことも可能になっています。(ktmidi-ci-toolはKotlin Multiplatformアプリであり、LinuxやMacOSでも同様に動作します。)
- MIDIキーボードアプリでたまに見られるのは、仮想MIDI入力ポートも作成しておいて、そこに届いたメッセージをそのまま自らがクライアントとして接続しているMIDI出力に送信するというものです(たとえばvmpk, kmmk)
- UMP用の仮想ポートを作成して、MIDI 1.0デバイスにUMPから変換して送信するような機能も実装可能でしょう。MidiDeviceServiceもMidiUmpDeviceServiceも動的にポートを追加することは出来ませんが、MIDI1のデバイスであればUMPのグループを別々に扱うことで1つのUMPポートから16デバイスまではアプリケーション側で対応可能でしょう(デバイス名等をクライアント側に認識させるにはUMP StreamメッセージのEndpoint Discoveryを送ってEndpoint Name Notificationを受け取る必要がありそうです)
MIDI 2.0 UMPのクライアントアプリケーションの作り方は、Android 13の頃と変わりません。MidiManager.getDevicesForTransport()
でUMP対応デバイスを取得し、あとはMIDI 1.0の頃と同じAPIで、ただしMIDIメッセージのByteArrayにはMIDI 1.0バイトストリームではなくUMPを流す、というだけです。
MIDI 2.0アプリケーションを開発するのにMIDI-CIの機能は必須ではありませんが、MIDI-CIが必要であれば、C++アプリならJUCE 7.0.9から存在しているjuce_midi_ci
モジュール(解説)、Kotlinなら上述のとおり筆者のktmidiでそのための機能を提供しています…と言いたいところですが、ktmidi-ci-toolの開発の過程でAPIはひっちゃかめっちゃかなので、API整備を待ったほうが良いでしょう。