2月の開発記録(2021)

今月は前のめりにまとめた完全自分用の開発作業記録です。今月は上旬にちょっとDSPまわりの勉強をしていた以外は、だいぶAndroidAudioPlugin関連の開発に時間をつぎ込めた感じです。

s:/aria2web/aqua/

割とどうでも良い前フリから。もう半年以上前ですが、aria2webというsfizzサンプラーARIA extension GUIをWebViewとしてロードできる仕組みを実現したLV2プラグインを作りました。

atsushieno.hatenablog.com

プロジェクト名がイマイチだったので*1、いずれAAPに取り込んでWeb UI化するときに名前を変えようと思っていたので、今月ようやく着手しようと思った時点でaquaに改名しました。

XML namespacesを適切に利用した拡張管理

そういうわけで、先月末にいよいよGUI統合に取り掛かりたいと書いていたやつですが、実は未だに何もない状態です。先に片付けるべき課題があったためです。具体的には、プラグイン個別のGUIに対応するより先に、GUIのないプラグインの汎用パラメーターコントローラーリストのようなUIを(特にWebUIで)作るべきだろう、と考えました。

そのために実際に必要になるのは、パラメーターの種別に応じたUIコントローラーであり(値の範囲が整数なのに小数値を入力したくはないわけです。あるいはそもそもLV2 Atomのように数値でない場合とか)、そのためにはまずパラメーターのプロパティが規定される必要がありました。LV2で言えばport propertiesというコンセプトです。LV2であればlv2:portPropertyとかunits:unitなどがこれに相当します。

ポートのプロパティは拡張可能でなければなりません。拡張可能な情報を素直に実装するとしたら、適切な名前解決が重要です。 さし当たって、それまでdefault, minimum, maximum属性として規定されていた<port>要素のプロパティはurn:org.androidaudioplugin.port名前空間に属することになりました。これまでAAPは自由に拡張可能としてきたわけですが、C APIレベルでの拡張性しか実装していなかったので、ポートのプロパティのようにメタデータaap_metadata.xml)を拡張するやり方が未整備でした。

この拡張を実装するにあたって問題になったのが、AAPの内部でtinyxml2を使ってメタデータを処理していたのですが、tinyxml2はXML namespacesを処理できないことでした。マジか…他に何が使えるんだ…と思って調べてみると、PugiXmlもRapidXMLもダメ、使えるのはexpat(APIが完全に古い), libxml2(glib依存が厳しい), xerces-c++(でかい)…といった感じでした。ここは20世紀か?という感じの古参ばかりですが、あまり深入りしたくなかったので、Android側でのメタデータ解析は全てAndroid APIでやることにして、デスクトップのみlibxml2にして回避しました。ちなみにJUCEに含まれるXMLAPIダメです

LV2みたいにTurtle文法を選択してパーサの選択肢が無くなるよりXMLにしたほうが絶対良いでしょ、と考えていた(いる)わけですが、C++がこんなにxmlnsをサポートしていないパーサばかりだとは思っていなかったので、自分がC#やらKotlinやら使ってコードを書いていたのはずいぶんスポイルされているんだなあ、となりました。しかし、うーん…さすがに2021年からC++XMLパーサを実装するのはどうかな…

ちなみに今月はこのポート プロパティの基盤を実装しただけで、それを反映するGUIの構築には着手していない状態です。

flatten LV2 dev. environment

aap-lv2が12月にリポジトリを分割して開発しやすくなったので、aria2webをaquaに変更する過程で気づいた、sfizzで期待通りにオーディオデータが生成できなかった問題に着手しました。が、中で使われているLV2 toolkit (serd + sord + lv2 + sratom + lilv)がバイナリ参照になっていて、デバッグ時に中まで追えずに苦労していました。その度にデバッグのためにリポジトリの構成を変更してLV2 toolkitを一緒にビルドしてはコミット前に戻す、みたいなことを繰り返していたのですが、こんなことをするくらいなら全部aap-lv2のリポジトリでビルドすべきだ、と考えるに至りました。

実際これを避けていたのは(1)公式のビルドスクリプトがwafだったので公式に可能な限り準拠したやり方にしたかったのと、(2)Androidの4アーキテクチャ分 x この数のネイティブライブラリビルドの増加はCIビルド時間に有意に悪影響が出ると考えていたからなのですが、wafをスキップして直接CMakeでまとめてビルドしてみると個々のライブラリのビルド時間はそれほど長くも無かったので、全部フラットにビルドすることにしました。これでデバッグ体験はかなり良くなっています(現状やるのはわたしだけだと思いますが)。

ちなみにこの作業自体は前述のaquaをAAPに取り込んでWebUIの実験台に使おうと思ってまずsfizzの動作確認…となってオーディオ側の問題に気づいて着手したのですが、前述の通りGUI統合に入る前にポートのプロパティを…となってこれも未着手です。

Hera (Juno 60 emulator) port to AAP

今月はRolandからJuno 60プラグインが出て話題になりましたが、ほぼ同時期にADLplugやsfizzの開発者がHeraという新しいJuno 60エミュレーターのJUCEのプロジェクトを公開していました。そういうつもりはないんだけど完全に開発者の追っかけみたいなムーブになっている…

github.com

既存のエミュレーターにJUCEのMPEサポートを加えて作ってみた、というコンセプトで作られているようです。RolandのほうはMPE対応はしていなさそうですね(featuresを見ているだけですが)。

f:id:atsushieno:20210226001343p:plain
Hera on Waveform 11.5

CMakeベースのJUCEプラグインだったので、ちょうどCMakeでInstrumentとして機能するやつがほしかったんだ…! と思って、その日のうちにAndroidに移植しました。

github.com

aaphostsampleで開くとちゃんと音が出るので、最近ではOB-Xdと並んで簡単に動作チェックする時に活用しています。移植作業もEqとChowPhaserを移植していたのを応用しただけです。Heraとは全然関係ないのですがChowPhaserの作者から「Androidに移植してるの?? 試せる??」みたいなメールをいただいて「すまん…JUCEのaudio inまわりがおかしいからまだだ…!」みたいなお返事を書くことに…

Helio workstation with AAP

AAPのプラグイン移植はずいぶんバリエーションが増えてきたのですが、ホスト側はさすがに使い回せるアプリがほとんど無く、自作のaaphostsample、JUCEのAudioPluginHost、あとtracktion_engineを使うaugeneが一度も演奏できたためしがない…という状況でした。

JUCEを使ったDAWのひとつにHelio Workstationというのがあるのですが、これがTabletのみを意識しているとはいえAndroidでも動作すると謳っているので、これにAAPサポートを追加したいというのがひとつのプロジェクト完成形の姿でした(任意のDAWで任意のプラグイン移植が使える状態といえるわけです)。

