11月の開発記録(2022)

毎年「えっ、11月って31日まで無かったっけ?」って思ってしまう人です(時候の挨拶)

勉強会: Zrythmから読み解くDAWの作り方(12/11)

music-tech.connpass.com

10月に「あと11月のうちに1回オンライン勉強会をやっておきたいですね。」などと書いていたのですが、その頃考えていたのがこのZrythm勉強会です。Zrythmは新進気鋭のDAWで開発に勢いがあってプロジェクトが成長するのを見ているのも面白いし、開発者のalexteeせんせいは日本語もいけそうなので「もしかして日本語勉強会が出来るのでは…?」と思いついたのは天才的だったと思います(自画自賛) 「ちゃんと勉強会として提案できる内容になるかな…」といろいろ下調べをしていたら時間がかかってしまって、12月になっちゃいましたが、参加を快諾していただいたalexと、開催コミュニティのおふたりの協力をいただいて開催の目処が立てられました。

Zrythmはプラグインのサポート作業をCarlaにオフロードしているので、他の部分に注力できている一方で、GTKアプリケーションという(この分野では)割と特殊な全体構造なので、そこにあまり踏み込まずに話せるだろうか…とか、今もスライドを作りながらいろいろ落とし所を探っているところです。Zrythmに終始する勉強会にはせず、使い方の話で終わらせもせず、Zrythmへのcontributionにも道を拓くようにする…くらいの立ち位置を目指します。

ちなみに勉強会自体はMastodonTwitterFacebookでお知らせしていたら1日ちょっとで30人枠全部埋まってしまいました(今までにない流れ…!)。すでにwaitlistが出来ていて、たぶん何かしらの救済策を立てますが、これからのフル参加は難しいと思います(すみません)。

ADC2022(オンライン参加)

audio.dev

先月分を書いたときはまだ現地参加するかも…みたいなノリでしたが、その直後にオフライン参加が締め切られてしまったので、今年もオフライン参加になりました。まあ正直ハイブリッド開催におけるオフラインオンライン参加のメリットは小さいですね。gather.townに来るのは多くても100人くらいで、スピーカーなど主な参加者はオフラインにいる間はオンラインには絶対来ません(自分もよほど物足りなかったりしない限り見に行かないと思います)。セッション中にその場で質問するとか、あとはgather.townでスポンサーブースにいる人たちと会話するのが主な「参加」の楽しみ方となるでしょう。

もちろんセッションコンテンツを見られること自体も意味があると考えることはできますが、正直有料で参加していても「さっさと動画を公開してくれたほうがいい」と思います。ADC2021の動画はなかなか公開されず(youtubeにずっとunlistedで上がっていただけ)、結果的に参加したわれわれが他の人と動画鑑賞会も開催できないうちに興味/関心が薄れてセッション録画の価値が下がっただけだなー、というのがわたしの所感です。

それはさておき、今回の話題はこの辺だったかな、というのをいくつか挙げるとしたらこんな感じです:

個人的にはMIDI 2.0のセッションを聞いてアップデート情報を見て「MIDI-CIのprotocol negotiation無くなるの? マジ?」とかいいつつ、MIDI協会のブースに行って中の人としゃべったりして、ADC後にミーティングを設定してもらったりして、謎のコネクション(?)が増えました。聞いたところによると、MIDI 2.0の話とか書いたりTwitter(いつまであるかわからんけど)に書いたりしていると漏れなくヲチされるらしい…(適当) 「このブログを読んだんだけど…」と言われてここが英訳された画面が出てきたりとかして、心臓に悪かったです(これも読まれる可能性がある)。日本語もそこそこ通じたので今度はMIDI 2.0勉強会とかやったら来てくれないかな…(無理筋)

書き物

今月は「Android NDKでAPIレベルによる条件分岐を実現する」という割と長めのネタを書きました。

本文中でも触れていますが、「NdkBinderがminSdk 29で使えなくなっているんだけど…」っていうissueを春先に立てていて、(変更が複数コンポーネント = 複数チームに渡るので割と重く)ようやく解決したっぽいので、その過程で得られた知見をまとめておこうと思ったのでした。

あと、公開されるのは12月ですが、明日12/1はDTMテクニック集 Advent Calendar 2022atsushieno/open-vital-resourcesの話を、明後日12/2はJUCE Advent Calendar 2022でJUCE 7.0のLV2サポートの話を出すので、今月はそれらを書いていました。これで来月書くものは少なくなったはず…!

AAP: update plugins to work as AudioPluginService.V2 plugins

今月は結局ADCをリモート参加にしたのでその分自分の時間が取れたはずで、開発もそれなりに進んだはずなのです…が、成果を見るとそうでもないような…。AAPのパラメーターサポートの実装をやっていました。

先月はプラグインパラメーターの出入り口を(オーディオ・MIDIと同じ)Portから(独自の)Parameterに変更する調整作業を今月に持ち越していました。それはaap-lv2、aap-juceともに何とか片付いたのですが、UMPからMIDI1へのtranslatorを実装していて、「UMPのAssignable Controllerをパラメーター変更に使っているとプラグイン側でNRPNを受け取れないな?」ということに気付きました。もう少し具体的に説明すると、UMPのAssignable Controller (AC)はMIDI 1.0でいえばNRPNに相当するもので、UMP単体で見たらACはMIDI 1.0には存在しないメッセージなので既存のMIDI 1.0に基づくプラグインMIDI入力とかぶることはないのですが、MIDI 1.0とMIDI 2.0の間で変換処理を行うと、NRPN-DTEの組み合わせはMIDI 2.0ではACに変換されることになるので、やっぱりこのメッセージもMIDI 1.0用プラグイン潜在的に処理するメッセージと競合することになるわけです。

ACを使ったパラメーターの変更という設計は賢いと思っていたので、捨てるのがもったいなくてしばらく悩みましたが、これを残していたらVST3のCCと同様に使えない仕様になってしまうと考えて、やはり変えることにしました。新しいメッセージフォーマットではSysEx8を使います。SysEx8であれば今度こそ問題ないはず…と思いますが、manufacturer IDやsub IDは割と雑に00だったりするので、もしかしたらマイナーチェンジが必要になるかもしれません。ともあれ、この変更のためにcmidi2から微妙に手を入れ直して、aap-lv2もaap-juceも全体的にプロトコルを修正する作業でいっぱいいっぱいになりました。

ちなみにパラメーター変更にSysEx8を使うアプローチは実は初めてではなくて、MMLからUMP経由でTracktion Engineのパラメーター変更を実現する際にも利用していました。この頃は「音源別パラメーターってSC-88Proのインサーションエフェクトみたいなもんだからシステムエクスクルーシブでしょ」って雑に考えてやっていました。ただし、今回のフォーマットはもう少し情報を付加することになりました。MIDI 2.0のAssignable Controllerはノート別(Per-Note)パラメーターもサポートしていますし、プラグインとしてもCLAPのようなフォーマットではパラメーター変更メッセージにノートナンバーが含まれます。また、そもそもNRPNはchannel voice messageであり、チャンネル情報がa prioriに付いてきますが、SysExにはこれが無いんですね。そういうわけで、チャンネル情報とノートナンバー、あと今後ノートがkeyではなくnote IDの方式で渡される仕組みが一般化するような事態を考慮してextraフィールドを作ったので、16ビットのSysEx8単体パケットはほぼ使い切ってしまいました(いざとなればもう1パケット増やして32ビットにしてもいいのですが、パラメーターの変更ひとつに32ビットか…という気持ちは正直あります)。まあ、そんな感じでこの辺の設計にもいろいろ試行錯誤があります。

LV2のlv2:Patchサポートを除いて実装上はひと通り対応したはずなんですが、まだバグフィックスが必要になったりaap-juceを実装しているうちにaap-lv2がリグレッションを起こしたりしていて(実装してみるとわかりますが、client/serverが別アプリで非常にテストしづらいし、リポジトリを合体させるわけにもいかないのでビルドも統合できなくて、だいぶ悩ましいやつです)、めでたく12月に持ち越しです。

