5月の開発記録 (2023)

5月はGoogle I/Oがあったり(リモートでひと晩見てただけ)、ADC-xがあったり(リモートでひと晩見てただけ)、技術書典14があったり(サークル出展して当日スタッフやってただけ)、割とタイムゾーンを越える必要があって、完全に疲労困憊していました。そんなわけでアウトプットも少なめです…いやそうでもねえかなこれ…

オープンなアプリケーションストアを改善する? PackageInstallerとそのアップデート

5月の上旬にTechBoosterで技術書典14向けの新刊を出すという話が出ていたので、Android 14向けにアップデートが含まれていて、サードパーティアプリケーションストアの文脈でそれなりに時流に乗ったトピックであろうPackageInstallerの話を書きました。が、その新刊自体がポシャったのでzennで供養しました(他の著者もやってたのでまねっこ)。

zenn.dev

この記事では完全に余談扱いのandroid-ci-package-installerですが、zennの別記事にも書いた通り、PackageInstallerベースの実装は(書いてはあったものの)パッケージインストールまでまともに進むことがなく、ずっとIntentベースのインストーラーになっていました。今回この記事の調べ物に合わせて、これを何とかして動くようにしました。

ついでにこの記事でも書いた事前承認のAPIもサポートしたので、Android 14以降のtargetであれば、事前承認さえあればインストール処理が完全にサービスベースになって、GitHubからのダウンロードが完了したときにAndroidシステムから承認UIが「飛び出てくる」こともなく「ちゃんと使える」ようになります。

ADLplug-AEとaap-juce-adlplug-ae

OPL3/OPN2エミュレーターのオーディオプラグイン版であるところのADLplugにはCMakeのビルドスクリプトが存在しているのですが、JUCE6で新しく導入されたCMakeモジュールではなく開発者の独自ビルドスクリプトで何もかもが構築されている(!)ので、すごいんだけどJUCE CMakeモジュールとしての応用は何ひとつ効かないという問題がありました。aap-juceを使ったビルドもProjucerで行っていました。

Projucerサポートもだんだん無理が出てきて、0.7.6ではついにビルドが通らなくなってしまったので(もはや原因を覚えていない)、独自にJUCE公式スタイルのCMakeサポートに置き換えたものをADLplug-AEという名前で新しく作ることにしました。ADLplugは実質もうメンテされていないので(そもそも最近jpcimaが何をしているのか、霊圧を感じない…)、独自版として自分の名義で開発してもいいだろう、みたいな判断です。

github.com

従来のADLplugと異なり、JUCE7に移行して、モーダルダイアログの類も全部禁止して再構築してあるので、Android版のビルドもシームレスに行えます(ADLplugにも自分のパッチを当てていたので、自分としては何の変化も無いに等しいのですが)。ついでにclap-juce-extensionsも組み込んだので、CLAP版もビルドできます。LV2サポートは従来のADLplugにもありましたが、JUCE公式のAtom portとLV2 Patchを使ったアプローチが2023年のLV2の王道スタイルなので、ADLplug-AE版を使ったほうがおすすめです。

これに基づいてaap-juce-adlplugは引退してaap-juce-adlplug-aeに置き換えられました。

相変わらずADLplugとOPNplugのどちらかしかビルドできないのはまだイマイチなのですが、手が空いてやる気が出たら複数プラグインをビルド出来るような仕組みを考えます…

aap-juce-ddsp

今月はNeuralNoteが話題になって、そういえば最近NNを活用する類のプラグインは何も移植してないな…となって、雑にmagenta/DDSP-VSTをAAP化しました。

github.com

シンプルなJUCEプラグインなら小一時間あれば移植できるようになってきたのですが、DDSPにはLinuxでちゃんとビルドできない問題があったり、tensorflowliteのビルドでちょっとハマったりして、1日くらいかかっていました。ただまあさすがにAndroidでは重いですね(とはいってもパフォーマンス分析までやっていないので、いつもみたいにJUCEの問題かもしれません)。実用性は疑問符がつくところだと思います。

NeuralNoteも気が向いたら移植しようと思いますが、すでにBYODで移植が実証できているRTNeuralだけでなく、onnxruntimeも使われているので、それ自体はAndroidで動作済としてもC++プロジェクトで使えるかわからないので、それなりに気合いが必要そうな印象です。

rtmidi-javacpp

5月はktmidiをいじる機運だと思っていたのですが、ktmidiの応用(ktmidi, mugene-ng, kmmk, augene-ng)をLinux以外の環境でいじるにはktmidiのRtMidiAccessを使えるようにしなければならず、先月書いた「rtmidi-jnaがApple Siliconでビルドできない問題」に向き合わざるを得なくなりました。

実際にはずっと向き合ってはいたのですが、(a) JNAeratorを何とかしてM1で動かすアプローチ (b) PanamaをAndroidでも使えるような代替を作るアプローチ (c) JavaCPPをrtmidiに適用するアプローチ、のどれも中途半端に試して成果が出ていなかった、というのが実態です。どれもそれなりに問題があります:

  • JNAeratorは仮にdawrin-arm64でビルドできるようになったとしても、そもそも延命して使い続けるのが妥当なのか? JNAeratorはBridJのためにあった側面が強く、Panamaとjextract(機能的にはこっちがJNAeratorに相当)の正式版が出たら完全にリタイアするのではないか? といった疑問が拭えない
  • Panama自体は当然ながらまだ正式版が出ておらずAPIも変更されうるし、あと根本的な問題として現行のjextractには単独でネイティブライブラリのロードが可能な仕組みがなさそう
  • JavaCPPはJNAやPanamaのようなJNI glue不要の仕組みではないので、ビルドしたバインディングライブラリにはネイティブコードが含まれ、各プラットフォーム/ABI用にビルドされなければならない。

いろいろ問題があるとはいえ、やってみればなんとかなるかもしれないと思って、とりあえず一番無難そうなJavaCPP版を真面目に取り組んで見ることにしました。さまざまな部分でハマりましたが、一応MIDIポートの列挙くらいはできています。

github.com

ただ、現状これをMaven Centralでパッケージ配布するところまでは進めていません。「無理そう」という印象があります。これは「darwin-arm64版をGitHub Actionsでビルドできない」のと「darwin-arm64ビルドを用意しなければならない」の2つが組み合わさって生じる問題なのですが、長くなりそうなので(先にコレを書き終わりたい)気が向いたら別の機会にまとめます。

Logic Pro for iPadの登場でオーディオプラグイン/DAW開発は変わるのか?

という記事をAppleの発表直後(実物が出る2週間くらい前)に書きました。

zenn.dev

2022年にはCLAPがオーディオプラグイン開発の世界を席巻しましたが、今年はAUv3化が最大の波になりそうな気がします。AUv3の発表は2015年で、すでに8年が経過しており、VST3並みに「無視されてきた」フォーマットといっていいと思いますが、Logic Pro for iPadが大々的にフィーチャーしてきたことで、AUプラグインのv3対応が始まる可能性は高いと思います。すでにBeatScholarなど、23日のLogic Pro for iPadのリリースに合わせて出してきたところもあります。

Compact ViewがプラグインUIとして求められるようになったら、プラグイン開発にだいぶインパクトがあるなと思っています。iOS AUv3のadoptionが進めば、macOS上でのデフォルトプラグインフォーマットもAUv3にシフトしてくる可能性が高いです。そして各社DAWがAUv3をサポートしようとすると必然的に「Androidではオーディオプラグインをサポートしないの?」っていう話が上がってくるはずなので(たとえば今月ではないけどこんな感じ)、AAPの開発をそれなりの段階まで進めないとなあ、と思っています。

exploring SurfaceControlViewHost for AAP GUI vNext

AAP GUI統合のアプローチとして、主にWeb UIを使ったプロセス境界越えとSystem Alert Windowがあるという話を、4月まではずっとしていて、ここはAndroidフレームワーク側も改善の余地があるんじゃないの?みたいな話を書いていましたが、ここにきてSurfaceControlViewHostという新しいAPIを発見したので、最近はそのアプローチでAUv3相当のプラグインUIを実現できるのではないかと考えています。

SurfaceControlViewHostはAndroid Rで新しく追加されたAPIで、「(主に他の)アプリケーション上のSurfaceViewをBinder経由で操作する」仕組みです。現状このAPIOSSの世界ではWear OSでしか使われていません。使い方は以下の記事が詳しくまとめてくれています。というかこの記事しか情報源がないし、自分もたまたま目にしなければこのクラスの存在自体知ることがなかったし、この記事でも「Googleが何も詳細をアナウンスしていない」と書いていたり…

commonsware.com

CommonsWareは最近わたしが新しいAndroid APIについて調べているとだいたい引っかかるサイトで、その手の情報はここから見始めるといいんじゃないかと思います。

描画先としてSurfaceViewが選ばれるだけで描画元は「Display IDだけあれば描画できる」ものであれば何でもよく、UIインタラクションも適宜伝播されるという感じなので、試しにComposeViewを転送するサンプルを何も考えずに書いてみましたが、全然できませんでした。いろいろ試行錯誤して出来るようになったので、サンプルコードを新しく適当に作ったリポジトリに公開してあります。

github.com