github.com

ただhelio-workstationのプロジェクトは、Android用のプロジェクトなどが既にProjucerで生成された後の構造をいじって作られているようなので、既存のaap-juce移植のモデルにはうまくフィットしませんでした。何回か失敗した後で、スクラッチからHelio向けに独自のアプローチでビルドプロセスを構築することで(といってもどっちも自分しかやっていないのですが)、ようやくAAPサポートを追加できました。

github.com

ほぼオリジナルのコードに手を加えること無く実現したので、無理にビルド手順を一般化しないほうがいいのかなあと思い始めています。昨日も公開されたばかりのVitalを移植しようとしたのですが、このプロジェクト構造も一般的ではないのでAndroidビルドを追加する時点でかなり無理っぽさが出ています。いずれやるとしても何日かかかりそう…(本家でやってほしさある)

ちなみに現時点で、プラグインの一覧が取得できるものの名前の表示がイマイチ(これはオリジナルがそう)、オーディオグラフの設定画面ではオーディオ出力ポートが出てこないので生成結果を一切音声出力できないっぽく見える、その割に実はプラグインインスタンスも生成できていてノートに合わせて音が生成できる、ただし出てくる音がノイズまみれの謎音…みたいな状態なので、実用性はまだ無いです。

f:id:atsushieno:20210226002110p:plain
Helio+AAP
f:id:atsushieno:20210226002135p:plain
Helio+AAP, plugin details

プラグインインスタンス生成の安定化

AAPで初期からずっと困っていた最大の不安要素のひとつが、プラグインインスタンスを複数回生成するとプラグイン側のサービスが落ちてホストも一緒に落ちる、という問題でした。これだとちょっと使うだけですぐホストが落ちるので使用体験を語れるほどのレベルにも至っていない感じだったので、port propertiesが落ち着いたところでがっつり取り掛かりました。

いろいろ試行してみてわかったのは、どうやらandroidaudioplugin.aarをbuild.gradle上でmavenLocal()から解決していると発生するが、デバッグするためにsettings.gradleをいじってプロジェクトの一部にandroidaudiopluginを追加してbuild.gradle上でproject(':androidaudioplugin')として参照すると発生しない(!)ということでした。

何でこの話を書こうと思ったのかというと、事実関係を確認する前提が割と罠だらけなので記録しておきたかったわけです(自分用メモなので!):

  • mavenLocal()でパッケージを解決する時は、ローカルでビルドして修正を加えているバージョンが参照されているかどうか、build.gradleの内容から念押しする必要がある(settings.gradleで追加しても自動的にproject(':...')で解決するわけではない)
  • CIでビルドしても通るようにsubmoduleのandroid-audio-plugin-frameworkmakeでビルドしているので(前述のaap-lv2リポジトリ分割について書いたエントリを参照)、別のソースツリーでandroidaudioplugin.aarやandroidaudioplugin-lv2.aarに手を加えたものをアプリで確認するなら、maven localのパッケージを上書きしないようにビルドする必要がある
  • debugビルドとreleaseビルドの両方でmaven-publishされる構成が変わるので、自分の期待通りのものがaarとして発行されているか、build.gradle内のpublishToMavenLocalタスク関連の記述をきちんと確認する

今回の問題は急場しのぎ的にreleaseビルドのaarをpublishToMavenLocal~/.m2/repository以下にデプロイしないように変更したら直りました。それ以上のビルドの細かいオプションのどこに違いがあったのかは、まあやる気が出たときに…。関連するネタとしては、JUCEプラグインAndroidに移植しているときにJNI_OnLoad()がreleaseビルドでだけ衝突する問題が発生したことがあって、その時はldに-fltoが渡されていたのが引き金になっていた、というところまで調べたりしました。これもそれ以上の原因は追及していなかったり…。

いずれにせよ、この処置を施した後は、プラグイン インスタンスの生成まわりで不定期的にクラッシュする場面はほぼ無くなったので、ようやく安心してある種のアプリでは使えるようになったんじゃないかと思います。

今後の方針

現時点で直しておきたいのは(1)Frequalizerを含むJUCEのエフェクタープラグインが全滅している問題(JUCEのバグ)と(2) HelioでプラグインをロードしたときにAudio Outが適切に認識されない問題(AudioPluginHostでは出てくるのでたぶんHelioのバグ)、の2点で、着手するならこの辺でしょう。

あとは今月ようやく2021年版ロードマップ(2021年中に全部やるとは言ってない)を作ったので、その中からおそらくGUIまわりを中心に消化していくことになるでしょう。

github.com

とはいえ4月にM3 2021春があってそっちに向けて何かしらまとまったものを作りたいので、開発はしばらく休止かもしれません。あと多分何かしら別の原稿を書くことになると思いますし。はーどうしよ…

*1:aria2webだとただの変換処理系みたいに見えるので。実際、最初はそうだったのですが…

音声信号処理勉強会のKPT

1年前にMusic Tech Meetup #1 (Night)を開催したのですが、その頃すでにコロナの足音が聞こえてきていて、オフライン勉強会の開催はギリギリ可能という感じでした。その後はとても同様の勉強会を開催できるふいんきではなくなってしまいました(夏頃ならできたかもしれませんが、いずれにしろ今は冬ですし)。

まだ日本だけは1年くらいコロナ禍が収束しそうにないし、オンラインでも勉強会を開催できるようになっておきたかったので、とりあえず一度やってみようと思って、音声信号処理勉強会というものを開催してみました(ここで告知してもよかったのですがまあライトにやりたかったものです)。参加してくださったみなさまありがとうございました。

connpass.com

というわけでそのKPTです。無理やりKとかPとかTとか付けたけど適当です。あ、参加していない人向けにどんな話題が上がったかをふいんきで分かるものとして、イベントページに資料集のgistがあります。

平均的にはおよそ10人くらいでの開催になりました。今回は無難な選択肢としてZoomを使ってみましたが、無料ユーザーなので40分に1度強制切断されながらの開催です(!) 多少のトラブルはありつつも続行は可能は可能でした(K)。ただ中断体験とかチャットログ全消滅はいまいちだった…んじゃないかな…と思うので(P)、今度やる場合はDiscordあたりで試そうかなと思っています。(T)

Discordのビデオチャットは、せいぜい4人くらいまでしかやったことがないので、同じくらいの人数でいい体験になるかは正直わかりません。上限はコロナ禍の間は50人らしいですが(これ日本だけ残り続けても米国が収束したら縮小するんだろうなあ…)、チャットの収拾がつかなくなるので、人数はほぼ変えないと思います。(K)

