新刊のお知らせ(MIDI 2.0 UMPガイドブック)ほか

(8月の作業記録のつもりでまとめ始めたのですが思いのほかMIDI 2.0祭りになってしまったので新刊告知エントリとして生まれ変わりました…!)

MIDI 2.0 UMPガイドブック @ 技術書典9 / M3 2020秋

技術書典9にはサークルとして復活します! 何しろ当日スタッフとして動き回る会場がないからな…!

今回なんと新刊が2冊も出ます。1冊は(改めてエントリを書きますが)7月にはほぼ書き終えていたLV2開発者ガイドです。もう1冊は前回ちらっと言及しましたが、MIDI 2.0に関する本です…!

techbookfest.org

MIDI 2.0 UMPガイドブック」は、2020年2月に正式に公開されたMIDI 2.0仕様のうち、もっとも目を引く内容であるMIDI 2.0 UMP(ユニバーサルMIDIパケット)仕様を中心に、MIDI 2.0をサポートするデバイスやソフトウェアが提供する、あるいは実装する必要がある機能について解説する書籍です。

UMPが何なのかというと、一連のMIDI 2.0関連仕様の中で、MIDI 1.0のときに80hはノートオフ、90hはノートオン、B0はCC、F0〜F7はシステムエクスクルーシブ…といったメッセージを規定していた部分に相当する部分です。「MIDI 2.0で拡張・強化された機能」というと伝わるでしょうか。

本書は、MIDI 2.0というキーワードに関心のあるMIDIのユーザーと開発者のどちらもターゲットとしていますが、MIDI 2.0をサポートするソフトウェアは2020年8月の本書執筆時点で皆無に近いので、本書を実用できる読者はどちらかといえば開発者が多いでしょう。これまでは、MIDI 2.0について詳しく知ろうと思ったら、AMEIまたはMMAで公開されている英語の仕様書の原典にあたるしかありませんでした。本書がその状況を少し切り拓くことになればと思っています。

本書のデジタル版は2020年9月13日からオンラインで開催される技術書典9で、ペーパー版は同イベントでのオンライン販売および2020年10月25日のM3 2020秋にて販売します。サンプルページ等は技術書典9のサイトで書籍データが公開されるようになったら出てくる予定です。

なお、同イベントでは、新刊として並行して執筆した「LV2オーディオプラグイン開発者ガイド」も同時にリリースする予定です。(というか、この本自体はライブラリ開発のついでに書き始めて、LV2開発者ガイドの販促用オマケにするつもりだったのが、なぜか単独で完成してしまったやつだったり…)

本書がどのくらい有用かというと、筆者がこの執筆後に開発に着手した自作のオーディオプラグインフレームワークにLV2を統合する過程でMIDI 2.0をサポートするためのLV2拡張機能MIDI 2.0サポートのCライブラリを自作しているときに、ほとんどの場面で仕様書ではなく本書の内容だけで疑問を解決出来ているレベルです(MIDI 1.0の仕様に該当する部分は基本的に省略しています)。

lv2-midi2

MIDI 2.0 UMPまわりを調べていたのはただの趣味ではなく…いや、趣味の延長ではあるのですが…LV2拡張としてMIDI 2.0をサポートして、これを自作オーディオプラグインフレームワークMIDIメッセージング基盤として使いたいと思ったからでした。

MIDI 2.0サポートを追加するのに必要なフレームワークへの変更は(驚くべきことに)ほぼ皆無で、プラグインが任意のポートの内容種別にMIDI 2.0ストリームであることを指定するだけで足り(現状ここがホスト側ではenumになっているのですが、もしかしたらLV2みたいにURI(D)にしたほうがいいのかも)、ホストがポート情報を取得してMIDI 2.0ポートであればvoid*のバッファにUMPバッファを流すだけなのですが、実際にそれらを処理するLV2とJUCEのブリッジでは実装が必要になります。

というわけで、まずLV2サポートを作っています。LV2にはもちろん標準でMIDI 1.0のサポートがあるのですが、MIDI 2.0のUMPは(上記書籍でも解説しているのですが)MIDI 1.0とは根本的に構造が違うので、ざっくりとLV2拡張のかたちが似ているだけです。

github.com

