JUCE vNext?に入りそうなMIDI 2.0サポートについて

JUCE Advent Calendar 2020 5日目が空いていたので、今週developブランチに追加されたMIDI 2.0 UMPサポートを実装するコードについて解説します。

https://github.com/juce-framework/JUCE/commit/9032f58

先月のADC 2020でJUCEブースに行って「MIDI 2.0をサポートする予定はある?」って質問してきたばかりだったので("We're working on it" みたいな感じでした)、もう出てきたか…!という感じですが、UMP仕様の正式版が決まってからもうすぐ1年になろうとしているので、順当にin a timely mannerで出てきた感じではあります。ADC 2019の頃に「そのうちリリースする」って言っていたMMA (MIDI Manufactures Association) のMIDI 2.0実装ライブラリはどうなったんだろう…

MIDI 2.0とUMPについて(復習)

MIDI 2.0 UMPについてはMIDI 2.0 UMPガイドブック (PDFのみ版)で詳しく解説したのでここで繰り返すことはありませんが、MIDI 1.0ではMIDIイベントとかMIDIメッセージと言われていたものに相当するMIDI 2.0の仕様です。「MIDI 2.0ノートオン」「MIDI 2.0ピッチベンド」「MIDI 2.0ノート別コントローラー」「MIDI 2.0システムエクスクルーシブ8」といったものが規定されています(そんなにMIDI 2.0ってprefixは付けないかもしれない)。

SMF(MIDIファイル、*.midファイル)の仕様や、特定のトランスポート(USBとかBLEとか)で転送する方式の仕様は、MIDI 2.0ではまだ(?)規定されていないので、今回のコードにも関係ありません。またMIDI-CIで新たに可能になった双方向のやり取りに関する仕様もUMPの範囲ではないので、今回のコードに関係ありません。

MIDI 2.0に対応「する」ハードウェアデバイスとしては、Roland A-88 MkIIが知られています。あと最近どこかがタッチパネル系のコントローラー(大昔にあったJazzMutant Lemurみたいなやつ)をMIDI 2.0対応させるって言ってindiegogoかなんかに出していた気がします(うろ覚え…)。MIDI 2.0 USBトランスポートは今年の6月に正式に規定されているはずなので、もう対応しているかもしれません。

プラットフォームAPIとの関係

これを書いている時点で、主要プラットフォームのAPIMIDI 2.0 UMPサポートが含まれているのはAppleの最新版OS上のCoreMIDIのみです。iOS 14.0、macOS 11.0, Mac Catalyst 14.0以降ということになります。Windows APIにもALSAにも対応するAPIはありません。

UMP仕様自体はパケットデータのフォーマットを規定しているといえるもので、この意味ではプラットフォームAPIとは無関係にAPIを規定できるものです。その意味ではプラットフォームAPIに対応するものが無いことは、ほぼ障害になりません。

一方で、プラットフォームAPIが無いうちは、そのプラットフォーム上に「有効なMIDI 2.0デバイス」を登録したりそれを発見したりする術がないので、特定のソフトウェア上のプロトコルに基づいてのみ、MIDI 2.0の機能が使えるということになります。たとえばUSB MIDI 2.0クラスは既に存在している(はずな)ので、USB接続から独自にMIDI 2.0デバイスを発見して読み書きすることは可能でしょう。ただしWinMMやALSAMIDIサポートは未だに1.0のみなので、それらのAPIを使用してデバイスとのメッセージをやり取りしようと思ったら、MIDI 1.0の範囲(精度)に丸めるしかありません。

CoreMIDIが追加されたAppleの各種OSについては、CoreMIDI APIMIDIエンドポイントに接続して、CoreMIDI APIのUMPパケット(MIDIMessage_64など)に変換して送信する(あるいはその逆で受信する)などの実装が追加されています(どちらかというと、既存のC++実装がObjective-C実装に書き換えられているので、MIDI 2.0とは無関係にひとつの大仕事っぽいというのが実態ですが…)。

MIDI 2.0 UMP仕様では、実のところ、MIDI 2.0 UMPをMIDI 1.0の範囲で表現できるデータに変換する方式が規定されており、今回JUCEに追加されたコードでもこれが実装されています。

オーディオプラグインAPIとの関係

これを書いている時点で、MIDI 2.0を直接サポートしているオーディオプラグイン規格はありません。

ただし、VST3.7がリリースされたときに、Steinbergは「MIDI 2.0で規定されている機能をVST3の機能でカバーすることは可能であるという」アナウンスを出していて、MMAはこれをもって「VST3はMIDI 2.0をサポートしている」とアナウンスしています。

(わたしはこれは不正確で不適切なメッセージだと思うので、常に正確なところを表現しようと思っています。実際VST3ではMIDIサポートが落とされた結果、VST2の頃に実現できていたマルチチャネル対応が不可能になっていたりします(具体例)。)