lv2:Patchサポートは、まだこれを必要とするLV2プラグインが無いので優先度が低いというのが正直なところですが、やらないわけにはいかないやつです。実のところJUCE 7.0のLV2ビルドではlv2:Patchが使われることになるので(この辺はJUCE Advent Calendarの記事に詳しく書きます)、それらを「LV2プラグインとして」AAPに取り込むなら必須になるのですが、JUCEプラグインならストレートにaap-juceを使えばいいじゃん…となります(!)

あとは、プラグイン単体だと機能するけどMidiDeviceServiceとして使おうとすると音が出ない、みたいな問題も大きく、今月はまだ安定的に使えるバージョンをtag打ちできないなあ、ってなっています(3ヶ月くらいできていない)。今年中に一区切りつけたいですね。

Mastodon

最近は、日常的な技術的発見の多くはmastodonで書いています(英語は @atsushieno@g0v.social 、日本語は @atsushieno@mastodon.cloud )。Twitterは概ね反応器としてしか使ってない感じです(まあイベントの宣伝とかには使うと思いますが)。可能なら正常な治安が回復してほしいですね。Twitterはほとんどリアル知り合いばかりをフォローして狭い世界を構築していたのですが、今どきそんな古臭いことをしなくてもいいだろうということでMastodonはもっとカジュアルに使っています。

10月の開発記録(2022)

M3 2022秋の出展

10/30のM3 2022秋 (50回目だったらしい)に「オーディオプラグイン研究所」として出展参加しました。技術書典13で発行した「CLAPオーディオプラグイン開発ガイド」に加筆修正を加えた「正式版」の印刷版・電子版と、「Linux DTMガイドブック」の電子版が新刊ということになります。前者はboothで取扱開始しました。技術書典のときに「紙版がほしかった」という方はこちらからどうぞ。本当は無償配布にしたいところですが、印刷代だけでも「売れば売るほど赤字になる」設定なので、ひとつよしなに…

xamaritans.booth.pm

Linux DTMガイドブックも同様に「完成版」を発行したかったのですが(たとえば、サンプラーなど「器」だけ言及していて楽器サンプルへの言及がゼロなのを何とかしたい)、時間的に厳しくあきらめました。M3では新刊の頒布に合わせてAndroidプラグインの展示も行う計画で開発を進めていて、そこまで手が回らなかったためです。(Androidプラグインの展示に合わせて最新版を安定させる計画は間に合わず、少し古いバージョンにしたのですが…!)

このLinux DTM本と既刊の「MIDI 2.0エコシステム構築術」「MML to MIDI 2.0 to DAW」も含め、展示では幻の「印刷版」を見本として用意しましたが、これらの印刷版はありません。デジタル版でお求めください。(この2冊は割と「自由研究をまとめてみた」性格の本で、現状ではそれなりに時代とともに価値が薄れていく過渡的な内容だと思っています。)

DroidKaigi2022(遊びに行っただけ)

10月上旬にはDroidKaigi2022もありましたね。今回はトーク応募すらしなかったので、単純に東京ドームシティまで遊びに行きました。開催地が遠くなってしまったこともあって、宗教上の理由で午後からの参加のみでしたが、いつものDroidKaigiのふいんきがだいぶ戻ってきていて(ランチとかコーヒーとか夜のパーティが戻ってくるのはまだ無理そう)、開催してくれてありがたい〜という感じでした。年単位で会っていなかった知人のみなさんとも久々に顔合わせできたし、新しく開発者のみなさんとも知り合いになれました。

Gradle Managed Virtual Devicesで変化するエミュレータ活用術」の外山さんに「GitHub ActionsのLinuxホストで使えるようになってほしい(KVMのnested virtualizationをサポートしてほしい)んだよ〜」とか詮無きことを言ったり、「Jetpack Composeを用いて、Canvasを直接触るようなコンポーネントを作成する方法」を見て自分がComposeで作っていたWaveform描画コードを直したり、「Android "を" ビルドしてAndroid Systemを覗いてみよう]」みたいなそこそこ低レイヤーのセッションが聞けるのうれしい〜とか言いながらAOSPアプリのビルドに手を加えるのを眺めたり、「Considerate App Update Delivery」で相変わらず多種多様な他人のアプリをビルドしないといけない仕事やべえ知見がたまりそう〜とか思いながら眺めたり、「人の声を可視化する」でオーディオAPIの話したい〜と思ってask the speakersでお話ししてみたらここの読者の方だと判明したり(そんなことあるんだ…!ってなりました)、「Android アプリの内と外をつなぐ UI」を見ながら自分のアプリのonBackPressed()onBackPressedDispatcherで書き換えたり、あと他の人と雑談していて気がついたらセッションを見逃したり()みたいないつもの体験を楽しみました。

全部かはわからないけどYouTubeセッション動画も公開されているようなので、裏番組とかで見られなかったやつを少しずつ追っかけようと思います(昨日まではさすがにそんな余裕なかった)。

ktmidi 0.4.0

今月は先月から続いていたktmidiの実装を直す作業がひと段落して、新しくバージョン0.4.0を公開しました。 Kotlin Native環境用にRtMidiのcinteropバインディングを作って、RtMidiNativeAccessという実装を追加して(JNAを使ったKotlin/JVMバインディングもあることを考えるとだいぶ二度手間感…)、Midi2Playerをネイティブでも意味あるものにしたという変更が大きいですが、UMPベースの楽曲データのフォーマットやAPIに破壊的変更を加えたからバージョンを0.1上げたというのが建前上の理由です。

楽曲データの問題はlong-standing issueで、元々トラックヘッダーにUMPの件数を指定していたのですが、これでは読み飛ばしができないのでバイト数に変更したものです。楽曲のMETAイベント…に対応するSysex8データ…も間違っていて、テンポ指定が適切に反映できなかった問題があり、今回新たに追加したUMP to MIDI1 translatorの実装と合わせて、ようやくMIDI 1.0と同等のMidi2Playerの動作がreal-worldなやつとして確認できました。mugene-ngもこれを反映しているので、MMLからMIDI 2.0楽曲を引き続き生成できます。

この過程でAPIドキュメントジェネレーターのdokkaがビルドエラーを引き起こす問題にしばらく悩まされて、結局Kotlin Slackで開発者にいろいろ助けてもらって何とかしました。自分の中でdokkaの評価がだいぶうなぎのぼりに上がりました(!?)

AAP: プロトコル変更"V2"の再設計

8月にオーディオプラグインのパラメーター変更にMIDI 2.0 UMPを使ってプロトコルを再設計する話を書いたのですが、その手助けとなるはずだった上記のMidi2Playerをプラグイン試用のアプリに組み込もうとしたものの、ネイティブコード側の実装には使えず、デバッグに使うにも微妙なので、そっちの路線は一旦留め置いて、まずmidi2プロトコルで従来どおりの固定のメッセージを処理できるPoCを(今度はUMPで)作って、それをaap-lv2やaap-juceに適用していこうと考えました。

それで、最初は従来の「midi1ポートもmidi2ポートもサポートする」方向で実装を練っていたのですが、早晩「これは開発体験が悪い」と気づきました。これまではむしろ「MIDI2ポートはいらない。UMPをサポートしたければ、MIDI1ポートでMIDI-CIのSet New Protocolを送って切り替えればよい」と考えていたのですが、プラグイン開発者が「どっちで来るかわからないから両方サポートする」ことになるのは理不尽ですし、「MIDI2を処理できるプラグインは現状ほぼ無い」にしても、「パラメーター変更はMIDI2でしか受け付けられない」、「パラメーター変更をサポートしないプラグインはほぼ無い」、「MIDI2をMIDI1に(可能な範囲で)変換するのは難しくない」…という状況を鑑みて、むしろ「MIDI2ポートのみ存在すれば良い」という考えに至りました(ここまでで何日かかかった)。

これは相互運用性の観点ではドラスティックな変更になる(完全に別物になる)ので、AAPのプロトコルのバージョンを上げて対応すべきところですが、現行バージョン0.7.4までで "V2" として作り直しが進んでいるので、そのままV2プロトコルがこの設計を反映したものになるでしょう。

