Q4の活動記録 / ネイティブコードを伴うAARパッケージ編成のベスト?プラクティス

10月後半から今日に至るまで最近の活動記録を流していなかったわけですが、実際引っ越したり錬金術変拍子ゲームをやっていたり技術記事を書いたりしていて、あんましコードを書いてはいませんでした。というのは半分くらいは本当で、半分くらいは、やっていたことが半分くらいは前回公開したAndroid NDKのPrefabに対応しようとしてポシャっていたからです。まあまあ光明が見えてきたので、半ばQ4の作業記録を兼ねてまとめておこうと思います。

目次


aap-lv2 0.1.3 releases

今なにやら自分のメインプロジェクトとなっているっぽいAndroid用オーディオプラグイン機構ですが、開発を快適に進めるために必要なパッケージの分割と参照のあり方をいろいろ模索していて、しばらくビルドのリファクタリングを模索していました。それが昨日ある程度着陸したので、一つの開発リリースバージョンとしました。

github.com

パッケージ分割と再編の背景

AAPの現状として、コアライブラリと「最低限これくらいはほしい」を実現するためのプラグインの移植を含めて、ざっとこんなものが今のビルド対象です。Kotlinの参照とネイティブの参照が両方あったりなかったりするのですが、細かく入れると見づらいので消しました(どっちにしろ「多い」って言いたいだけですし)。

  • core (android-audio-plugin-framework) : コア部分
    • androidaudioplugin : C++とKotlinのプラグイン用ライブラリ
    • androidaudioplugin-ui-androidx : プラグインリストや詳細表示に使われるKotlinのUIライブラリ(appcompatベース)
    • aaphostsample : Kotlinのホストサンプル、その2つのネイティブ実装(appcompatベース)
  • aap-lv2 : LV2プラグインを移植するプロジェクト
    • androidaudioplugin-lv2 : LV2の共有ライブラリとTTLファイルなどがあれば残りをコーディング不要でプラグイン化できる基盤ライブラリ
    • aap-mda-lv2 : LV2プラグインのリファレンス実装ともいえるmda-lv2プロジェクトの移植(開発中によくdogfoodingで使われている)
    • aap-ayumi : ごくシンプルなPSG音源をLV2化して取り込んだもの(開発中によくdogfoodingで使われている)
    • aap-guitarix : Guitarixの移植。これがあれば「ポピュラーなエフェクターがある」って言えそう
    • aap-sfizz : Sfizzの移植。これがあればSFZベースのさまざまな楽器が使える
    • aap-fluidsynth : Fluidsynthの移植。これがあればSF2/SF3ベースのさまざまな楽器が使える
  • android-native-audio-builders : 以上のプロジェクトで使われるネイティブコードだけをビルドするリポジトリ
    • serd/sord/lv2/sratom/lilv : いわゆるLV2 toolkitとなるライブラリ
    • mda-lv2 : mda-lv2のAndroid用ビルド(Androidではネイティブだけでは動作しないのでaap-mda-lv2に組み込む)
    • guitarix : GuitarixのAndroid用ビルド(これもaap-guitarixに組み込む用)
  • aap-juce : JUCEホストとプラグインを移植するプロジェクト
    • AudioPluginHost : JUCEに含まれるプラグインホスト。オーディオノードグラフ化して繋いだときの動作確認とかに便利(まだ正常動作するほうが少ない)
    • andes, SARAH, OB-Xd, Magical8bitPlug2, OPNplug : JUCEで作られた各種シンセプラグイン(aap-lv2のほうはシンセらしいシンセが無い)
    • AugenePlayer : tracktion_engineを使った楽曲プレイヤー(まだ一度も正常動作していない)

最初の最初はこれ全部が1つのandroid-audio-plugin-frameworkリポジトリに入っていたんですが、LV2とJUCEのプラグイン移植が増えてくるとすぐ無理になってきたので、aap-lv2とaap-juceに分割しました。これがどうやら1年前くらい。

今年はこれにGitHub ActionsでCIを設定して回ったのですが、けっこう大規模なビルドで、aap-lv2は30分くらい(一度60分くらいかかったのがあるのですがさすがにGitHub側の異常値っぽい)、aap-juceに至っては3時間かかっている状態でした。JUCEのほうは未着手なのでまだ長いままなのですが、さすがに待ち時間が長すぎるので何とかしようと思いました。

もうひとつの問題は、開発に際してプロジェクト参照のネストが深くなりすぎるところです。aap-fluidsynthのリファクタリング前のGradleプロジェクト構造はこんな感じでした。

  • aap-fluidsynth (in aap-lv2)
    • androidaudioplugin-lv2 (in aap-lv2)
      • androidaudioplugin (in android-audio-plugin-framework)
    • androidaudioplugin-ui-androidx (in android-audio-plugin-framework)

当時はaap-lv2がandroid-audio-plugin-frameworkとside by sideでチェックアウトされることが前提となっていて、ビルドの正しさを検証する意味では良くありませんでした(CIビルドを実行するタイミング次第でandroid-audio-plugin-frameworkの最新版がチェックアウトされてくる状態だった)。それでsubmodule化したわけですが、そうするとプラグイン移植ごとにさらにリポジトリを分割するとネストが一層ひどくなって、これが複数のプロジェクトで修正が同時に稼働するとなると、変更を加えたソースツリーが行方不明になりがちです。

このままの状態ではリポジトリを分割できない。というわけで再編策を練りました。

基盤ライブラリの参照を安定化する

プロジェクト全体がこれくらいの規模になってくると、コア部分の変更が直接アプリケーション側に影響する場面が少なくなり、またアプリケーション側も最新のコア部分に追従しなければならない場面も減ってきます。昔取った杵柄でいえば、monoランタイムの変更にgtk-sharpmonodevelopが追従する必要はないし、monodevelopのプロジェクトシステムを使ったIDEアドインとか、そもそもGtkアプリケーションのプロジェクトが追従する必要も無いわけです。

AAPのコア部分であるandroidaudioplugin.aarには今後も変更がどんどん修正されていくわけですが、基本的にはandroidaudioplugin-lv2.aarで対応するものであり、アプリケーション側が追従を迫られる場面はそうそう無いので、概ね参照バージョンを固定してしまっても問題がありません。(JUCE側のアプリケーション分割は別の大掛かりな作業が必要になるのですが、それは主にProjucerが生成するAndroidプロジェクトのテンプレートとどう向き合うかみたいな大きな技術的課題があるためなので、今後まとめて考えることにしています。)

そして、近年になってGradleのMaven Publish pluginAndroidプロジェクトでも現実的に使えるようになってきており、Mavenパッケージをローカルシステム上のリポジトリキャッシュ上に発行したりそこから参照解決したりできるようになっています(このGradleプラグイン自体はかなり昔からあるみたい)。

リファクタリング前のaap-fluidsynthプロジェクトの構造は、プラグイン移植の開発者にとってのコードの構造としてはいささかオーバースペックです。プラグイン開発を一般に呼びかける段階になったら、androidaudioplugin-lv2.aarはMavenリポジトリから取得されるべきです。build.gradleでmavenLocal()からMavenパッケージを参照できるかたちにすれば、自分みたいなコア部分からアプリケーションまで全体的にいじる可能性のある開発者でも、プラグイン移植のみの開発者でも、同じようなプラグインプロジェクトのコードが書けます。

変更箇所 リファクタリング リファクタリング
dependencies (build.gradle) implementation project(':androidaudioplugin') implementation 'org.androidaudioplugin:androidaudioplugin:0.6.3'
settings.gradle include ':aap-fluidsynth', ':androidaudioplugin' include ':aap-fluidsynth'

この他にもsettings.gradleにはprojectDirの指定などがあるのですが、まあ省略します。リファクタリング後の書き方のほうが一般的なアプリケーションプロジェクトっぽくなっていることがわかるでしょう。

コア部分に変更を加えたいときは、ローカルのmavenリポジトリキャッシュに発行して、それを使ったプラグインでは参照のバージョン番号を変えます。アプリケーションのビルドを別途実行するのは面倒だという場合は、一時的にリファクタリング前のプロジェクト構成に戻してやれば良いだけです。

現状でMaven Centralには何も発行されていないので、AAPのアプリケーションをビルドする場合は誰もがandroid-audio-plugin-frameworkをビルドしてGradleでpublishToMavenLocalタスクを実行する必要がありますが、一度やっておけばよいことです。

同じことがaap-lv2のandroidaudioplugin-lv2についても言えます。

現状ではどのLV2プラグイン移植のプロジェクトもaap-lv2をsubmoduleとしていますが、これは半分くらいはMavenパッケージが発行されていない現状では一度ローカルでビルドする必要があるためであって、その意味ではパッケージを発行するようになったらいつでも.gitmodulesからいつでも消せます。

ネイティブ参照解決地獄

さて、ここまでは綺麗にできるKotlinの部分です。AAPのメイン部分はC++で書かれていてNDKでビルドされるネイティブコードです。前回Prefabの記事を書きましたが、PrefabはAARの参照とCMakeLists.txtのちょっとした記述の追加だけで参照を自動的に解決できる仕組みであり、そしてまだ実用に耐えられるものではありませんでした。

