1月までの開発記録

前哨戦

2019年はちょっと仕事をしてすぐ辞めてまた無職に戻るという謎ムーブをしていましたが、それは主として自分のやりかけのプロジェクトをかたちにしてそっちの道を進んだほうが良いと考えたからでした。

11月下旬から週休5日になってだいぶ余裕は出来たのですが、ADC2019に参加した後TechBoosterの冬コミ(C97)向け同人誌のためにJetpack Composeについて調べて25ページくらいの解説記事を書いていて、自分の本来の活動は12月中旬になってようやく再開しています。

そういえばここで宣伝していなかったんだけど、この同人誌はboothから入手できます。Jetpack Compose、Flutterが実現できなかったwidgetをクラスでメモリ中に残さずに関数で処理できるモデルを実現しているのは最先端で面白いところで、それをKotlin Compiler Pluginで実現しているんだよ、みたいな話をComposeのソース(androidx.uiとandroidx.composeの両方)を追っかけながら解説しているやつで、多分まだそうそう書かれていない内容だと思います。さいわい前章もComposeでComposeのコードがどんなもんなのか解説されているので単体では置いてきぼりになりそうな人も安心です(?)

techbooster.booth.pm

12月までの半年分の地下活動

本題に戻って。去年の5月くらいからAndroidでも動作するオーディオプラグインの仕組みがあるべきだし無いなら自分で作るしかねーかなーとか思っていました。っていう話を6月に書いていました。これまでの時点でAndroidでリアルタイムオーディオでメッセージングするための基盤は出来ていたし、Google I/O 2019で何かしら発表されていてほしかったけど何も出なかった、という話を書いていました。

atsushieno.hatenablog.com

オーディオプラグインの仕組み、プラグイン側のAPIはずいぶんシンプルな内容みたいだし、自分でも何か作れるんじゃないか?と思って、5月からJUCEで独自のAudioPluginFormatを作る辺りから実験的に非公開でコードを書き始めていました。

https://github.com/atsushieno/android-audio-plugin-framework/tree/1f28c8f

JUCE APIの中身を埋めながら、LV2を組み込めるような感じで実装していました。ただLV2のバックエンドを作りたいわけではないので、自分のフレームワークのブリッジとして実装しています。ひと月くらい後にREADMEを書いた時は、まだガワしかないのですがだいぶソースツリーのリポジトリっぽくなりました。(こんな状態だったのか…みたいなのが見えるようにチマチマ書いてます)

https://github.com/atsushieno/android-audio-plugin-framework/tree/1431d9e

この辺から、ちゃんとガワだけじゃなくて実装も作ってAndroidアプリとして動かそう…みたいになっていって、2ヶ月目が終わるくらいにはVSTからLV2に移植されたmda-lv2のエフェクトプラグインを適用できるくらいになっていました。AndroidでLV2がちゃんと使えることがわかったので、方向性としては適切だったこともわかりました(これが6月の上記エントリーの段階)。

https://github.com/atsushieno/android-audio-plugin-framework/tree/7c0715e

その後この辺の作業は7月上旬からお試しで始めた仕事でだいぶテンポダウンしてしまったのですが、半ば週末プロジェクトになっていきましたが、いよいよホストとプラグインのアプリケーションを分離して、DAWとオーディオプラグインが別々のアプリケーションベンダーになっても問題なくいけるような、AIDLとndkbinderを使った通信の実装に入りました。ndkbinderが入るとminSdkVersion 29になって未来感が溢れてきますね。ndkbinderは特にまだbindService()に相当する機能がまだ無い…ということがAPIドキュメントからは読み取れず、これを実装しようと躍起になってたくさんの時間が消えていきました…。

https://github.com/atsushieno/android-audio-plugin-framework/tree/5a4b697

プラグインフレームワークとしての最低限の体裁も整えていましたし、ここまで出来てくると、そろそろ本業にできるんじゃないかコレ…?みたいな気持ちになってきますよね。とはいえこの頃から仕事がフルタイムになって開発が完全に止まってしまい、どうしたものか…となっていました。10月に踏ん切りをつけてこっちをやっていこうってなるまでは。実際この後11月までノーコミットです。