一方でAndroidAudioPluginMidiDeviceServiceがUMPを前提にするわけにはいかないので、MidiDeviceServiceからの入力を8月に実装したUMP Translatorを使ってUMPに変換し、逆にプラグイン実装側ではUMPからMIDI1にダウンコンバートするTranslatorの真面目な実装を新たに作って使ったり、プラグインプレビューのアプリではパラメーター変更入力をポートへの直接入力から新しいUMPのAssignable Controllersメッセージに変更したり(これでようやくCLAPやAtom Sequenceのような新しいイベント処理のスキームに切り替えられたといえます)、同じダウンコンバーターをaap-lv2の実装にも適用したり…といったことをやっていました。実のところまだ完了しておらず、LV2 portとAAP portのマッピングどうしよう…みたいなところで現在でも泥沼にハマっています。先にaap-juceで対応したほうが早かったかも…

11月の予定

11月は正直まだどうするか決めていません。「どうする」というのは具体的には例年11月にロンドンでやっているアレです…多分オンライン参加で妥協すると思います。去年より自由になったとはいえ、まだ一生後遺症が残り得るLong-Covidの問題が続いていることに変わりはなく、現地で自己アッピール()に使えるようなソフトウェアはまだ完成していない状態なので、今行くと「オーディオクラスタにしては妙にAndroid開発に詳しいやつ」というビミョい位置付けで固まってしまう気がします(正しいのですが)。まあ何か理由があれば現地参加する可能性もあります。

あと11月のうちに1回オンライン勉強会をやっておきたいですね。ネタはあるといえばあるのですが、状況次第です。何か準備できたら改めてTwitter(いつまで使うかわからないけど)などで告知します。

9月の活動記録(2022)

9月がいつの間にか過ぎていました(忘れてた)。9月はまだ半分以上台湾で過ごしていたんですよね。いつの間にかすっかり日本で落ち着いてしまいました。9月は表立った活動としては技術書を2冊書いたのが主だったところです。

CLAPオーディオプラグイン開発者ガイド

9月には技術書典13がありました。経験上「最終日に新刊が出ていれば大丈夫間に合う」という見込みで8月には何を出すかも決めていなかったのですが、9月になってさすがに何も計画が立っていないのはヤバい…ということで、いくつか候補を考えましたが、まず着実に書けそうなものを具体的に書き進めることにしました。その1冊目がCLAPです。

techbookfest.org

書き終わってから、サークル名を「オーディオプラグイン研究所」に変更していたことを思い出したのですが(!?)、結果的には完全にサークル名にふさわしいトピックになったと思います。なお9/11のオフライン開催には参加していません。台湾にいたし。

一方で、内容についてはだいぶ荒削りで、特に拡張機能まわりはここに以前書いた文章をそのまま使いまわしている部分も多いです。今回の執筆には、(コピペに手を加えた部分がそれなりに多いので)1週間くらいしかかかっておらず、途中から「なんだか初日に間に合いそうだしこのまま出せるスピードで初版を出しちゃえ」というノリになっていた側面はあります。実際、技術書典13の初日(9/10)には間に合いました。

今回は同人誌を「書き上げた」というよりは、開発に必要・有用そうな自分の過去記事を断片的に繋いでまとめた感じです。本当は30ページもいけば十分だと思っていたのですが、それで結果的に50ページ弱ものボリュームになりました。同人誌を完成させる作業のうちそれなりに悩ませられる要素である表紙のデザインについては、過去にLV2開発者ガイドを書いていたやつを色違いにして出すというやっつけ仕事で済ませました(!) 成り立ちがそんな感じですし、同人誌のちまちました収益に期待するよりは広く読んでもらったほうがいいだろうと考えて(仮に1冊1000円で100冊売れても10万円の売上にしかならんですし、現実には10冊売れるかどうかというレベルだと思います。トピックがCLAPだぞ??)、今回は無料配布しています。(前作「MML to MIDI 2.0」の本も実用性はほぼゼロの自由研究レベルだったので500円にしましたが、それと比較したら1000円でもよかったかも…?)

ついでにGitHubリポジトリも公開していますが、これは改訂版の作業を完了できたら有料販売のために閉じるかもしれません。

Linux DTMガイドブック

CLAP開発者ガイドブックは無難に書けるものを書いた(新刊として出せる安牌のネタを拾った)だけでしたが、別口でLinuxユーザー向けに「LinuxでもDTMはそんなに難しくない…!」って言えるような、DTMソフトをあれこれ紹介する本を出したいとは前々から思っていたところでした。サウンド系のコミュニティでそんな話が出てきたので、じゃあ書けそうな機運があるうちに1冊作っておこうと思って、CLAP本を出して落ち着いた後で、カジュアルに書き始めました。今度こそ技術書典最終日(9/25)が最終締切のやつ…!

techbookfest.org

最初はほぼ「Linuxで動くソフト」の紹介本のつもりで、一方で自分が特にプラグイン側の説明を滔々と書けるほどのトラックメイカーではないので(一方でDAW開発者でもプラグイン開発者でもないし、自分の立ち位置が何なのか今でもよくわからない)、大半がテキスト数行の紹介 + スクショで済むだろうと雑に目論んでいました。が、実際にDAWの使用感を説明しようと思ったらALSAやJACKをどう制御するかを全く説明しなかったら、多分DAWを全く使えないし、Wine経由でWindows用のプラグインを使える技術についても言及しておくべきだし…という感じでyabridgeの紹介を書いたり、そのために動作検証したり…といった感じで、作業量と内容が膨らんでいったところがあり、最終的にはソフトの紹介は半分ちょっとくらいになったんじゃないかと思います(数えてない)。

あと、この本のベースになった構想は草稿レベルでいくつか貯まっていて、最終的にこの本にまとめる時間がなかったネタとして「サウンドフォントやサンプラーの類をいろいろ紹介していく」などがあります。これもCLAP本と同じような感じで「最終的な完成版は改めて出す」ことにして、今回は無料で配布しています。技術書典主催の🐑も「これが無料なのはありえん」って感じで紹介してくれていましたが、ちょっとサービスしすぎたかも…まあ、仮に売れたとしても利益はせいぜい5桁なので、それよりは読まれてくれたほうがいいです。完成版は運が良ければM3 2022秋に間に合うかもしれません。

ちなみにこちらの本は技術書典13では最後の4日間だけ配布というかたちになってしまったわけですが(予定通りではある)、CLAP本は150件くらい、Linux DTM本は200件くらいダウンロードされており、トピック的にLinuxのほうが面白かったようです。CLAPが面白いって言える音楽技術関係者やプログラマーは多くないはずなので、これはまあ予想されてしかるべきところですね。

MidiPlayerのMIDI 2.0サポート用のMIDI-CI実装

8月はAAPでプラグインのサンプルクライアントを書き換えたいという話を書きましたが、そのためには現状の決め打ちのMIDI入力・オーディオ入力を渡すだけでなく任意のMIDIシーケンスを演奏できるようにしたほうが後々テストが楽になります。そのため、ktmidiのMidiPlayerを取り込んで演奏できるようにしたいと考えました。

ただ、ktmidiの特にMidi2Playerは、実際に演奏できるMIDI出力デバイスが存在しないため、実装の動作確認が何も出来ていない状態でした。Midi2Playerがきちんと動作するか確認するためには、デスクトップではMIDI2のUMPストリームからMIDI1のバイトストリームに変換して(8月にここで書いたやつです)、それを直接MidiOutputに出力するモードが必要で、その辺を実装したりしていました。AndroidではAAPのMidiDeviceServiceに垂れ流せば済むのですが、デスクトップで動作確認してからのほうが楽です。

Midi2Playerの動作確認に有用なのはMIDIプレイヤーアプリの実装なのですが、実はmanaged-midiから移行できていない最後のひとつがMIDIプレイヤーxmdspで、これをMidiPlayer基盤の動作確認が必要なレベルで作り始めるのはちとチャレンジングすぎます。そういうわけでコンソールプレイヤーのサンプルを追加したり、そのためにKotlin Multiplatformのプロジェクト構成を見直したり、あとMaven Centralへのパッケージ発行で罠にハマって(新バージョンのreleaseが失敗してもこちらに何も報告されない問題とか…)問い合わせで数日使ったり、あとサンプルデータを生成するためにmugene-ng(MMLコンパイラ)のリポジトリをビルドし直そうとして、むしろビルドを通すためのリグレッションに追われたりなど、地味な作業が進んでいるところです。