MIDI 2.0の機能をカバーしている」というのは、具体的にはノートエクスプレッションのサポート(MIDI 2.0ではノートの「属性」のサポート)、高精度のピッチベンドなどで、これは実際正しく、MIDI 2.0 UMPでMIDI 2.0デバイスから受け取ったメッセージをVST3のコントローラーメッセージに変換してVST3のフィルター(プラグイン)チェインに処理させることは、部分的には可能かもしれません(DAWMIDIレコーディング機能などと連動させるのは無理かも)。

Roland A-88 MkIIみたいなデバイスがVST3対応のプラグインを作ることも、やろうと思えばOSサポートとは無関係に独自にできるわけです(A-88 MkIIのためにどんなソフトウェアがいま提供されているかは知りません)。

AppleのCoreMIDIには、MIDI 2.0 UMPをあらわすAPIやそれをデバイスとやり取りするAPIは定義されていますが、AudioUnitにUMPを扱うAPIが追加された様子はありません。AudioUnitはVST3とは異なりMIDIを明示的にサポートするAPIが含まれているので、MIDI 2.0 UMPをサポートする段階になったらそのためのAPIが追加されるはず…というのがわたしの理解です(ただAUについてはそんなに頻繁・入念にチェックしていないので、見落としているかもしれません)。

とはいえ、UMPは所詮バイト列で表現できるデータフォーマットにすぎないので、CoreMIDIのMIDI-CI実装がMIDIバイスとホストアプリケーションの間のプロトコル情報のやり取りを適切に処理できていれば、アプリケーションはUMPパケットをバイト列で受け取るだけでMIDI 2.0のメッセージを受信できているかもしれません。

AudioUnitにMIDI 2.0固有のAPIが何も追加されていない現在、JUCEで独自にMIDI 2.0サポートがAudioProcessorなどに追加されていることもありません。VST3にプラグイン開発者が独自に対応するようにJUCEが独自に追加することはあるかもしれませんが、おそらくAudioUnitに正式にMIDI 2.0サポートが追加されるのを待つのではないかと思います。その際にはVST3用のAudioPluginFormatなどにもUMPサポートの実装が追加されることになるでしょう(現在は何も追加されていないはずです)。

UMPサポートAPIの詳細

そういうわけで、まだMIDIバイスにもオーディオプラグインにも十分な(もしかしたら「必要な」)サポートがなさそうなMIDI 2.0 UMPですが、バイトストリームからのUMPの解析とUMP構造の取得、あるいはUMP構造とUMPストリームの生成と仮想的な「送受信」は、今回追加されたAPIでも可能です。今回追加されたクラスはたとえば次のようなものです(全てを列挙はしません)。

  • Packet<numWords> - 1つのUMPをあらわす
  • Packets - add(), clear(), uint32_t* data() などUMPシーケンスをあらわす
  • Factory - makeJRClock(), makeNoteOnV1(), makeAssignableControllerV2() などPacket<T>を構築できる関数をもつ
  • Iterator - uint32_t*から生成され、UMP1つ1つを取得するためのiteratorとなる(個別の要素としてはViewoperator*()operator->()で返される)
  • View - uint32_t*から生成され、1つのUMPをあらわす(こちらにもbegin()end()が定義されているが、これは1パケット中の4バイト単位のデータをiterateするためのもの)
  • Midi1ToBytestreamTranslator - uint32_tのUMPシーケンスをMIDI 1.0のMidiMessagesに変換する
  • Midi1ToMidi2DefaultTranslator - MIDI 1.0入力をMIDI 2.0のメッセージに(精度などを)変換する
  • Receiver - MIDI 2.0のメッセージを受信する役割を担うインターフェース。開発者はpacketReceived(const View& packet, double time)を実装する必要がある。
  • U32ToBytestreamHandler - MidiInputMidiInputCallbackから生成して、pushMidiData (const uint32_t* begin, const uint32_t* end, double time)の呼び出しでUMPシーケンスを渡されると、それをMidiInputCallback.handleIncomingMidiMessage()に流すようになっている
  • U32ToUMPHandler - PacketProtocolReceiver&から生成して、pushMidiData (const uint32_t* begin, const uint32_t* end, double time)の呼び出してUMPシーケンスを渡されると、それをReceiver.packetReceived()に流すようになっている

これで何が出来るの?

これらのAPIだけでどんなことができるかは、具体的な実装なしで説明するくらいしか現時点ではできませんが(今その辺の作業をやっているわけではないので…)、たとえば現在Fluidsynthには部分的にMIDI 2.0の精度でピッチベンドなどを処理するAPIが追加されているので、UMPストリームを何らかの方法で生成して*1、UMPの内容を(サポートされている機能の範囲で)Fluidsynthに渡すようなReceiverを実装して、それをU32ToUMPHandlerで処理するようなアプリケーションを書けば、FluidsynthにMIDI 2.0のデータを演奏させることができるでしょう。あるいはSteinbergが「サポートしている」といっているVST3プラグインに処理させることもできそうです。

とはいえ、まだ正式に発表された機能でもないですし、APIが変更される可能性もあるので、そこは(試すにしても)気をつけて使ってみるのが良いでしょう。

*1:たとえばMIDI 2.0に対応したMMLコンパイラを作るとか! (SMF相当のフォーマットが無いけど)