任意のオーディオプラグインをMIDI 2.0デバイスとして使用する(したい)
巷にある任意のVST3/AU/LV2/CLAPプラグインをMIDI 2.0音源化するためのアプリケーションを作っていて、次の週末のM3 2025秋ででも展示するので、それについて解説しようと思う。GitHubリポジトリで公開してある。
- uapmd-service : a virtual MIDI 2.0 device that translates UMP to and from a synth plugin (VST3/AU/LV2/CLAP)
- midicci-keyboard: a MIDI 2.0 keyboard
MIDI 2.0アプリケーション開発用としては便利だけど、打ち込みで使えるといえるほどの実用性は正直まだ無い。
screenshots
(uapmd wikiに上げてある画像のコピーだけど、説明のために持ってきている。)
仮想MIDI 2.0キーボードでMIDI 2.0仮想デバイスコンソールで登録したプラグインを演奏できる。

macOSだとMIDI 1.0クライアントからMIDI 2.0デバイスにアクセスできるので、MIDI 1.0用のWeb Audio APIでもオーディオプラグインを演奏できる。

LV2, CLAP, AU, VST3の各フォーマットに対応している(JUCEを使わない自前実装なのでいろいろ動かない)

はじめに
MIDI 2.0は全然世の中に普及していないが、その理由として「MIDI楽器は現在のDTMのメインストリームではない」と「MIDI 2.0デバイスが世の中に全然存在しない」がある、ということにはおそらく反論が無いだろう。前者は簡単に覆せるものではないし単純に覆すものではない。しかし後者は実は簡単に解決できる問題だ。MIDI 2.0デバイスがなければMIDI 2.0アプリケーションを作れないし、MIDI 2.0アプリケーションが無いのにMIDI 2.0デバイスを作るのは投機的にすぎるが、MIDI 2.0物理デバイスが存在しないのであれば、MIDI 2.0仮想デバイスを作れば良いのである。
さいわい、ALSAでもCoreMIDIでも仮想デバイスを作成するのは簡単だ。何ならAndroidでもできる(アプリケーションのインストールというかたちになるので、任意のアプリケーションが動的に作成できるわけではないが)。Windows 11 Insider Previewを手元で動かしている先進的なWindowsユーザーなら、Windows MIDI Servicesで仮想デバイスを作成できるはずだ。それ以外のWindowsユーザーについてはWindows MIDI Servicesがリリースされるまで待たせておいて、われわれは先に進むとしよう(WindowsユーザーはWMSのgithubリポジトリでせっつくと良いだろう。自分は期待していない)。
uapmd-serviceとmidicci
今回これを書くことにしたのは、いま実験的に開発しているMIDI 2.0関連ツールがそれなりに実験用に使えるようになってきたためだ。MIDI 2.0開発に必要なデバイスもアプリも現状ほとんど無いので、実験的なツールであってもそれなりに使い道があるだろうと思って、uapmd本体が粗雑な出来のうちに公開しておくことにした(リポジトリ自体は開発当初からずっと公開しているので、「公開」とはこの記事のことを指している)。
uapmd
atsushieno/uapmdというGitHubリポジトリ(現時点で)は、以下の3つの主要なライブラリに基づいていて、現状どれがメインとも言い難いものになっている:
- remidy: VST3/AUv2/LV2/CLAPの汎用オーディオプラグインホスティングライブラリ
- remidy-tooling: remidyのAPIにプラグインスキャナーなどの機能を追加したライブラリ
- uapmd: オーディオプラグインをMIDI 2.0デバイス化するためのライブラリ
これらのライブラリはMITライセンスで利用できるのが大きな特徴で、特にVST3SDKを利用していない点が大きい(DPFに含まれるtravestyを使用している)。もちろんJUCEも使っていない。
追記: これ書いたのしばらく前だったので忘れていたのだけど、VST3SDKは10/23にMITライセンスに変更された(!)ので、「VST3SDKを使っていない」の部分は早々に変更する予定である。
このリポジトリにはライブラリ以外にも以下のツールが含まれている:
- remidy-plugin-host: オーディオプラグインホスト(remidyのdogfooding)
- uapmd-service: オーディオプラグインをMIDI 2.0デバイスとして使うツール(uapmdのdogfoodingプラスアルファ)
uapmd-serviceには最近ImGuiを使ったUIも取り付けたので、まあまあ直感的に試せるようになっていると思う。
midicci
atsushieno/midicciはMIDI-CIの実装、というかatsushieno/ktmidiに含まれるktmidi-ciとktmidi-ci-toolのC++移植版だ。自分で移植しているというよりClaude Codeに移植させている。GUIはCompose MultiplatformからQtに移植している。MIDI-CI機能の実装が(不足していて)必要になったら、まずktmidi-ciで実装してからClaude Codeに移植させる、というやり方でこのライブラリにも反映している。Qt以外ではElectron + WebUIやFlutter + FFIを試したが、FFI前提のコードはDevinにもClaude Codeにも荷が重すぎた。Codexならできるかもしれないがもう試していない。
uapmd-serviceにMIDI-CI機能を統合するにあたって、ktmidi-ciのKotlin-Native版とni-midi2を一応検討したが、K/N版をC/C++から呼び出すにはC APIでktmidi-ciを操作しなければならないし、ni-midi2はもうずっとアップデートされていないので、やる気はないだろうと踏んで自前実装にした。juce_midi_ciは取り込むと全体がAGPLv3になり、uapmdの目的と合致しないので除外した。JUCEもそんなにMIDI-CIはやる気ないだろうし(実際Process Inquiryなど不足している機能もあるのに最初のリリースからずっと放置されているし、MIDI 2.0サポートが拡充される次バージョンでも放置されている)。
MIDI-CIは機能が複雑だし先行実装もほとんど無いので、AIコーディングにはあまり向いていない側面もあるが、自分でC++実装をゼロから作るのは面倒なので、致命的に困らないうちはClaudeに書かせることにしている(まあ何度も「もう無理かな…」となってはいるけど)。
このリポジトリには以下のモジュールが含まれている:
-
midicci:ktmidi-ciの移植 -
midicci-tooling:ktmidi-ci-toolの非GUI部分の実装 -
midicci-gui:ktmidi-ci-toolのGUI部分の移植 -
midicci-keyboard: midicciを利用して作られたMIDI 2.0対応キーボード
このリポジトリのモジュールはどれも主にClaude Codeに書かせているので(自分で直したほうが早いところは自分でやっている)、クオリティもダメなところはとことんダメダメではある。
前提: 仮想MIDI2.0デバイスを作る
仮想MIDIデバイスの仕組みについては筆者が2025年6月に刊行した「MIDI 2.0アーキテクチャ ガイドブック」で詳しく解説してある。そもそも本書の第5章にはこの記事で解説する内容が手短にではあるが書かれている。
- BOOTH https://booth.pm/ja/items/6978593
- 技術書典オンライン https://techbookfest.org/product/q0aTZk8eNmmWxEVpB8ZZg4
MIDI 2.0デバイスに固有の問題として、プラットフォームによってはMIDIトランスポートプロトコルの違いが表出しないということが挙げられる。CoreMIDIの場合、UMPトランスポートのデバイスにMIDI 1.0トランスポートのクライアントから接続できるし、逆もまた真なりである。一方でALSAはそのようには作られていない。ALSAでは、MIDI 1.0トランスポートのデバイスを列挙するコードと、UMPトランスポートのデバイスを列挙するコードは別物だ。
仮想MIDI2.0出力デバイスを作る
MIDI出力デバイスをソフトウェアで作ったものは、いわゆる仮想MIDIシンセサイザーのような位置付けになる。古典的なWindowsユーザーならMicrosoft GM Synthesizerを使っていたことがあるかもしれない。Linuxにはtmidity(++)やFluidsynthがある。仮想MIDIデバイスはプラットフォームMIDI APIを使えば任意のMIDIクライアントから接続できるので、MIDIデバイスと同様に使うことができる。
仮想MIDI 2.0入力デバイスを作る
MIDIキーボードはソフトウェアでも作れる。仮想MIDIデバイスは、プラットフォーム上のMIDIシステムに自分のアプリケーションを登録して、そのアプリケーションが「MIDI入力を受け取った」とMIDIシステムに通知することで成り立っている。入力の元はマウスでもタッチ入力でも何でもいい。そのアプリケーションがでっちあげてMIDI入力を通知するだけでも成り立つ。筆者はMMLを演奏するMIDIプレイヤーの出力を送るなどもしている。
実際にこの機構を利用したアプリケーションとしてVMPKなどが挙げられる。ただしこれはMIDI 1.0を前提にしているので、ALSAの場合はUMPデバイスとして列挙されない。
オーディオプラグインをMIDI 2.0デバイス化する
基本的な仕組み
オーディオプラグインをMIDIデバイス化するのはある意味簡単だ。プラグインホストを作って、プラグインのインスタンスを生成して、クライアントから(より正確に言えばMIDIシステムから)受け取ったMIDIメッセージに対応するプラグイン命令を呼び出せばよい。
「簡単」が「ある意味」でしかないのは、プラグインホストの開発は死ぬほどめんどくさいからだ。正確に言えば、いろいろちゃんと機能するプラグインホストを作るのは面倒だ。プラグインホスティングの機能はjuce_audio_plugin_clientなどに他人任せにしてしまったほうが楽だろう。
筆者は独自のプラグインホストを開発していて、今回事例として紹介するMIDI 2.0デバイス化プラグインホストもそれに基づいているのだけど、控えめに言って茨の道なのでお勧めはしない。
MIDIでできることとできないこと: 入門者向け
MIDIはオーディオプラグインの前身的な存在だったこともあって、さまざまな機能がオーディオプラグインに引き継がれている。そのため、MIDIメッセージをオーディオプラグインの命令に変換するのは、たいがい難しくない。
逆に、オーディオプラグインのあの機能を使いたい、この機能を使いたい、といったニーズには、限定的にしか応えられない。わかりやすい例を挙げるなら、オーディオ入力を処理する仕組みは無い。MIDI入力をワンショットで渡したら、その先はそのMIDIデバイスのオーディオ出力まで含めて、何も調整できない。
プラグインの「状態」「設定」をGUI上で調整する必要があるものについては、MIDIだけでゼロから設定を代替するのは困難または不可能であることが多いが、プリセットを選択してパラメーターを微調整する程度の使い方であれば、プリセットにプログラムチェンジを、パラメーター設定にNRPNを、それぞれ関連付けてやれば良い。
プラグインホストはMIDI入力 - のようなもの - を受け付ける
一般的には、オーディオプラグインホストは、次のようなサイクルで動作している:
- プラグインホストがオーディオグラフと呼ばれる接続構造を構築する
- プラグインホストがプラットフォームのオーディオI/Oをノードとしてオーディオグラフに差し込む
- ユーザーがプラグインを選択する
- プラグインホストがそのプラグインのインスタンスを生成して設定を調整し、オーディオグラフに差し込む
- プラグインホストがオーディオ処理を開始する(プラットフォームのオーディオAPIは大抵コールバック方式でオーディオ処理を呼び出す)
- プラグインホストはMIDI入力なども蓄積しておいて、オーディオ処理の呼び出しに応じてプラグインにMIDIイベントやそれを変換したプラグインイベントなどをオーディオ入力と合わせて渡す
- プラグインはホストから渡されたオーディオとMIDI等の入力をDSPにかけて、オーディオ出力を生成してホストに返す
- プラグインホストはプラグインの出力を、オーディオグラフの流れに沿って処理し、最終的にオーディオデバイスに出力する
ここまでの流れを把握できたら、仮想MIDIデバイスをどう実現するかは簡単な問題だとわかるだろう。その仮想MIDIデバイスが受け取ったMIDIメッセージを、プラグインのMIDI入力に渡してやる(VST3の場合はVST3イベントに変換したうえで渡してやる)だけでよい。
技術詳細編
MIDIでできないこと: 一般論
オーディオプラグインフォーマットのうち、VST3以外のほとんどのフォーマットは(少なくともAudioUnit / LV2 / CLAPは)、MIDIメッセージの内容を情報を損なわずに渡すことができる。VST3は例外的にMIDIを「排除した」仕組みになっていて、MIDI入力はプラグインの命令にマッピングしないとプラグインに渡せない。VST3以外のフォーマットでも、ホスト側でMIDI命令をプラグイン命令にマッピングして、MIDIメッセージは渡さないような挙動にすることもあり得る。
MIDIはオーディオプラグインの前身的な存在だったこともあって、さまざまな機能がオーディオプラグインに引き継がれている。そのため、MIDIメッセージをオーディオプラグインの命令に変換するのは、たいがい難しくない。ノート命令、CCやNRPNを使用したパラメーターのコントローラー、ピッチベンドやチャンネル/キープレッシャー(プラグイン側にこれに対応するコントローラーがある場合に限られるが、対応する機能がMIDIデバイス側に無ければ機能しないのはハードウェアでも論理的には同様)は普通に対応できる。
ただし、オーディオプラグインにはMIDIチャンネルに相当する一般的な概念が無い。VST3ではパラメーターに「MIDIでいうところのnチャンネルに相当する」といった定義のみが可能で(unit)、マルチチャンネル対応音源(一般的なGM音源などはそう)をMIDI CCやピッチベンド等に対応させようと思ったら、同じパラメーターをチャンネルごとに定義しないといけない。VST3プラグインにはパラメーターが2100件以上存在するものが少なからず存在するが、これは130 * 16件くらいこのノリでパラメーターが追加されているためである(128 + ピッチベンド + チャンネルプレッシャー)。
逆に、オーディオプラグインのあの機能を使いたい、この機能を使いたい、といったニーズには、限定的にしか応えられない。わかりやすい例を挙げるなら、オーディオ入力を処理する仕組みは無い。MIDI入力をワンショットで渡したら、その先はそのMIDIデバイスのオーディオ出力まで含めて、何も調整できない。
他にも、プラグインのGUIを表示する機能はMIDIには無い。そういう仕様ではないからだ。
ただし、これもSysEx(システムエクスクルーシブ)という抜け道を使って独自機能として対応することは理論上は可能だ。また、MIDI-CIにはプロファイルという概念があって、独自拡張機能を共有する仕組みがある。"Plugin GUI Profile"のようなMIDI-CIプロファイルをでっち上げて、所定のSysExをやり取りできるようにすれば(SysExの内容はMIDI接続の両端が合意していれば何でもいい)、プラグインAPIでやり取りされるような大抵の情報はMIDI化できる。筆者はAAPというAndroid用の独自プラグインフォーマットを開発しているが、そこでやり取りされる拡張機能の呼び出しは全てMIDI 2.0 SysEx8(8ビットSysEx)で行っている(こうしておくとABIレベルでの不一致によるライブラリのロードエラーのようなことは起こらなくなる)。
ただし、これをホスト側が野放図に開放すると、よくわからんMIDIクライアントから突然GUIを表示するSysExが仮想MIDIデバイスに送られてきて、突然画面上にGUIがダイアログ表示されてくる…といった問題が生じるので、ホスト側で何らかの明示的な許可を渡す仕組みが別途必要だろう。
パラメーターリスト
MIDI 1.0では考えられなかったような機能が、MIDI 2.0を使えば可能になることがある。MIDI 2.0クライアントでは、MIDI 2.0デバイスからパラメーターのメタデータのリストを取得できる。パラメーター名などがわかれば、コントローラー上にはその名前を表示しておけば、ユーザーがパラメーター番号とその用途を覚えたり調べたりする必要がない。
オーディオプラグインでは当たり前の機能だが、MIDI 1.0では「リクエストを送る」機能が無かったため、この種の機能はあくまでデバイス名から推測して提示することしかできなかった(そのような仕組みはかつてProTools発祥のMIDNAMというXMLフォーマットのファイルでカバーされていた。日本でもMIDIシーケンサーDominoで独自のフォーマットがサポートされていた)。
MIDI 2.0の一部であるMIDI-CI仕様は、さまざまな「標準プロパティ」を規定しているが、そのひとつにコントローラーのリストを返すAllCtrlListとChCtrlListというプロパティがある。これをデバイスに問い合わせて回答が返ってきたら、DAWでプラグインのインスタンスを生成した時に取得できるパラメーターのリストに相当する機能が、MIDI 2.0標準だけで実現できるというわけだ。この機能に対応するMIDI 2.0ハードウェアは筆者の知る限りまだ存在しない(KORG KeyStage, Multipoly, Wavestate, Modwaveがこれに近い機能を実装しているが、あくまで独自プロパティなのである)。
AllCtrlListでは、もちろんノート別コントローラー(PNRC、PNAC)も取得できる。これらが各プラグインフォーマットの機能にきちんとフィットするかどうかはフォーマットごとの問題である(たとえばAudioUnitやCLAPでは単純ではない)。
Stateの保存と復元
何百件もあるようなパラメーターの初期値の設定や、そもそも32bitや64bitのパラメーターでは表現できないようなプラグインの設定値は、バイナリBlobを使ってホストとプラグインの間で「保存」「復元」する。このような仕組みは当然MIDI 1.0には無い…と思われるかもしれないが、一般的なフォーマットとしてはともかく、実装としてはすでにYAMAHA DX7のCartなどで用いられていた。当時このフォーマットで保存されたプリセットのデータは、今でもDexedなどのさまざまなDX7エミュレーターでロードできる。つまり汎用的なフォーマットになっているのである。
MIDI 2.0ではGet and Set Device Stateという標準MIDI-CIプロパティが規定されている。これを使うと、このDX7のCartのようなものを標準で使えることになる。
問題はStateを自在に調整するにはたいがいGUIが必要になることで、この点では、プリセットを事前に作成しておいて、仮想MIDIデバイス上ではあくまでプリセット選択をプログラムチェンジ上で行う、といったやり方が確立している必要がある。
ただし、この仕組みを使えば直ちに相互運用性のあるStateが利用できる、ということにはならない。State APIには複数の用途があり、楽曲データの保存に使われるのはその一部にすぎない。相互運用性の無いかたちでStateを保存することも利用することもあって、それらを楽曲保存用に使ってしまうと、いくらMIDI標準としてデータを保存していても意味がない。これはそこそこ複雑な話題なので、以前「理想のオーディオプラグインフォーマットを求めて」に書いた記事を参考にされたい。
プログラムチェンジに対応する音色名のリスト
MIDI-CI標準プロパティにはProgramListという「プログラムリスト」(ソースコードの意味ではない)を取得する機能がある。プログラムチェンジで指定できる音色名のリストと思えばよい(MIDI 2.0のC0hはバンク・セレクトも含む)。
筆者の観点でいえば、これが一番関連付けられるべきはプリセット選択機能だが、実のところこれはそれほど単純ではない。まず、GUI上でプリセット機能を提供しているプラグインの大半が、プラグインフォーマットが提供するプリセットの機能に対応していない。プログラムチェンジに相当する機能はJUCEでも提供されているが、JUCEシンセでもこの機能がほとんど実装(サポート)されていない。筆者は自作プラグインフォーマットに移植するためにJUCEシンセを何十件も見てきたが、プリセットを提供しているのはDexedやAudiblePlanetsやBYODなどごくわずかな例のみである。VitalもSurge-XTもサポートしていない。Serum2などの商用製品も多くが対応していない(自分にはプラグイン製品を買い込む趣味が無いので、そんなに試せる手持ちが多くない)。
これにはいくつか理由があると思うが、実用性の問題と、仕様の複雑性の問題があると思う。
プログラムチェンジで指定するindexには後方互換性・前方互換性が求められるが、安定的なindexを提供できるのは固定プリセットのみであって、ユーザープリセットにこの機能は割り当てにくい。LV2の場合はさらに、プリセットに番号が割り振られないので、URIでのみ識別することになるが、これは連番のindexを前提とするプログラムチェンジとは相性が悪い。もちろんインスタンス生成時に動的に割り振られるURIDで代用することもできない。
また、MIDI 1.0音源が現役だった時代もそうだが、音色をパラメーターだけで気軽にリアルタイム処理できるのはあくまで送信する側であって、受信した側はこれをリアルタイムに反映することはできない。チャンネルが16しか無かった頃は演奏中にプログラムチェンジを送るのも一般的だったが、オーディオプラグインの時代はそうではない。とはいえ、非リアルタイムでもプリセットの選択が反映できるほうが望ましいことは確かだ。
そういうわけで、この機能は、実装は一部フォーマットで一応してみたものの、サポートしているプラグインがほとんど無く、現状まだ宙ぶらりんの状態だ。
このプロジェクトの意義と射程
MIDI 1.0でできる範囲のことをMIDI 2.0でやるだけであれば、その意義はほぼ「UMP接続をテストする」以上の意味合いはない。MIDI 2.0アプリケーションとして使うのであれば、MIDI 1.0でできる範囲を超えた部分にメリットがある。
MIDI 2.0キーボードやMIDI 2.0シンセでは、UMPのサポートによって、高精度のパラメーターとノート別コントローラー、ノートのアーティキュレーションやノート別周波数の設定が可能になる。ただし、MIDI 2.0デバイス側が常にこれらをサポートする必要があるわけでもないので、常に利用可能なわけではない。いずれにしても、これらの機能はUMPさえサポートされていれば使えるので簡単だ。
一方で、コントローラーリストやプログラムリストの取得には、MIDI-CIのサポートが入出力の両方で必要になるし、これには双方向接続が必要になるので、あまり簡単ではないし、MIDI-CIの実装は世にほとんど存在しない。実装の難易度で言えばMIDNAM方式のほうがはるかに低い(オーディオプラグインをMIDI楽器にする場合はまずMIDNAMを生成するところから必要になるが)。
uapmd-serviceとmidicci-keyboardは、MIDI-CIをサポートしているという点でひとつの特異点になっている(この部分に手を出しているのは昨今のKORGのMIDI 2.0対応製品だが、前述した通りAllCtrlListはまだ拾えない)。MIDIでは機能的に足りないのでオーディオプラグインを使いたい、というニーズのうち、オーディオトラックの作成という部分を取り除くと(これもMIDI 2.0 Container Formatが公開されると変わってくる可能性がある)、MIDI 2.0の仕様だけで大部分がカバーできそうだ、ということを、これらのプログラムでproof of concept的に示している。
ただし、現在のuapmd-serviceのオーディオエンジンは全く高度なものではなく、複数トラックをリアルタイムに結合して再生できるようなものではないし、オーディオグラフの基盤がきっちり作り込まれているわけでもない。現在のコンピューティング技術では、1つのオーディオエンジンで全ての仮想MIDIデバイスのオーディオ出力を処理することが重要だ。これを実現するならtracktion_engineのようなバックエンドを使う必要がある(そうなるとJUCEに換骨奪胎ということになる)。
MIDI 2.0はクロスプラットフォーム技術に似ている。プラグインフォーマット固有の機能をじっくり使い込む必要がある人はネイティブAPIを使って開発するほうが良いだろう。しかし実際にはほとんどのDAWユーザーは一部のプラグインフォーマットの機能しか使っておらず(DAWがそのように作られているのである)、マルチフォーマット対応のプラグインSDK(ホスティングSDKとは少し異なる)はそれで困っていない。
MIDI 2.0の範囲で困らないのであれば、DAWのような包括的で小回りが利かないアプリケーションよりも、MIDI 2.0シーケンサーのようなものでも十分ということになるし、それでポータブルな楽曲データを作成できて流通させることができたら、さらにユーザー体験が改善される。
remidyはオーディオプラグイン技術の共通基盤を現状よりもう少し高度な水準に持っていきたいという思惑もあるが(今回は言及していないが、前出の同人誌にいろいろ書いてある)、uapmdは、音楽制作に必要な技術を引き下げて、プラグインホスト開発の知見が無くても何とかなる、という辺りを目指したい。
追記
M3当日はE-04bで展示しています。
Discussion