2月の開発記録 (2024)

Linuxデスクトップ環境がずっと死んでいて消耗していますが、今月はかろうじて生き延びました…(!?)

2月はAndroid 15というでかいリリース(developer previewですが)があった関係でだいぶ揺り動かされました。先にそっちから書きます。

Android MidiUmpDeviceService対応

数日前にAndroid 15のMIDI 2.0対応についてここに書きました。

atsushieno.hatenablog.com

AAPはイベントメッセージのフォーマットとしてUMPを採用しているし、任意のインストゥルメントプラグインMIDI 1.0のMidiDeviceServiceとして公開できる仕組みを構築しているわけで、MidiDeviceServiceのUMP対応が出てきたら、当然やらないわけにはいきません。

resident-midi-keyboardを開発したときのコンセプトのひとつは「AAPと連携してUMPをやり取りできる仕組み」であり、従前から作り込んでいたので、特にAPIとしてほぼMidiDeviceServiceと同じであれば、24時間以内に全部対応できるでしょ…!と意気込んで着手したわけですが、なぜかうまく動かない…原因は↑で書いたドキュメンテーションのバグだったわけですが、泣く泣く24時間以内リリースはあきらめました(!?)

「新API公開から24時間以内にリリース」は自分がXamarin.Androidの開発で昔いつもやっていたことだったので(もちろんそれが許されるくらいのプロダクトの規模感だったときの話ですが)、ちょっと懐かしいノリですね。

いずれにせよ、↑で書いた通り原因は判明したので、resident-midi-keyboardにはUMP対応がクライアント/サービスともに追加され、AAPはMidiUmpDeviceService化できるようになりました。Androidオーディオチームのリーダー曰く、MidiUmpDeviceServiceをサポートした世界初の事例だそうです。そりゃそうだ…!

ktmidi-ci-toolにも仮想MIDIポートとして使えるMidiDeviceServiceがあるので(Android OSとしては動的に作成できる仮想ポートの類は無いので、あくまで固定のソフトウェアMIDIポートです)、こちらもMidiUmpDeviceServiceを追加したのですが、MIDI 1.0の時と同じように「自分自身のvirtual MIDIポートと繋いだらinitiatorとreceiverのテストができるのでは…?」と考えて接続しようとしても、「同一ポート」への接続を試みていることになってしまうので(実際そうだし)、UMP接続ではメッセージがdropされてしまうようです。自分ではEchoUmpDeviceServiceみたいなやつを作って、in portに来たものをそのままout portsに送りつけています(EchoというかForwardというべきかもしれない)。

ktmidi-ci-toolのほうは、「UMP対応!」と言うためにはgroupの扱いがきちんとできている必要があって、ここはgroupの概念が存在しないMIDI 1.0しかサポートしてこなかったので、その対応を進めているところです…といいたいところですが、まず先送りしていたMIDI-CIサポートAPIの整備から着手しているところです。まあUMPサポートがあるだけでも大きいので、現状のまま出してもいい気がしてきましたが…(!?)

ktmidi 0.8.0-pre work

MidiAccessのUMPポート対応

ktmidiは「MIDI 2.0をサポートするライブラリ」ですが、これにはプラットフォームMIDI APIのサポートが含まれていません。UMPはApple OSとAndroid 13以降とLinux kernel 6.5以降 & alsa-lib 1.5.10以降でしかサポートされていなかったので、ずっと先送りにしていました。Android 13もUSB MIDI 2.0デバイスのみの対応だったので、自分には関係ない話でした。

Androdi 15でこの状況が変わったので、MidiAccessAPIでは、MidiPortDetailsに暫定的にUMP用ポートか否かを判別するプロパティが追加されています。Android APIでいえばMidiDeviceInfo.typeALSAでいえばsnd_seq_client_infoに追加されたtypeに相当する情報です。resident-midi-keyboardのはktmidiのAPIMIDIバイスを列挙して接続するので、

ALSAなら普段使いのデスクトップですぐできるだろ?と思われそうですが、AlsaMidiAccessが利用しているalsaktはプラットフォーム上に存在するlibasound.soをロードして使うjavacppのライブラリだったので、まずこれをalsa-libをstatic linkしてlibjnialsa.so(JavaCPPが自動生成するネイティブライブラリ)を構築するように変更する必要があります。

これが以外と筋が悪く、libasoundはシステム上に存在するconfigをロードして使わなければならないところ、alsa-libがstatic linkされている場合にこのconfigファイルはどこに存在するのか/バンドルしていいものなのか…といった問題があり、現状ではconfigのロードで問題が出るくらいなら従来のlibasound.soを動的にロードする方式に戻すか…となっています。そうなると、システム上に存在するlibasound.soUbuntu 22.04ならlibasound2-devが提供しているもの)がalsa-lib 1.5.10以降でない環境では、たとえkernelが6.5以降が動いていても、まだUMPが使えないことになります。

そんなわけで悩みどころなのですが、今Linuxデスクトップ環境が絶賛死亡中なので、いつ着手できるかわからない状態です。マウスコンピューターを捨ててMSI機を復活させるしか無いか…

rtmidiバインディングとの格闘(現在進行形)