進め方ですが、基本的に雑談スタイルで進めて、その間に皆さんから資料を出してもらえたり、話題を提供してもらえたりで、まあ悪いというほどではなかったんじゃないかと思います(K)。ただアツシエノがファシリテーターとしてずっとしゃべり続けるのは、しゃべるほうも聞くほうも体験が悪いと思うので(P)、今度はLT的な発表を少し募って主にしゃべる人(というのが決まっているわけではないですが)を切り替えながらやってみようと思います。(T)

内容の方向性ですが、今回は特定の書籍の読書会というベクトルでまとめていて、これは割と良い選択だったと思います(K)。実際の話題があまり本に縛られないようにしたのも、悪くはなかったと思います(K)。本に縛られない方向性のほうがいいのかな、とも考えなくはないですが、それだと本書の内容についてわからないことを議論するのは難しいので、デフォルトで使える話題として据え置こうかなと思います(K)。具体的な書籍は変えたり増やしたりするかもしれません。(T)

…という感じで、また1ヶ月後くらいを目処にまた開催してみようと思います(確定ではないです)。

1月の作業記録 (2021)

恒例の自分用作業記録です。これが自分用でなくなる日は来るのだろうか。

making aap-juce hackable again

aap-juceandroid-audio-plugin-frameworkからJUCEプラグインの移植が無駄に大きくなりそうだったので切り離して作られたリポジトリですが、これも昨年末の時点でGitHub Actionsでビルドを全部通すのに3時間かかる有様でした。これ以上は増やせないし、それでも移植できていたプラグインはほんの5本くらいで、各種の機能のproof of conceptとなっていないといけないのに、これ以上は増やせないというのでは困るわけです。また全サンプルが同じリポジトリにあると、本体の変更を即時全てのサンプルに反映する必要があり、これが開発の妨げになっていました。

そういうわけで、この際、新しいプラグインを移植する作業をテンプレ化できるようにするというタスクの消化も兼ねてリポジトリを分割しました。

ある程度は新規プラグイン移植タスクをテンプレ化できていたので、リポジトリの分割はまあまあ早く実現できたのですが、GitHub ActionsでAndroid Studio Canaryが前提とするAndroid Gradle PluginとCMakeの相性が悪い問題などがまあまあハマりどころでした。ちなみに今でもGitHub Actionsの問題が原因でOSXビルドが出来ない状態です。

リポジトリを分割したら収拾がつかなくなるという状態はなるべく避けたかったので、aap-juce-worldというリポジトリを1つ作って、最終的にはそこでリリースバージョンのようなものをまとめることにしました。これが従来のaap-juceリポジトリに近いものになっています。ビルドが5時間くらい、ダウンロードできるapk群も700MBを超えるレベルなので、スケールしなさそうではありますが、そもそもOSSで公開されているJUCEプラグインもそんなに数多くは無いし、あるものを全て取り込むわけでもないので、まあしばらくはもつんじゃなかろうか…

あと、ビルド時間を短縮する手段として検討しているのが、JUCEのライブラリを共通化してCMakeでビルドするアプローチですが、そもそもCMakeサポートが実現できていなかったので、まずCMakeサポートを実装してみました…というところからスピンアウトしたのが前回のエントリーです。

とはいえ、そもそもCMakeを使っているJUCEプラグインがほとんど無いのが現状です。前回のエントリーを書いた時点でwitte/Eqを移植していましたが、1つだけだと移植作業のテンプレ化には結びつかず、汎用性が担保できなかったので、もうひとつChowPhaserを発掘して移植しました。

移植してみた感想としては、いったんやり方が固まってしまえばCMakeのほうがずっとシンプルです。Projucer版では、.jucerファイルも(少なくとも現状では)手作業で作り換えないといけないですし、Androidプロジェクト自体がProjucerで保存するたびに消えてしまうので、修正を加えていても安心感がありません。jucerベースで作られているプロジェクトもCMakeで書き換えて移植したほうが早いかもしれません。

最終的に1月には12件の新規リポジトリが生えました(めんどくさいのでgithubからコピペしてきた)。1つだけJUCEとは無関係のFirefoxアドオンで、もう1つだけaap-juceと無関係の前回書いたJUCE+Android+CMakeの実験用リポジトリです。

10件のaap-juce関連リポジトリのうち、odin2とFrequalizerはProjucerベースのプロジェクトの新規移植、witte/EqとChowPhaserはCMakeプロジェクトの移植(当然新規)です。

とはいえいずれも完動品というわけではなくて、むしろJUCEのAndroidまわりのissueがいろいろあって特にinput channelがまともに取れないのは割と厳しみある感じです。このレベルの問題に今さらぶち当たったのは、これまで移植してきたプラグインが全てInstrumentでEffectではなかったというのが理由なのですが、なぜEffectが増えなかったかというとまさにaap-juceに移植を増やせなかったせいだったので、順当に問題が出現している感じです。JUCEチームは明らかにAndroidには力を入れてないので、自力で直さないといけないことになりそう…。LV2のエフェクトは当然ふつうに使えるので、むしろLV2化して作ったほうが実現可能性は高いのかもしれません。JUCEの今後の出方次第ですね。

一方でodin2くらい大規模なプロジェクトが、さまざまなUI関連コードを殺しながらであっても一応ビルドが通ってデフォルト音色だけでもホストから音が出ている状態なので、ちゃんと本格的なやつも動かせそうなふいんきになってきました。まあ実際にはあの画面をそのままAndroidに持っていっても実用に耐えないので、モバイル向けにUIを作り変える必要はあるでしょう。

来月の方向性

ともあれ、LV2とJUCEの両方が最新のリポジトリ整理で継続的に開発可能な状態になったので、長らく手を付けられなかったUI統合に手を伸ばす機運が高まってきました。ちょうどChrome 87でAtomicsがAndroidでもサポートされるようになっていて、juce-demosに置いてあるプロジェクトがAndroidでも(オプションを有効にすれば)動作することが確認できたので、JUCE側もUIを再利用出来る可能性が十分にあります。今月はjuce_emscriptenの更新も取り込もうとしていたのですが、いまいち上手く行ったかどうかわかっておらず、完全でもないので(DemoRunnerで音は出せているという程度)、JUCEでやるならそこからです。まあJUCEより先にsfizzのARIA統合として作ったaria2webをLV2 UI取り込みの例として使ってみたい気もしています。