ktmidiのこれまでのMIDI2サポートは、基本的にMIDI-CIで調整された後のストリームの送受信を前提としていたり、あるいはMIDI-CIのSet New Protocolメッセージを盲目的に受信して切り替えられるようにしていましたが、本来ならそこにMIDI-CIにおけるsource/destination MUIDのチェックやTest New Protocolのやり取りが必要なわけで、その辺も実装しておかないと他のMIDI 2.0システムとの相互運用は難しいところでしょう。これは先月たまたま発見したのですが、ALSAでもMIDI 2.0サポートに目が向き始めている様子があるので、MIDI-CIも少し整備しておきたいところです。そういうわけで、MIDI-CIのプロトコルに基づく状態管理に使えるクラスを作ったりしていました。ただ、まだプリミティブな実装になっていて、同じMIDI-CIを実装しているAppleのCoreMIDIのAPIと比べると使いにくいです。HTTP/3をリクエスト/レスポンス別々にユーザーが扱わなければならないような状態なので、たぶんKotlinのsuspendを使った非同期APIにすると思います。

作っていて気付いたのですが、MIDI 2.0における3つのPと言われているやつのひとつProperty Exchangeは、最新のCoreMIDIでもサポートされていませんでした。JSONを使ってプロパティをやり取りするやつで、これをサポートするにはJSONリアライザーを統合しなければならないわけですが、その辺でどういうAPIにするのが適切なのか、たぶんAppleも決めあぐねているところがあるのでしょう。わたしにもいいアイディアが無いというか現状そこまで考えるメリットがなさすぎるので(この仕様もしかしたらGeneral MIDI 2みたいに使われずに死ぬんじゃない?くらいの印象)、とりあえず様子見です。

10月の予定

9月末からずっとゲームばかりやっていて忙しくて他のことに手を付けていられず、今週もDroidKaigiオフラインを眺めに行く予定なので、まだ見通しが立たないところですが、9月の積み残しのMidi2Playerを片付けて、それをもとに8月の積み残しのAndroidプラグインクライアントを片付けて、あと↑の同人誌の穴埋めが出来たらM3 2022秋に少数部でも紙版とか持っていきたいところではあります。最近の新刊はずっと電子版ばかりでカタログ上の存在しかなかったので、ちょっとグッズを持っていきたい気持ちがあるんですよね。個人的にはもう紙版と電子版があったら後者しか買っていないのですが。まあそんな感じです。

8月の開発記録 (2022)

ちょっとだけ台湾生活のようす

台湾で日常生活を送りながら日暮しPCに向かいて過ごしています。日本も医療崩壊がひどいことになっていますが、安全に過ごせる度合いは台湾とどっこいどっこいなところがあるなあと感じています。公共交通機関でマスクを外す人間は皆無に近いので(これはきちんと規制があるためでしょう)、日本とは違って安全にMRTやバスで移動できています(日本では2ヶ月に1回くらいしか載っていなかった)。MRTはもともと飲食禁止で、日本みたいにホームに自販機も売店も設置されていないですし。

最初の1ヶ月はそこそこ他人と会うことがあったのですが、最近は外出する理由もなく、ほとんどYouBike(貸自転車)しか使っていません。いつの間にかYouBike 2.0とやらができて、大抵500mも歩かないうちにバイクスポットが見つかるくらい増えているので、ちょっとGoogle Mapで茶店(作業場)を探して空いていたら(空いているかどうかはざっくりGoogle Mapsアプリでわかる)行ってみるか…みたいなことは簡単に出来ています。昔は短時間なら完全無料でしたが、今もほぼ毎回5NTしか払ってないしほぼ無料です。昔は自転車を買っていましたが今は買わなくてもいいな…となっています。町中でもYouBike以外の自転車をだいぶ見なくなった気がします。

安全なのは移動くらいで、食・住は日本のほうが安全だなあと思います。もともと日本人は家庭持ち以外はたいがい一人暮らしだと思いますが、こっちではたいがい複数人でルームシェアリング(正確にはflat sharing)で暮らしていますし。わたしも7月は本当はそうなる一人暮らしになる予定だったのですが、airbnbのホストとのやり取りで完全に騙されて、いざ行ってみたら他の住人が…みたいなことになっていて、しかも初日から隣の部屋から止まらない咳が聞こえてくる…みたいな恐怖体験で、もう自室に入るたびにアルコール消毒するストレスフルな生活でした。家族や自分の同意のある知人ならともかく、どこで何をしているかもわからんどこの馬の骨とも知れぬ輩から伝染されるのはさすがに…。今は完全に自分1人しかいない部屋を借りているのですが、日本の自宅並みに安全です。ただ当然ながら家賃が高くて、今は日本並みに払っています。これはサステナブルではない…

食が安全ではないと思うのは、特に国レベルで圧倒的に自炊に向いていないためです。キッチンのない部屋も多いし(たとえば591で部屋探しの検索条件に「可開伙」 = 料理可能 を追加するとそこそこ絞り込まれます)、食材は日本以上に高いものも多いし、外食したほうが総合的に安いと言っていいでしょう。そんなわけで多くの人にとっては外食が基本になりますし、飲食店は混んでいます。持ち帰りが簡単にできるのはいいところですが、料理を作っている側も日本に比べたら感染対策は緩いですね。店員は休憩中に店内でマスクを外して過ごしていることが多く、何なら調理中も…みたいな様子を、日本より頻繁に見かけます。弁当類は東京のコンビニ同様に台北のコンビニでも買えるので、それで毎日過ごせるというのであれば、こっちは十分安全でしょう。

引越し前は住居付近に便當の店がたくさんあって毎日ローテーションで健康的なものを買えたのですが(共用のキッチンなんて使う気になれなかったし)、今はほとんどなくて代わりに夜市が近くにあるので、たまに油断してその辺で買って帰るか、数少ない便當の店まで足を延ばすか、ランチだけ外でちょっとお高いブランチの類(大抵200NT超え)を食うか、チェーン店(バーガーキングとか)で済ませるか、自宅にある大同電鍋やIHホットプレートを使ってできることだけやる(ただし調味料が何もない)…みたいな生活です。日本では最近ほぼ毎日自宅で食事を作っていたので、天地の差です。

AAP: port-config拡張(未了)と自動定義ポート

6月頃から始めたAAPクライアントの再構築(ネイティブコード中心化)を皮切りに、state拡張をextension化して、7月にはAIDLによる初期化プロセスに破壊的変更を加えましたが、8月にもまずこれを応用したプラグインインスタンスメタデータによらないポートの生成を実現しました。これはport-config拡張機能として実装される予定ですが、現状では大したことをやっておらず、この拡張機能が使われていたら<ports>要素をプラグインメタデータres/xml/aap_metadata.xmlに書かなくてもよろしくやってくれる、というものです。

本来はこれでオーディオポート生成も自動調整できることが期待されるのですが、ひとまずはこれでポートの定義を自動生成中心に置き換えることを目論んでいます。今後「ポートが自動生成されるように作成されたプラグインのであれば、新しいポート設計にも自動で対応する」ような変更を加えやすくなりますし(現状ではポートをハードコードすることになるので、コード上の後方互換性は間違いなく維持できませんが)。

port-configに関連する話でいうと、これ自体はオーディオバス(ポート)設定の自動調整を主眼においたものなのですが、プラグインのオーディオ処理の中心process()やその前段階としてのprepare()に渡すオーディオバッファポインターの集合体AndroidAudioPluginBuffer構造体の構成についても、再設計すべきか否か常日頃から悩んでいて、結論が出ません。CLAPのようにdata32data64を分けたりports x channelsの論理単位を分ける設計が魅力的なので模倣するのも良いと思っています。特にportごとにlatencyを設定できるのが美しいですね。ただこのノリでstrongly-typed APIを導入してコロコロ破壊的に変更し始めると多分癖になって良くない…という警戒心があります。これまでAAPは2年近くプラグインAPIレベルで破壊的変更を加えてこなかったので。6月からどんどん崩していますが(今だけ…というつもり)。

AAP UMP based parameters