どの点で実用に耐えなくなるか、一応説明しますが、このプロジェクトではネイティブライブラリの参照がこれくらい深くなります。今回はリファクタリング後のリポジトリ構成、aap-lv2からaap-fluidsynthモジュールをaap-lv2-fluidsynthという独立リポジトリに移動させた後の構成を見てください(断片的です)。

  • aap-fluidsynth.so (in aap-lv2-fluidsynth repo)
    • libandroidaudioplugin-lv2.so (in androidaudioplugin-lv2.aar, in aap-lv2 repo)
      • libandroidaudioplugin.so (in androidaudioplugin.aar, in android-audio-plugin-framework repo)
    • libfluidsynth.so (in aap-fluidsynth, in aap-lv2-fluidsynth repo)
      • libsndfile.so (in android-native-audio-builders repo)
        • libogg.so, libvorbis.so, libFLAC.so (in android-native-audio-builders repo)

こんな感じで、 だいぶ深い参照の連鎖になっています。前回のエントリで説明したとおり、Prefabは複数の依存関係での参照ができただけで対応できなくなるので、Prefabの生態系に乗り換える・コミットしてやっていくのは、現時点ではあきらめるしかありません。

Prefabが使えないとなると、androidaudioplugin.aarやandroidaudioplugin-lv2.aarに含まれるネイティブライブラリやそのソースのヘッダファイルへの参照をなんとかしないといけないわけですが、aap-lv2のプラグインに関しては、LV2のプラグインのバイナリのビルドにはAAPへの参照が必要ない(LV2のプラグインの仕組みに則ってlibandroidaudioplugin-lv2.soに含まれるlilvがホスティングを担う)ので、基本的にはLV2プロジェクトのソースにLV2ヘッダが入っていない等の事情でも無い限り、ヘッダの追加参照は不要です。AAPのヘッダも同様に不要です。

ただし、プロジェクトによってはAndroid assetからファイルをロードするように変更されているものがあるので(aap-fluidsynthなど)、その関係でAAPのヘッダが必要となっている場合はandroid-audio-plugin-frameworkのリポジトリから直接コピーしてきたものをaap-include-hackというディレクトリに適当に放り込んでいます。やや治安が悪いですが、そうそう変更されるものでもないので、現実的にはたぶん大丈夫です。LV2ヘッダも同様です。LV2 toolkitの他のライブラリはあくまでホスティング用に取り込まれたものであり、libandroidaudioplugin-lv2.soでのみ使われているので、気にする必要はありません。

一方でlibandroidaudioplugin-lv2をビルドするときにはlibandroidaudiopluginのヘッダが必要になるし、これは頻繁に変更が加えられるホスティングAPIをふんだんに活用しているので、ヘッダファイルのやっつけコピーは回避します。ヘッダファイルについては、まあsubmoduleのディレクトリを参照してもいいだろうということで、CMakeLists.txtから直接相対パスを指定しています。

問題はリンク時に-Lオプションで指定すべき共有ライブラリのパスですが、これはPrefabが来るまではまともな解決は無理だろうということで、libandroidaudioplugin-lv2のビルドにおいては、build/intermediates/merged_native_libs/debug/out/lib/${CMAKE_ANDROID_ARCH_ABI}をCMakeLists.txtでlink_directories()に指定しています((ちなみにAndroid SDKに含まれているCMake 3.10.2ではtarget_link_directories()はまだサポートされていません))。リンク先のlibandroidaudioplugin.soがandroidaudioplugin-lv2.aarにバンドルされる必要はありません。androidaudioplugin.aarにj含まれているので。

その他のLV2プラグイン移植プロジェクトでも、たまにlibandroidaudioplugin.soのAPIを参照しているものがあり(主にAssetManagerのやり取りのため)、これもビルド時に-lオプションで解決できる必要があるのですが、これも使用しているAPIのABIが変わることはそうそう無いだろうと判断して、GitHub上でリリース済みのandroidaudioplugin.aarを適当にダウンロードしてきて展開してそれを参照することにしました。これもやっつけポイントでPrefabがまともになれば不要なやつです。

参照をまともに解決する仕組みが無いだけでここまで面倒なことになるとは…という感じですが、これまでもネイティブコードのAndroid対応ビルドの状況は惨憺たるものだったので、とりあえず自分流でもレシピが出来ただけでまずは上々だと思っています。

ビルド時間短縮と開発作業が継続可能な構成

だいたいこのようなアプローチでaap-lv2リポジトリを分割し、参照を編成し直したことで、およそ以下のような手順で、整合性のあるビルドに基づいて変更の動作確認ができるようになりました。

  • android-audio-plugin-frameworkの変更はトップレベルでチェックアウトしたものをビルドしてpublishToMavenLocalでローカルのMavenリポジトリに置く。
  • aap-lv2やプラグイン側のプロジェクトではAndroid Studioなりgradlewでビルドするようにする(トップレベルのmakeはsubmoduleのandroid-audio-plugin-frameworkをビルドしてpublishToMavenLocalで上書きしてしまうので呼ばない)

これでおよそ技術上の課題がおよそ解決したようにも思えますが、実際にはそんなことはありません。リポジトリとパッケージが「適切に分割された」状態のままでは、LV2プラグインの移植をデバッガで追跡できる範囲が非常に限定的です。リファクタリングの成果は実際のコーディング作業を継続的に行える構成になっている必要があります。

Prefabでも挙がっていて、Android NDKまわりで永遠に進展が見られない課題のひとつとして「参照パッケージにソースを同梱しておいてそれらもデバッガーで追えるようにしたい」というものがあります。つまり、今はできません。

共有ライブラリになってしまっているものをデバッグできないというのであれば、ソースを参照できるかたちにする必要があります。そのためには、まずaap-lv2のandroidaudioplugin-lv2モジュールの参照を、project(...)形式で別途チェックアウトされているソースへの参照に置き換える必要があります。これでaap-lv2自体に含まれるソースはデバッグできるようになります。

実際には、これでもまだデバッグ作業はすぐ行き詰まります。というのは、serd/sord/sratom/lilvのソースに踏み込むには、android-native-audio-buildersでビルドされる以前のソースでもステップ実行できる必要があるためです。これはもうlibandroidaudiopliugin-lv2のCMakeLists.txtに手を加えて、これらをソースから直接ビルドするようにしています。そのために必要な手作業もおよそかき集めてスクリプトひとつで移行できるようにしました(戻すのは面倒なのでgit reset --hardで)。

実のところこのモードでLV2 toolkitの全てをフルビルドしてもそんなに時間がかからないので、いっそこっちをデフォルトに…と考えなくもないのですが、デバッグ作業だからABIひとつだけビルドすれば足りているのであって、同じことを4つのABIでやるとまあまあの時間になってCI待ちの体験は悪化するな…と思っています。まあとはいえ開発体験のほうは良いので迷い中です

その他のハマりどころ

…もいろいろ書こうと思ったのですが、もはや覚えていないこともいろいろあってまとめきれる気がしないので、とりあえず覚えている躓きポイントをいくつか書いておきます。

  • Android Studioのネイティブデバッグ: 基本的に4.1以前はC++ソースのデバッグは出来ない前提でいたほうがよい。Arctic Foxを使う。
  • CMakeLists.txtで参照しているファイルに変更を加えてもビルドがトリガーされない場合がまあまあある
  • 共有ライブラリが更新されないままデバッガが起動してデバッグ作業が妨げられることがままある。手動でアンインストールしてからデバッグするのが安牌っぽい。
  • デバッガが接続できないままタイムアウトすることがまれによくある。ASを落としてもダメなことが多い。プロジェクトの.ideaを消すとほぼ直る。
    • ASを落とす時はkillall -9 javaでGradleデーモンを全部殺す。

今後の課題

AAPの開発はリグレッションを起こしながらだとなかなか進みません。ホストの開発において安定的に動作確認に使えるプラグインは必須ですし、逆もまた真なりです。LV2でうまくいってもJUCEでうまくいかない、みたいなこともあってまだ治安が悪いので、今度はaap-juceの大規模改修に取り掛かる必要があるでしょう。とはいえ、aap-lv2までが少なくともCIで見えるところまでのレベルでは正常化できたのは大きなところです。

実のところCIが整備されただけでは不十分で、複数のリポジトリに散在するプラグインを全体的にテストする仕組みが必要なのですが、まだテスト自体ろくに出来ていない状態なので(そもそもホストとプラグインを入れないとテストできない)、テスト実行できるためだけのアプリケーションを作って、アプリもapkをリリースから引っ張ってきてインストールしてinstrumented testsを実行する、という感じでしょう。

その他の作業記録

AAPのLinuxデスクトップビルドの復元

AAPプラグインの移植・開発をAndroid上でやるのはしんどいので、デスクトップでも可能にするという前提でビルドできることにしているのですが、まずデスクトップ用のAAPコアのビルドを再整備して、ローカル環境に~/.local/lib/aap/などにインストールされているプラグインを列挙できるところまでは実現しました。