本当はUI統合より前に楽曲再生機構としてのaugene(tracktion_engineのフロントエンドプレイヤー)を動作させたいところなのですが(編集UIが出せても再生が出来ないのでは意味がない)、ローカル環境ではともかくGitHub ActionsでAndroid NDKのclang++が謎のクラッシュを起こしてビルドがコケる状態なので、手を付けられるところから手を付けたいと思います。いずれにしても、ここしばらく主な作業対象がCMakeとMakefileとビルドスクリプトばかりで、その前も原稿とか書いていたので、全然プログラムを書いている気がしなくて良くないので、来月こそはコードを書きたいですね。

JUCE+CMake+Android now works

JUCE6はCMakeに対応していますが、実はCMake対応の恩恵を受けられないプラットフォームが存在します。Androidです。

「は?」というのが多分一番正しいでしょう。何しろCMakeはだいぶ前からAndroid Studioでサポートされているわけで、むしろ一番恩恵を受けるべきプラットフォームです。Win/Mac/Linux/iOSのいずれもCMakeネイティブなビルド環境ではないのに、むしろCMakeがネイティブのビルド環境としてサポートされているAndroidがなぜかサポート外になっているのです。このままではいけない。

目次


Missions

2021年1月にリリースされたJUCE 6.0.6の時点で、AndroidはProjucerによってのみサポートされています。ProjucerはAndroid StudioからビルドできるようなAndroidアプリケーションのGradleプロジェクトを生成します。アプリ開発者がProjucerからAndroid Studioを起動してそこからデバッグ実行できる状態のものができています。JUCEアプリケーション部分はAndroid NDKを用いたネイティブコードのアプリケーションとなり、Projucerが生成するプロジェクトはこれを独自のView上にネイティブコードで描画し、UIイベントをAndroidフレームワークとネイティブコードの間で相互運用します。

Projucerで生成されるAndroidアプリケーションでは、AndroidManifest.xmlやbuild.gradleのさまざまな情報がProjucerのAndroidExporterで規定されたプロパティとしてカスタマイズ可能な項目となっていますが、これらはAndroidアプリケーション開発者が自前で調整できるほうが、自然でメンテナビリティの高い高品質なコードになります。Androidプロジェクト上で何がカスタマイズ項目であるかを知っているだけで設定できる方が、それに加えてProjucerの何というプロパティでどのように設定する必要があるかを知らないといけないよりも簡単であるのは自明でしょう。Projucerが生成するプロジェクトは不自然で、古臭く、アンチパターンに陥っている箇所もあります。

JUCEチームがAndroid開発のエキスパートをかかえていて、常に最新のトレンドにキャッチアップして適切なプロジェクトモデル生成をProjucerに実装できるなら話は別ですが、これが現実化することはまず無いでしょう。そもそもProjucerで全て生成するというのは筋が悪いです。Projucerは切り捨てて、CMakeのモデルに移行していくのが今後の望ましい姿です。

CMakeサポートに求められるのは、Projucerに代わるアプリケーション全自動生成機構ではありません。C++Android NDKをサポートするAndroidプロジェクトではCMakeサポートが組み込まれており、JUCE + Android + CMakeサポートに期待されるのは、Android Studio (Gradle) プロジェクトのCMakeサポートの部分に、いかに違和感なくJUCEのCMakeプロジェクトを適用できるか、にあります。

Projucerを使わないAndroidアプリケーションのビルド機構としては、アプリケーションのActivityから巡り巡ってJUCEアプリケーションのブートストラップ処理に入るネイティブコードのエントリーポイント関数を呼び出せれば、ミッション完了です。本当はそれに加えてAndroidのアプリケーション ライフサイクルに沿った状態管理なども必要になるのですが、どうせProjucerで生成されたアプリケーションでもそれなりにしか出来ていないですし、JUCEアプリケーションに固有の問題ではないので、ここではあまり気にしなくても良いでしょう。

これを実現するためには、ProjucerでどのようなAndroidアプリケーションのファイル群が生成されているのかを把握し、何をユーザー(Androidアプリ開発者)が作成し、どのようにJUCEアプリケーションを繋ぎ込むかを手順化して、実現可能なワークフローを確立する必要があります。

理想を言えば、「既存の」CMakeプロジェクトをそのまま取り込めれば、Androidサポートの可能性が格段に広がります。とはいえ現状では厳しいので、既存のCMakeプロジェクトとの差分を最小化する方向性のみを堅持していくのが良いでしょう。無理に既存コードをそのまま取り込めるようなtoolingの実装に開発コストをかけすぎると、そのツール自体のメンテナンス性が下がります。

以上のような前提で、今回は以下の2つを目標として設定しています。

完成品から(だけ?)見たい人は以下のリポジトリを見ると良いでしょう。

特に後者のプロジェクトには元のプロジェクトに対するパッチが含まれているので、どれくらいの差分でAndroid用ビルドが実現しているのかわかりやすいと思います。自作のandroid-audio-plugin-framework対応コードも含まれているので、実際にはさらに小さい差分で足ります。

分析編

このセクションでは今回のミッションを達成するために調べたことをまとめます。ソーセージの中身に興味がない人は飛ばして読むと良いでしょう。(何でこんなことをしているのか、を把握せずに読み進められるかな…?)

一般的なAndroid C++サポートの実現方法

Android StudioC++サポートの付いたプロジェクトを新規作成すると、この画像のような構成になっています。これが目指すべき状態ですres以下には大量のファイルがあるので折りたたんでいます。

vanilla Android C++ project

一方で、JUCEのAndroidプロジェクトはこうなっています。とはいってもこれは不完全なリストです。具体的には、C++のコードが表示されていません。このトップディレクトリの外側に存在しているためです。

vanilla Projucer Android project

いろいろ違うところはありますが、共通している部分が多いことも見て取れるでしょう。

ProjucerのAndroidExporterで生成されるファイル

この節ではProjucerが生成するAndroid Gradleプロジェクトの内容を読み解きます。

生成されるファイルは、GUI ApplicationやAudio Pluginなど、プロジェクト種別である程度は異なりますが、大枠では大差ないはずです。

build.gradle, local.properties, settings.gradle, gradlew(.bat), gradle/

これらは何も特別なところがなく、普通にAndroid Studioでプロジェクトを生成するのとほぼ変わりません。バージョン番号などが異なり、またAndroid Studioのプレビュー版などではMavenリポジトリが追加されていることがありますが、Projucerの出力はシンプルです。

app/src/debug/res, app/src/release/res

@string/app_nameを定義するstring.xmlだけが含まれています。しかしProjucerのAndroidリソースの生成はややいびつで、debugビルドとreleaseビルドでディレクトリを分けており、これは一般的ではありません。Projucerでビルド設定ごとに異なる内容を指定できるようにしているのを愚直に再現しているのが悪いので、ゼロから作るCMakeプロジェクトで配慮する必要はありません。