今月はこの流れでいよいよパラメーター変更を1つのMIDIポートからすべて処理する仕組みを実装しました。LV2でいえばAtomポート、CLAPでいえばclap_process_tevents_inevents_outに相当するやつです。これでOdin2みたいに数百個のパラメーターがあるやつが数百個のパラメーターポートを生成してそれぞれにバッファが付く、みたいなことをする必要が原理的には不要になります。

原理的に…というのは、実のところこれがまだaap-juceやaap-lv2には適用されていないというのと、メタデータによらないポート生成との食い合わせが悪く、プラグインプレビュー( = テスト)のActivityでMIDIシーケンスを再生できていないためです。現状、MIDIシーケンスはMIDI1で生成されていて、MIDI1イベントとして送信されていますが、新しいプラグインパラメーター変更はMIDI2のAssignable Controllerを利用しており(bank/indexでパラメーター番号、値はfloatのビットパターンを32bit intとしてそのまま反映)、MIDI1に変換すると情報落ちが発生します。floatの列挙値で不一致が発生することを考えるとこれは容認できません。

MIDI1ポートとMIDI2ポート(あるいはパラメーター変更などに特化したポート)を並立させることも考えられますが、パラメーター変更イベントはもともとtimestampedでなければならないことを考えると、MIDI1ポートと並立していることは大きなデメリットになります。これはCLAPのWebサイトにあるMIDI & Event Queuesというページにある解説が詳しいですが、特にVST3のパラメーター変更とノートを別々のイベントキューに分岐するやり方ではイベントの処理順序が維持されないという問題があります。これを回避するためにsample accurateだったはずのイベントを1サンプルずつずらすみたいなdirty hackが用いられるわけです。

そういうわけで、MIDIサポートは、ホスト-プラグイン間のプロトコルとしては全て1本のMIDI2ポートでやらざるを得ない気がしています(プラグインAPIとしてはUMPのみ用いて、MIDI1への変換はプラグイン開発サポートライブラリみたいなものを作って対応する)。現行のMIDI1サポートもSMFのtimecodeをそのまま送るようなやっつけ仕様で、プラグイン側が複数のtimecode decoderを持つのは負担にしかならないはずですし。

最近CLAPのコミュニティでやり取りしていて知ったのですが、CLAPではMIDIなどのイベントポートを複数使えないわけではないんですね。ただホストとプラグインを繋ぐ論理ポートは1本でなければならず、その中で流れる各種イベントにはこれとは別の概念であるportが存在するようです。たとえば、Kontaktのport 1〜port 8に、あるいはSC-88ProのPort AとPort Bに(めっさ古い例えだ…)、同時にイベントを送信できるわけです。追記: イベント構造体にport_indexとして指定するスタイルです(clap_plugin_note_ports_t.get()で渡すindexに対応するはず)。

プラグインパラメーターの変更とプラグインからの変更通知に関してはUIが絡む場合も考えなければならず、特にAAPの設計ではホストからの変更(ホストプロセスUI)の他に、プラグイン本体からの変更(インプロセスUI)と、「プラグインUIアプリ」からの変更()純粋なアウトプロセスUI?)についても検討していたので、設計をいろいろ検討…というか再検討…する必要がありました。それで「オーディオプラグインのパラメーター変更と通知に関する覚書」を書いたりもしていました。

MIDI 2.0 UMP仕様書日本語版(未遂)

7月にCOSCUP 2022でMIDI 2.0のセッションをもったわけですが、MIDI 2.0の情報ってまだあまり無いんですよね。わたしは最大の癌はMMAの「ログインしないと仕様書がダウンロードできない」時代遅れの組織であることが原因だと思っていますが、共同使用策定者であるAMEIのサイトに行くとログインなしでダウンロードできます…という話をそのセッションでもしてきました。

それで「そういえば割と長いこと待っているけど日本語仕様書出てこないな…?」「AMEIにはMIDI 2.0の日本語情報ページがあるんだけど半年くらい更新されていないな…?」となっていたのを思い出して、「UMP仕様書とか短いから2, 3日あれば本体(Appendix以外)は訳せるだろうし、適当に作ってみるか」と思って、8月のはじめの頃に3日くらいで結局全部(Appendixも含めて)全部翻訳しました。

翻訳できたはいいのですが、これどうしようかな…勝手に公開というわけにもいかんだろうし、とりあえずAMEIのMIDI規格委員会のページにはこうあるのを見てとりました。

私たちは、共にMIDI関連の楽器やプログラム、ガジェットの開発/研究を行っていただける仲間を求めています。音楽に関わるお仕事をされてている方々、MIDIに興味をお持ちの学生の方で「個人情報の取り扱いについて」に同意いただけましたら、お名前、メールメールアドレスを記入の上、ご意見、ご要望をお送り下さい。尚、コメント欄は空欄でも結構です。MIDI関連のイベント等について情報を発信すると共に、今後の活動に役立ててまいります。

訳文はAMEIの訳語統一方針とか何も知らない状態で作ってあるので(調べようと思えばMIDI 1.0仕様書から推論できるといえばできますが)、とりあえず仮訳とか参考訳とか、もし何も無ければベースに使えなくはないだろうと思ってprivate gistにmarkdownを貼って送りつけてみました。3週間後に返事が返ってきて(夏季休暇の時期だし「定例会があれば議題に取り上げてもらえるだろう」くらいの推測でひと月は完全に反応待ちのつもりだったので、適切に対応してもらえたと理解しています)、翻訳作業そのものがMMAと示し合わせて進んでいるような状態なので静観してほしい、という内容だったので、訳文は非公開のまま成果物が出てくるのを待とうと思います。

MIDI 1.0 to 2.0 Translator in cmidi2 and ktmidi

仕様書を翻訳していて気付いたのですが、UMPのMIDI 1.0 from/to MIDI 2.0の変換を規定するDefault Translationに対応する実装がまだktmidiでもcmidi2でも出来ていなかったので、前述のAAPポートのイベント変換まわりを実装するときにも必要になるなと思って、とりあえず1.0から2.0への変換機能を追加しました。2.0のUMPを1.0のバイトコードに変換する機能は、以前から部分的に実装してあったので、そこはそのままです(sysexだけ複雑になるのでやっていなかった)。cmidi2はsingle header, inline functionのみの実装を想定していたので、この辺までやってしまうとだんだんktmidi並に複雑になってしまうのでやりたくない…と思っているのですが、まあ変換くらいならいいでしょう。

この辺をがっつりやるのであれば、本来ならMIDI 1.0メッセージからMIDI 2.0 UMPへの変換だけでなく、MIDI 1.0 UMPからMIDI 2.0 UMPへの変換もできたほうがよいのでしょうが、MIDI 1.0 UMPのストリームを使う場面がまだ自分のユースケースで出てこないので、後で考えよう…となっています。JUCEだとこの辺をByteStreamInputHandlerU32InputHandlerで分けている感じで、これくらいなら複雑になってもいいか…と思わなくはないですが、なるべくシンプルで直感的なAPIにしたいところではあります。(まあJUCEのUMPのAPIがわかりにくいのは、この場合は過剰な共通化ではなく単なるドキュメント不足だと思いますが。)

9月の予定(のつもり)

パラメーターポートまわりの次世代実装のProof of Concept実装が使えそうだとわかったので、現在はPluginPreview(プラグインのテストプレビュー実装)とそのUI (Activity) まわりを改造しています。現状でもlv2applyの入力を固定したやつみたいな感じですが、もう少しMIDI playerに近いものを作りたいところです。

あと9月には技術書典13があって10月にはM3 2022秋もあるので(出展予定)、新刊を用意しないと…(技術書典は最終日が締切日くらいに思っているところがある) …というわけでコーディングは少なめになるかもしれません。

オーディオプラグインのパラメーター変更と通知に関する覚書

最近自分のプラグインフレームワークプラグインパラメーターのサポートを設計しているのだけど(パラメーターのサポート無かったの!?と思われそうだけど、これまでは古いLV2と同じでパラメーター毎に「ポート」を用意するスタイルで実現していたのを再設計している)、特に変更通知まわりは割と面倒なところがあるなと思ったのでちょっとまとめておきたい。