ただ、プラグインをホストと接続するためには、デスクトップ環境にはAndroid Binderがないので無理があります。GitHubではBinder for Linuxというプロジェクトが見つかるのですが、試した範囲では最新のforkでもUbuntu 20.04ではビルドできませんでした。まあこのアプローチだと、コードの再利用は楽になりますが、Linuxでしか動作しないことになってあまり将来性が無いですね。

というわけで、gRPCでも使って通信部分を抽象化することにしました。それで仕組みだけは実装してみたのですが、Android Serviceにintentを発行してソレに基づいてプラグイン側のアプリケーションのプロセスを立ち上げる部分はまだ面倒なので未着手で、結果的にまだ何も出来ていない状態です。気が向いたら先に進めるでしょう。

あとaap-lv2やaap-juceもデスクトップで使えるように実装対応する必要があるのですが、何もやっていないので、実態としてはまだ使えるプラグインがほぼありません。まあ使えるホストが先にほしいですね。

Jetpack Compose実験

ビルドのリファクタリングとは別に、AAPのホストアプリケーションのサンプルをJetpack Composeで組み直そうとしていたのですが、Jetpack Composeを利用する上で必須のバージョンとなるAndroid Studio 4.2(でしか使えないAndroid Gradle Plugin)…から2020.3.1となったArctic Foxがまだまだバグバグで、ろくにデバッグできない状態だったので、issuetrackerで報告するためにバグ再現条件を調べたり修正を待っていたら、12月も上旬が過ぎていました。

何やら最新版でもまだしんどい場面があるみたいですし、その間にリファクタリング作業がメインになったので、こっちはそのうち再開するだろうという状態です。

Jetpack Compose for Desktop実験

Jetpack Composeに手を出したのは、これならJetBrainsのCompose for Desktopで動作させればデスクトップでも同じホストやプラグインUが動作するんじゃないかと思ったからなのですが、ちょっと調べてみると、Architecture Componentsに依存しているうちは移植性が無いし、自分のアプリだけ対応すればいいとしてもJetpack Composeの典型的な参考資料がほとんど使えない(たとえばStateに関するドキュメントがいきなりLiveDataに依存している)という状況なので、今手を出すならAndroid専用ということにしよう…となりました。

特にCompose for DesktopプロジェクトをKotlin MPPで構成していると、IDEAでもcommonモジュールをデバッグ実行できないみたいですし。この方面ではまだFlutterを使ったほうが安牌っぽい気がします(とはいえKotlin用channelとC++ffiの口を用意するのも面倒なので多分使わないと思う)。

Compose for Desktopまわりで調べたことなどはTechBoosterの新刊Up To 11に記事として収録されています。ただホントにうっすらとした記事なので、いつもの「これ以上調べてる人はおらんやろ…!」みたいなノリではないです。ご了承ください(?)

techbookfest.org

「DAW・シーケンサーエンジンを支える技術」第2版リリース

昨年M3 2019秋で刊行した「DAWシーケンサーエンジンを支える技術」の第2版を改訂版としてリリースします。第1版のPDFをお持ちの方は後述の方法ですぐ入手できます。

技術書典10でも新刊とみなされて販売されます(第1版の書籍を第2版としてアップデートしたためですね)。既に購入いただいている方はアップデート版を無償でダウンロードできるようになるはずです(もうなっているかも。わたしには確認できないので実態はわかりません…)。

techbookfest.org

boothで販売中の電子版も更新してあります。第2版のデータを追加したので、別途ダウンロードできるようになっているはずです。

xamaritans.booth.pm

第2版は72ページとしていますが、印刷と同じ数え方でいえば76ページ、前回の56ページから3割増といったところです。主な加筆部分はプラグインGUIのインタラクション、オーディオグラフの実装技術、サンドボックス機構の説明などです。どちらかと言えば少し散逸気味だった初版の構成を見直して、より体系的に内容を編成した部分が大きいです。文字が多くて理解が困難そうだった部分にもいくつか画像を追加しています。

今回は電子版のアップデートのみです。第2版を印刷して販売する予定はありません。かなり客層の狭いトピックを扱っていて、第1版は実のところまだ印刷版の在庫があって印刷コストを回収できていないくらいなので、これで第2版も印刷するというのはいくら何でも道楽が過ぎ経済に対して失礼すぎるというものでしょう。紙版は電子版のおまけ、いわばグッズなので古くても問題ないという方向けに引き続き初版をboothでも購入でき、紙版購入者の方には第2版のPDFをメッセージにてお渡しする運用とします。紙の旧版はこちら:

xamaritans.booth.pm

実はこれでもまだフォローできない層がありまして…紙の印刷版をM3会場なりboothなりで購入いただいた方には、個別にダウンロードリンクやそれが印刷されたQRコードをお渡ししていたので、簡単にアップデートができません(アップロードしたものも古い版を上書きできない/するのが適切とはいえないので)。やむを得ず、第1版PDFをお持ちの方ならmd5sumコマンドで生成できるハッシュをパスワードにしたzipファイルを公開します*1md5sum sequencer-book.pdf のように実行してその出力をパスワードとして使ってください。これでもよくわからない、あるいはどうもうまくいかなかった、という方は、mastodon.cloudなりTwitterなりfacebookなりでatsushienoを探してコンタクトしてもらえれば対応いたします。

https://drive.google.com/file/d/1gcT64EjGLwcg5p_E2GbrwayddYzjyUrI/view?usp=sharing

また、第2版で加筆・修正した部分を知りたいという方は、大変見づらいですがMarkdownのdiffを公開するので、そちらを参照してください。見づらいのはセクションを割とアグレッシブにまるっと移動したり、その結果として実質的に重複していた内容を削ったりなどしたためです…第1版よりはだいぶ読みやすくなったと思うので、diffの読みづらさはご容赦ください。

https://gist.github.com/atsushieno/11cf7e8ba46bfbfdce3638ff9ab91eac

なお、12/26から始まる技術書典10ですが、今回はこの本のほかに純粋な新刊を出す予定はありません。前回から3ヶ月程度しか経過しておらず、これで同人誌の執筆ばかりしていたら本来やるべきコーディングが進まないので、ソフトウェアの開発や創作がある程度満足できるところまで進まないうちは、それ以外の活動は気分転換になる時だけ && イベントの締切までに完成させることを絶対に目標にしないということにしました。書き物は完成した時が出せる時であり、イベントはそれに従うものです。宣言しておかないとまたイベントが近づいた時に同じことを繰り返しかねないので…

まあ前回のMIDI 2.0本は1週間くらいで8割方書いてたので、それくらい時間が取れれば十分なのですが、その時にネタがあるか、その時に無人の荒野を切り拓きたい気持ちがあるか(今はあんまし無いです。そうでなくても日々の生活が他人から断絶しているのでもうお腹いっぱい)、といった諸条件に依存します。

*1:わたしに見える場所でmd5sumが公開されているのを発見したらその時点で削除してどこで誰によって公開されていたかを告知します

prefab: Android Studioの最新ネイティブライブラリ依存解決機構

Android Advent Calendar 202020日目はAndroid NDK方面で今開発が進んでいるPrefabパッケージ機構についていろいろまとめます。タイトルに書いている「ネイティブ」とはC/C++等のコンパイラが生成するCPUネイティブなコードの意味です*1

目次

Prefabとは?

Prefabとは、Android NDKを使ったネイティブコードを活用するアプリケーション開発の世界で新しく登場した、C/C++などのコードのビルドでサポートされるパッケージ解決のための仕組みです。Android Studio 4.0がリリースされた時にアナウンスされた新機能のひとつです。

android-developers.googleblog.com

その後Android Studio 4.1のリリースでもこれが大幅に改善されています。 ("Export C/C++ dependencies from AARs" のセクション)

android-developers.googleblog.com

Android Studioでは現在、(このPrefabに限らず)ネイティブコードサポートの機能がどんどん進化しており、現在Canaryビルドとして開発されているAndroid Studio 4.2 Canary…からArctic Foxこと2020.3.1 Canaryへと改名されたリリースでは、ついにCMakeを使っているプロジェクトでLLDBを使ったネイティブコードのデバッガーがちゃんと機能するようになりました*2。NDKを使っているアプリ開発者はArctic Foxを使うようにしたほうが多分ずっと開発体験が良いです(まあバグを踏む可能性はあります)。

AARパッケージの仕組みと、それだけでは足りない部分

Android SDKとKotlinでアプリ開発をやっている人は、日頃からAARやJARを使って(build.gradle上でdependenciesを指定して)開発していると思います(そうでない人は滅多にいないですよね…? *3)。これは全てMavenの仕組みに乗っかっています。

Mavenは元来JavaOracleなどのやつ)のJARパッケージの依存関係を解決するものでしたが、Androidのパッケージ依存関係も基本的には同様に解決できるので、Mavenが使われるようになりました。Androidのプラットフォームに近いJetpackなどのパッケージはmaven.google.comで指定しますが、他のライブラリはけっこうBintray jCenterから落としてきますね。