app/src/main/AndroidManifest.xml

WifiBluetoothなど、利用するモジュールによって必要になるpermissionなどのmanifest項目が増えることになります。この程度のことはアプリケーション開発者が自分で作業すべきでもあります。

あと、Manifestはマージできるので、JUCEモジュールごとにAARを構築してそれぞれにAndroidManifest.xmlを付けるという手がありますが、とりあえずそこまで求めなくても十分です。

app/build.gradle

一番特殊かつ意味があるのは、カスタムsourceSetsの追加部分で、JUCE標準モジュールの中に含まれるJavaのソースが追加されている部分です(これについては後述します)。基本的にはここで追加するのはあまり適切ではなく、削られるべきものもあり、残しておいてもまあ悪くないというレベルのものもあります。

他にも、signingConfigなど、一般的に必要ではないものが生成されており、これはProjucerにそういうオプションがあるのが悪いです。CMakeビルドでProjucerの負の遺産を引きずる必要は無いですし、productFlavorsもあえて生成する必要はありません。必要な開発者が自分たちの都合に合わせて自前で設定すべきです。

app/CMakeLists.txt

このファイルにはさまざまなオプションが使用しているJUCEモジュール次第で追加されます。

juce_audio_devicesモジュールが含まれていると、Oboeのビルドが追加されます:

set(OBOE_DIR "/media/atsushi/extssd0/sources/JUCE/modules/juce_audio_devices/native/oboe")
add_subdirectory (${OBOE_DIR} ./oboe)

これが必ず含まれていると思いますが、もしかしたらオプションかもしれません。add_definitions()の内容はオプション次第です。<...>/JUCE/modulesは(個人の環境になっていますが)グローバルパス設定から来ています。プロジェクトのUIDのようなものが含まれているプロパティもあるのですが、さすがにいらないようです。

add_library("cpufeatures" STATIC "${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c")

set_source_files_properties("${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c"
    PROPERTIES COMPILE_FLAGS "-Wno-sign-conversion -Wno-gnu-statement-expression")

add_definitions("-DJUCE_ANDROID=1"  "-DJUCE_ANDROID_API_VERSION=16"  "-DJUCE_PUSH_NOTIFICATIONS=1"
    "-DJUCE_PUSH_NOTIFICATIONS_ACTIVITY=\"com/rmsl/juce/JuceActivity\""  "-DJUCER_ANDROIDSTUDIO_7F0E4A25=1"
    "-DJUCE_APP_VERSION=1.0.0"  "-DJUCE_APP_VERSION_HEX=0x10000")

include_directories( AFTER
    "../../../JuceLibraryCode"
    "/media/atsushi/extssd0/sources/JUCE/modules"
    "${ANDROID_NDK}/sources/android/cpufeatures"
)
enable_language(ASM)

config次第でオプションが追加されますが、長いので省略します。ほとんどは標準のJUCE CMakeサポートが肩代わりして不要になります。

if(JUCE_BUILD_CONFIGURATION MATCHES  "DEBUG")
    add_definitions("-DJUCE_DISPLAY_SPLASH_SCREEN=1"  "-DJUCE_USE_DARK_SPLASH_SCREEN=1"  
        "-DJUCE_PROJUCER_VERSION=0x60005"  "-DJUCE_MODULE_AVAILABLE_juce_audio_basics=1"  ...
        "-DDEBUG=1"  "-D_DEBUG=1")
elseif(JUCE_BUILD_CONFIGURATION MATCHES  "RELEASE")
    add_definitions("-DJUCE_DISPLAY_SPLASH_SCREEN=1"  "-DJUCE_USE_DARK_SPLASH_SCREEN=1"
        "-DJUCE_PROJUCER_VERSION=0x60005"  "-DJUCE_MODULE_AVAILABLE_juce_audio_basics=1"  ...
        "-DNDEBUG=1")
else()
message( FATAL_ERROR "No matching build-configuration found." )
endif()

残りの大半はadd_library()でソースを列挙し、プロパティを設定しています。実際にはソース列挙だけで十分でしょう。

最後にtarget_link_libraries()などが記述されます。eglなどは多分juce_gui_basicsが無ければ不要でしょう。

target_compile_options( ${BINARY_NAME} PRIVATE "-fsigned-char" )

if( JUCE_BUILD_CONFIGURATION MATCHES  "DEBUG" )
    target_compile_options( ${BINARY_NAME} PRIVATE)
endif()
  
if( JUCE_BUILD_CONFIGURATION MATCHES  "RELEASE" )
    target_compile_options( ${BINARY_NAME} PRIVATE)
endif()

find_library(log "log")
find_library(android "android")
find_library(glesv2 "GLESv2")
find_library(egl "EGL")

target_link_libraries( ${BINARY_NAME}
    ${log}
    ${android}
    ${glesv2}
    ${egl}
    "cpufeatures"
    "oboe"
)

JUCE/modules/juce_core/native/javacore/init

init/com/rmsl/juce/Java.java というネーミングがアレなファイルだけが入っています。中身は短い。

package com.rmsl.juce;
import android.content.Context;
public class Java
{
    static
    {
        System.loadLibrary ("juce_jni");
    }
    public native static void initialiseJUCE (Context appContext);
}

この実体部分はjuce_coreモジュールの中でJNIで実装されています。このJNI呼び出しは存在している必要があり、JUCEモジュールの中に存在している必要はありません。同等のJavaクラスをKotlinで作ってしまえば不要になります。

JUCE/modules/juce_core/native/javacore/app

com/rmsl/juce/JuceApp.java というファイルだけがあります。これも短い。

package com.rmsl.juce;
import com.rmsl.juce.Java;
import android.app.Application;
public class JuceApp extends Application
{
    @Override
    public void onCreate()
    {
        super.onCreate();
        Java.initialiseJUCE (this);
    }
}

実のところ、これはベストプラクティスに反するので削除すべきです。AndroidアプリケーションでApplicationクラスから派生できるのは1つしかないし、この内容でその希少な価値を独占するのは悪です。現代ではJetpack App Startupを使うべきだし、使わないとしてもContentProviderにできる(すべき)案件です。ちなみに、削除するといっても、build.gradleのsourceSetsの列挙から外すだけです。JUCEのソースから削除する必要はありません。

JUCE/modules/juce_gui_basics/native/javaopt/app