パラメーター操作の分類

オーディオプラグインのパラメーターを操作する主体は2つ、あるいは3つある。

(1) ホスト(DAW)のシーケンサープラグインprocess()とかrun()とかに相当するオーディオ処理関数が呼び出されたときに、MIDI…に類するイベント…としてパラメーター設定命令が(理想をいえばタイムスタンプ付きでsample accurateに)そのprocess()あるいはrun()のサイクルの分をまとめて渡される。データの元がMIDIマッピングされたMIDIシーケンスであろうと、オートメーションであろうと、APIとしてはこれらの関数に行き着く。これらの関数はMIDI…に類するイベント…のアウトプットも取得でき、ホストはこれを現在処理中のフィルターチェインにある次のプラグインに渡すことができる(これはおそらくは一度MIDIに類する抽象イベントに変換することになる…というのは、たとえば、VST3のチェインの次にAUが繋がっていたらVST3イベントそのものを渡すことはできないからだ)。

(2) プラグインのUI操作。一般的に、GUIはリアルタイムで処理できないので、リアルタイムのオーディオ処理ループからは切り離されていて、したがってスレッドコンテキストも分離しているので、GUI上で生じたパラメーター変更のイベントは、いったんプラグインの内部でキューに溜め込まれて、次のprocess()run()の中で(理想をいえばタイムスタンプ付きでsample accurateに)処理される。ホスト側でプラグインのパラメーターの状態(現在値)を把握しておきたいことを考えると、UIによるパラメーター変更はホスト側に通知されるのが理想的で、したがってプラグインがホストに通知イベントを送信する仕組みが必要になる。

(3) オーディオ処理そのもの。これはパラメーター変更の入力ではなく出力 = 通知の話になるが、パラメーターには他のプラグインパラメーターの変更によって計算される性質のものがあり得る。これらはオーディオ処理に直接影響するとは言い難い(その関連パラメーターの変更は上記のいずれかですでに渡されている)が、これによって生じたパラメーターの再計算は、その変更を通知されるホストにとっては、プラグイン上にに生じたパラメーター変更と同義だ。

ただ、これら(1)〜(3)の処理モデルは、リアルタイムのオーディオ処理ループが稼働していることを前提としている。もしオーディオループが回っていなければ、UIによる変更を反映する次のprocess()run()は回ってこないので、それに伴って生じるべき変更通知はずっとやってこない。オーディオループが回っていない状態でもイベント通知が滞りなく届くようにするには、オーディオ処理が回っていない状態でもパラメーター変更が処理される仕組みが必要だ。そうしないとプラグインGUIで設定したパラメーターの値が、ホストで必要な箇所に反映されなくなってしまう。

UIイベントをプラグインに蓄積させるAPIプラグインにイベントを処理させるAPI

上記のうち(2)に関しては、一般的にはGUIプラグインのプロセスの中で動作しているので、GUIが発生させるイベントは同じプラグインの内部でイベントキューを保持しておいて、オーディオ処理が走るたびに内部で(1)のイベントストリームとマージして処理することになると考えられる。この場合、外部にAPIとして表出させることはない。

一方で、LV2 UIのように、プラグイン規格そのものがGUIをきちんと分離して、外部から入力をデータとして渡す仕組み担っている場合、あるいはその他のプラグインでもUIをきちんと分離する仕組みにしている、あるいはプラグイン本体とプロセス分離せざるを得ない場合(AAPがUIをホスト側で生成するとそうなる)、イベントキューイングのためのAPIプラグイン側から表出している必要がある。LV2 UI拡張のLV2UI_Port_Subscribe LV2UI_Write_Functionはこれを体現したAPIと考えてよいだろう。このAPIはイベントのキューイングのためのAPIであって、イベントを処理するためのAPIではない。

一方で、前述の通り、リアルタイムのオーディオ処理が停止している状態でもパラメーター変更が処理される仕組みが必要で、これはprocess()run()と同様に、イベント出力を生成する必要がある。このAPIの具体例と言えるのがCLAPのparams拡張に含まれるclap_plugin_params_tに含まれるflush()だ。LV2のGUIに変更を反映させるための仕組みとは性格が異なる。(このflush()の位置付けは実のところ曖昧だというissueも立っているが、本質的には書き方の問題であって仕様の設計がぼやけているということではないはずだ。)

別ベクトルの解決策として、そもそもprocess()run()をリアルタイム処理中以外でも呼び出せるようにすれば良いのではないか、そうすればイベント処理の実装はこれらのオーディオ処理関数で一元化できるのではないか、とも考えられるが、それはそれできちんと入力を処理できないプラグイン(すなわちオフラインレンダリングに対応できないプラグイン)が存在する可能性があって、この路線を押し通すには事前に調整されて然るべきということになる(たとえばAAPではまだ仕様が策定中なのでそういう前提を作れる)。

これが多分難しいのは、DSPの処理がリアルタイム前提で時間計算するようになっていたら、オフラインレンダリングには対応できないということと、DSPは特定のプラグインフォーマットから独立して実装されていることが多く、既存のDSPコードがオフラインレンダリングに対応できていない場合、process()run()がオフラインレンダリング対応を前提にしていると、そのDSPそのものが利用不可能になってしまうかもしれない、とかもしれない、といったことが考えられる。

非同期イベントとしての通知出力と、同期イベントとしての通知出力

リアルタイムのオーディオ処理のバックグラウンドで動作するUIを経由したパラメーター変更は、一般的には非同期イベントとなる。これに対して、オーディオ処理を経てのパラメーター変更通知が非同期となるべきかどうかは議論の余地がある。一般的には、UIコードがパラメーター変更のAPIを呼び出しても、前述の通りいったんキューイングされるだけだ。一般的にはキューイングするだけの処理の中で、パラメーター変更通知に値するプラグインの処理が発生するとは考えられず、一般的にはオーディオ処理の「結果」として通知に値するイベントが生成されるはずだ。キューイングの時点で通知イベントが生成されることを期待するようなAPIにしていると、何がしたいのかよくわからないぼやけたAPIになってしまう。

前述のclap_plugin_params_tに含まれるflush()を取り上げると、この関数はリアルタイムオーディオ処理が走っていないときに呼び出されてclap_output_events_tにイベント出力を格納することが想定されており、これは同期的なAPIだ。逆に、LV2 UI拡張にあるLV2UI_Write_Functionport_eventのようなAPIでは、その関数呼び出しから何らかの通知イベントが生成されることは期待されていない非同期的なAPIだ。そもそもGUIからプラグインを経ずにイベントを生成することは推奨されない。

CLAPのparams拡張のような同期的にイベント処理を行う方式については、もうひとつ言及しておくことがある。CLAPの場合、パラメーターの変更はプラグイン側で発生するイベントだが、プラグイン側からホスト側に対してイベント出力を直接送信する仕組みにはなっていない。その代わり、プラグインはホストから事前に渡されているホスト側のコンテキストであるclap_host_params_tのメンバーrequest_flush()を呼び出して、あくまでホスト側からclap_plugin_params_tflush()を呼び出すことを期待している。こうすることで、通常のオーディオ処理とイベント通知による処理のモデル(特にスレッディングモデル)を統一できる。オーディオ処理がリアルタイムで動作している状態であれば、request_flush()を呼び出さなくても、次のサイクルで通知イベントを出力するだけでよい。

パラメーター拡張APIは誰のためのものか (8/31追記)

前回はパラメーター変更と通知のAPIの類型について解説したが、特にLV2とCLAPでの位置付けがだいぶ異なることが見て取れた。なぜそのような違いが生じるのかを、少し踏み込んで考えたい。

一番根本的な違いは、それらのAPIを「誰が」使うのかを明確にすると見えてくるだろう。

  • CLAPの場合、拡張プラグインAPIを呼び出すのは常にホストであり、拡張ホストAPIを呼び出すのは常にプラグインだ(と自分は理解している)。パラメーターのflush()はホストが呼び出すものだし、プラグイン側はプラグインAPIを呼び出すことはない。プラグインGUIコードがプラグインDSPコードで利用するパラメーターを設定するために使用するAPIは存在しない。それはCLAPのスタンスでいえば、プラグイン本体が内部的に解決(提供・実装)すべきものであって、プラグインとホストのインタラクションに影響しないなら規格化されない。オーディオプラグインデザインパターンのようなものは、CLAP全体として標準化されない傾向があるように見える。
  • LV2の場合、LV2 UIの実装はLV2 DSPの実装とは別の共有ライブラリになり、パラメーターの変更と通知は接続されたポート経由で行う。LV2 UIの実装とLV2 DSPの間でインターフェースすなわちAPIが必要になる。

