8月の開発記録 (2023)

8月は忙しかったわけではないのですが、やはり生活が変わると開発者生活に影響が出て、まあいいかとなって適当に遊んでいる時間が増えて、あんましアウトプットはありませんでした。今日もひと月暮らしていた部屋を出て夏休み旅行(とは)に出ながら書いています。そんなわけで軽くまとめていきます。

INSIDE [技術のヒミツ] @ TechBooster

TechBoosterのC102新刊で「新世代のリモートUIをSurfaceControlViewHostで構築する」というタイトルで寄稿しました。

booth.pm

内容はここで何度か言及してきたSurfaceControlViewHostについて、他のアプリケーションからUIを引っ張ってくる最先端の方法としてまとめてあります(Jetpack Glanceなんかも「最先端」っぽいふいんきを出していますが、アレは本稿でも言及しているAppWidgetなのでカウントしない)。システムとしては割と重要な機能になると思うのですが、情報源が海外ブログ1本しか無いので、日本語資料があるのは悪くないと思います。

上がってきた本を見たら半分くらいメンバーがやってきた活動のまとめみたいになっていて、わたしも無職エンジニア生活のはなしに差し替えるかと思いましたが思いとどまりました。

Resident MIDI Keyboardのリリース活動

先月末にopen testingまでやっていたResident MIDI Keyboardを、正式リリースとしました。その過程で、ようやくKVR Developer accountを作ったのですが、その時に「開発者公式サイト」とか「プロダクトのページ」とかいろいろ求められ、しかも「facebookページとかtwitterプロフィールとかはナシで」みたいになっていたので、しょうがないからこの際androidaudioplugin.orgのページをちゃんと作るか…となって、いろいろJAMStackなstatic site generatorsを調べて時間を溶かしました(まあまあ楽しい)。結局調べたことは何の役にも立てておらず(あんまりしっくり来なかった)、サイトは手書きHTML + bootstrapです。

RMKのKVRページもできました。ここにしかない情報のようなものはほぼありませんが、ここは一般ユーザー向け音楽ツールを出したら宣伝しておく場所としては定番ですね。

あと、英語ブログのほうをひさびさに更新して、RMKとComposeAudioControlsとついでに最近のAAP GUIの開発状況を、ざっくりまとめていました。

AAP: refresh plugin manager

AAPの現在の主要なマイルストーンは「プラグインの入出力を整備してちゃんとインタラクティブにする」ところにあって、具体的にはホストからのパラメーター等の変更がきちんとプラグインに届き、プラグインからのパラメーター変更通知などがホストにちゃんと渡ってくるようにしたいのですが、そのためにはまずホスト側がちゃんとインタラクティブに動くようになっている必要があります。

インタラクティブなホストとしてはaap-juce-simple-hostが現在はそれなりに機能しているのですが、AAPのデフォルトUIをGPLv3なJUCEにするわけにはいかず、また「ホストはKotlinでも作れる」という部分を維持しておくために、AAPのコードだけで一応ちゃんとしたホスティング(↑で書いたような要件を満たすやつ)ができるようにしておく必要があります。

これはデフォルトActivityとしてandroidaudioplugin-ui-composeというUIモジュールとandroidaudioplugin-samples-host-engineという非UIモジュールが実装していたのですが、前者も2年半前に作ったものでだいぶ古くなり、後者はそもそもUIがJetpack Composeになる前から存在していたもので、最新の実装でもMIDIメッセージは決め打ち、オーディオファイルも固定WAVを前提としたKotlinコードでロード、オーディオ処理は静的にprocess()を適用…みたいな最低限の動作確認用でした。任意のMIDI2メッセージを受け取れたりオーディオファイルを他のものに差し替えても動くようなコードにしたいところですが、優先度が低かったのと、静的な処理を前提としていることでいろいろ無理が生じていたので、後回しにしていました。

そういうわけで、今月は主にプラグインの新しいデフォルトActivityになるUIと、そのバックグラウンドで動くオーディオ処理の再実装に取り掛かっていました(実際には7月には構想を練ったり試験的なコードを書いたりしていました)。昨日ようやくちゃんとした音が出るようになったので、mainブランチにマージされています。

androidaudioplugin-ui-compose-app

新しいCompose UIは、基本的なmaster-detailスタイルのプラグインマネージャーで、対象はこれまで通りアプリ内プラグインだったりシステム上のプラグインだったりですが、detailのほうは常にプラグインインスタンスが生成されて、オーディオ処理が有効になるかどうかはstart/stop(プラグインAPI的にはactivate() / deactivate())で決まります。

old new
old UI new UI

Waveform Viewは「何となくオーディオ処理の結果が正しそうに見える」以上の使い道が無いのと、リアルタイム処理に合った表示にはならない(そしてリアルタイム表示するようにしても正しさがわからない)ので、シンプルに削除しました。代わりにMIDI2イベントを送信できるキーボードを載せています。そしてこのUIからaudio inも取得できるようにしました(自分で全然使わないので未確認ですが)。"Play Audio"ボタンはオーディオファイル(現状相変わらず決め打ち)を再生するために存在します。その前にエフェクトプラグインを適用して動作確認するためのものです。