ここには2つソースがあります。com/rmsl/juce/JuceActivity.javaは、Activityに実装することの弊害(AppCompatActivityなどを利用できない等)のほうが遥かに大きいので廃止すべきですが、JNIシグネチャーが絡んでいることもあるので、このレールから外れるやり方でappNewIntent()に相当する機能を呼び出せるか検証する必要があります。

package com.rmsl.juce;

import android.app.Activity;
import android.content.Intent;

//==============================================================================
public class JuceActivity   extends Activity
{
    //==============================================================================
    private native void appNewIntent (Intent intent);

    @Override
    protected void onNewIntent (Intent intent)
    {
        super.onNewIntent(intent);
        setIntent(intent);

        appNewIntent (intent);
    }
}

もうひとつ、com/rmsl/juce/JuceSharingContentProvider.javaのほうは、割と長い内容になっているので、そのままアプリケーションに取り込んだほうが良いでしょう。ContentProviderの独自実装であり、設計上も特に悪いところは無いはずです。(まあ、Javaで書かれていますが。)

ブートストラップ

AndroidでのJUCEアプリケーションのブートストラップは、次のような流れになっています。

  • GUIアプリケーションの場合、juce_gui_basicsに含まれるJuceActivitycom.rmsl.juce.Java.initialiseJUCE()を呼び出す
    • GUIアプリケーション以外は同様の手順をService.onCreate()などで踏む必要がある
  • Javaクラスはlibjuce_jni.soをloadLibrary()でロードする
  • Java.initialiseJUCE()JNI_OnLoad()によってjuce_JavainitialiseJUCE()JNIEnvregisterNatives()で関連付けられており、ネイティブのThread::initialiseJUCE()を呼び出すように実装されている
    • 何でわざわざそんな名前にしているのかは不明(デフォルトでJava_com_rmsl_juce_Java_initialiseJUCE()に関連付けられるはず)
  • Thread::initialiseJUCE()は最後にjuce_juceEventsAndroidStartApp()を呼び出す
  • juce_juceEventsAndroidStartApp()は、juce_getExecutableFile()で得られた実行中のアプリケーションの共有ライブラリのファイルを別途dlopen()でロードし、その中からdlsym()juce_CreateApplication()を取得して呼び出されている
  • juce_eventsモジュールにjuce_Initialisation.hで定義されたjuce_CreateApplication()が含まれている
  • juce_CreateApplication()はマクロJUCE_CREATE_APPLICATION_DEFINE(AppClass)で定義されるもので、プラグインフォーマットごとに規定されるが、Androidの場合はJUCEがサポートするプラグイン規格が存在しておらず、Standaloneのみ対応しており、その生成コードには含まれている。

実装編

Androidアプリケーションテンプレートとして作る

今回の目的を実現するために、まずは標準的なAndroidアプリケーションを作成して、そのapp/build.gradleで指定されたCMakeLists.txtがJUCEアプリケーションをビルドして、正しくロードできるように調整する、というステップで目標を達成することにします。

まずAndroid StudioC++アプリケーションを作成します。筆者はゼロからファイルを作ります(正確には、既存のアプリからコピペしてきます)が、簡単ではないでしょう。Gradle関連のファイルはそのまま使えます。筆者はAndroid Studio Arctic Fox (Canary)を使っているのでgradle 6.8-rc-1とAndroid Gradle Plugin 7.0.0-alpha04を指定していますが、多少古いバージョンでも問題ありません。

app/build.gradle

app/build.gradleには(部分的な内容ですが)次のように指定します。buildTypesやproductFlavorsなど不要なものをほとんど削ったので、Projucerが生成するものと比べるとかなり短い内容になっています。

defaultConfig {  
  applicationId "com.yourcompany.newproject"  
  minSdkVersion    16  
  targetSdkVersion 30  
  externalNativeBuild {  
    cmake { arguments "-DANDROID_STL=c++_static", "-DANDROID_CPP_FEATURES=exceptions rtti" }  
  }
}  
  
sourceSets { main.java.srcDirs += [
  "../JUCE/modules/juce_core/native/javacore/app",
  "../JUCE/modules/juce_core/native/javacore/init",
  "../JUCE/modules/juce_gui_basics/native/javaopt/app"
  ] }

sourceSetsは実のところandroidx Application Startupなどを使うことでもっと減らせますし、減らしたほうが適切なのですが、今回はそこまで説明しないことにします。(これを説明するとKotlinのコードも追加しないといけなくなるので。)

app/CMakeLists.txt

app/CMakeLists.txtには次のような内容を指定しています。ちょっと長いですが全部載せます。前述のjuce_cmake_vscode_exampleリポジトリから引っ張ってきたファイルに追記していったものなので、その名残がちょっとあります。

# Automatically generated makefile, created by the Projucer
# Don't edit this file! Your changes will be overwritten when you re-save the Projucer project!

cmake_minimum_required(VERSION 3.15)

PROJECT(JUCE_CMAKE_ANDROID_EXAMPLE
LANGUAGES C CXX
VERSION 0.0.1
)

# for clang-tidy(this enable to find system header files).
if(CMAKE_EXPORT_COMPILE_COMMANDS)
set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES})
endif()

if (ANDROID)

# defs, some are specific to Android and need definisions in prior to `add_subdirectory(JUCE)`.
add_definitions(
    "-DJUCE_ANDROID=1" 
    "-DJUCE_PUSH_NOTIFICATIONS=1" 
    "-DJUCE_PUSH_NOTIFICATIONS_ACTIVITY=\"com/rmsl/juce/JuceActivity\"" 
    )

# Enable these lines if you use juce_audio_devices API
set(OBOE_DIR "../JUCE/modules/juce_audio_devices/native/oboe")
add_subdirectory (${OBOE_DIR} ./oboe)

# libcpufeatures

add_library("cpufeatures" STATIC "${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c")
set_source_files_properties("${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c"
  PROPERTIES COMPILE_FLAGS "-Wno-sign-conversion -Wno-gnu-statement-expression")
enable_language(ASM)
endif (ANDROID)

# build JUCE
add_subdirectory("../JUCE" ./JUCE)

# build App code (e.g. libExamplePlugin_Standalone.so)
add_subdirectory(src/main/cpp)

if (ANDROID)
add_library(juce_jni
    SHARED
    dummy.cpp
    )
target_link_libraries(juce_jni
    ExamplePlugin_Standalone
)
target_compile_options(ExamplePlugin PRIVATE "-fsigned-char" )
endif (ANDROID)

if (ANDROID)からendif (ANDROID)まで囲まれた部分が2箇所ありますが、それ以外はデスクトップのCMakeLists.txtと変わりません。前半ではProjucerが生成する定数をいくつかそのまま指定しています。このアプリケーションではpush notificationを使っていないと思いますが、指定しないとビルドに失敗するので残してあります。