2月はJavaCPPの使い方もいろいろ調べ直していました。JavaCPPは元来Java用のバインディング生成機構で、ビルドもMavenが前提になっているところが割とあり、gradle-javacppが公式といえど情報が少ないやつです。特にGradleプラグインは「いつ」必要になるのか不鮮明で、今のところ「バインディングライブラリ用のGradleプラグインと、そのライブラリを参照するモジュール用のGradleプラグインが別々にあり、参照する側のモジュールでは直接参照しないアプリケーションでもこのプラグインが必要になる」という理解で動かしています。.NETでいえばPCLのプロファイルの概念が曖昧なまま、要件としては(JavaCPP固有のレベルで)存在している感じです(と書くとどれだけ面倒なやつか伝わるかも)。

このプラグインをちゃんと使わないと、パッケージの参照解決で「存在し得ない」プラットフォームネイティブコードを含むパッケージが要求されるようになったり(Linux上でWindows用jarを要求されたりする)、逆に実行時に必要なjnirtmidi.soを含むjarがビルド時に参照として解決されずにUnsatisfiedLinkErrorが発生したりすることになります。

あと、これだけいろいろ調べて解決してきたのに、「rtmidi-javacppをMaven Centralから引っ張ってきたやつはjarにクラスが含まれていないかのように失敗し、MavenLocalから引っ張ってきたやつはうまくビルドできる」みたいな問題がまだ発生していて、こんなに問題を引き起こすならrtmidi-jnaを復活させるほうがまだ筋が良いか…?みたいになっています。が、librtmidi.*をバンドルする等の問題が発生するはずなので、それもそれで手を出しにくい…

rtmidi cinteropバインディングとの格闘(現在進行形)

rtmidi-javacppはKotlin/JVMの問題なのですが、rtmidiはKotlin-Nativeでも問題になります(!) Kotlin-Nativeのライブラリも、参照関係をstatic linkしないと実行時に動的に必要になる…というのでstatic linkするように作り変えました。これで特にApple OS上ではうまく行っているはずなのですが(iOSはそもそもrtmidiがサポートするようになったのが最近なので未確認)、Linux上でこのバインディングを使うアプリケーションでALSAのリンクを動的に解決できず失敗する…みたいな問題が起きています。多分どこかに簡単に対応できるやり方があるんだけど多方面に問題が出すぎていて未解決です。とにかく人手が足りない…(!?)

misc. AAP work

Gradle/AGPとの格闘

2月はまず、ずっとリリースできていなかったAAP v0.8.0をリリースするための作業から始まりました。一般向けにはこれで終わりです。この節の残りは実際に何をやっていたかを記録しておきます。(開発記録なので!)

0.8.0のコードは11月上旬からあまり変わってなかったのですが、リリース作業が面倒でやっていなかったわけではなくて、AAPのaarをプロジェクト内でimplementation(project(...))で参照しているとビルドできるのにMavenパッケージとしてimplementation("org.androidaudioplugin...")で参照しているとオーディオファイルが正常にロードできないという謎の問題がリリースを阻んでいました。

AAP 0.7.8のリリース時には、AAPの全派生プロジェクトでaap-coreをgit submoduleにしてaarをプロジェクト内参照する構造にするという前提で、控えめのアップデートを行っていたのですが(0.7.8に追従させたパッケージは少ないです)、このアプローチではaap-juceの特にProjucerを前提としたプロジェクトが移行できませんでした。仕方ない、根本的にビルドシステム(GradleとAGP)に踏み込んで問題を解決するか…という意気で、GradleやAGP (AOSP) のソースを、主にPrefabやCMakeのサポートを中心に読んで過ごしました。

問題の一端は、project(...)で参照解決しているとデバッグビルドが流用され、Maven(MavenLocalを含む)経由で参照解決するとリリースビルドが使われる、というGradleの挙動にありました。そしてリリースビルドでのみ想定通りに動作しなかったのは、Cコードの未定義動作がLLVMの最適化フラグによって挙動が変わった結果であって(LLVMのvectorizationが問題のトリガーになっているところまで突き止めました)、問題が再現するかどうかはデバッグビルドかリリースビルドかに依存する、という「よくある問題」のいち類型でした。project(...)で参照していたらデバッグビルドになる、とは一般的には理解されていないと思いますが、そんなハマり方があるんですね…

なお0.8.0のWhat's newはすでに11月に書いていたとおりです(これ以上書くことがない…)

NdkBinderのcallbackとの格闘

AAP拡張機能の新バージョンで目玉だったはずのホスト拡張機能のサポートですが、ホストとプラグインが別々のアプリケーションだとBinderがcallbackオブジェクトを渡してくれないという問題が残っていて、これが情報が多くなくて「BnCInterfaceをJava APIでいうところのStub代わりに使えばよい」というところまで辿り着くのにしばらくかかりました。NdkBinderもだいぶ自分が草分け的に使っているので、情報がほぼ無いんですよね…

3月の予定

3月は割と日本にいないので進捗は悪いだろうなと思います。あとM3用(あとたぶん技術書典用)の新刊も書かないとですし。あと、ひさびさにオーディオ勉強会をやることになると思って画策しています(やるなら4月)。