11月中旬にADCでAndroidオーディオチームに1 on 1で相談するチャンスがあったので、この時点までで出来ているものを見せながら、このアーキテクチャでオーディオプラグインの仕組みを実現しようと思っている、基本的にはiOSのAUv3と同じようなもんだからいけるんじゃないかと思っている、みたいな話をしてきたのでした。実際には1 on 1というのはウソで、チーム全員vs.わたし1人で、裁判か…!みたいな感じでしたが、控えめに言えばだいぶ受けが良かったです。後で「うちで仕事しないか」って言われるくらいには。まあソースのスパゲッティを見てないから()

(およそ)1月の活動記録

さて週休5日になってゆとりが出来た…かと思いきや、冒頭に書いた別のタスクに追われ、11月にもちょっとだけコミットがあるのですが、ほぼ「8月には出来ていたビルドがもうできなくなっていたから直し続けた」状態でした。なので開発が本格的に再開できたのは12月下旬になってからでした。

直近のおよそひと月は、まずmonorepoを解体してcerbero依存まわりのビルドの簡略化を図るところから始めました。Android向けのLV2関連バイナリはcerberoの依存部分も含めて全て切り離してビルドしています。GitHub Actionsで自動的にバイナリをリリースできるようにしたので、以降ここはノータッチでいけるようになって心理的負担が軽くなりました。他人にも試してもらいやすくなったし(cerbero依存になった時点でほぼ自分しかビルドできなかったはず)。AndroidでLV2を使いたい人はここからバイナリを拾っていけます。

github.com

ビルドが軽くなったので、いよいよJUCE統合に取り掛かりました。プロジェクトの発端になったJUCE用モジュールがついに組み込まれるわけです。juce_gui_basicsがAndroid上で動作するとわかったので(これは知らずに始めていて、単にオーディオ部分が使い回せるだけだと思っていた)、付属のAudioPluginHostも動かせるのではないかと思ってやってみたら、フレームワークの修正ゼロで動かせたので、光明が見えました。JUCE…神なのでは…!?

この頃から、そろそろソースを公開しても良いだろうと思うようになりました。もともとオープンなフレームワークとして使えるようになるといいなと思って作っていたものだったので、クローズドのままでは意味がなかったわけです。ただソースはぐちゃぐちゃだし、まだ外部の人にいじってもらえるような状況ではない(バグも罠もたくさんあるしガンガン破壊的変更が入る)ので、あまり宣伝はできないし早すぎる公開が予期しない採用と持続困難をもたらすのは好ましくないと思っていました。しばらく悩みましたが、ソースは公開して、宣伝はしない、という方向性にしました。

この時点でソースを公開するというのは、どちらかといえば必要な時に他人に見てもらいたい時(たとえばADCで会ったGooglerに相談したい時)に有用なのでそうしたわけですが、他の誰にもビルドできない状態で公開してもちゃんとした相談にはならないので、この頃からいろいろCIセットアップを試行錯誤しています。現在はBitriseでフレームワークとKotlinのサンプルがビルドできている状態です。実のところJUCEまわりは未だに解決しておらず厳しいのですが、これはBitriseのUbuntuのDocker imageがUbuntu 16.04なのとAndroid SDKにndk-bundleがある頃の古い仕組みに基づいているせいなので、いずれ何とかしたいところです。

公開問題が片付いた(?)のでJUCE統合の作業に戻りましたが、これがビルドは通っても全く期待通りに動いてくれない…そして問題の切り分けが超絶困難なわけです。JUCE統合のやり方が間違っているかもしれないし、LV2やlilvの使い方が間違っているかもしれないし、何ならAndroidオーディオの使い方もおかしいかもしれない。しかもLV2関連バイナリはビルドに手を加えるのが困難でデバッガでも追えない…。