後半のポイントのひとつはtarget_link_libraries()で、今回はプラグインプロジェクトのStandaloneビルド(ExamplePlugin_Standalone)をリンクしています。Android用のプラグインプロジェクトとしてビルドできるのは(Shared Codeのビルドを除けば)Standaloneのみで、これはAndroid上ではexecutableではなくshared libraryとしてビルドされます。これがJUCEアプリケーションの本体になりますが、一方でアプリケーションのブートストラップではlibjuce_jni.soが名指しでロードされます。アプリケーションのCMakeLists.txtを書き換えて生成されるライブラリをExamplePluginからjuce_jniにしても良いのですが、なるべく元ファイルに変更を加えずにそのままビルドできるようにしたいので、libjuce_jni.soを別途ビルドするようにしています。

app/dummy.cpp

アプリケーションファイルには、もうひとつ追加が必要です。このCMakeLists.txtdummy.cppというファイルを指定していますが、これはadd_library()に何もソースを指定しないとCMakeがビルドしてくれないためです。空っぽのファイルで十分なので適当に作成しておきます。

app/src/main/cpp/CMakeLists.txt

JUCEアプリケーション本体の部分(juce_cmake_vscode_exampleでいえばsrcディレクトリの内容)は、今回のプロジェクトではsrc/main/cppといディレクトリにコピーします。そしてこの中のCMakeLists.txtの内容を少しだけ追加してあります:

if (ANDROID)  
  
# dependencies  
find_library(log "log")  
find_library(android "android")  
find_library(glesv2 "GLESv2")  
find_library(egl "EGL")  
set(cpufeatures_lib "cpufeatures")  
set(oboe_lib "oboe")  
  
target_include_directories( ExamplePlugin PRIVATE  
  "${ANDROID_NDK}/sources/android/cpufeatures"  
    "${OBOE_DIR}/include"  
)  
  
endif (ANDROID)

target_link_libraries(ExamplePlugin PUBLIC
...
${log}  
${android}  
${glesv2}  
${egl}  
${cpufeatures_lib}  
${oboe_lib}
)

最初にAndroid固有の追加ライブラリをfind_library()で検索し、それらをtarget_link_libraries()で追加しています。

あと、juce_cmake_vscode_exampleではバイナリアセットとしてSVGファイルの追加も指定されているのですが、assetsディレクトリに置くとAndroid assetsと混同してしまうので、juce_add_binary_data()の呼び出しではjuce-assetsという別のディレクトリを参照するように微修正してあります。

app/src/main/AndroidManifest.xml

アプリケーションに加える最後の変更はAndroidManifest.xmlです。<manifest>要素の内容にいくつか変更を加えます。

<supports-screens android:smallScreens="true" android:normalScreens="true" android:largeScreens="true"
   android:anyDensity="true" android:xlargeScreens="true"/>  
  
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>  
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-feature android:glEsVersion="0x00030000" android:required="true" />

<application android:label="@string/app_name" android:hardwareAccelerated="false">  
 <activity android:name="com.rmsl.juce.JuceActivity" android:label="@string/app_name"
   android:configChanges="keyboardHidden|orientation|screenSize"  
   android:screenOrientation="userLandscape" android:launchMode="singleTask" 
   android:hardwareAccelerated="true">  
   <intent-filter>  
     <action android:name="android.intent.action.MAIN"/>  
     <category android:name="android.intent.category.LAUNCHER"/>  
   </intent-filter>  
 </activity>
</application> 

ApplicationやActivityのクラスがJUCEのもので固定になるのが特徴です(筆者のリポジトリではJuceAppは取り払っています)。一応<supports-screens><uses-feature>をProjucerが生成したままの内容で残してありますが、無くても動作するでしょう。<uses-permission>は必要に応じて追加します。

JUCE本体の修正

ここまででアプリケーションはほぼ完成しているのですが、このままビルドして実行しても、何も表示されないブランクActivityが起動するだけです。これは、ブートストラップのセクションで説明したjuce_CreateApplication()をJUCE本体がアプリケーションの共有ライブラリから発見できないのが原因です。

JUCEのモジュールは、どうやらODR (one definition rule)を維持する目的で、全てPRIVATEでリンクされており、これは-fvisibility=hiddenが指定されているのと同等です。juce_CreateApplication()はビルドされたライブラリにコードとして含まれてはいますが、隠蔽されているのでdlsym()で発見できません。JUCEはこの場合JUCEアプリケーションループを開始しないので、単に何も起きずにブートストラップ処理が終了します。この問題は次のone liner patchで修正できます。

https://gist.github.com/atsushieno/7da120ef87826c9d8fdf8ad6542a16f6

この程度の変更で、AndroidでもCMakeで構築したJUCEアプリケーションが実行できるようになります。

vanilla C++ project

既存のJUCEプラグインアプリケーションを移植する

https://github.com/atsushieno/aap-juce-witte-eq には、witte/EqというCMakeで作られたプラグインのプロジェクトを取り込んでビルドしています。このアプリをsubmoduleで指定して、それに対するパッチを当てた上で、ここまで説明してきたテンプレート…に少し手を加えたもの…をCMakeLists.txtからadd_subdirectory()で追加しています。パッチファイルを見ると分かりますが、基本的にはここまでで説明してきたfind_library()の追加などの変更を加えたものです。対象のプラグインのビルドにStandaloneが含まれていなかったのを追加していますが、これも前述の通りAndroidではJUCE本家でサポートされているフォーマットが他に無く、これをshared libraryとして参照する必要があるためです。

とはいえ、この移植は自作のAndroidプラグインフレームワーク向けのプラグイン化したものであり、そのために必要なJUCEモジュールの追加などもこのパッチの中で行っています。

CMakeで作られたOSSのJUCEアプリケーションはまだそんなに無いのですが、他のアプリケーションもこんな感じで移植できるのではないかと期待されます。

witte/Eq on Android

2020作業記録総括

2020年はあまりアウトプットに結びつかない1年(当社比)だったような気がしたので、そもそもどういう流れで今に至ったのかを振り返ってみました。(android-audio-plugin-frameworkに関連する話題がメインなのでMusic Tech Meetupなどの話は書いていません。)