あと多分MIDI入力ポートが複数並立しているとややこしくなるので(LV2では基本的にすべてのポートがconnect_port()で繋がっていないといけない/複数ポートで競合するようなストリームの処理が未定義動作にならざるを得ない)、UMP対応プラグインでは基本的にポートは1つ、内容種別はextension_data()でホストとの合意に基づいて決定する、というMIDI-CI的なやり方にしました。(MIDI 2.0ではMIDI 1.0プロトコルMIDI 2.0プロトコルを排他的に選択し、MIDI 1.0のメッセージはMIDI 2.0プロトコルでは送信できないようになっています。逆も同様。)

現時点では自作プラグインフレームワークのほうでMIDI 1.0サポートを切り捨てておらず、MIDI 1.0のストリームがMIDI 2.0前提のプラグインに流れ込んでくることが想定されるので、バイトストリームをUMPに変換する作業が必要になります。むしろ一般的にはプラグインがUMPをサポートしていないためUMPをMIDI 1.0のストリームに変換して送信する処理が必要になります。これは実装したのですがまだUMPを送ってくるホストは無い状態です(まあホストも自作しているというか自分のフレームワークではホストはAndroidサービスの受け口なのですぐ作れるわけですが)。

JUCEのほうはLV2が出来たら着手しようかなと思っていますが、そもそも今コーディング作業を勧めているべき段階ではないはずなので(!)、M3 2020秋が終わる10月末までは着手も未定です。上記書籍の肥やしにはもうならなくなっていますし。

cmidi2: allocation-free, header-only MIDI 2.0 UMP library

LV2でUMPサポートを実装するためには、当然UMPを操作するためのライブラリが必要になります。しかし、8月時点では何一つ発見できませんでした。Xcode 12にCoreMIDIでUMPをサポートするAPIがいくつか生えましたが、こっちとしては当然ながらどのプラットフォームでも使える必要がありますし、それは現時点でも皆無です。

lv2-midi2サポートを実装するにあたっては、LV2 AtomのようなAPIが必要になります。LV2はVST3SDKなどとは異なり軽量なheader onlyなCライブラリの集合体で、LV2 AtomAPIもすべての関数がstatic inlineで定義されています。LV2 Atomはオーディオ処理の中で解析・生成されるので、当然ながらメモリアロケーションも許されません。すべて事前に用意されたバッファで処理します。

そういうわけで、まずはLV2 Atomと同様の構成でUMPを操作するためのライブラリを作りました。それがcmidi2です。

github.com

UMPはuint32_tuint64_tuint128_t…という標準型は存在しないのでまあuint64_t * 2ですね*1…で表現できるので、自然とアロケーションフリーで扱いやすい構造です。単なるバッファ処理でしかないので自然とクロスプラットフォームです*2。基本1日で作ったやつですし(ちょくちょく手を加えているので今は500行を超えちゃいましたが)。

名前はさすがにシンプルにmidi-2.0とするわけにもいかないので雑にCを付けました…。おそらく類似のMIDI 2.0 APIを既存のktmidiにも生やすことになると思います。managed-midiには…MicrosoftLinuxデスクトップ開発者をきちんとリスペクトしてLinux向けにVS for Linuxを出すなりかつて存在したMonoDevelopなどをまた使えるようにしたら、やるかもしれません。

lv2-midi2のために作ったライブラリですが、汎用的に使えるはずなので、MIDI 2.0に興味のあるCプログラマーは取り込んでみてください。なおheader onlyなのでCライブラリであってもdlopen()・dlsym()を前提とするFFIにはほぼ向いていません。

ayumi-lv2

7月に作っていたPSG音源AY-3-8910のエミュレーターayumiをLV2で使うためのプラグインayumi-lv2ですが、まともに発音出来るようにMIDIメッセージからのAPI呼び出しを調整できたので、最大3音ですが音が出るようになっています。

本当はこれをlv2vst経由でVST2をサポートしているDAW(Tracktionなど)から使いたいのですが、手元の環境でlv2vstがクラッシュするようになっていてまだ試せていません。調べるよりJUCEプラグインとして作り直したほうが早そう…しかし今やり始めるとまずい…!

ayumi自体はもとのハードウェアのレジスタ類をエミュレートしている音源であって、MIDIノートではなく周波数を設定するかたちになっているので、MIDI 2.0におけるpitch 7.9(前回書いたやつ)も問題なく計算して渡せるでしょう。ホスト側が調整できたらこの辺から動作確認も含めてサンプルとして作り込んでみようと思っています。(しかし今やり始めるとry)

*1:calccrypto/uint128_tなどを使う手もありますがこれはheader onlyではないので…

*2:特定のendiannessにも依存しないようになっているはず…