さて、これはあくまでKotlinあるいはJava言語のパッケージの話であり、Android NDKを中心とするC/C++その他のネイティブコードの世界に当てはまるものではありません。C/C++のビルド機構とKotlin/Javaのビルド機構は前提が全く異なります。

たとえば、C/C++ではヘッダファイルを#includeで指定する必要がありますが、これらは既存のAARには含まれません。AARにはjniLibsにネイティブ共有ライブラリそのもの(*.so)を含めることができますが、ヘッダファイルはアプリケーション開発者が別途何らかの手段で入手しないと、そのネイティブライブラリに依存するコードをビルドできません。また、共有ライブラリを参照してリンクする際にはその共有ライブラリのファイルも必要ですが、ライブラリのパスを適切に解決するやり方が、ndk-buildの仕組みから少しでも外れると存在しません。

Android NDKを使った開発はCMakeを使ったプロジェクトモデルにシフトしており、CMakeでまともにヘッダファイルやライブラリファイルのパスを指定する手順が無いことが、特にNDKで既存のC/C++資産を使う上での障害となっていました。そもそも、Android向けにライブラリのパッケージが存在するのであれば、clangに-Iだの-Lだのが渡されるようにアプリ開発者がいちいち指示せずとも、パッケージをdependenciesに指定するだけでよろしく解決してほしい、とアプリ開発者なら思うところです。

さすがにdependenciesを追加するだけで自動解決というのは無理(不適切)なのですが、dependenciesを追加してCMakeLists.txtにちょっと依存パッケージの記述を追加するだけで解決してしまう、というのが、今回のトピックであるPrefabの機能です*4

ちなみに、これまではじゃあどうしていたのかというと、ヘッダーファイルを直接リポジトリに取り込んだり、Androidでもビルドしやすいようにビルドスクリプトをndk-buildやAndroidのCMakeサポートでもビルドできるように書き換えたり、AGP (Android Gradle Plugin) が展開するビルド中間ファイルbuild/intermediates/... 以下のパスをCMakeLists.txt中でlink_directories(...)で指定したり(!)していたわけです。当然ながら本家がバージョンアップすると動かなくなることもよくあるわけで(ビルドエラーで済めばまだいいけど実行時に初めて気づくような問題になったりとか…)、メンテナンス性が下がるわけです。

Prefabツールによる状況の改善

prefabメタデータとprefabツールの役割

Prefabは、アプリケーションあるいはライブラリであるDerivedがライブラリであるBaseに依存するとき、BaseのAARに含まれるネイティブコードBase-nativeに依存するライブラリDerived-nativeを、その他の外部のファイルに依存せずにビルドできるようにします。そのためには、Base-nativeC/C++ヘッダーはDerived-nativeのコードに記述する#include ...を解決するためにほぼ間違いなく必要になるので、BaseのAARの中にバンドルします。