Q1: JUCE_emscripten gallery: 自作プラグインのUIを動作させるのに一番手っ取り早いのはJUCEプラグインUIのwasm化ではないかと思って、GUI ApplicationだけでなくAudio Pluginプロジェクトをビルドできるようにしたり、Web MIDI API統合を実装したりしていました。肝心のUI統合自体は他のことをやっていて進んでいない状況です。UI統合より先にやるべきこと・やりたいことがある感じなのと、AndroidChrome (WebView)でAtomicsがサポートされていないので意味なかった(これは近いうちに実装が載るはず)という話があって棚上げ状態です。そういえばJUCE6対応もやっていない…(Dreamtonics社が先にやるかもしれない)

この頃からメインマシンだったHP Spectreが頻繁にキーボード/トラックパッドが死んでは修理が必要になるようになって、結局7月に買い換えるまでに4回修理に出し、その間まともに作業できない状態が続きました。買って1年も経っていないのに4回は多すぎる。

Q2: オーディオプラグインUI統合まわりでは別のシナリオを考えることにしました。JUCEからの移植は未来の案件ということにして、フルスクラッチのHTML UIのものをとりあえずPoCで作ろうと思いました。それで、(UIとは別の観点で)今年はOSSKontakt/nkiの代替を目指せるようなフォーマットとしてSFZ形式のサンプラーに注目するようになっていたので、当時GUIの無かったsfizzにARIAっぽいGUIをWebViewで付けてみようと思って、LV2 UI拡張をwebkitgtkで作ってみたりしました。sfizzは成長が早くて、今はvstguiを使ったGUIが付いている状態です(ARIA的なものではありませんが)。

sfizzはファイルシステムではなくassetからデータをロードするような変更も加えようとしたのですが(Fluidsynthでは実績があるので)、そこまで綺麗にローダーができていなかったのと、libsndfileまでパッチを当てないと無理っぽかったので、この計画は(この時点では)ポシャりました。

Web UIがちゃんと機能する感じになってきたので、こっちの作業はここで一区切りとしておいて、それまでmda-lv2くらいしか無かったLV2移植を拡充することにしました。sfizzとguitarix、後からfluidsynthの独自LV2プラグイン版が追加されました。これを実現するには、LV2 toolkit(serd/sord/sratom/lilv)と同じようにAndroid用のネイティブバイナリをビルドしないといけないので、ここでcerberoをアップデートして対応する作業をこなしました(毎回NDKの仕様変更が大きくて割と面倒なやつ)。

あとQ2に入った頃にAndroid NDK方面でPrefabが出てきたのでいろいろ試行錯誤して、だめだこりゃってなったりまだまだいけるか…?ってなったりしていましたがやっぱりダメでした(というのが今のステータスです)。割とこれで時間が溶けたはず…

Q3: 台湾のCOSCUPで初めてAAPの話を公にしたり、LV2開発者ガイドを書き始めたり、ついでにMIDI 2.0本を書いたりして(コレはホントにオマケのつもりで始めたやつですが完成度も割と満足しているし一番売れてます)、割とnon-coding taskに時間を費やしていた気がします。MIDI 2.0 UMPを扱うライブラリが一向に出てこなかったので、cmidi2という軽量ライブラリを作って、ついでにLV2とMIDI 2.0を繋ぐものがまだ無かったので、lv2-midi2という拡張機能も作りました。ただまだ使う場面が無いです。AAPには組み込んだのですが、MIDI 2.0に対応しているプラグインが無い状態です。

そして本当はM3 2020秋に向けて作品を作りたかったのですが、完全にインフラが足りませんでした。インフラというのは、MMLと音源設定だけでtracktion_engineベースのプレイヤーに変換できるような仕組みを構築しきれていなかったというところです。Tracktion Waveform 11はVST2のみ、tracktion_engineのプレイヤーはVST3のみのところにがんばってLV2対応を追加…といった感じでちぐはぐで、創作に耐えるシチュエーションではありませんでした。(別に以前にやったような構成でMIDIだけで完結させてからDAWに持っていってもよいのですが、もともと創作が目的ではないし得意でもないので…)

あとsfizzで使える音色と使えない音色があって、たとえばUnreal InstrumentsのKSOPの類は残念ながら全滅だったので、FM音源でも使ってお茶を濁そう…とか思ってFM音色を自分のデータから引っ張ってきたり、SSG音源もほしいよなあと思ってayumiをLV2化したりそれをAndroidで動かしたり、あとJUCEアプリだったOPNplugをAndroidに移植したりとかやっていました。Androidで作品を作るつもりだったのではなくて、この辺なら「Androidでも鳴らせます」っていうデモができるかなーと目論んでやっていたのですが、プラグイン機構が上記のような感じで平仄が合っていなかったのと、プラグイン機構の違いからstateのBLOBに互換性がない問題などが露見して、「そもそもポータビリティのある音楽データが実現できていないのでは…?」みたいな、この業界の課題に直面することにもなりました。

tracktion_engineも複雑過ぎるのか演奏開始するなりクラッシュする状態で、デバッグを続行するにはJUCE統合まわりのビルドも複雑でLV2サポートもビルドが不安定で…みたいな状態で、ダメだ…腐ってやがる…早すぎたんだ…と反省して、ビルドと開発の基盤を整備しよう、となりました。

Q4: M3が終わってから引っ越したりADC 2020に参加したり、Jetpack Compose / for Desktopとかいろいろ調べたりしていて大したことはやっていなかったのですが、今は開発を継続できる体制を整備する作業が進んでいるところです。前回書いた感じの作業ですね。1年前に比べるとやはりだいぶ違うソース構成になっていて、いくつかのプラグインがそれぞれ要素としては稼働していることを考えると、もうしばらく続けるとだいぶtoolchainとしては期待通りのかたちになるんじゃないかという気はします。てか1年前にはJUCEプラグイン移植が何も存在しなかったのか…(独自プラグインフレームワーク用のJUCEモジュール開発については2月に書いていて、プラグインはその後に一気に移植しています)。

今年の積み残し課題は2つあって、GUI統合と音楽プレイヤーなのですが、特に後者と関連する音楽データのポータビリティは大きな課題なので、来年もいろいろ模索しながら進めることになると思います。

パブリックな活動はほとんどしていなかったと思うのですが、LV2仕様やLV2 toolkitにはちょいちょいcontributeしていて、LV2 Wikiにもなぜか自分のプロジェクトやら同人誌(!)やらがチマチマと載り出したので(多分ほとんどがめっさ日本語を解するZrythm DAWの作者氏によるもの)、「何か見覚えのあるやつ」くらいのポジションにはなったような気がします(それまではlilvの.NETバインディングくらいしかやっていなかったので)。

とりあえず振り返りはこれくらいですね。来年の方針については来年考えてもよさそうです。災禍の時勢なのでのんびり考えようと思っています。

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が公開されているのを発見したらその時点で削除してどこで誰によって公開されていたかを告知します