ちなみに、この一連の覚書でCLAPに関してparams拡張の話だけしてgui拡張の話を一切しないのは、CLAPのgui拡張は本当にGUIを扱っているだけで、GUIから発生するであろうパラメーターの変更イベントに関しては一切関知していないからだ。この辺CLAPの切り分けは美しくできている。最初に見たとき「拡張機能が"UI"ではなく"GUI"なのはセンス無いな…」と思ってしまったものだけど、この拡張は本当にGUIのためのものであって、たとえばUIとDSPの分離などを目的としてはいない。

Android 13のMIDI 2.0サポートについて

Android 13正式版がリリースされてAOSPにcode dropされたようです。

Android 13には興味深い新機能がいくつかあって、個人的にはLE Audioサポートの追加で音楽ソフトの体験が変わりそうなのとAndroid 12Lで追加された(主に)タブレット向けの複数Activity並列動作のサポートが気になるところですが、それらと並んでMIDI 2.0サポートが追加されたことがAndroid Developers Blogに書かれているので、これを少し解説します。

MIDI 2.0 - Android 13 adds support for the new MIDI 2.0 standard, including the ability to connect MIDI 2.0 hardware through USB. This updated standard offers features such as increased resolution for controllers, better support for non-Western intonation, and more expressive performance using per-note controllers. More here.

MIDI 2.0プロトコルサポートの概要

MIDI 2.0サポートに対応するAPIとしては、android.media.midi.MidiDeviceInfoに追加されたgetDefaultProtocol())メソッドのように、新しいMIDIプロトコル定数を利用するものがいくつか増えた程度で、Android 10で追加されたNative MIDI APIのような目新しさはない(小さい)です。

Android 13でMIDI 2.0サポートが追加されたのはUSB MIDI 2.0デバイスのサポートのみです。しかも「USBプロトコルでネイティブにMIDI 2.0サポートに対応したもの」だけが新規にサポートされたことになります。

MIDI 2.0はMIDI 1.0との後方互換性を強く意識して設計されています。その成果のひとつとして、MIDI 2.0のメッセージをあらわすUMPのパケットは、MIDI-CIとMIDI 2.0 Protocol Negotiationを利用することで、MIDI 1.0な接続環境でも送受信できるように接続を調整できます(これを実現するためには、仕様上は、送受信の双方向接続が確立される必要があります)。5-PINのMIDIケーブルでの接続まではカバーできないような話がMIDI 2.0仕様書には書かれていますが、USBやソフトウェアによる仮想MIDIバイスに関してはMIDI 1.0の接続さえあれば、そこにMIDI 2.0 UMPを流し込めるというわけです。

Android 13にはMIDI 2.0 UMPを「扱う・処理する」APIは存在しません。そもそもMIDI 1.0を扱う・処理するAPIAndroidには存在しません。MIDIメッセージもUMPストリームもただのバイト配列であり、こういうのはアプリケーションで趣味に合わせて都合の良いものを使えばよく、プラットフォームAPIとして存在する必要はありません。筆者もそういう意識でMIDI 2.0 UMPを扱うライブラリktmidiを作ってGitHubで公開しています。C++ならJUCEのUMPサポートを使うのもいいでしょう。

100%手前味噌なのですが、この辺は先月COSCUP 2022でも話してきた話であり、また1年ちょっと前に発行した同人誌「MIDI 2.0エコシステム構築術」でもまとめています。

xamaritans.booth.pm

ネイティブMIDI 2.0プロトコルバイスのサポート

今回Android 13でサポートされたのは、このMIDI-CIとProtocol NegotiationによるMIDI 2.0プロトコルへの昇格ではありません。これはAndroid 12以前でも利用できたものである、と考えてよいのです。実際、筆者は上記ktmidiプロジェクトの一環として作成した仮想MIDIキーボードアプリkmmkで、MIDI 2.0プロトコルに昇格して…した気分になって(Protocol Negotiationの応答処理をすっ飛ばして)…UMPを送りつけるモードを実装して、別途Android用のオーディオプラグインフレームワーク(自作)から作り出した仮想MIDIデバイスに実際に送りつけて音を出すところまで作ってみたことがあります(今はちょっとまともに動作しない状態になっているので言及するのもちょっとアレですが…)。

Android 13でサポートされるようになったのは、MIDI 2.0プロトコルネイティブな接続です。MIDI 1.0の頃は、MIDIプロトコルMIDI 1.0によるものだけでした。MIDI 2.0の時代になって、このUMPのみを送受信するMIDIバイスという概念が新たに発生することになりました。すなわち、MIDI-CIによるプロトコル昇格を経るまでもなく、最初からUMPでやり取りするデバイスがあり得る、ということです。

あるUSB-MIDIバイスが最初からMIDI 2.0プロトコルしかサポートしていないかどうかは、USB-MIDI 2.0デバイスであるかどうかをUSBプロトコルに則って得られる情報から判断できます。そしてその情報が、冒頭で言及したAndroid 13の新しいAPIであるMidiDeviceInfo.getDefaultProtocol()などで取得できるというわけです。

もうひとつ、MIDIアプリケーション開発者にとって重要な機能としては、MidiManager.getDevicesForTransport())というメソッドを使うと、MIDI 1.0あるいはMIDI 2.0専用のデバイスに限定してリストを返すことができます。

なお、android.media.midiのAPIリファレンスには、USB-MIDI 2.0をサポートするデバイスでも、MIDI 1.0プロトコルによる接続もサポートするべきである、と書かれています("A MIDI 2.0 USB device should create two interfaces, one endpoint that accepts only MIDI 1.0 packets and one that accepts only UMP packets.")。

また、Android 13のAPIで追加された定数は、MIDI 2.0プロトコルのいくつかのオプション(たとえばJRタイムスタンプを伴うか否か等)に関連するものですが、これらについてはMIDI 2.0 UMP仕様書の"3. MIDI Protocls in UMP format"などを参照してください。

MIDI 2.0ネイティブのサポートはUSB-MIDI 2.0デバイスのみ

この意味では、Android 13に追加されたMIDI 2.0サポートはUSB-MIDIに限定されたものであるように見えますが、実のところそのとおりになっています。USB-MIDIMIDI 2.0プロトコルネイティブなものを作成できるようになっているのであれば、仮想MIDIバイスでもそうなっていてほしいと考えるのは自然なことだと思いますが(!)、現状ではMIDI 2.0ネイティブなMidiDeviceServiceは作成できません

MidiDeviceInfoクラス経由でわれわれMIDIアプリケーション開発者が取得できる情報は、別のわれわれMIDIバイス開発者がdevice_info.xmlなどの「デバイスマニフェスト」で公開できる情報に含めることができる情報であり、ここに「MIDI 2.0プロトコルネイティブだよ」と記述すればOKとなってほしいところです。そう考えてGoogleissuetrackerにfeature requestを出したのですが、先送りになってしまいました。

理由はこのissueに書かれているのですが、もしデバイスマニフェストプロトコル情報を追加するだけで対応してしまうと、MIDI 2.0プロトコルネイティブのMidiDeviceServiceはMIDI 1.0と後方互換性が無いにもかかわらず、MIDI 1.0デバイスに接続するアプリケーションからも接続可能なデバイスとして列挙されてしまいます。これではユーザーがちゃんと使えるデバイスを利用できないことになるので、恐らく別のIntent Actionが必要になることでしょう。

そもそもUSB-MIDI 2.0対応デバイスは現状ほぼ存在しない