Composeが使えるくらいならSystem Alert Windowを置き換える存在として十分に役に立つだろうと考えて、AAPにもサポートを追加してみましたが、現状では(SystemAlertWindowと同様に)単にWebViewを使ってdogfoodingいるだけで、GUIイベントのプラグイン本体とのインタラクションも十分に行えていない状態です。in-process UIになるのでプラグイン実装のコストは格段に下がるはずですし、もうちょっと有用なデフォルトUIをComposeで用意したいところです。

aapinstrumentsample w/ native UI

Compose Knob Control

SurfaceControl UIを正式に推奨アプローチにするなら、まずネイティブUIとしてのちゃんとしたdogfoodingが必要だと思っているのですが、現状AndroidのネイティブUIとして動作する有用なコードが存在せず、WebViewで作ったUIのほうがまともなので、とりあえずComposeで作った雑なUIでいいから動くものがほしいかな…となっています。

しかしこれは待っていても出てこない類のものなので、とりあえずKnobから作るか…と思って自作することにしました。Jetpack Compose Knob などでぐぐるとそれっぽいyoutube動画が出てくるのですが、実装アプローチも操作方法もコレジャナイ感が強いというか、われわれがKnobコントロールに求めるのはKnobGalleryのフリーKnob素材がそのまま使えることです。超縦長(たまに横長?)の画像の、もっとも値に近い矩形を表示するという原始的なアプローチですが、コードなしにビューを実装できるという点でこれ以上移植性の高いknobは無いでしょう。AAPのWeb UIで使っているのもwebaudio-knobでKnobGalleryにある画像ですし(作ったのは自分ですが)。

そういうわけでざっくり作りました。近日中にWaveform Viewなんかも移して独立したリポジトリにまとめようと思います。

ComposeKnob sshots

ちなみに矩形と書きましたが正方形しか(まだ?)サポートしていません(正方形でない画像はvalidなのか、validだとしたらどうやって適切な1要素を切り出せるのか、何もわからない)。

関連して knob or slider? みたいなことも考えたのですが、現状以下のような理由でモバイルにはknobのほうが合っているかな?と思っています:

  • sliderのほうがでかい(横か縦に伸びる): モバイルUIでスペースをとるのは基本的に好ましくない
  • 視認性はsliderのほうがわずかに良いが、knobでもいったん指を左右に「どける」ことでなんとかなる(それで値が「見えれば」十分)
  • sliderは本質的に指の位置と表示位置が一致しないといけないが、knobはドラッグ操作の開始点として存在さえすれば十分で、動きに合わせた値の変更幅も調整できる
    • sliderもknobもホールド操作などをトリガーとして拡大スライダーをオーバーレイ表示するようなアプローチがあり得る(ただ、ホールド位置がスクリーンの左端や右端などだとそこから動かせる範囲が限られるし、縦スクロールバーにしても上端・下端で同じことになる)

現状、Compose Knobは上下ドラッグでのみ値を変更するので(デスクトップでは一般的な挙動だし、モバイルでもいちいち2本指で回転とかしたくないわけです)、スクリーンの上下端に置くと変更範囲が限られるわけですが、2本目の指を使って反転操作や微細操作(webaudio-knobのshift+マウス操作)に対応する必要はあるかもしれません。

操作中だけ表示するツールチップラベル表示もデフォルト実装があって(常時表示も可能、実装カスタマイズもComposableとして可能)、Knobの最小サイズとして50.dpを初期値に割り当てていて(推奨しないけどより小さくできる)、これはDPの160dpi = 1inchという定義から約0.8cmとなり、細い指であれば値ラベルを別途用意しなくても見えるとは思いますが、指で完全にノブが隠れてしまったとしても、指を左右に水平にどければ何も値を変えずに見えるようになります。1本指のときに左右に操作を割り当てると指を「どける」操作ができなくなるので、これは避けたいですね。

来月の予定

6月はひさびさに何も締切やイベントがないので、AAP GUIサポートなど仕掛りの作業をのびのびと進めたいと思っています。5月にはMIDI 2.0のアップデートが出てこなかったので、ktmidiに手を加える必要が生じるとしたら6月になりそうです(さらに順延されていなければの話ですが)。

技術書典14 サークル参加情報

告知が直前になってしまいましたが、5/21の技術書典14にサークル「オーディオプラグイン研究所」としてオフライン参加予定です。配置番号は「え-01」です。エノ列です。