パラメーターリストはgeneric native UIと同じものを使いまわしていますが、イベントの送信コードはgeneric native UIはローカルプラグインプロセス用で使い回せないので、別途実装しています。スライダーは消えました。もともとcompose-audio-controlsを試験的に採用した時点で消してもよかったのですが、一応未成熟なライブラリだったのでまあ妥当な移行策だったと思います。

MidiDeviceService機能のテスト用に存在していた"MIDI"ボタン(と"Stop MIDI"ボタン)はなくなりました。この機能は純粋にAndroid MIDI APIを経由して実装していたものだったので、今後はResident MIDI Keyboardがその代替となるでしょう。そのために作ったものなので。

UIは相変わらずWeb UIとNative UIを表示できるようになっていますが、ある意味ここが最大の違いで、今回からこれらのUI上での変更がプラグインに反映されるようになっています。従来のUIはプラグインを静的に適用するためにローカルのパラメーターリストを持っていただけで、オーディオ処理を適用するときに初めて適用されるもので、GUI拡張のインスタンスとは一切関係がない存在でした。新UIではインスタンスの操作がパラメーター適用と連動しています。

androidaudioplugin-manager

今回のプラグインマネージャーにおいて、GUIの刷新は表面的なもので、主役は非GUI部分です。これは(現時点では)androidaudioplugin-managerという、主にC++でオーディオとMIDI2のイベントルーティング実装を含むモジュールです。Kotlin APIとしてはorg.androidaudioplugin.manager.PluginPlayerというクラスが存在するのみです。C++側には同名のaap::PluginPlayerを含め、もっとたくさんのクラスがあるのですが、どこまでユーザー向けのAPIにするかは未定です。

従来のマネージャーUIは、Kotlinでいろいろ実装しすぎていて(たとえばwavファイルをロードしてKotlinのバイト配列にしたり、Kotlinでオーディオ処理用のバッファを生成したり)、aap-juceで使える・C++のみで実装した機能がうまく噛み合わなかった場面が多いものでした。新モジュールでは全機能をC++で実装しています。従来のUIでも段階的にKotlin実装のAudioPluginInstanceクラスから単なるC++ポインターのハンドルでしかないNativeRemotePluginInstanceを使うように変えてきましたが、新UIでは古いKotlin APIを全く使わずに実装しています。

この"manager"と名乗っているモジュールですが、表向きにはプラグインを(動的に)適用できるPluginPlayerの機能しか含まれていません。現状ではandroidaudioplugin-ui-compose-appの一部としても違和感が無いものですが、独立分離しているのは、ライブラリの実態がオーディオグラフであって、MidiDeviceServiceの実装などでも使い回す可能性があるためです。

このオーディオグラフ部分が、今回の新UIの主要な実装部分です。といっても、実際にはオーディオグラフがまともに(DAGを使って)実装されているわけではなく、「単一のプラグインを対象に従来のUIよりは柔軟にオーディオ処理を適用できる」「柔軟すぎるオーディオグラフは実装しない」というスタンスで作られています。本当は自分で実装もしたくなくて、LabSoundやminiaudioなどを使いまわしたかったのですが、これらを使ってMIDIサポートなどを追加するコードを書くほうがむしろ煩雑になる(あとLabSoundはどこがnot RT-safeなのかわからなかった)という理由で自作しました。

このオーディオグラフでできることはシンプルで

  • バイスからオーディオ入力を受け取る(そしてオーディオ処理に流す)
  • オーディオファイルを再生する(オーディオ処理に渡す)
  • MIDI入力を受け取る(そしてオーディオ処理に流す)
  • 単一のプラグインを適用する
  • MIDI出力をイベントハンドラーに流す
  • バイスにオーディオ出力を流す

というだけのものです。設計ドキュメントを見たほうがわかりやすいかも(ちょっと名前が実装と不整合な部分がありますが)。

audio graph pipeline

SimpleLinearAudioGraphという、1本のstd::vectorに格納できるレベルのグラフのみ存在しています。もう少しちゃんとノードを接続できるBasicAudioGraphというクラスをAPIだけ規定してあるのですが、そこまで必要なかったので実装していません。グラフの編集機能も実装していません。複数のプラグインにも適用できるでしょうが、今実装に求められている機能ではないので試していません。そういうものがほしい場合はJUCEのAudioProcessorPlayerとか、tracktion_engineに含まれるtracktion_graphなどを使うのがいいでしょう。

このネイティブ実装はTracktion/chocを試験的に使っています。主にオーディオバッファのinterleavingとか(従来のUIではこれをKotlinで実装していたり)、オーディオファイルのロードとresampling(従来のUIではresampling未対応)といった部分で使っています。オーディオ処理は正直ちょっと重い気もしますが(iteratorとか無理に使う必要はないはず)、単一のプラグインを処理するだけならまあいいかな…と考えています。

Oboeの部分やAAPのオーディオバッファの部分を抽象化したり、オーディオコールバックからの処理をプラットフォーム非依存にするように整備したりで、APIはちまちまと調整してあります。モデルさえ合えば他のプラグインフォーマットでも使い回せる可能性が微レ存…(最初はデスクトップで作ろうとしていました)