AARをビルドするAGPとしては、Base-nativeでビルドされるファイルのどれがDerived-nativeのビルドやパッケージングの際に必要となるかわかりません。ヘッダファイルにも、公開APIを定義するものとそうでない内部実装むけのものがあり、機械的には判別できません。内部実装用ヘッダーはよくincludeではなくsrcディレクトリに含まれているものですが、あくまでパターンであり、公開機能と非公開機能など他の分け方になっていることもあります。またpkg-configをサポートしているライブラリは、利用する側のライブラリなどでもpkg-configでパッケージを解決する前提でlib/pkgconfig/*.pcファイルを含める必要があります。つまりC/C++ヘッダーだけを含めれば良いというものでもありません。

そういうわけで、これを指示するprefabビルド用メタデータBaseのビルドのために必要となります。これはJSONで記述されるもので、その内容は、 https://google.github.io/prefab/ で詳しく説明されています。ただし、このgithub pagesで書かれている情報のほとんどは、prefabコンソールツールの使い方やメタデータのフォーマットに関するものであり、われわれアプリ開発者にとっては無関係ですアプリ開発者はむしろ(後述する)Android Studio 4.1以降でサポートされているbuild.gradleの書き方を知っている必要があります。今回ここでprefabのJSONフォーマットについて説明することもありません。

PrefabはAGPでサポートされていますが、prefabツールは独立して呼び出してビルドシステムが必要とするファイルを自動生成するように作られています。このツールにおける「ビルドシステム」とはCMakeとndk-buildのことであり、生成されるのはCMakeのfind_package()で参照できるパッケージの*.cmakeファイルであったり、ndk-buildでAndroid.mkからインクルードできるスクリプトであったりします。

AGPは、build.gradleに記述されているprefab関連のセクションの内容をもとに、cmakendk-buildを呼び出すより前にprefabツールを呼び出して、生成されたファイルをこれらのツールで参照できるようにコマンドライン引数などを調整します(cmake -DFIND_ROOT_PATH=... など)。

Prefabアーカイブの汎用性

ちなみにPrefabパッケージはAARですが、ディレクトリ構成としてはトップレベルにprefabというディレクトリが一定の内容・構造で存在し、必要なメタデータテキストさえあればよい、という程度で、JARファイルすら存在する必要がありません。実際、GoogleのオーディオI/O用ライブラリOboeのパッケージにはJavaやKotlinのライブラリコードが含まれません。

Prefabパッケージの配布形態はAARであり、AARは単なるzipなので、prefabツールなどを用いずに自分でヘッダー、ライブラリのバイナリ、メタデータなどをアーカイブしても良いです。例えば、Prefabを具体的に使っている例としておそらく最もポピュラーなのはOboeだと思いますが、Oboeのprefabパッケージングはシェルスクリプトで行われています。

https://github.com/google/oboe/blob/master/prefab_build.sh

また、Microsoft/vcpkgにはPrefabパッケージをビルドする機能も追加されています。

vcpkg.readthedocs.io

一方で、パッケージを消費する側を担うprefabツールはndk-buildとcmakeのみを対象としていますが、Prefabパッケージのファイル構成やメタデータの仕組みそのものは特定のビルドシステムを前提としない作りになっているので、prefabツールと同様にメタデータから各ビルドシステムが期待するパッケージ解決ファイル等を生成するツールを用意すれば、他のビルドシステム(IDE固有のものなど)からも利用できるかもしれません。

モジュール

1つのPrefab AARパッケージに複数のネイティブライブラリを含めることもできます。Prefabパッケージ1つには、1つ以上のモジュールが含まれ、この1つ1つがネイティブライブラリ1つ1つに対応します。パッケージのユーザーは、全部ではなく一部のモジュールのみをCMakeのfind_package()などで依存関係として使うことができます。

複数のライブラリの依存関係をスマートに解決するには、1つのPrefabパッケージの中に複数のモジュールを無用にバンドルしないほうが良いでしょう*5。たとえばGNOMEちほーで使われているglib/atk/pango/gtkを全部まとめて1つのパッケージとして配布するのは多分アンチパターンです。一方でglibとしてひとまとめで配布されているものをgobjectとgmoduleglibと…のように共有ライブラリ1つ1つに分けて配布するのもやり過ぎでしょう。

ちなみに本稿執筆時点でglibのPrefabパッケージは存在しません。glibをAndroid向けにビルドするだけでもそれなりに大変なので…参考までに、5年前に書いた記事を貼っておきます。(この頃からNDKとライブラリのパッケージ解決は問題だったという問題意識の共有も含めて…)

qiita.com

Android StudioでPrefabパッケージを使う・作る

現在のAndroid Studioの安定版であるAndroid Studio 4.0以降では、Prefab AARパッケージに含まれるヘッダーファイルやライブラリを展開してアプリケーションから(別途ヘッダーファイルを用意したり不安定なintermediatesのパスを指定したりすることなく)合理的なスクリプトでビルドできるようになっています。またAndroid Studio 4.1以降では、AndroidのライブラリプロジェクトをPrefabとしてビルドできるようにもなりました。

以下に、Prefabによるビルドを有効にしたい場合にbuild.gradleに追加すべき内容を説明します。Kotlin scriptで書いている場合は適宜読み替えてください。(1-a) (1-b)のいずれかと、(2)が必要になります。

(1-a) AGPでPrefabパッケージを使いたい(ネイティブコード依存関係を解決したい)場合は、buildFeaturesの一部としてprefabオプションを有効にする必要があります。

android {
    ...
    buildFeatures {
        prefab true
    }
}

これを有効にしない場合でも、アプリケーション側にネイティブビルドでこのライブラリに依存する部分が無いのであれば、そのprefabパッケージのAARにjniLibs含まれていれば特に問題なくビルドできます。この条件が後で割と問題になってくるのでですが、今はとりあえず気にしないでおきましょう。

(1-b) AGPでPrefabパッケージを作りたい(ライブラリを提供する側としてビルドしたい)場合は、今度はprefabPublishingという機能を有効にする必要があります。そして、prefabセクションでモジュールごとセクションを作り(以下の例ではmylibrary)、そのモジュール名をname、ヘッダーファイルを含むディレクトリをheadersとして指定します。

android {
    ...
    buildFeatures {  
        prefabPublishing true}  
    }
    prefab {
        mylibrary {
            name 'foobar'
            headers '../../external/foobar-master/include'
        }
    }
}

(2)そして、もう1つ実質的に必要になるのでやっておくべきこととして、CMakeビルドのオプションとしてANDROID_STL=c++_sharedを指定します。ライブラリ提供者と利用者の両方で行う必要があります。

android {
    ...
    externalNativeBuild {  
        cmake {  
            arguments "-DANDROID_STL=c++_shared"  
        }
    }  
}

パッケージの作成と参照の解決はこれで出来るようになりました。参照する側はさらにCMakeLists.txtAndroid.mkに修正を加える必要があります。Android StudioでCMakeサポートが正式に含まれるようになっている現在、ndk-buildを使う意義はほぼ無いので、ここではCMakeでのみ説明しますが、追加で必要になる記述はこの程度です。

find_package(mylib REQUIRED)
add_library(myapp SHARED app.cpp)
target_link_libraries(myapp mylib::mylib)

PrefabとAGPのPrefabサポートの現在の課題

AGPのPrefabサポートは、まともな問題意識と解決のためのアプローチを採用している側面があり、一見魅力的なのですが、現時点ではPrefab機構の設計に影響するレベルでいくつか問題を抱えており、安定版に含まれている機能だからといって全面的に依存できる気がしない…というのが、現時点でのわたしの評価です。

以下に代表的な問題点をいくつか出しておきます。全て、自分のプロジェクトをPrefabに移行しようとして直面した問題です。あくまで本稿執筆時点での問題なので、近い将来に解決する可能性があります。(解決してほしい…)

build.gradleでヘッダーディレクトリが1つしか指定できない

prefabPublishingのモジュール別のheadersプロパティは単一の文字列で、複数のディレクトリを指定することができません。これはUnix系ライブラリでいえば「利用者に提示できる-Iオプションを1つしか指定できない」というのに等しい機能制限です。

Prefabパッケージとnon-Prefabパッケージの排他的関係

現在、AGPのNDKサポートでは、あるファイル名をもつネイティブライブラリが複数のAARに含まれていると、内容の競合であるとしてビルドエラーとする仕組みになっています。

現在、Prefabパッケージには、prefabディレクトリ以下にCPUアーキテクチャ別に格納された共有ライブラリと、従来型のjniLibsディレクトリの両方に、同じファイル名をもつ、内容のリンクのされ方が少し異なるものが存在しています。実は、prefabbuildFeaturesで有効にしたアプリケーションでこのAARを参照すると、これらの2つのディレクトリそれぞれの内容が参照解決時に競合となってビルドエラーとなるという(割と信じられないレベルの)問題があります。

この問題を解決するには、build.gradleでjniLibsに一切共有ライブラリを含めない設定にする必要があります。

android {
    ...
    packagingOptions {  
        exclude '**.so'
    }
}

jniLibsに共有ライブラリが含まれないとどうなるのか? 当然ながらPrefabを有効にしていないアプリケーションでUnsatisfiedLinkErrorが起こることになります。あるいはビルド時に何らかの方法で-Lのパスを指定していたらリンク時にundefined referenceになります。

この問題は一見ささいな実装不足ですが、どう解決するのが妥当なのか…と考え出すと、割と面倒な問題でもあります。あと開発チームがNDKチームとAGPチームで分かれて全然問題意識が共有されていなさそうでもあります。絶賛放置されていますし。

全てのネイティブライブラリで同一のlibc++_sharedを使う必要がある

前項の問題と関連する…というよりはこちらのほうが問題の出発点だったのですが、Prefabを使う場合はSTLについてlibc++_sharedを使うことを前提として共有ライブラリをビルドすることが(実質的に)求められます。というのは、AGPlibc++_staticを使ってビルドされたライブラリを全てサポート外として却下するからです。なぜlibc++_staticがサポート外とされるかというと、簡単にC++ODR (One Definition Rule)に違反するような競合を引き起こすからです。

STLはCMakeの場合は-DANDROID_STL=...オプションで指定できます(externalNativeBuild.cmake.arguments)。ちなみにデフォルトの値はc++_staticです。つまり明示的にc++_sharedを指定する必要があります。*6

android {
    defaultConfig {
        externalNativeBuild {  
            cmake {
                arguments "-DANDROID_STL=c++_shared" (, ...)
            }
        }
    }
}

Android NDKにおけるSTLの扱いの詳細についてはこのページを見ると良いです。

developer.android.com

これはおそらくクロスコンパイルによってアプリケーションをビルドする開発環境では多かれ少なかれ同様の問題があるのだと思いますが、Prefabの場合は全てのAARにlibc++_shared.soが(packagingOptionsで排除しない限り)含まれることになるので、"one STL per app"のルールに照らして、1つのlibc++_shared.soを「選ぶ」必要が生じてきます。しかし、NDKのリビジョンが変わる度に別々のlibc++_shared.soが含まれてきて、それがAARにバンドルされてくるとなると、厄介な問題になります。

ライブラリはさまざまな開発者が提供しさまざまな状況のアプリ開発者が利用することになるので、何かしらの包括的な手当てが必要だと思うのですが、現状そういう手当ては何も実装されていません。このリンク先のドキュメントには「ReLinkerを使うとよい」とあるのですが、それならAGPのNDKサポートのビルドステップとして実装しておいてほしい…

staticビルドがサポートされない vs. JNIを利用するコードはstaticでビルドしなければならない

Prefabはlibc++_sharedを前提として設計されているのに、JNIを使っているライブラリではこれを使ってはならないという制約があるようです。

Caution: JNI libraries distributed with Java AARs must not use the shared runtime to avoid conflicting with other libraries and the app. The warnings below still apply. See the documentation for Middleware Vendors for more information.

もう完全にどうしたらいいのかわからない。

ndkports

この他に、GoogleではNdkPortsというプロジェクトで既存のUnix系ライブラリを移植して蓄積したいという目論見があるようです。ndkportsはPrefabの公開時点でAOSPで公開されており、jsoncpp, openssl, curlの3つが含まれていました。そして現在は…

https://android.googlesource.com/platform/tools/ndkports/+/refs/heads/master/ports/

jsoncpp, openssl, curlの3つが公開されています。つまり…開発が進んでもいないし、誰も貢献していません。これ、冒頭のAndroid Developers blogの投稿で「パッチを送ってください」って書いているくらい開放していて、わたしも一度これでglibをビルドしてみようとしたのですが、自分でコマンドを呼び出す以外で出来ることが何もなく、まともに依存関係が解決できなさそうだったので、これは忘れてもよさそうです。Googleは以前にもCDepというプロジェクトでポシャっています。

build.gradleで project(...)で参照している同一プロジェクト内のモジュールへの参照解決が実装されていない

Prefabモジュールを書いて、そのサンプルアプリのモジュールを同じプロジェクト上に作るのはとても一般的で、その際にはproject(...) でライブラリを参照することになりますが、現状のAGPではこのprefabの展開が実装されていません。つまりサンプルプロジェクトがまともに作れないわけです(!)

https://issuetracker.google.com/issues/175291074

こんな根本的な問題がなぜ放置されているのか理解に苦しむレベルですが、要はこれまでのネイティブコードのプロジェクトでは(多分)すでに力技で解決してきているので、破壊的に変更されているわけではないからでしょう。

補足: Prefabトラブルシューティング

…は、書ききれなかったので、この辺の知見は、もしちゃんとまとまったら技術書典10なりzennなりで小冊子にでもして出したいと思いますが、とりあえず困ったらbuild/intermediates/cmake/debug/obj/armeabi-v7a/prefab_stderr_cmake_armeabi-v7a.txtみたいなファイルを(自分のABIに合わせて)読んでみると良いでしょう。

*1:FlutterやXamarinなど一部界隈でKotlinやJavaのことを(プラットフォーム中立なバイトコードと対比する意味で)「プラットフォーム・ネイティブ」などと呼んでいるのとは全然違います。

*2:多分これ以前はCMakeLists.txtを辿ってデバッグ対象のソースファイルを発見することが出来なかったんじゃないかと想像します。

*3:Bazel使いとか、クロスプラットフォームSDK使いでビルドスクリプトをGradle無しで自作する必要がある人とか? 自分がかつて部分的にそうだったわけですが…

*4:Prefabは、prefabコマンドツールの名称でもあり、Android Gradle Plugin(やAndroid Studio)がサポートする機能の名称でもあるのですが、前者の意味で使われる場面はめったに無いので、以降は基本的に機能の名称と思っておけば大丈夫です

*5:複数の応用ライブラリが別々のモジュール依存関係のツリーを構築して、ライブラリのユーザーの手元でバージョンの異なるものが競合しやすくなります

*6:こんなデフォルト値に何の意味が??と思われるかもしれませんが、今デフォルト値を変えたら従来のアプリのビルドが阿鼻叫喚になるわけで…

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相当のフォーマットが無いけど)

JUCEで作られたOSSオーディオプラグインをかき集めてみた

JUCE Advent Calendar 2020 3日目のエントリーはゆる〜く自分がこれまでかき集めてきた「JUCEで作られたOSSのオーディオプラグイン」をいろいろ紹介します。

目次

Rationale

そもそも何でそんなのを集めているのか?という話ですが、単に趣味で集めていたわけではありません(いや、あるか?)。まずChrome*1でこのページを見てみてください(宣伝)。

juce-demos.atsushieno.dev

わたしがちょっと手伝っていたjuce_emscriptenのデモとして、世に出回っているJUCE系アプリケーションをWeb Assemblyアプリとして動かしているものです。ファイル関連APIなどいろいろな機能制限もあるため、完全なかたちではないにしても、さまざまなアプリケーションが既に動いています。作ったのは今春くらいなのですが、juce_emscriptenも特に進化していないので*2これが最新版という感じです。

もうひとつ、Android用に自作しているオーディオプラグインフレームワークがあって、JUCEからの移植もサポートしていて、少し手を加えるだけでJUCEプラグインのオーディオ部分を鳴らすことができるものが少なくないので、気が向いた時に動かせそうなものを探しています。

github.com

もちろん、自分のLinuxデスクトップでも使えるオーディオプラグインは常に探しています(何しろ選択肢が少ないので)。わたしのようなユーザーにとっては、ソース付きで公開されているJUCE系のプラグインは良い狩り場(?)になるのです。大概はjucerファイルをちょっと加工すればLinuxでもビルドできるようになるので。

そんなわけで、JUCE系のプラグインやアプリケーションをいろいろ探しては試す癖がついていました。それなりに使えそうな知見になってきた気もするので、この機会にいろいろ紹介しようと思います。こういう契機なので紹介内容はOSS…少なくともソースコードのあるソフトウェア限定です。最近リリースされたVitalとか、(juce_emscriptenの原点である)Synthesizer Vとかは含まれません。

サンプラー → シンセ → FM/PSGエミュレーターエフェクター くらいの雑な順番になっています。

Birch-san/juicysfplugin

github.com

Fluidsynthを経由してサウンドフォント(SF2/SF3)をサポートするサンプラー音源です。MIDI音色に相当するインストゥルメントプラグインとして使うにはコレが手っ取り早くて助かります。SF2はかなり古いフォーマットですが、まだ使われることもあるようです(やや練度の足りない内容ですがこれも2年前にまとめてあります)。Fluidsynthは今でも開発が継続していて、最近ではMIDI 2.0 UMPサポートなども実装されつつあるようです。

移植性の観点では、まずfluidsynthのビルドを取り込まないといけないところで躓くことになるかもしれません。Androidビルドは公式にCIがあるので(わたしがAndroidサポートを追加したときに書いたビルドスクリプトをCI設定してくれた人がいたらしい)、そこから引っ張ってくるのが安牌です。また割と無邪気にFluidsynthを共有ライブラリとして参照解決するので(互換性のないバージョンを参照したり、共有ライブラリのロードパスの関係で問題を引き起こしたりするので、依存関係はなるべく静的に取り込んでビルドするほうが好ましいです)、ホストによってはロードできずに落ちたりすることもあります。

サウンドフォントでよく言及されるのはDebianなどで配布されているFluidR3_GM.sf2ですが、探すと他にもいろいろ見つかります。自分の場合は最近は musical-artifacts.com で見つけた https://musical-artifacts.com/artifacts/1057 を使うことが多いです。

osxmidi/SFZero-X

github.com

SF2ではなくSFZのサンプラープラグインとして作られているのがSFZeroです。osxmidi/SFZero-Xは今では開発されていないオリジナルのstevefolta/SFZeroからの派生版で、音源部分はSFZeroModuleという別リポジトリに含まれているsfzeroという非JUCEモジュールの実装です。

ただ、SFZフォーマットは現在でも継続的に開発されている現役のフォーマットなのですが、SFZeroの音源部分はもう5年近く開発されていないので、わたしがSFZサンプラーとしてSFZero-Xを推薦することはないです。サポートされていないSFZ opcodeも数多くあります。今一番アクティブに開発されていてopcodeのカバレッジも広いOSSsfztools/sfizzですが、これは今はVSTGUIでUIが作られているVST3/LV2音源なので、今回はSFZero-Xの紹介としました*3。どうしてもJUCEでやる必要がある/移植する必要がある場合はこちらでしょう。

TheWaveWarden/odin2

github.com

モダンでクールなGUIを備えた高機能シンセサイザーです。JUCE6で全プラットフォームVST3に対応しているほか、LV2 Porting Projectを使用したビルドにも考慮しています(README.mdで詳しく説明されています)。Surgeと同様Odin2もOne Synth Challengeで数多くの楽曲が作られています

プリセット音色もたくさん用意されているのですが、現状のビルドではバンクに相当するodsファイルが何もビルドされず、ユーザー定義音色の格納場所についても問題を抱えているので、プリセットを1つ1つ手作業で選択していく作業が必要になっています。この辺りが(多分)次のリリースで改善されるとだいぶ実用的になると思います。

f:id:atsushieno:20201202211921p:plain

artfwo/andes

github.com

オシレーター1つのシンプルで移植の難易度が低いシンセサイザーです。もっとも、シンプルとは言っても、このオシレーター自体はPerlin noiseをオーディオ処理に適用するという実験を実装したもので、Linux Audio Conferenceのサイトで論文(というのが適切なのかは分かりませんが)も提出されています。

移植作業の最初の一歩として試すには良い選択肢です。ただしPerlin noiseの処理にそれなりにコストがかかるのか、(自分がAndroid環境で試した限りでは)オーディオ処理はあまり軽量ではありません。素でglitch noiseが発生するほどではありませんが、組み込みデバイスなどで最初に試すのには向いていないかもしれません。

f:id:atsushieno:20201202212453p:plain

asb2m10/dexed

github.com

このはてなブログではちょいちょい登場していますが、YAMAHA DX-7のエミュレーターをなぜかGooglerがAndroid向けに趣味で(?)開発したもの*4を、外部の開発者がデスクトップでJUCEのGUIを付けて公開したものです。DX-7の音色バンクとなるCartのサポートなども追加されています。

音源側のコードであるmusic-synthesizer-for-Androidについては2年前に書いたのでそちらを見てください。

f:id:atsushieno:20201202212020p:plain

jpcima/ADLplug

github.com

これもこれまでちょいちょい登場しているFM音源(OPL/OPN/OPM)のエミュレーターです。実態はlibADLMIDIとlibOPNMIDIにJUCE GUIを付けたものと言えます。

これらのライブラリはさらに別のさまざまなFM音源エミュレーター(libOPNMIDIならNuken, MAME, Neko, PMDWinなど)を包括的にサポートしていて、エミュレーション結果が気にならなければ別のエミュレーターを試せるようになっています。

個人的には、今年はFM音源エミュレーターのコードにコントリビュートしてもsfizzのコードにコントリビュートしてもJUCE LV2のコードにコントリビュートしてもこの開発者が出てきて、この人強すぎでは…??ってなりました。

f:id:atsushieno:20201202212512p:plain

yokemura/Magical8bitPlug2

github.com

ゲーム向けのPSG音源を再現するインストゥルメントプラグインです。オシレーターはシンプルですが、ADSRのほかにLFOや自動ピッチベンドなどゲーム用途らしいパラメーターが用意されているのが、いかにもという感じの使いやすさをもたらしている音源です。

コードベースがJUCE 5.4.7と新し目なので、これも移植難易度が低く、実験には有用です。JUCE 5.4.7はアプリケーション次第では(?)そのままだとLinux gccビルドがコケる鬼門バージョンでもあるので、失敗したらCXX=clangでのビルドを試すようにすると良いでしょう。

f:id:atsushieno:20201202212112p:plain

ffAudio/Frequalizer

github.com

6点マルチバンドEQを実現するJUCEプラグインです。最初やや取っ付きにくいのですが、たぶん小一時間くらい使って慣れると、多機能でよく出来ていることがわかります。リアルタイムで入出力の波形が表示されるので、適用結果を視覚的に確認できるようにもなっています。

取っ付きにくい理由は主に(1)最初に6点それぞれのどこにコントロールポイントがあるのか完全にわかりにくい(細い縦線が見にくいのと、発見できた後もy軸の中央すなわち0dbの位置に点があるのが見えず丸型スライダーで値をずらさないと発見できない)のと、(2)制御点のそれぞれがPeak/Low Shelf/High Passなど10種類以上の選択肢があって、慣れないと意図した曲線を描けないのと、(3)初期値で6点は多分多すぎるためです。これさえ把握していれば多分何とかなります。点の多さはアクティブな点を減らすことで解決できるでしょう(Aボタンがたぶんactivateの意図なんですが、これで制御できます)。

ffAudioにはPluginGuiMagicというプラグインGUIをパラメーターから(たぶん)簡単に構築できるJUCEベースのGUIコンポーネントのコレクションもあり、Frequalizerはこれを使って構築されています。

f:id:atsushieno:20201202212151p:plain

GuitarML/SmartGuitarAmp

github.com

Googleのメディア/アート系プロジェクトに深層学習を応用するWaveNetを使ってギターアンプやペダルの既成品をシミュレートする GuitarML/PedalNetRT というプロジェクトがあるのですが、これをJUCEと組み合わせてオーディオプラグイン化したのがSmartGuitarAmpです。

紹介するのは同じpedalnetを使用している(そしてこのプロジェクトの元ネタでもある)damskaggep/WaveNetVAでも良かったのですが、新しいほうを選びました。WaveNetVAのほうはデモページでさまざまな適用結果を聞くことができます。

ただ、これは環境依存なのかもしれませんが、WaveNetVA同様、CPUコストが非常に大きく、まだまだリアルタイムでのまともな発声は無理だと思います。standaloneでビルドしたアプリでJUCEのオーディオテストがブツ切れになるレベルです。非リアルタイム用途では使えるかもしれない、といったところです。

関連: hacker newsのスレッド

surge-synthesizer/surge-fx

github.com

surgeは、旧くはVermber Audioから配布され今はOSSで開発されている多機能シンセサイザーです。surge-fxはそのエフェクト部分をプラグインとして抜き出してJUCE GUIを付けたものという位置づけになるようです。surgeはsurgeで独自にUIが構築されています。surgeは基本的にJUCEを使っていないようなので今回は紹介しませんでしたが、JUCEを有効にしたビルドオプションというものも存在するようです(patch_playerというコードもあってこれがJUCEアプリケーションになっているようですが、詳しくは未確認)。

surge-fxとして単独で使用したい場面がどれほどあるかわかりませんが、JUCE6にアップデート済なので、JUCE6を使用したビルドの動作確認には有用です。ただ、内部的に巨大なsurgeのコードを(全部ではないにしても)ビルドすることもあって、移植性は高くないかもしれません。*5

f:id:atsushieno:20201202212245p:plain

*1:もしかしたらChromium Edgeでも可かも

*2:春先に自分が手を入れたWebMIDIサポートが取り込まれたくらい

*3:以前はsfizz-juceというものがあったのですが

*4:初期チェックインの後はメンテナンスされていません

*5:半年ほど前にjuce_emscriptenでビルドしようとした時は、xmmintrin.h依存が解決できなくてあきらめたのですが、emscriptenではサポートされている雰囲気もあるので、がんばればビルドできるかもしれません。

M3 2020秋 サークル参加情報

今回も直前まで書かずに来てしまいましたが、明日10/25のM3 2020秋にサークル"ginga"として参加しています。割と最近(数週間くらい)に気づいたんですが、同人音楽サークルとしてgingaって名前だとgingaレーベルと紛らわしいんですよね…*1

追記: 素で書き忘れていましたが(!)第一展示場G-22です。Web会場も銀08としてLV2開発本のみ委託販売します*2

頒布物

音楽技術同人誌3冊の紙版を部数少なめに持っていきます。全部で50冊無いくらい。いずれもイベント価格¥500で頒布します。原価割れライン…! ダウンロード版も会場でお渡しします。

※boothにリンクしていますが、全て電子版へのリンクです。紙版はM3終了後に残った部数を設定して販売する予定です。

xamaritans.booth.pm

技術書典9で発表したMIDI 2.0 UMPガイドブックです。私の知る限り世界初です*3。それなりに売れたのですが発注部数より多く刷られていたので*4、M3でも販売できる運びとなりました。

xamaritans.booth.pm

同じく技術書典9で発表したLV2オーディオプラグイン開発者ガイドです*5Linuxデスクトップで使われている代表的なオーディオプラグイン規格の解説書として、おそらく世界でも類を見ないものだと思うのですが、これは予想通りだいぶニッチだったのでまだまだ在庫があります。*6 展示コーナーとも合わせてたのしめる本です。*7

xamaritans.booth.pm

M3 2019秋に発表した、DAWの仕組みを探るためにソフトウェア構成部品を理解しよう!という本です。これも他に類を見ない書籍として書いたつもりです。こちらは紙版も既にboothで購入できます(上記リンク先から探してください)。

xamaritans.booth.pm

2019年3月の幻想音楽祭でリリースした音楽作品です。自作コンパイラMMLとTracktion Waveform10で打ち込んだデータもお渡ししています。

展示コーナー

今回、新譜を作るつもりだったのですが、ソフトウェアが間に合わずとても着手には至りませんでした…。その話は後で書きますが、取り急ぎ代替となる展示物として、今回の頒布物であるLV2オーディオプラグインをその場で体験できるPCコーナーを設置します。*8

またPCに触れなくても、LV2プラグインにどんなものがあるのか、気軽に立ち読みできるパネル的なものを、書類ケースに入れて置いておきます。

また、これらのプラグインのいくつかはAndroidにも移植しているので、Androidエミュレーター上で試せる範囲で遊べるように環境を用意しておきます。

併せて、Ardour6, QTractor, Zrythmなど、ホスト/DAW側も、ほぼ最新のバージョンをお見せできるようにビルドしてあります。

今回展示できなかったもの

今回は、ほんとうはtracktion_engineを使いつつWaveformなしで最後まで打ち込んでAndroid上で再生できるところまで作りたかったのですが、Android上ではAudioPluginHost上でプラグインから音が出るところまでだけ出来ているという状態です…ここ2週間くらいのAndroid Studio 4.2-alpha(13, 14あたり)でネイティブコードのデバッガーが壊れていてデバッグ作業が困難なので(最近気づいたのですが、Android StudioC++サポート機能はプロプラエタリでAOSPに含まれていないんですね。原因を追求できませんでした)、いつ開発作業が再開できるのかわからない状態です。

Androidから離れて、Linux環境ではMML…とプロジェクトXMLファイル…からコンパイラとtracktion_engineだけで打ち込みができるところまでは出来ています。tracktion_engineにjlv2を追加してLV2プラグインとVST3プラグインを混ぜて利用できるようにしたので限りなく便利です。もっとも、Waveform11 on LinuxはLV2もVST3もサポートしていないので、DAWで微調整が一切できない楽曲になってしまいます(!?)

ひとつクリティカルな問題として、MMLからSMFにコンパイルして、それをJUCE AudioPluginHostのfiltergraphファイルと合わせてtracktioneditファイルにコンパイルして、それを再生するというツールチェインのパイプラインになっているため、プレイヤーが毎回オーディオプラグインをメモリ上にロードするのが重くて制作ワークフローとしてはイマイチです。これはtracktioneditコンパイラとプレイヤーを統合して、MIDIシーケンスの部分だけをhot reloadするような仕組みに作り変えれば改善する見込みです。そのためには.NETアプリケーションであるところのコンパイラをJUCEベースのコードに作り直す必要があるのでお手軽ではなく、今回は先送りにしました。

あと、前回audio plugin portabilityの問題として書きましたが、通常のオーディオプラグインはオーディオプラグインフレームワークを跨いでstateデータを再利用できるように構築されていないので、その解決策を模索していて時間切れになったこともあります。最初から移植性のあるstate情報・パラメーター情報を保存する仕組みを作ってそこに載せないとダメそうですが、これは日を改めて考察をまとめたいと思います。

この辺の話はいろいろ出てくるので、当日会場に来られる方で興味ある方がいたらぜひお話ししましょう。混雑するブースになる予定はありませんし*9

*1:うちは2004年にはてなに引っ越してくる前に使っていた名前なので多分こちらのほうが先なのですが

*2:これはWebイベントの設計が良くなくて、当日の販売状況に応じて販売数を調整できないようになっているためです

*3:最近洋書で他にも出たっぽい話を見ました。既刊本の改訂のついでのようでしたが。

*4:一般的にあることです

*5:もともとM3向けに計画して書き始めていたやつですが、技術書典9のほうが先に来たので

*6:MIDI 2.0本も原価割れなのですが、面倒なので価格を統一した感じです

*7:といっても会場で読む人はいないと思うので、展示を思い返して楽しむ感じになるかと思います

*8:広いスペースがあるわけではないので、そこはご理解ください

*9:これフラグになんねーかなー(

Q3〜10月前半の開発記録

7月の作業記録は別途書いてあって、8月の作業記録はMIDI 2.0本の告知がだいぶ持っていってしまったのですが、他のこともちょろちょろとあったので、最近までの自分用開発記録をまとめておきます。時系列は順不同です。

ADLplug/VST3 on LinuxとJUCE6+LV2

JUCEが 6.0でようやくLinux対応したので、ADLplugのようなJUCEベースのオーディオプラグインがまた本家JUCEだけでもビルドできるようになったのですが、ADLplugは独自にLV2対応を進めていたこともあって、JUCE6へのアップデートは行われていません。

このままだとVST3版は永久に出てこないことになって、LV2版を使っている自分としては特に困らない予定だったのですが、いま自分のaugeneプロジェクトでMML + tracktion_engineを使った打ち込み環境に全面移行を図っていて、そこではまだLV2サポートをホスト側で統合できていないので、VST3版が必要になってしまいました。

本家JUCEに切り替えた上でLV2サポートを切り捨ててビルドしても良いのですが、毎回ビルド切り替えのためにJUCEブランチを切り替えるのも面倒なので、とりあえずADLplugで使われているLV2サポート付きのJUCEをJUCE6にアップグレードしました。

github.com

これをもとに更新したADLplugも公開してあります。

github.com

ひとつ問題になっているのが、JUCE6にアップグレードした段階でVST2 SDKの代替として機能するVeSTigeが、既存のコードだけではJUCEのVST2 Wrapperを十分にビルドできなくなってしまったため、VST2サポートが切り落とされてしまっていることです。VeSTige、もともとはLMMSの一部なのですが、いろんなところで改良版?が作られているので、どこに使えるバージョンがあるのかよくわからないんですよね…。そういうわけでコレを使う限り & 本家VST2 SDKを使わない限り、VST2ビルドは失敗します。

あとVST3版にするとマルチチャンネルで使う時にプログラムチェンジが正しく処理されないという問題があって、これはアプリの問題ではなくVST3の仕様レベルでの問題っぽいというのが現時点での評価です。この問題があるのとJUCE6アップデートに上記の副作用があるのとで、本家にはマージされない可能性が大いにあります。作者もFM音源からは割と足を洗ってsfizzに集中しているそうですし。

fmbank

OPNplugは音色を定義して使えないとあまり意味がありません(プリセットで満足するタイプならそれでも良いのですが、わたしは他人の作ったFM音色を探すことに意義を見出せないので…)。そういうわけで、大昔にFM音源をいじっていた頃に自作していたOPNのFM音色をつかうわけですが、OPNplugなどで使えるフォーマットが全然違うので、まずはOSSでよく使われていそうなopm形式のテキストデータにしてgithubで公開しました。

github.com

FM音色データとして流通?しているものにもいくつかのフォーマットがあって、OPNplugでサポートされているのはwopnというバイナリのフォーマットのみです。この辺のフォーマットの変換にはOPN2BankEditorというプロジェクトが便利です。古いMMLデータ形式から新しいmml2vgm形式(やmucom88形式とかも)、それと各種FM音色エディタの形式がサポートされています。

github.com

ただ、エディタ上にコピペしてペーストするような作業になるので、大量に音色がある場合はコンバーターを頑張って作ったほうが良いかもしれません。わたしはコンバーターを書く時間のうちに手作業で変換が終えられてしまいそうなので手作業でやっつけました…。あとちょっと変換処理に問題があったので修正して反映してもらったりしていました。

この辺のツールの使い方は時間が無限に使えるようになったらmusilあたりに書いていきたいと思っています。

sfizz on AAP

sfzサウンドフォントをオーディオプラグインとして使えるsfizzを、Android assetsから読み込むアプローチをとりあえず一旦放棄して、ローカルファイルとして格納するようにしたら、無事プラグインとしてロードして動作するようになりました。ただなぜかホストに依存するようで、JUCE AudioPluginHostからだとどうもうまくいかないのでまだ検証が必要なやつです。

最近この辺をちょいちょいいじりながら本家のバグを見つけてプルリクなどを送ったりDiscordの開発コミュニティに顔を出したりしていたら、大して貢献しているわけでもないのにcontributorsリストに名前が載ってしまったので、もうちょっとsfzが広まるようにやっていくか…みたいなモードになっています(安い釣り針だ)。ARIA UIの実装実験もAndroidプラグインも既に彼らに知られていたので、その辺が大きかったのかもしれません。協力できるところは協力していこうと思っています。UI Standard GuitarのKSOPとかUI Metal GTXが使えるところまではもっていきたいですし(そこまで手を出せる技術知は今のところありませんが)。

ちなみにsfizzではlibsndfileに依存している部分を置き換える作業も行われていたりするので、これが通るとファイルシステム依存部分が減ってassetからのロードが無理なく実現できるようになるかもしれないので、現時点では今のコードに無理にassetサポートを追加しない方針で様子見です。(libsndfileはFluidsynthでもsf3サポートのために使われているので、libsndfileのファイル名を指定するAPIに依存しないアプリケーションコードを書くことも可能なはずではありますが。)

aria2webのスクショを見てわかる人はわかると思いますが、sfizzはKontaktの次の時代を作るOSSとして発展できるポテンシャルは十分にあると思います(SforzandoがOSSになってLinux対応できればそれでもいいのですが)。Kontakt、Komplete 13でもVST3版が出ることはなかったですし、この辺は世代交代を期待したいところです(とはいえVST3には前述の問題もあるのでVST2が使われ続けることになるのかも…それはそれで不幸な話なのですが)。現状sfzをサポートするOSSはopcodes実装がSforzandoに遠く及ばないとされていたのが、sfizzがえらい勢いで進化しているので、しばらく経つとこの辺の勢力図が変わってくるのではないかと思います。

tracktion_engine app on Android

しばらく前からaugeneというMML => MIDI + AudioPluginHost (filtergraph) => tracktion(edit) という流れのツールチェインを構築していて、最近ようやくMMLからOPNplugやsfizzでギター音源sfzの奏法をMMLで指定して演奏できるところまで実現できたので、次はこれをAndroidに移植する作業を進めていました。

tracktion_engine自体はStepSequencerDemoを動かしたことがあるので*1、基本的には移植で困ることは多分ありません。多分、というのは、StepSequencerDemo以外で使われている機能については使ってみないとわからないためです。実際、Editをファイルからロードして演奏する処理については、Android用にassetsからロードする仕組みはNDKにもJUCEにも無いので、sfizzと同様にローカルファイルストレージにいったんコピーしてからロードするというやっつけ工程が必要になりました。

あとJUCE6がCMakeに対応したので、外部ライブラリに依存しているaugeneもCMake化したほうが楽だと思って移行したのですが、Androidサポートについては、Projucerで行われているプロジェクトファイル生成はCMakeでは全くサポートされていないので、自前でテンプレートを用意するかどうかで試行錯誤した結果、とりあえず従来のProjucer方式でプロジェクトを生成しています。aap-juceのビルドシステムがProjucer前提で組まれているのが最大の理由です。CMake前提のビルドに書き換えようとも思ったのですが、移植対象のアプリがそもそもCMakeに書き換わっていない(そして古いアプリもあるので多分書き換わらない)ので、10月中は無理だと判断しました(この辺をいじっていた頃はM3出展を視野に入れていたのです)。

audio plugin portabilityの問題

Linuxデスクトップ上でMMLで打ち込んで生成したtracktioneditと、Androidに移植したtracktion_engineアプリがあれば完結するかと思われたこの課題ですが、本当の(そして現在進行系の)課題は、複数の環境をまたいで編集・再生できるポータブルな楽曲編集環境を構築するところにあります。

AAPはVST3をサポートできないので(そもそもvst3sdkがまだAndroid NDKでビルドできないので)、VST3でビルドした音源を使った楽曲がAndroid上でシームレスに演奏できることを期待するのはちょっと無理があります。正確には、音源そのものはJUCEでビルドされていてaap-juceでビルドできるのですが、AudioPluginHostで編集したfiltergraph上にはプラグインのstateが生バイナリをbase64変換して保存されており、この生バイナリのデータが他のプラグインフォーマットとは共有できません。つまり、ユーザーが打ち込んだデータのクロスプラグインフレームワーク互換性の問題です。

これは別に自分のプロジェクト固有の問題ではなくて、オーディオプラグインの世界全体の問題です。たとえばRoland Sound Canvas VAをVSTで打ち込んでいたものをAUに置き換えても互換性が無いことになっています。

本当にstateデータを共有できないのかどうか試してみたのですが、JUCEのVST3サポートの実装ではVST3固有の情報を出力に含めていて無理でした(OPNplugの実装でいえば、stateのXMLのroot要素判定に"VST3PluginState"が渡される)。手作業で回避コードを書くこともできるのですが、課題はADLplugという個別のプラグインを動かすことでもなければJUCEベースのプラグインのみパッチ対応することでもないので、そういう対応は現状考えていません。

現状で一番現実的に可能そうなのはtracktion_engineをLV2サポート付きのJUCEでビルドしてAudioPluginHostもaugeneもLV2を使って制作して、それをaap-lv2でビルドしたプラグインを使って再生する、といったところです。JUCEのLV2サポートもホスト側(juce_audio_processors)について実装されている必要があり、現状ではjlv2というモジュールしか見当たりません。ADLplugのLV2サポートなど大半のLV2サポートブランチはjuce_audio_plugin_clientのみなのです。

この問題をどう解決するか、については、そもそも音楽制作環境におけるクロスプラットフォーム・クロスプラグイン環境をどう実現すべきか?という課題について整理して別の機会にまとめたいと思っています。進展があればgithubの自分のプロジェクトである程度まとめていくかもしれません。

*1:ちなみにStepSequencerDemoはjuce_emscriptenを使ってWeb上で動作していて、tracktion_engineの開発者にも知られています