オフラインサークル参加は自分でもびっくりしたことに2018年の技術書典5以来、なんと5年ぶりなんですね。当時は音楽技術サークルではなくXamarinサークルだったので(MMLコンパイラ本があったのですが、当時はC#アプリケーションだったのでまあギリ一貫してる…?)、完全に別物としての参加です。

オーディオプラグイン研究所としては(前身のcircle gingaも含め)もう5年分の成果があるわけで、発行している同人誌もけっこうな数になってきました。オフラインの技術書典に参加するのはある意味「初めて」で、これまでは主にM3で頒布してきたので、改めてオーディオプラグイン研究所としてこれまでどのようなストーリーラインで刊行物を発行してきたのか、ひと通り紹介していきたいと思います。この順番だと新刊紹介が一番最後になってしまうので、新刊にだけ興味がある方は最後のほうまで飛ばしてください。

MMLコンパイラmugeneによる音楽制作ガイド

MMLコンパイラmugeneによる音楽制作ガイド

この本を書いたのは技術書典4の頃だったのですが、Xamarinチームに在籍していた頃も趣味でmanaged-midiとかmugeneという自作MML to MIDIコンパイラを作っていたこともあって、ずっと「~(.NETは飽きたので)~音楽系ソフトに主軸を移したい…!!」という思いが高じてついカジュアルに書いてしまったやつです。MMLコンパイラとしては割りと柔軟にマクロを定義して自由にMIDIバイナリを出力できたので、それなりにおもちゃ以上の存在ではあったと思います。「こういう本があるべき」みたいな高い意識はなく、単に同人誌として書きたかったことを書いたものです(!)

mugeneのrepo自体は今はarchivedで、mugene-ngとしてKotlinで作り直されていて、新たにMIDI 2.0もサポートしており、Kotlin/JSを活用してnpm化もして、言語ランタイム不要なvscode-language-mugeneという拡張機能に組み込めています。

mugene fantasy suite (CD)

mugene fantasy suite (CD)

2018年にXamarinを卒業してからも、しばらくの間はmanaged-midiやmugene MML compilerなどC#で書いてきたプロジェクトをしばらく維持していましたが、上記のMMLコンパイラmugeneを音楽制作にちゃんと使ったことは無く、そのことに忸怩たる思いがあったので、2019年3月に幻想音楽祭という比較的小規模なイベントで音楽サークルとして参加し、そのためにdogfoodingを兼ねて(!)制作に使うことにしました。詳しくは↓に書いています

https://atsushieno.hatenablog.com/entry/2019/02/22/181235

今回はそのCDをグッズとして販売します。まあ音楽作品としてはアレですがMMLの実例として…!

DAWシーケンサーエンジンを支える技術 [第2版]

DAW・シーケンサーエンジンを支える技術 [第2版]

上記CDは、音楽の打ち込み部分はLinuxデスクトップとMIDI音源として大昔に使っていたRoland SC-8820を使ってMMLだけで完結させたものの、最終的にはMac上でTracktion Waveform (DAW)とKontaktOzone等のオーディオプラグインを使って素人マスタリングしたものでした。

音楽ソフトに主軸を移していこうというのに、いつまでも古臭いMIDIの知識で生き延びられるはずはないし、ちゃんとオーディオ処理と向き合っていこう…という気持ちになったものの、当然ながらC#でまともなオーディオアプリケーションが開発できるはずは無く、modern C++を勉強していこうという初心者の気持ちになって、2019年から本格的にJUCEを使ったソフトウェアの開発に入門していきました。今思に開発していて今回の新刊の題材であるAudio Plugins For Android(当時はandroid-audio-plugin-framework)もこの頃に雑に始めたものです。

CD制作でTracktion Waveformという割とマニアックなDAWを選んだのには理由があって、2018年にこのDAWGUI以外の部分がTracktion EngineというOSSで公開されたのです。TracktionはJUCEのメンバーが開発してきたDAWで、商用DAWとして本格的な実用性があって(それなりに安定もしていて)エンジンがOSSで公開されているというのであれば、いざとなれば音楽プレイヤーをOSSで提供できる楽曲データをDAWでも打ち込める…! というアドバンテージがあったわけです。

Waveformの楽曲データは単なるXMLで、これは自分としてはntracktive, kotracktiveといったTracktion用の解析ライブラリをC#やKotlinでも簡単に作れるものでした。Tracktionの楽曲フォーマットを調べることで、MIDIでは出来なかったDAWの各種機能が、割と幅広くわかるようになりました。そういったオーディオプラグイン用のプレイヤーやエディターみたいな「シーケンサーエンジン」の設計については、ほとんど情報がなく(そもそもVSTホスト開発の情報などが希少なわけです)、そういうのをざっくり把握できる本があるべきだなと思って書いたのがこの本です。

LV2オーディオプラグイン開発者ガイド

LV2オーディオプラグイン開発者ガイド

自分のメインのデスクトップは2011年にWindowsから乗り換えて以来Ubuntuなのですが、この方面に飛び込んでから2021年頃までは「Linux DTMがしんどい」と感じることが多い期間でした。Steinbergが2018年にVST2 SDKの公開を停止し、VST2のプラグインがビルドできなくなっていく中、当時ROLI傘下にあったJUCE 5.xではVST3プラグインLinuxでビルドできず(この状況はPACE買収後のJUCE6で改善しています)、Linuxで利用できるプラグインはLV2だけでした。多くのJUCEプラグインが非公式のLV2 forkでビルドされていました。

LV2のプラグインにも面白いものがあり、特に当時は「いつまでもFluidsynthでサウンドフォントというわけにはいかない…OSSKontaktに代わるものは何を使えばいいのか…」となってSFZを発見し、その最先端実装であるsfizzにちょいちょい関わるようになり、android-audio-plugin-framework(当時)でも使えるようにしたい…となっていわゆるLV2 SDKAndroidに移植するようになったり…と、少しずつLV2に詳しくなっていきました。日本でもLV2プラグインを開発している人が何人かいるのでとても最先端ではありませんが、例によってホスト側の視点に立つことが多く、たぶんそれなりにユニークな知見が溜まっていました。技術書典9とM3が近づいてきたので、一度この辺の経験をもとにLV2開発本を書いてみよう、となってこの本ができました。

開発以前にそもそも「使う」知見も必要じゃないか…となって、結果的に「LV2とはなんぞや」から入るような本になったのですが、同人誌らしいユニークなやつになったと思います。執筆当時はまだでしたが、2022年にはLV2がJUCE7でも正式にサポートされ、Reaperでも使えるようになって、一気に利用場面が広がっています。

この頃うつぼかずらさんのVST3プラグイン開発本も出て、JUCE Japan本と合わせてプラグインの開発にとっかかるには概ね困らなくなったと思います。(まだAUが無いし、AUv2とAUv3はだいぶ違うという話もある)

MIDI 2.0 UMPガイドブック

MIDI 2.0 UMPガイドブック

2020年。オーディオプラグイン研究所で一番ポピュラーなところを狙った一冊です。

謎の勢いでLV2本を書いたものの、冷静になると「さすがにこの本は売れないだろうな…」とは考えざるを得ませんでした。もう少しキャッチーなトピックの音楽技術ネタは無いものか…そういえばMIDI 2.0の正式版が出ていたな…ちょっと調べてみるか…という感じで、雑に始まったものでした。最初のMML本は3日で書いたので、1週間もあればカジュアルな本が1冊くらい書けるだろうと高をくくっていました(実際1週間くらいで書いています)。

MIDI-CIはしばらく前から存在していたので、新しく登場したUMPくらいがいいだろうと思って調べ始めてみると、これが割とモダンなDAWとオーディオプラグインのイベント入力にも十分に耐えうるデータフォーマットで、これはもっと普及してもいいと思うようになりました。もしMIDI 1.0について知っていれば差分は難しくないので、単なる解説書程度なら簡単に書けると思います。ただそれだけだと「仕様書とほぼ同じ内容」になってしまうので(それでも日本語資料は皆無なので価値はあるといえばありますが)、仕様書では読み取ることができない「なぜこうなっているのか」の部分を自分なりに解釈して解説しています。

MIDI 2.0エコシステム構築術

MIDI 2.0エコシステム構築術

MIDI 2.0本は当初の目論見どおり()順調に売れてよかったのですが、MIDI 2.0を採用したプロダクトはなかなか登場しませんでした(今でもほとんど無いと言わざるを得ないでしょう)。MIDIのように相互運用性が課題の技術では、鶏と卵の問題に陥りがちです。

でも…この場合の鶏と卵って一体何のことを言うんだろう?という考えが浮かびました。われわれは既にMIDI音源を使ってDTMをやっていないわけです。われわれが使っているのはオーディオプラグインであり、ソフトウェアシンセサイザーです。であれば、プラグインMIDI 2.0をサポートするようになって、DAWMIDI 2.0をサポートするようになれば、ハードウェアは後回しでもいいのでは? と考えるようになりました。

この頃、ついに重い腰を上げて、C#のmanaged-midiをKotlinにktmidiという新しいライブラリとして移植していたのですが、せっかくUMPにも詳しくなったのだからMIDI 2.0サポートを追加しちゃえ、となって、UMPサポートを追加していました。managed-midiにはMIDIプレイヤーのAPIもあったので、MIDI 2.0用のMIDIプレイヤーなども独自に作り込んでいました。MIDI 1.0プレイヤーは当然SMFをターゲットにしていたのですが、MIDI 2.0にはそもそもSMF相当のものが無いので(今でもありません。仕様はMMAで開発中ステータス)、そういうものを作る必要があるよね…といった考えを持つようになりました。

また2019年に作り始めたAndroid用のプラグインフレームワークでも、MIDI 1.0のメッセージでイベント入力を受け付けるようにしていたのですが、「MIDI 2.0も受け付けられるようにしたらカコイイんじゃね?」などと考えるようになって、実際に実装していました。MIDI 2.0をサポートするプラグインAPIは、当時最先端だったと思います。

こんな感じで、MIDI 2.0を音楽制作の世界で採用するための下地を整備していこう、という「オピニオン」がいろいろ蓄積されていたので、それを一度アウトプットしておこうという気持ちで書かれたのがこの本です。技術書としては「エッセイ」に近いかもしれませんが、AppleAUでUMPをサポートし、JUCEもMIDI2サポートを次のターゲットにしていて、ここに書いてきたことがどんどん実現しつつあります。

MML to DAW via MIDI 2.0: 次世代MMLコンパイラ開発研究

MML to DAW via MIDI 2.0: 次世代MMLコンパイラ開発研究

2021年刊行。上記の薄い本ではMIDI 2.0採用に関する「主張」を書いたわけですが、やはりここでも「主張したソリューションを自分で実践すべきでは…?」という思いが残り続けました。

この頃、managed-midiからktmidiへの移行が過渡期にあって、MMLコンパイラが生成したSMFからTracktion Engineの楽曲データを生成するaugeneというツールを、ktmidiでも移植してaugene-ngというツールにしていました。そのとき、「ktmidiはMIDI 2.0をサポートしているんだから、MMLMIDI 2.0対応にできるし、augene-ngはMIDI 2.0の32ビットCCやNRPNからオーディオプラグイン操作もできる次世代MMLコンパイラにできるのではないか?」という発想が浮かんできて、そのまま実装されることになりました。

MIDI 1.0時代のaugeneは、2019年にロンドンで行われたAudio Developers Conferenceで行われたLT大会で紹介してきたこともある程度には古いのですが、この時点でもJUCE AudioPluginHostで設定したプラグインのルーティング情報をプラグイン設定として扱う実装を作っていたものの、打ち込みに実戦投入はしていませんでした。MIDI 1.0の範囲でしかできないことをやってもな…という気持ちがあったのですが、今回は32ビットパラメーターを直接操作できるというので、MMLサンプルもめでたく完成しました。この本はこの具体的な成果を踏まえて「MIDI 2.0を技術的に組み込んだ打ち込みはできるんだ(MMLで出来たことはDAWでも出来るでしょ…!)」という話を書いたものです。この後、Appleも実際にAUMIDI 2.0を組み込んで、Logic ProもMIDI 2.0をサポートしてきたので、すっかり追いつかれてしまいました(!)

ちなみに、サンプル制作は、前回制作したCDの楽曲でも出来なくはなかったのですが、「OSSなんだしパブリックCIでMP3レンダリングまで再現できるようにしたい」というこだわりが生じて、それを実現させること自体がひとつのプロジェクトとなった感じでした。結果的には何とかなりました。これは本書の余談でこの本には書かれていません。

CLAPオーディオプラグイン開発者ガイド

CLAPオーディオプラグイン開発者ガイド

2022年にはCLAPという新顔が登場して、オーディオプラグイン開発の世界が大きく揺れた1年でした。それまでVSTAU、あと圧倒的に事例は少なくなるけどLinuxのLV2、といった感じで大勢が決まっていたところに、新しい汎用プラグインフォーマットのAPIが登場して、既存フォーマットの多くの問題点を解決したモダンなAPIだというので、大いに注目を集めました。

新しいオーディオプラグインフォーマットを作るというのは勇気のいる仕事です。「すでにプラグインフォーマットがたくさんあるのに、これ以上新しく対応したくない」という声が出てくるのは必定です。当然ながらそういう反応も出てきましたが(そして今でもCLAPは苦戦しているといえば苦戦しているので、そういう反応が多くを占めているともいえます)、割と歓迎されて将来を期待されていた(いる)ようです。LV2ともろに競合するところもあって、CLAPコミュニティには「LV2はいらなくなった」などと書いているユーザーもいて、あまり治安が良くない側面もあるのですが、オーディオ開発界隈あるあるかな…とは思っています(そう思うようになった)。

Android用のプラグインフォーマットAPIで独自の戦いを邁進している自分も、当然ながら大きな関心をもち、オンライン勉強会も開催して、いろいろ調べたりしました。この本はそれらの成果のひとつの集大成です。CLAPの本ではありますが、前述のLV2本を書いてあったこともあって、この本は「他のプラグインフォーマットとはどう違うのか」という部分を全面的に押し出して書いています。

Linux DTMガイドブック

Linux DTMガイドブック

CLAP本は技術書典13とM3 2022秋に向けて書いたものですが、書き上げた後で「CLAPプラグインの本なんて売れな(ry」とまた思うようになりました。もう少しキャッチーなトピックの本を追加しよう…と思ったものの、特にそんなネタは思い浮かばず、「そういえばLinux用の音源に関して自分のスタンスでいろいろOSSプラグインを紹介したやつがほしかった…」と思っていたのを思い出し、~「プラグイン紹介本」ならスクショだらけですぐにページ数稼げると思って~この本が生まれました。

Linuxオーディオは(他のプラットフォームも多かれ少なかれそうだとは思いますが)Jackの排他制御をきちんと扱えるようになったりALSAやPulseAudio、最近ではPipeWireなども知悉していないと扱えない…みたいな敷居の高い世界観になりがちですが、そうではない、カジュアルにDTMを始めたい勢がWindowsMacでも使っている人がいるような音源をLinuxでも使える…といった世界観を作り出したいと思って書いたものです。

Audio Plugins For Androidの設計と実装

Audio Plugins For Androidの設計と実装

ここまででたまに言及してきましたが、わたしの最近の主な開発プロジェクトは、Audio Plugins For Android (AAP) というAndroid用のプラグインフォーマットのエコシステムを作ることになっています。オーディオプラグインの仕組みは、デスクトップだけでなくiOSにすら存在するのに、自由に他のアプリケーションと連携できるはずのAndroidにはその仕組が存在せず、DAWは自分専用のプラグインをユーザーに使わせることしかできません。自由な音楽制作ができない、たいへんよろしくない事態だと思っています。

AAPはこの状況を打開するために開発されています。ここまでの書籍でも言及してきたような現代的な機能も含め、オーディオプラグインに求められる機能を実現できるよう、MIDI 2.0ネイティブでモダンな設計を目指し、JUCEアプリなどマルチフォーマットでプラグインをビルドできるプラグインを数多く移植しています(とはいえ、まだまだ開発中で色々足りていないのですが)。

プラグインフォーマットがAndroidに存在しないのは、デスクトップとはアーキテクチャが全く異なるというところに根本的な原因があるのですが、Androidで全く出来ないというわけではなく、できるところまではそれなりにiOSのAUv3に近づけられます。何がモバイルプラットフォームにおけるオーディオプラグインを実現困難にするのか、このAAPというプロジェクトがそれらの障害をどうやって乗り越えたか、またオーディオプラグインフォーマットを設計するとはどういうことなのか、といった思想的な話など、AAPの開発に際して書いておくべきことを(公式ドキュメントとは別に)いろいろまとめあげたものです。

続きはオフライン開場で(!?)

当日の頒布品は以上の10点です…いくらなんでも多くない!? 当日スタッフが見本誌チェックするの(やります)、ちょう大変そう…すまんの…

オフライン開場には、今あるAudio Plugins For Androidのデモ環境も用意して持っていく予定です。まだ大したことができるわけではないのですが、もしこの方面に興味のある方がいたらぜひ試してみてください。

今回は当日スタッフとしても動いていて、また1人で店番をやる予定なので、もしかしたら不在になっているかもしれませんが、いる時間のほうがずっと多いはずなので、もし不在でも懲りずにまた見に来てやってください。

4月の開発記録 (2023)

4月は終わっただなんて、またまたそんな嘘を…(時候の挨拶)

4月は(予告通り)あんまし開発に時間を割いていない感じで、AAPの宣伝活動強化月間みたいになっていました。リリース作業も行っていたので、生産期から収穫期に移ったような感じです。

「(AAP) の設計と実装」の執筆

予定通り、昨日のうちに(ギリギリでしたが)boothで販売を始めました。

xamaritans.booth.pm

来月は技術書典14があるので、それまでにもしかしたらアップデートがあるかもしれません。無いかもしれません。実は技術書典オンラインのほうはCLAP本もアップデートが半年弱くらい反映されない状態なので(イベントが終わった途端全くレビューしないフェーズに入るとは思ってなかった)、AAP本も今後は同じように著者から自由にアップデートできない可能性が高いです。そういうわけで、基本はアップデートの体制が整っているboothでの購入をおすすめします。技術書典オンラインにそれを期待するのは無理かなと。

AAPの設計と実装の執筆作業は、設計に関する思索を巡らせる契機になることも多く、今月は特に「AAPは仕組みとしてはAUv3に近づけてもいい気がする」とか「AAPの仕組みをAPIから独立させて実装アプローチとしても規定したいが、具体的に何が考えられるか」とか(関連issue)、「拡張APIとして規定するとして、実質的に『必須』の拡張があるならそれは任意とは言えないので、明確に準拠プロファイルとして規定すべき」、みたいなことを考えました。

執筆したことの半分くらいは前提知識を埋めるためのものだったのですが、残りの部分は設計の取捨選択とか設計の理由とかに関するもので、これは公式ドキュメント…とはならないまでも公式参考資料として、英訳して使い回したいと思っています。LV2 Bookが近いコンセプトでしょうか。AAPの宣伝本なんだから無償でいいんじゃないの、等も考えましたが、内容的には公式ドキュメントに載せるべき情報とは異質な技術読み物としての側面が強いので、今のところは普通に(?)趣味の技術書として読まれてほしいです。

AAP 0.7.6 Released

本当は5月でいいかなとか、GUIサポートの問題がいろいろ修正されてからでもいいと思っていたのですが、2月初頭にリリースしてから2ヶ月以上経って、これ以上変更を溜め込んだら周辺プロジェクトが追従するときに追いきれなくなると思って、早目にやっておくことにしました。

リリースノートはここにまとめてあるのですが、今回もずいぶん更新が多かったです。

github.com

前回0.7.5の時に「これはずいぶんでかいリリースになったな」と思いましたが、0.7.6もそれと同程度以上のインパクトがあったと思います。個人的には、AAPのクオリティ面での修正が多く含まれていて、long-standing issueも多く閉じることが出来て、いい傾向だったと思います。クオリティというのは、実装の安定度もそうですが、仕様も着実にブラッシュアップされています。(今までが雑だっただけかもしれませんが…!)

aap-juce improvements

最近のaap-juceのホスティングまわりの改善は効果が著しく、最近はhelio-workstationの移植の上で初回起動時に出てくるサンプル楽曲のInstrumentを全部mda-lv2のプラグインで置き換えてもちゃんと再生できるレベルまで来ています。これは先月書いたJUCE_DONT_AUTO_OPEN_MIDI_DEVICES_ON_MOBILEに加え、「JUCE on AndroidアプリがデフォルトでOboeを使うようになっていない問題」と、次のJUCEのリリースには含まれているであろう「Oboeオーディオループで毎回calloc()が呼ばれている問題」のバグフィックスを個別パッチとして取り込んだためです。

「JUCEでもそんなレベルのバグがあるんだ」って思われるかもしれませんが、やっぱりリアルタイムオーディオ処理って簡単ではないし、フレームワーク開発者はアプリケーションのレベルまでdogfoodingする機会がなかなか無いのと、Androidサポートの優先度が高いプロジェクトではなさそうなこともあって、ユーザー(この場合は開発者)が発見する機会が少なからずあると思います。JUCEチームは小さくてAndroidの問題にきちんと取り組める程度までスケールしないので、この状況を打破するにはJUCEと戦える競争相手が必要そうな気がします。といっても商用製品としてのJUCEは適切な価格設定だと思うので(v5くらいの頃なら「価格の割に価値が大きい」くらいは書いていたと思う)、収益のために競争相手を作るよりは、「プラットフォームネイティブでいいものを作る」みたいな「競争」のほうが現実的かなと思います。

rtmidi-jnaのFFIをどうにかしたい問題に取り組む(進行形?)

NAMM 2023のタイミングに合わせてMIDI 2.0のバージョンアップが出ると見込んでいたのが5月以降までずれ込んで(見込みが妥当だったかは別としてNAMMのMIDI2関連セッションでもそれくらいと言及されています)、それだったらktmidiというかkmmkをもうちょっとブラッシュアップしてPer-Note pitchbendくらい扱えるようにしておきたいなあとか思っています。

ただ、現状でktmidiがrtmidi-jnaで利用しているJNAeratorでブロックされている側面が割とあって(たとえばSiliconで動作させるのに必要なネイティブビルドが無かったり)、さすがにFFI機構でJNAeratorに依存している部分を置き換えたいなあと思っています。手軽に対応するならJavaCPPあたりが正解だと思いますが(ただ過去に一度失敗していて理由を思い出せない)、最近はPanamaもだいぶ使えるようになってきているようだしなあ…と思って、いろいろ調べたりしている感じです。

Panamaは割と面白いですね。BridJをいじってた頃、あるいはそれ以前に自分でdyncallなどを使ってJNAの代替を作ろうとしていた頃を思い出します。Panamaは、libffiでのfallback実装(があります)を越えてやっている部分はBridJに近そうだし(BridJはdyncallを使っていたけどPanamaはJavaでmanaged solution的に実装している)、そこにMemorySegmentとMemoryLayoutのコンセプトを持ち込んでBridJが扱えなかったstruct return by valueの問題にも対応できています。array return by valueだけまだ怪しそう。JNRもその辺はBridJより新規性に乏しい設計でした。この辺の話は、(Mastodonにも書きましたが)JVM language summit '19のセッション動画が参考になります。

www.youtube.com

まあrtmidiバインディングをJNAからPanamaにしてしまうとKotlin/JVMでの動作要件が厳しくなりすぎてしまうので、そうはならないような選択肢に落ち着くと思います。JavaCPPにするか、バインディングを生成済みのやつに切り替えるか…

5月の予定

そんなわけで、AAPの作業よりktmidiの作業をメインにしている可能性がそれなりにあります。5月は技術書典14もあるので、そっち方面で原稿を書いてるかもしれませんし、MIDI2のアップデートも出てくるかもしれないし、Google I/Oで何か出てくるかもしれないし、思いのほかAAPに「戻ってくる」まで時間がかかるかもしれません。

オーディオプラグイン研究所: M3-2023春 サークル情報

こんにちは、ライフステージの変わらないatsushienoです(時候の挨拶)

また直前の告知となってしまいましたが、4/30に東京~靴~流通センターで開催されるM3 2023春にオーディオプラグイン研究所として出展します。配置番号はい-14b(第2展示場1F)です。

androidaudioplugin.org

今回は新刊「Audio Plugins For Androidの設計と実装」を持っていきます(見本誌のみ、販売は電子のみです)。

Audio Plugins For Androidの設計と実装(cover)

オーディオプラグインフォーマットをモバイルプラットフォームで実装してみて、これはデスクトップでプラグインフォーマットを設計するのとは全然違う部分がたくさんあるな…ということがわかってきたので、モバイルプラットフォームでの適用も考慮したプラグインフォーマットの設計と実装について、解説書を書いておくべきだと思って、今回の執筆に至りました。タイトルはBSD悪魔本のノリですね()

目次を上記サイトで公開してあるので、詳しい内容はそこから推測してください。

コレに先立って、先週4/21に(予告通り)Shibuya.apk #41 で「Androidに楽器のエコシステムを作る」という大味なタイトルで15分しゃべってきました。15分、しかも非オーディオクラスタ向けにしかしゃべっていないので、だいぶうっすらとした内容になっていますが、スライドは公開してあります。

speakerdeck.com

これを150分くらいしゃべるとこの1冊ぶんくらいに…というには多分まだ時間が足りないと思います。80ページくらいあります。Linux DTMガイドブックみたいにスクショだらけとかでもない80ページなので、いつもよりちょっと長いやつです。

当日は、この新刊と連動してAAPのデモ環境としてAndroidタブレットを用意してあるので、これを合わせて展示する予定です(Linuxデスクトップをデモ環境として用意した前回よりはスペースの余裕が…!)。といっても、現状まだ音楽を再生できるレベルには至っていないので、音を出すレベルまでです。

というわけで、M3に参加される方は、ぜひ現地ブースい-14bまでお越しください。

AndroidのオーディオプラグインUIと実行形態についての覚書

Audio Plugins For Android (AAP) で曲がりなりにもGUIサポートを実装できたので、オーディオプラグインDSPGUIの連動形態について考えるようになった。

リモートプロセスを前提としたUI-DSP interop

AAPではWeb UIがDAWにとって自由にUIをコントロールでき、Androidのセキュリティプラクティスとしても問題がない推奨アプローチとなっているが、Web UIはホストプロセスで動作しており、たとえばGUI単体でサンプルファイルを開こうとするとプラグインプロセスではなくホストプロセスのパーミッションの範囲でしか開くことができない。プラグインが事前に用意していたコンテンツには全くアクセスできないことになる。そのため、ファイルを開く操作などはGUIではなくDSPのプロセスで行わせることになるが、そのためにはWeb UIとプラグインプロセス側のコードの間でインタラクションが必要になる。

これはLV2ではGUIDSP(オーディオスレッドで動いているコードという意味ではないのでやや雑な表現だが、「GUI以外」を示すものとしてはなあなあで使われてしまっている表現なので、ここでも単にDSPと書く)の間でプラグインのポートにAtomメッセージを書き込むLV2UI_Write_Functionを経由してやり取りすることになっている。Atomメッセージは任意の形式になるので、ここにUIとDSPで共通して理解できるコマンドのやり取りを「やま」「かわ」のレベルで自分で定義してやり取りしろ、というのがLV2のアプローチだ。実際には「開くファイル名のポート」にデータを渡されたらファイルを開く、くらいの運用になるし、典型的な操作はLV2:Patchを使えばいい。

実装アプローチとしては明確なのだけど、すべてのUI操作についてところどころこうやって「コマンド」を実装してやり取りしなければならないというのは、しかるべきアーキテクチャに沿って設計・実装するのでなければ、シンプルに面倒だ。こういうのはMV-Whateverのやり方で統合されていることが望ましそうだ。

UIのインプロセスホスティングの可能性

しかしそもそもプラグインUIがDAW上で自由にGUIを表示できればこの問題は生じないわけで、可能であればインプロセスで処理できたほうが開発体験が良くなるのではないか。現状ではSYSTEM_ALERT_WINDOWパーミッションがあればプラグインプロセスのUIを表示できるわけで、Androidでは技術的に困難というよりはOSポリシーの問題だ。AudioUnit V3の場合、App Extensionの拡張点としてGUIを作成していれば、DAWのプロセス上にいてもプラグインUIを表示できる。

AppleがApp Extensionについてこれを許しているのは、あくまでUIの実行はプラグインプロセス上で行っていて、AU拡張としてユーザーが明示的に許容したタイミングでしかUIを表示できないから、ということができるだろう。それならば、Androidでも同様の仕組みを作ることができるのではないか。

つまり「オーディオプラグインとしてシステムにアプリケーションのセットアップを登録できる」ようにすれば、SYSTEM_ALERT_WINDOWのようにアプリケーション側が任意のタイミングでUIをポップアップ表示することはできなくなり、DAWがオーディオプラグインUIを表示するとしたタイミングでのみ、ポップアップUIを表示することができる。この仕組みであれば、セキュリティ上の懸念はなくなるといえる。

これはAndroid上でIMEを有効にするためにSettingsで独自のパーミッションを有効化しないと使えないのと同じ仕組みだ。オーディオプラグインとして可視化されリストアップされるのであれば、SYSTEM_ALERT_WINDOWを表示しようとする他のアプリのリストを汚すことにもならず、明確に管理できる。

realtime audio priority over AIDL

オーディオプラグインアプリケーションをシステムに登録できることのメリットはもうひとつ考えられる。オーディオプラグインアプリケーションとしてDAWから接続されるServiceでは、オーディオ処理で必要となるリアルタイム優先度をもつBinderの接続を許容させることができる。realtime Binder IPCが一般に開放されていないのは、一般向けに開放するとすぐにリアルタイムスレッドが枯渇するからだと考えられるが、オーディオプラグインの処理にしか使えないようなIPCに開放することが、絞り込みとしては可能になる。

ただ、ここにどういう要件で絞り込みをかけられるかはまだ明確に見えてこない。AUv3の場合は、おそらくDSP処理もAudioUnit Extensionの拡張点としてAUの関数をオーバーライドすることで呼び出せるようにすることで、DAWのコードで直接IPCを呼び出させるのではなくAudioUnit FrameworkからDSP部分をリアルタイム優先度でIPCできるように実装したのだと考えられる。

AAPのようなオーディオプラグインの場合、リアルタイム優先度で呼び出されるべきはprocess()関数のみなので、この関数を含むServiceクラスが存在すればよいことになる。この関数が直接プラグイン開発者(あるいはプラグインフォーマット開発者)に開示される必要はない。あくまでフレームワークの一部として存在すればよいことになる。

ただ、従来のAndroid System上に存在しているリアルタイム優先度のサポートされたアプリケーションとは異なり、この仕組ではクライアント側だけでなくサービス側もユーザー実装が動作することになる。その中でどれだけ停止時間を含むコードが動作するかが未知数になるという事態は、Googleとしては避けたいところだろう。なので、この仕組み上で動作するAIDLでは最大実行時間に制約を設ける(そしてシステム上のデバッグ設定でデバッグアプリケーションについてはこの制約を無効化できる)といった対策が必要になるかもしれない。

オーディオプラグイン機構を「AudioBus相当」から「AUv3相当」にするために

これらの変更は、もちろんアプリケーションレベルでは不可能であり、Androidシステム(AOSP)に手を加えなければ実現しない。だからこれはだいぶ大掛かりなアイディアだ。とはいえ、AAP単独でできることにはこの辺に限界があって、現状ではiOSの世界でいうところのAudioBusのレベルでとどまっているということになる。ここから先に進むにはAOSPに手を出すしかなさそうだ。

もっとも、AAPは現時点でAudioBusのレベルに辿り着いているわけではない。GUI拡張を断片的にでも実装したことで、Android上のオーディオプラグインフォーマットとしては他の追随しない世界まで辿り着いたと思っているけど、まだ多くの部分が未完成だし、この領域に手を出す前にやることがある。それでも、ある程度の成果が出てきたら、次にやるべきことと、そのためのアプローチが見えてきたと思う。

3月の開発記録 (2023)

今月はだいぶAAP(Audio Plugins For Android)で成果の出た1ヶ月になったと思います。1ヶ月を振り返ってみて割とびっくり。

Music tech meetup: LT meetup (3/4)

2月に告知したとおり、3/4にこの名義で開催したのですが、公募からはLTが1件も出てこなくて、目論んでいたLT meetupとはなりませんでした。当日まで自分だけが話すというステータスだったのですが、運営に参加していただくようになったhotwatermorningせんせいにCPUキャッシュ最適化まわりを話していただきました。

自分の「再履修: 2023年までのリアルタイムオーディオ処理」のLTスライドはspeakerdeckに上げてあります。

speakerdeck.com

もはやlightening talkではなくlong talkという感じだったのですが、当日はスライドの半分くらいしか話せていません…(!) 「当日いきなり半分にも1/4にもなる可能性もあるからその前提でスライドを作る」という意気で書いたやつだったのですが、やっぱり本当に当日いきなり半分になったら自分の期待通りには回りませんでした…(!)

LT meetupとしてはだいぶ未遂に終わったのですが、このコミュニティの勉強会をここ数回やっていて、毎回「自分がしゃべるイベント」にしてしまっているのが良くないと思っていて(それはコミュニティの名義を「私物化」した個人活動になってしまっているし)、それは実現したいことではないので(個人でやるならAAPの話をしたい)、むしろ誰でもしゃべる機会があるmeetupを開催すべき、と思って今回の企画に至ったわけです。今回は「(運営を除いて)事前に特定の知人に発表を打診しない」公平モデルのやり方にしたのが、結果的にコミュニティの現状と合わなかった感じでした。誰も空白から最初の一歩は踏み出せず、鳥も卵も生まれなかったということです。

もっとも、事前にトークを打診するやり方は、いろいろ考慮することがあって、単に「やり方」の問題だけではないと思っています。自発的にしゃべってくれる人以外に依頼するとなると、プレゼンテーションの作業負荷を考えなければならなくなります(前回のZrythm勉強会を「自分が調べて話す」スタイルにしたのはそのためでした)。ワーストケースとしては「無償で講演を依頼されたんだけど」という「告発」を受ける可能性すら考慮しないといけなくなります。個人的にはそのテのやつを見ると「そりゃ無償でお願いするだろ」と思いますが、コミュニティ名義で依頼するならそうも言っていられない側面があります。公平な募集モデルは安全側に倒したやり方なんですね。

自分はオーディオ開発者コミュニティの分野では完全に有象無象なので、このテの依頼を懸念なく発行できるようなポジションにはいません。もうちょっと精進して自分の名前を売り込んでいかないと実現しないでしょう(あんましそうしようという動機がない)。

自分たちのコミュニティが特別に消極的だということはなくて、「LTを公募すればトークが勝手に集まってくる」「枠が空いていればここぞとばかりに応募が滑り込んでくる」みたいな状況は、他のコミュニティでもそんなに自然にはなさそうです。こんど4/21にはShibuya.apk #41で15分しゃべるのですが(AAPの話をするつもり)、この枠もずっと空いていて「そんなもんだったっけ…?」と思いながらサクッと取りました(Shibuya.apkはAndroid界隈では有数のコミュニティ勉強会)。

LTしたいひとがいないコミュニティだ、と捉えてもいいのですが、状況的に応募しにくかっただけの可能性も高く、そこを推測しても不毛なので、とりあえずは「他の人にしゃべってもらう」ことは忘れて、自分が勉強会のネタを思いついたときだけ開催するスタイルに戻ろうと思います(機会は作ったので「私物化〜」で忸怩たる気持ちはなくなった)。もちろんコミュニティとしては他の人が提案してもいいし、もし案を持ち込んでくれる(運営でもない)人がいれば対応するつもりです。

AAP new audio plugin buffer API and V3 protocol

2月の途中から、AAPのコアAPIに含まれていたAndroidAudioPluginBufferという構造体を引き回す設計を解体して、CLAPのようにaap_buffer_tという構造体に、ポートごとのポインターを取得する関数ポインタをホストが設定して、プラグインはそれを呼び出す、というスタイルに変更しました。これによって、内部実装のleaky abstractionを隠蔽できたと思います。構造体の設計自体がやっつけだったのが、全プラグインで使われることになっていて、ずっと直したかったやつのひとつです。(他にもAndroidAudioPluginExtensionTargetなんかも直したい…

この変更作業の過程で気付いたのですが、aap-juceがAudioProcessor::process()を呼び出すとき、そのバッファサイズは固定になっていて、これは場合によっては存在しない空白を作り出してノイズの元凶になっているのでは…と気付きました。JUCEのAudioProcessorでは、processBlock()にframeCountを渡すことになっていて、JUCEホスティングアプリケーションではこの数がAAPの想定するバッファサイズとは毎回異なっている可能性があります。それも各アプリケーションのレベルでOboe StabilizedCallbackのように固定されたほうがよさそうではありますが、そうなっていないときのために、processBlock()に渡されたフレーム数でAAPもprocess()を呼べるようにAPIを変更するのが筋だと気付きました。

そういうわけで、半年前に2年ぶりにAAP "V2" protocolに更新したばかりですが、今月からAAP "V3" protocolとしてprocess()process(frameCount)になっています。正式なリリースまでに他にも変更が加えられるかもしれませんし、現時点では破壊的変更がまだ躊躇なく行われるでしょう(関連リポジトリを全部自分で更新するのが面倒…という理由で躊躇することはあります)。

ちなみにこの変更の過程でMidiDeviceServiceのオーディオ出力が常にノイズまみれになるという割と大きなリグレッションが発生していて、しばらくその問題に取り組む作業が続きました…(問題はMidiDeviceServiceのリングバッファまわりの変更にあって解決済)

AAP: RT-safe input dispatcher locking in MidiDeviceService

LT meetup(と称する何か)でリアルタイムオーディオ処理について学びなおしたので、AAPでずっと雑に放置されていたMidiDeviceServiceでMIDI入力がAAPのprocess()の処理中に飛んできた時に何が起こるかわからない問題に真面目に取り組むことにしました。

今回は(1)MIDI入力はnon-realtimeでJava APIから飛んでくる、(2)オーディオ処理はOboeでリアルタイムスレッドから必要に応じて呼び出される、というのをやっています。MIDI入力を反映するタイミングは保証できないのだから、オーディオスレッド側が入力に対応できないときは飛ばし、入力側のスレッドは必然的にブロックするので(最低でも)ロックをかけてから更新するというレベルでは協調する必要があります。

これは、勉強会資料でReal-Time 101のセッションから引用したさまざまな排他制御シナリオのうちのひとつ、「非リアルタイム処理ではwrite lockをかけられるようになるまで待って更新し、リアルタイム処理ではread lockの取得に失敗しても良い」パターンです。オーディオループでread lockの獲得に失敗してもオーディオ処理が正常に流せるように、MIDI入力処理スレッド側ではオーディオ処理のデータを直接更新しません。更新は入力イベントキューへの追加というかたちで行われます。オーディオ処理側はロックを取得できたら、イベントキューの内容をコピーしてきてキューを空にし(この間ロック解除を待っている入力処理スレッドはブロックされている)、それをオーディオループに渡して処理することになります。Real-Time 101ではspin lockしていましたが、モバイル端末で野放図にspin lockしてバッテリーを消耗したくはないので、nanosleepしてspinしています(Androidで動けばいいので!)。nanosleepは当然システムコールですが、RT safeなオプションで使っています。

AAP: Oboe StabilizedCallback in MidiDeviceService

RT safeな更新処理を実装してみたものの、AAP MidiDeviceServiceのaudio glitchは依然として大きいものでした。他に原因があるはず…と思いながらいろいろ試行錯誤したのですが、OboeのStabilizedCallbackを使うようにしたら一気に安定したので、これを使うことにしました。

StabilizedCallbackというのは、そうでないunstabilizedな状態とは異なり、オーディオループすなわちOboeのオーディオコールバックで、バラバラなサンプル数のオーディオブロックが渡されることはない、という処理モードです。デフォルトでは、Oboeは自分のオーディオバッファの処理状態(次のオーディオ処理までの空き時間の状態など)に合わせて最適なサイズを計算してコールバックを仕掛けてきます。AAPのようにプロセス間通信が必要になったり、DSPでもオーディオループごとに行われる計算処理(あるいはmemcpy()呼び出しなど)が不必要に増えてくると、最適サイズとやらで小さいデータを送るようなmicro optimizationをかけられるとかえってパフォーマンスが落ちる…ということにもなりかねません。そういったオーディオアプリ開発者のニーズに応えて、Oboeには固定サイズでコールバックを回すStabilizedCallbackというAPIが用意されています。

StabilizedCallbackを「使うようにした」といっても、MidiDeviceServiceの中で使うようにした変更より(それもやりましたが)、JUCEホストアプリケーションの中で#if JUCE_USE_ANDROID_OBOE_STABILIZED_CALLBACKというundocumentedな定義を追加したのが一番効いています。要するに、JUCEではこれを使っていなかったということです。Androidにはオーディオプラグイン環境が無いので(!)、StabilizedCallbackを使うのがデフォルトとはなっていなかったわけです。もちろん、Standalone AppとしてビルドされていたJUCEプラグインの移植でも全てこのunstableなcallbackがガンガン回っていたことになります…(この問題が影響する範囲が割と大きかったといえますが、これは次節で詳しく書きます)

StabilizedCallbackを使うべき場面はおそらく割と限られていて、Oboeのリポジトリに含まれるサンプルコードでもStabilizedCallbackは使わずにLatencyTunerを使っている、みたいな話があったりして、この辺のベストプラクティスはまだ流動的に動いている可能性があります。

AAP MidiDeviceServiceは、AAPの中では一番まともに動いている部分ですが、それでもMIDIメッセージの「抜け」が頻発し、出音もプラグインによっては頻繁に飛ぶ、みたいな状況でした。メッセージが抜けるのはどうやらkmmkなどのMIDIクライアントアプリ側の問題で、音飛びの最大の理由も次に説明するJUCEの問題が大きかったので、MidiDeviceServiceの問題は相対的には小さかったのですが、とりあえず自分の実装を安心できるものにしておくことは重要です。

AAP: struggling with JUCE pitfalls

AAPのオーディオ処理が雑なのは年単位で放置されていた問題だったのですが、今年はAAPを実用的なオーディオプラグインのエコシステムとして使える状態にしたいと思っているので、いつまでもオーディオ処理が雑なのは良くないと思って、少なくとも理由もなくノイズまみれになったりするのは何とかしようと思って、いろいろ調べ始めました。AAPはアプリケーションの境界を超えるオーディオプラグインの仕組みとしては世界レベルで最先端なので(!)、できることには限界がありますが、できる範囲では対策していこうと思っています。

そういうわけで、今月はこの方面でいろいろ調査して改善を施しました。まず、圧倒的に悪かったのはJUCEですね(!) どういうことなのか説明していきます:

  • JUCEのオーディオプラグインプロジェクトでは、Standaloneプラグインフォーマットのアプリケーション(AudioPluginHostなど)は、iOSAndroidでは「MIDIバイスを全て検出して」「入力デバイスの1つを勝手に開いて使える状態にする」挙動になっている
    • これは#define JUCE_DONT_AUTO_OPEN_MIDI_DEVICES_ON_MOBILE 1 のように定義すると無効化できる(undocumentedな定義)
  • JUCE on Androidは、MIDI入力デバイスのリストにMIDI出力デバイスがリストアップされるメチャクチャな実装になっている(既知の問題)
  • JUCE on Androidが、Android MIDIバイスの検出過程で、MidiDeviceServiceを全部openするので、デバイス上の全MidiDeviceServiceが「起こされて」プロセスとして残り続ける
  • MidiDeviceServiceによってはこの時点でOboeのオーディオループが回り始める(MIDIバイスを "open" しているんだから、それはそう)
  • aap-juceで移植されたプラグインは、Activityを起動するとこのStandaloneモードのコードが走ることになる
    • JUCE on AndroidはJUCEの初期化処理の過程でActivity起動部分を乗っ取って、AndroidManifest.xmlのmain launcherに何を指定してもJuceAcitivityが起動するようになっている
  • AudioPluginHostや自作のプラグインホストでも(つまりjuce_audio_plugin_clientのどこか)、勝手にMIDIバイスを全部開くコードがある。前出の#defineはStandalone Appにしか適用されていないのでgrepでも見つからない…

要するに、JUCE on AndroidのStandaloneアプリケーションが立ち上がるとMidiDeviceServiceのどれかがOboeでオーディオループを回し始めるので(しかもJUCEは「入力デバイス」を開いているだけのつもり!)、複数のアプリケーションが同時にリアルタイムオーディオ処理を回している、ということになります。そりゃパフォーマンスも悪くなるわけだ…

これ調べるのだいぶ大変でした。

JUCEチームは、特にROLI傘下で開発を回していたときは、Androidサポートが明らかにおざなりで優先度が低かったので、こういうメチャクチャな実装が含まれていることが少なからずあります。プラグイン側はaap-lv2を使えばよさそうですが、ホスティングまわりではJUCE以外の選択肢がほしいところですね。JUCE以外の選択肢で最初に思いつくのはCarlaエンジンを使ったIlldaelなどですが、Carlaのビルドも現状あまりAndroidには向いていないです。

とりあえずの対策として、aap-juceではJUCE_DONT_AUTO_OPEN_MIDI_DEVICES_ON_MOBILEの利用を必須とすることにして、Projucerプロジェクト用のビルドスクリプトではこれを強制しています。CMakeプロジェクトの方は現状あくまでCMakeLists.txtに自分で書かないといけないので手作業対応になってしまうのですが、これ以上対策するかは現状では未定です。しかしAndroid端末側にopen時点でOboeループを回し始めるMidiDeviceServiceがあるともうどうしようもないので、根本的な対策にはなっていません。JUCEの問題コードを探り当てる必要があるでしょう。

とはいえ、これは全体的に見て悪いニュースではなく、この調査結果をもとにどういう対策を講じれば自分の環境では問題が発生しなくなるのか、だいぶ高い精度で分かるようになってきたので、たとえばスクリーンキャプチャーを公開したいと思ったときには、従来「奇跡的にうまくいくことがある」のレベルだったのが「だいぶ高い確率でできるようになった」まで向上しました。たとえばこれくらいの動画がカジュアルに録れるようになっています。

View post on imgur.com
imgur.com

AAP audio tracing support

JUCEの問題は、問題の全てというわけではなかったのですが、いかんせんどういう状態にある時にaudio glitchesが発生するのかを予測できないことが大きな問題でした。AAP自体のオーディオ処理で時間がかかりすぎている可能性もあれば、AAPで呼び出しているプラグインDSP処理自体が遅い可能性もあるし、AAPの問題だとしてもaap-coreの問題かaap-lv2 / aap-juceの問題かもしれない。ちゃんとした計測が必要です。

aap-coreでもaap-lv2でもaap-juceでも、調べたい時に処理時間を計測できるように適当にAndroid logに計測のサンプリング結果を出すやつは#if ... で有効化できるように残してあったのですが(ログ出力はリアルタイムオーディオ処理には向いていない)、もう少し真面目にプロファイリングしようと思って、今回はちゃんとAndroid profilerとATrace APIを使って結果を残すようにしました。Android profilerを使って実行するとAAP関連の項目が追加されるようになっています。

github.com

Androidオーディオのプロファイリング手法については、かつてAndroid Audioのdeveloper advocateだったDon Turnerが詳しくまとめていたのですが、彼はもうオーディオ専業ではなくなってしまって、この情報ではすでに計測できません。2023年のプロファイラーでは、CPU Tracingは記録だけを*.traceに残して、プロファイリング結果はPerfettoを使って読み解くのが公式に推奨されるやり方です。そしてAAudioを有効にする手順を踏む必要があります。

このPerfettoを使ったプロファイリングの手順は、自分がざっくりWebで探した限りでは誰もまとめていなかったので、このAAPのドキュメントに最低限必要な情報をまとめてあります。Androidバイス側でプロファイリング項目を設定する必要があります。あるいは、上記ブログの頃とは異なりAndroid Studioにはオーディオ関連のプロファイリングオプションを指定する手段が無いので、この辺の問題(報告した)ら、Giraffe Canary 10以降では修正が適用されたようなので、もしかしたらデバイス側の手順は(ASがオーバーライドしたプロファイル設定をadbで送れるように実装されているなら)不要かもしれません。

aap-juce-simple-host

JUCEの特にホスティングまわりは、AudioPluginHostのAndroid移植によって検証されていましたが、AudioPluginHostには様々な問題があって、特にAndroidバイス上ではUIが全く使い物になりません。PCのタッチパッドでもわかると思いますが、ためしに指でコネクターを接続しようとしてみてください。細かい上に指が邪魔で目視できません。AAPの開発ではAudioPluginHostのスクリーンショットがよく出てきますが、全部エミュレーターだからマウスでできていることです。

そういうわけでもう少しモバイルに適したUIのAAPのdogfoodingに特化したテストアプリケーションを作らなければならない…というのは長年自分のwishlistに乗っていた懸案事項でした。2月についに思い立ってゼロから作ったのですが、デスクトップで動くものをビルドしただけで、Androidに持ってきたら、やっぱりまともに動作しなかった…という感じで↑の調査に踏み込んだ、というのが先月から今月にかけての流れです。いずれにしろ、調査の過程で、誰でも確認できるようなコードが公開されていることが重要だと思ったので、開発中のある時期から(ちゃんと動かなかったけど)公開してあります。今はそれなりに動くはずです。

github.com

AAP: working AAP GUI extension

V3 protocolとしてオーディオバッファのAPIが整理され、JUCEがさまざまな問題を引き起こしていたことがクリアになって、AAP自身の問題はあまり無いことがわかってきたので、いよいよ先月ガワ = GUI拡張機能だけ作ってあって表示制御しかできていなかったやつに、実際にMIDIメッセージを送信する部分を実装できる状態になりました。

とはいっても、最終的にはシンプルに、いまGUI表示制御を行っているlibandroidadudiopluginの実装で、MidiDeviceServiceが行っているようなMIDI2イベントキューをクライアント実装に追加して、クライアントAPIにユーザー(アプリケーション開発者)から渡されたMIDI2イベントとマージして送るようにしただけです。Web UI上で発生したイベントは、JavaScriptInterfaceのAAP Interop実装によって、AudioPluginWebViewの生成した時に渡されたorg.androidaudioplugin.hosting.AudioPluginInstanceaap::RemotePluginInstanceから生成されたorg.androidaudioplugin.hosting.NativeRemotePluginInstanceに送られます。

(たびたびMIDI2イベントと書いていますが、aap-lv2やaap-juceはMIDI1のイベントをUMPに変換して送っているので、プラグイン開発者がMIDI2を直接送る状況は現時点ではあまり考えられません。自力でプラグインやラッパーを実装する人のみ関係ありです。AppleがCoreMIDIやAUで内部実装が完全にMIDI2化しているのを隠蔽しているのと似たような構図です。)

Kotlinクライアントの実装だけではネイティブコードからちゃんと使えるかわからないのでaap-juceのホスティング実装であるjuceaap_audio_plugin_clientでも、AndroidAudioPluginInstanceクラスでcreateEditor()をオーバーライドして常にWebView GUIを返すようにして(Web UIサポート実装の中にデフォルトUIが最初から用意されているので各プラグインが実装している必要はないわけです)、前記aap-juce-simple-hostをはじめaap-juce-plugin-hostでもGUI表示が行えて、鍵盤に反応して音が出ることを確認してあります。aap-juce-helioでもUIは表示されるのですが、モバイル環境だと(?)UI表示中はオーディオ処理が回っていないようで音が出ず、パラメーターの変更が演奏時には反映されているというかたちでしか確認できません。

この辺の成果は、前出のimgurにデモとして上げてあります。

これらの実装とは別に、そもそもGUIイベントがどのようにプラグインとホストの間で受け渡されるかが明確になっている必要があって、今月はその辺の設計を詰める作業も進めていました。GUIまわりはまだきちんと実装されていない部分も多く、これは5月以降の課題になりそうです。

AAPのGUIサポートは、2020年以来のlong-standing issueだったので、これをついに閉じたときは感慨深いものでした。(まあもっと古いopen issueも残っているのですが)

来月の予定

4/30にM3 2023春があるのですが、AAP最大の技術的課題であるGUIサポートに目処がついたこともあって、今回は満を持してこのAudio Plugins For Androidの設計と実装に関する同人誌を出す予定です。今月はそのための作業(執筆等)も始めていて、目次案を見返して「ホントに終わるのか…?」となっていますが、何とか月末までの発行に漕ぎ着けたいと思っています。そのため開発作業はだいぶ停滞する予定です。「5月以降の課題」と書いたのは勘違いではないです。

また、前出の通り4/21のShibuya.apkでもこの辺の話をする予定です。Android開発者はAndroid SDKくらいまでの知見はあってもNDKまわりはあんまし無いだろうし、オーディオプラグインの知見はほぼ皆無だと思うので、その辺からふんわり話して終わると思います。オフラインのみ・すでに満員のようなので、興味があればぜひ〜とは言いがたいところですが…! (興味がある方はM3のほうに来ていただければ、そちらで無限にお話ししようと思います)

4月はNAMM 2023もあってオンラインで眺めているはずで、おそらくNAMMではアップデートされたMIDI 2.0の仕様も公開されると予想されているので(アップデート自体はADC22とかで言われていた、Protocol Negotiationとかが消えるみたいなやつ)、だいぶ忙しくなりそうです。

2月の活動記録(2023)

いつものやつです。今月は割と短めですが、(最後にも書いてるけど)他にやらないといけないことがあるので時間かけてません。

aap-core 0.7.5 / aap-lv2 0.2.5 / aap-juce 0.4.5 released

先月は大幅な機能改善がまとまってきたのと、GitHub Actions設定の統一、Wikiの整備、開発版配布用にAAP APK Installer…といろいろ準備できてきたので、満を持して(?) AAPの関連リポジトリの全てでリリース作業を行いました。全部で25リポジトリ近くあるので、0.7.4リリース作業は1週間くらいかかっていたと思いますが、今回はほぼ24時間くらいで完了しています。やっていることがビルドエンジニアっぽい…

リリースノートはGitHub Discussionsを使ってまとめているのですが、ここに書いていくのはそのうち変えたほうがよさそう…

ちなみにビルドはほぼ落ち着いたと思っていたのですが、今月に入ってからlibs.versions.tomlを使ってバージョン管理するようになったりしていて、*.jucerファイルを変更する必要が全くなくなったのが割とインパクトあります。libs.versions.tomlはテンプレートプロジェクトから同じ内容のやつをコピーするだけで足りるので楽ちんです。

docs: AAP FAQ & Contributing

今月は新しく2つAAPのドキュメントを整備していました。

特にFAQは割と大作になっています。設計思想の話とか立ち位置とかいろいろ言明しておくべきところがあるので…(プラグインフォーマットを新しく作るというのは割と攻撃されうる)。後者は先月書いて出すのを忘れていたというのは内緒…(!?)

AAP GUIの実験的実装

月初にAAP 0.7.5をリリースして、ちょっと実験的な機能に手を出したくなったので、今月は長らく着手できなかったGUI拡張に手を付けてみました。GUIサポートにはいくつかの前提があり、もともと一筋縄ではいかない想定ではあるのですが、実装してみないと見えてこない課題もあるので、GUIを表示するPoCを作っていました。

これも設計を割と長めにまとめてあるのですが、まだ過渡期なのでどんどん変わっていく可能性があります。現時点では2通りのシナリオが途中まで実装されています:

  • Web UIをホスト側で表示してホスト側でUIイベントを受け取る: Binderでのprocess()送信時にはMIDIバッファ(等)にイベントがソート済みで格納されている
  • System Window Alertの特権を無理やり使ってプラグインプロセス側で表示する: イベントはネイティブで受け取って、(1)activate()が呼ばれている状態であればprocess()が呼ばれたタイミングでホストからのリクエストとマージして処理する (2)activate()が呼ばれていない(あるいはdeactivate()が呼ばれた)状態であれば、非リアルタイムループで即時処理してもらう(あくまで実装が自主的にやることであって、プラグインフォーマットとしては「お任せ」状態)

実際にはこの他にプラグインのActivityを起動してプラグインインスタンスIDを渡してGUIを表示させるアプローチが考えられます。Android 12L以降のFoldablesなどでActivity embeddingを利用する場合も、Activityを再利用するアプローチになるでしょう。

この実験の過程でJUCEプラグインのMain ActivityをAAPのデフォルトUIにする実験もしてみたのですが、JUCEの実装の内部で勝手にJuceActivityを起動する仕組みになっているので、JUCEの問題をそれなりに抜本的に手を加えて修正しないと実現しなそうです。

ホスト側でプラグインandroid.view.Viewを表示するためにはSystem Window Alertのユーザー許可が必要になるのですが、これはRuntime Permissionに属さない特殊なパーミッションで、Settings経由でユーザーに許可してもらわないといけないやつです。正直これを使うアプローチはあまり推奨したくはないです。一方でこれが一番簡単に実装できそうなやつなので、水が低きに流れるだろうな…という気はしています。

AAPのGUI実装はAndroid固有のものになるので、クロスプラットフォームを意識する必要がない部分では楽ですが、他のプラグイン拡張機能とは使い方も異なるものになりますし、Androidの流儀に沿ったUIサポートを実装しなければならないという意味では、AUv3に近いプラグインフォーマットだといえます。実のところAUv3はMIDI 2.0をネイティブでサポートするプラグインフォーマットとしてもAAPと似ています。

GUIを表示したり破棄したりする仕組みは作りましたが、そこからイベントをDSPに反映させる部分を実装するためには、いろいろ設計・実装しないといけない部分が多く、まだ実現していません。むしろその部分を実装する前に、オーディオバッファ操作APIのイマイチな部分などを修正したりしていて、まだ基盤をいじる作業を抜きにGUIの実装を進めるのは無謀だなあと思っています。

new plugin ports

今月はPeakEaterをaap-juceに移植しましたが、これはプラグインとしてはシンプルで特徴的なものではありません。これは「このくらいのプラグインなら移植作業を録画してみて、うまくいったら参照できるようにしよう」と思って着手したものでした。動画はGoogle Driveに上げたものをリポジトリからリンクしてあって、実際60分程度でひと通りの移植作業が完了することは示せたので、もう少しちゃんと解説動画っぽく作れたらYouTube等にも上げられると思います。

あとSEELEというJUCEプラグインも移植しましたが、これは完全にネタとしてやったやつで実用性はほぼ無いです。

今月は他にもいくつか移植を試みたやつがありますが(たとえばsurgeとか試してる)、ちゃんとAAPとして使えるところまで持って行ってないので公開はしていません。

music tech meetup: LT meetup

3/3にやるのですが、現時点でLTが全然集まっていないので、自分がしゃべらざるを得ない状態になっています。この話は別途書いて出そうかと思います。ちなみに自分のトピックはこんな感じです。

mastodon.cloud