前述のCOSCUP 2022のセッションでも話した(スライドにも書いた)のですが、2022年8月の時点で、MIDI 2.0に対応したMIDIキーボード等のMIDIバイスはほぼ何も存在しません。開発用の基板のようなものを作って売っている会社はあるようだ、というレベルです。MIDI 1.0インターフェース上でMIDI-CIのプロトコル昇格を利用してUMPをサポートするほうが現実的なので、MIDI 2.0ネイティブのデバイスというものがリリースされ、サポートされないと困る…!とまで言えるようになるのは、近くない将来のことになるでしょう。

そういうわけで、Android 13のMIDI 2.0対応は、現状ではほぼ無視してよいレベルであるといえます(断言)。一方で、USB-MIDI 2.0仕様はもう確定していて変わらないので、Android 13の現時点でUSB-MIDI 2.0に対応できているというのは良いことだといえます。MIDI 2.0ネイティブデバイスの普及期にはAndroid 13が普及機で動作しているというのが理想的な状態です。

MIDI 2.0サポートに向けてとるべき対応

われわれMidiDeviceServiceの開発者がMIDI 2.0に対応するには、ソフトウェアレベルでMIDI-CIのプロトコル昇格に対応してUMPを処理できるようにすることです。これをしっかり実装しようと思うと、それなりのMIDI-CI基盤を構築しなければならず、これは少し骨の折れる作業になるので(送受信のcorrelationが必要になる)、Androidオーディオチームではこの目的でみんなが使えるMIDI-CI実装を開発して公開するつもりであるようです…と先のissueでコメントされています。コメントしているのはAndroidオーディオチームのリーダー(Oboeの開発者でもある)なので、それなりの確度で出てくることでしょう。

MIDI 2.0をサポートするDAWなどのアプリケーションもまだまだAndroidでは存在していないので(完全にchicken and eggの問題)、われわれが「MIDI 2.0をサポートしてほしい」という機能要望を見るようになるまでにはしばらくかかることでしょう。その間にAndroidオーディオチームがMIDI-CI対応ライブラリやAPIを出してきたら、そこから対応を始めるくらいでも十分ではなかろうかと思います(個人的には自分でMIDI-CI対応機能を作ってしまうと思いますが)。

7月の開発記録 (2022)

COSCUP 2022に登壇するために台湾に来ています。

台湾はまだ観光ビザで訪問できないので、就業金卡というビジネスビザの一種で来ています。ある種の職能スペシャリストビザの一種で、いくつかの条件を満たせば申請が受理される見込みがまあまああるのですが、キャリア年数などのほか年収基準なども150万NTDくらいで、IT技術者(条件のひとつを満たす職種)ならそれなりの収入がある人も少なくないと思います…と書くつもりだったのですが、昨年自分が申請した時に比べて日本円の価値が暴落したので、割と厳しくなったと思います…

周りの外国人の知人は胡散臭い連中だらけだったのに割と漏れなく取得できていて、しかも3ヶ月くらいで取得できていたので、自分も…と思っていたら、承認が降りたのは1年前だったのにその頃にオリンピックのせいで感染爆発が起こって、手続窓口が今年の春先までずっと閉じていて、必要な手続は今頃になって進められたという…いま申請すればたぶん3ヶ月くらいでいけると思うので興味のある人は試してみましょう(滞在可能年数に応じた申請手数料がかかります)。

台湾は三+四天(天は日数)という入国者隔離政策があって、過去に比べるとずいぶん緩められていて、たとえば自宅隔離が許されていたり隔離タクシーでなくても家族が車で迎えに来たりできるので、わたしなんかは失策だと思うわけですが、今のところBA.5もほぼ阻止できているようです。以前に行き来していたときも事前準備していると割とナーバスになったものですが今回はさらに輪をかけていろいろ気にしながら進めました。特に就業金卡は日本では情報が少ないですし(わたしがもっと書けって話になりそうですが)、日本から出国する日本人のための情報は英語圏でも見つからないですしね…

オーディオプラグイン規格勉強会 (2022.7)

先月も書いたのですが、CLAPという新しい規格…いやホントは昔からあったのですが…が正式リリースされて脚光をあびるようになったので、CLAPの勉強会を開催…する前にそもそもオーディオプラグイン規格全般を調べながらみんなでわいわい討論できる勉強会をやりたい…ということで7/6に開催しました。これについては今月4回にわたってここに連載したものがあるので、それを見てもらえればと思います。これを勉強会のために下調べとして書いていたので(そうするとスライドもまとめやすいわけです)、それでそれなりの時間を使いました(といってもほとんど6月の話です)。

What for, Where and How to Adopt MIDI 2.0 @ COSCUP 2022

7/30に台湾の巨大OSSカンファレンスCOSCUP 2022にて表題のタイトルで「MIDI 2.0の技術はどこで応用できるのか」という話をしました。

coscup.org

MIDI 2.0は今使われているわけでもないし、デバイスもなければOSサポートも無く、MIDI 2.0が実現するのもオーディオプラグインの既存の技術で実現できていることが中心だけど、MIDI-CIを使ってMIDI 1.0のトランスポートの上に成り立つもので、オーディオプラグインがバラバラに実現してきたものがMIDI 2.0によってふたたび標準としてDAWの内部で音楽表現を担う可能性があるものとしての未来を見出してほしい、という内容です。スライドはspeakerdeckで公開してあります。

speakerdeck.com

申込み時に「まあ初歩的な話しか出来んやろ」と思って入門レベルとしていたので、特にコードもデモもなく表層的な話で終わっています。ただもしかしたら開発初心者ではなく打ち込み初心者が来ていた可能性がry(まあ「MPEとか使えばMIDI 1.0ベースの世界でもper-note pitchbendとかできるよ」みたいな話もしたので大丈夫なはず…)

本当はAndroidのオーディオプラグインにもMIDI 2.0サポートを組み込んであるよホラ見て、みたいな話をしたかったのですが、実装のバグつぶしに負われていて「無理はせんとこ…」ってなりました() こっちがうまく動いてもMIDI 2.0クライアント(ktmidi/kmmk)と協調できないと困るし…AAP本体もそうなのですがC/S系のコードになるので面倒くさいところがあります。デスクトップならまだ楽なんですが。

AAPクライアントの再構築

AAPの拡張機能を実装してから、AIDLベースのKotlinクライアント実装ではそれに対応できないのを全面的に再構築する必要があったわけで(先月の続き)、7月はそれを片付けました。

今主に進めているのは新しいポート設計の部分で、現状LV2のTTLのようにメタデータXMLに全部列挙しないといけなくなっているものを、プラグインのコード主体で設定できるようにするものです。LV2 dynamic manifestのように動的にポートを設定したいというよりは、(1)オーディオバスを柔軟に設定できたり、(2)MIDI-CI実現のためのポート動的生成を可能にしたり、(3)オーディオポートのメタデータ記述を不要にしたい(デフォルトでステレオ設定としたい)といった目的です。特に(2)でMIDI IN/OUTを自動生成できるようになると、拡張機能の実装にsysexを使えるようになるので、そこに期待しています。

これを実現するためには、KotlinクライアントのAPIもネイティブAPIメタデータではなくインスタンスからポートを取得する必要があり、そのためにはJetpack Composeで実装していたプラグインプレビューのアプリもメタデータではなくインスタンスを中心に構築する必要があり…と、いろいろ芋づる式に書き換えることになりました。これは不安定なクライアント(ホスティングAPIなので、まだ表に見えるメリットのある変更にはなっていない感じです。プレビュー機能もまあまあイマイチなので(ホントにテスト用でしかない)、もうちょっとまともな機能と実装がほしいところではあります。ただ、さすがに優先順位が低い…それとは別に、この辺の内部実装をいろいろ書き換えたことでaap-juceなどもさまざまな問題が表出するようになって、バグフィックスばかりやっています。もうちょっと楽しい機能追加やりたいなあ…

あと最近はAndroid Studioあるいはlldbまわりが不安定でデバッガが何も進められなくなったりとか、NDKのバージョン次第でscudoがエラーを返すようになったり(しかも意味のあるメッセージは何も出さない)とか、いろいろツールチェインの問題だらけで開発体験が非常に悪くなっているので、どうにかなってほしいところです。まあどうにか「する」にはオープンソースでないと無理なわけですが、オープンな部分でもまだビルドの全体像を把握しているわけでもないので、気が向いたら掘り下げようと思います(向かなそう)。