この状態では何も進展しないので、そろそろ頃合いかと思ってLinuxデスクトップ版をビルドできるようにしました。プラグイン開発者としても、デスクトップで開発してそれをAndroidに移植したほうがおそらく楽でしょうし。デスクトップで使う場合のプラグイン環境構築の方法なども規定しました。デスクトップにはAndroid binderの仕組みがないので、代替をどうするか考えたのですが、とりあえずインプロセスで全てロードしてやり過ごすことにしました。このほうがデバッグも楽ですし。アウトプロセスモデルデバッグする必要が出てきたときのために、いずれスタンドアローンサーバーは用意しようとは思います…。

フレームワーク本体がデスクトップで使えるようになって、JUCEのAudioPluginHost移植版もデスクトップで動くようになったので(デスクトップ版にAndroidプラグイン機構の加工を加えてからデスクトップに戻したというわけです)、問題をいろいろ切り分けられるようになったのですが、LV2とMIDIメッセージのやり取りをする部分がだいぶ難解(というか仕様が不鮮明)で、ここに多くの時間をとられました。

そもそもオーディオプラグインMIDIメッセージを処理するのは単純なタスクではありません。一般的には、オーディオバッファとMIDIバッファは1回のオーディオ処理サイクルでまとめて処理することになります。なぜならMIDIメッセージの処理だけを個別に回すのはスレッド切り替えコストがもったいないからです。そのため、単純なMIDIイベントのバッファを渡すだけでは足りません。MIDIサポートのためにLV2では7,8年ほど前に仕様に破壊的変更を加えてAtomというメッセージフォーマットを規定しました。MIDIメッセージにタイムスタンプを付加しないとリアルタイムで渡ってきたものを適切に処理できないためです。

ただMIDIメッセージのタイムスタンプの扱いは難しく、LV2では文面上は規定がありますがbeatTimeのセマンティクスは不明ですし、JUCEに至ってはホスト依存となっています。この辺は自分のフレームワークでも完全には整備できていません。とりあえずdivisionとdeltaTimeがSMF互換になるデータとして規定しています。

ともあれ、多くの時間を費やしはしたものの、全てのレイヤーに存在していた諸問題を解決して、ついにJUCEの自分用AudioPluginHostでキーボードからMIDIメッセージを送信して、エフェクトプラグインとチェインしたシンセを鳴らすことに成功しました。LV2で作られたオーディオプラグインについては、GUIサポートは無いものの音声合成に使うことが可能になり、JUCE製のホストなどを使えばアプリケーションから呼び出してパラメーターを調整することで操作可能なところまでは出来ています。

www.youtube.com

ここまでで開発のマイルストーンの一つ(個人的には大きなやつ)をクリアしたことになります。なので今回これをまとめた、という側面もあります。まあ月末なので活動報告を書くにはいいタイミングです(それがメイン)。

これからどうするか

このプロジェクトは、プロジェクトとしてはまだまだやることがたくさんあります。GUI統合について何かしらのソリューションを用意する(個人的にはFlutterが適切な解になるかと思っているのですが)、ちゃんとOboeのコールバックを組み込んでndkbinderでリアルタイム処理が可能なのか検証する、ちゃんとしたサンプラーを動かす(juicysfpluginあたり?)、tracktion_engineを使った再生くらいまでは使い込む、などなど…いろいろissuesに書いているので、ちまちまとやっていくことになると思います。

このプロジェクトの最大の目的は、実用的な楽器プラグインDAWAndroidLinux(あるいはそれに準ずる自由なソフトウェアのOS)の世界に持ってきてもらって、自分がWindowsMacに縛られること無く音楽を打ち込めるようにすることなので、いずれ既存のMMLコンパイラを中心とした制作環境からも使えるようにしていきたいと思っています。がそれはまた別の話かな。

そんなわけで、毎日フルタイムで開発業務をやっているようなものなので(給料も何も出ないわけですが)、実はわりと暇してはいない感じです。