Mastodon

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

1月の開発記録(2023)

1月はひさびさに開発作業に専念できた気がします。

rebranding AAP: Audio Plugins For Android

今月からAndroid Audio Plugin FrameworkをAudio Plugins For Androidに改名しました。GitHubリポジトリもatsushieno/android-audio-plugin-frameworkからatsushieno/aap-coreに変えてあります。

改名自体に喫緊の理由は無く、名前の先頭に他所の商標が入っていると問題になりそう、っていう予防的な対策です(喫緊ではないもののずっと気になっていた問題ではあります)。GoogleAndroid関連商標っぽく見えるので、公開イベント等で話す時に名前を出しにくくなるともったいないですし、今ならそんなにブランディングの変更も大して面倒ではないと思って着手しました。後ろにFor Androidを付けるのは割と一般的な回避策だと思います(Mono for AndroidWindows Subsystem for Android)。略称としては引き続きAAPを使いますし(AP4A、APfAでもいいけど)、コード上の命名に変更はありません。ここを変えるとしたら大々的に非互換変更を加えるときでしょう。

昨年末から少しずつプロジェクトとして回していくための作業が進行しつつあります。

AAP: previewing presets & MIDI settings

JUCEやLV2のプラグインは、ものによってはMIDIのプログラムチェンジに対応して音色…に対応するプリセット…を切り替える機能を実装しているものもありますが、そうはなっていないプラグインも多いです。それでも、プラグインAPI経由でプリセットを変更する機能が実装されているものは少なくありません。また、エフェクトプラグインについては、MIDIプログラムチェンジでプリセットを切り替えるというものはほぼありません。

モバイル環境でオーディオプラグインのパラメーターを細かく調整するのは作業として面倒ですが、プリセットを選択する程度であればカジュアルにドロップダウンリストのようなものを表示するだけなので、現実的な解だと思います。そういうわけで、とりあえずはaaphostsampleにプリセット選択機能を実装してみました。今月はBYODを移植したので、BYODのプリセットを選んでいろんなディストーションを適用して遊べるようになったのですが、割といい体験になったと思います…ちゃんと動けば。JUCEのプリセットサポートはややクセがあって(プログラムチェンジみたいに番号で選ぶようになっている)、AAPでもやや無理をしている部分があるので、割とちゃんと動かないことが多く、今後見直す必要がある課題みたいになっています。

aaphostsampleは、今は完全にやっつけプレビュー機能しかもっていませんが、MIDIマッピング設定などをshared preferenceで保存できるようになりましたし、今後はだんだん管理機能のようなものを追加していくことになると思います(aaphostsampleである必要は全く無いですが)。

ちなみにaaphostsampleでプリセットを選択できる機能をスクショしてmastodonに流したのですが(twitterにもforwardされる)、その数日後にJUCEのAudioPluginHostにも同じ機能が実装されましたね。いいところはどんどんパクってほしいですね(自信過剰)

new AAP ports

今月はしばらく前に移植を試みて動いていなかったJUCEプラグインが2つ動くようになりました。

(1) aap-juce-os251: OS-251のAAP移植が動くということはすなわちreact-juceプラグインが動くということです。最初に移植したときに起動時エラーで落ちていて、1年以上放置していたのですが、多少JUCE on Androidの罠がわかるようになってきて(主にこの 辺りなど)、今年改めてAAPの最新版に更新して改めてビルドして既知の起動時エラーも対処してみたら動くようになりました。

(2) aap-juce-byod: BYODはRTNeuralの応用事例であり、ギターエフェークターのリアルな実用例としても大いに移植のしがいがあるプラグインです。RTNeuralで使われているxsimdが古くてAndroid上でSIMD最適化が行われていなかったので、RTNeuralで最新のxsimdを使えるように多少コードの修正を加えて、Android上でも使えるようになりました。これも実は半年くらい前に試しに移植してみて動かなかったやつです(今でもUI起動前にクラッシュするのでちゃんと動くとは言い難いですが)。

去年は新しいプラグインをほとんど増やしていなかったので、だいぶ豊作な印象があります。実のところ、去年移植が全然増えなかったのは、これ以上増やしても大変っていうこともまああるのですが、あまり新しいJUCE OSSプラグインが出てこなかったということもあります。このBYODとOi Grandadくらいしかパッと思いつきません。LV2プラグインもそんなに増えているわけではないですが、master meとかが新しいかな。プラグインというとちょっと違うけどIldaeilも新顔ですね。

AAP parameters and presets on MidiDeviceService

MidiDeviceServiceとなったオーディオプラグインでは、入力がMIDIメッセージのみになってしまうので、パラメーターやプリセットの指定がそのままではできません。これがDAWであれば、MIDI mappingを使ってDAWMIDI入力メッセージをパラメーター設定やプリセット指定に置き換えたりできるわけで、同じような機能がMidiDeviceServiceにもほしくなります。特にAAP V2プロトコルではMIDI2が入力として使えるので、マッピングポリシーさえ事前に規定しておけば、それなりにストレートなやり方でマッピングが実装できます。

というわけで、AAP拡張機能の一部として、以下のようなマッピングを調整できるようにしました:

  • CCをパラメーター設定として使う
  • Assignable Controller (NRPN) およびPer-Note Assignable Controllerをパラメーター設定として使う
  • ユニバーサルsysex8で所定のビットパターンをもつものをパラメーター設定として使う
  • プログラムチェンジをプリセット設定として使う

一方で、VST3のようにマッピングを強制した結果オーディオプラグインでまともにMIDIのプログラムチェンジが受信できなくなったり、ノートとパラメーターチェンジが別々のシーケンスになって順序がおかしくなったりする問題の原因にもなるので、プラグイン開発者が「このメッセージは受信したら独自に処理する」と宣言しておくことで、MidiDeviceServiceのようなホスト側がそれらを「マッピング対象としない」ことが可能になっています。

実装当時はparameters拡張機能の一部としていましたが、プログラムチェンジの設定は明らかに「パラメーター」ではないので、今は「midi拡張機能」の一部としています。もともとmidi2拡張というのがあったのですが、イベント処理の内部実装が全てMIDI 2.0 (UMP)による実装になったので、拡張もへったくれもない状態で、それなら今回ついでにMIDIイベント処理に関する拡張機能として再利用しちゃえ、ということでこうなりました。

LMN-3-DAW-Android

2年前にJUCE+Android+CMakeのネタを書いてから、JUCE on Androidに詳しい輩とみなされるようになっていて(実際にはそんなにJUCE開発の経験は無いんですが)、ちょろちょろとメールで相談がやってくる日々です。今月は、LMN-3-DAWAndroidで動かしたいんだけど…みたいな相談がやってきたので、(自分では開発に関わるつもりは無かったので)とりあえずブートストラップとしてプロジェクト構造があればいいかと思って、1日でAndroid版を作ってみたら、案外そのまま起動できるところまでいったので、そのまま渡した(教えた)ら、翌日には公式リポジトリ持っていかれていました。

ただ引き渡してからが問題で、Windowsでビルドできない…という話になって、(もう1年以上Windowsをまともに起動していない)、調べ方だけいろいろ伝えて、某オーディオDiscordでも喧々囂々の議論が行われていたのですが(といっても自分はほぼ眺めていただけ)、あの記事は思いのほか勘違いされている(たとえばパッチが無いと動かないような修正を加えていると思われている)ということがわかり、ちょっとエゴサしてみるとforumやらdiscordやらでリンク出されては「難しくて読めねえ」みたいな感じの反応ばかり出ている状態だったので(!?)、気が向いたらそのうち何とかしよう…と思いました。

最近も「MIDI入力が期待通りに取れないんだけど…」みたいな相談を受けては「そもそもJUCEは入出力を取り違えているレベルでまともにAndroid MIDIを扱っていないので…」みたいな話をしていて、これもそのうちJUCEを直すしかねーかな…みたいな感じになっています(自分のコードで使う予定が全く無いのでモチベーションが低い)。

このプロジェクト、DAWのエンジン部分にはtracktion_engineが使われていて、AAPも組み込めれば利用事例になるといえばなるんですが、USBで接続するハードウェアコントローラーが無いとまともに使えないので(デスクトップ用にはコントローラーのエミュレーターになるJUCEアプリもあるのですが、Androidでそのまま使えるわけがない)、とりあえず今のところ実験用に使う予定はないです。

AAP APK Installerとパッケージングの全般的な統一

今年はAAPをある程度は実用性のあるオーディオプラグインのソリューションとして使えるようにしていきたいと思っているのですが、パッケージが多数のリポジトリに分散していて全く試せる状態になっていなかったので、何かしらのベータテスティング的なデプロイメントを実現しようと考えました。それで紆余曲折を経て、AAP APK Installerという仕組みが出来上がりました。AAP専用にするのはちょっともったいないので、AAP以外でも使えるようにある程度一般化して、自分でパッケージカタログ等を用意すれば何でもインストーラーに渡せるようなActivityを含むライブラリにもなっています。詳しくはzennのほうに書いています。

zenn.dev

それでAAPプラグインのカタログを自分でまとめていて、パッケージ名などが割とバラバラで散逸していたので、この機に全部統一的な名前にしようと考えて、全てorg.androidaudioplugin.ports.lv2.*org.androidaudioplugin.ports.juce.*みたいな名前にして、アプリケーション名も全部 "AAP xxx" の形式にしました。これで関連パッケージが全部インストール済みアプリケーションの先頭に AAP xxx としてまとまるのでスッキリしました。

あとaap-lv2-*リポジトリ群、aap-juce-*のCMakeビルドのリポジトリ群、aap-juce-*のProjucerビルドのリポジトリ群、の3つのカテゴリーで、それぞれGitHub Actionsのワークフローが同一の設定をほぼ使い回せる程度に共通化されていたので、Reusing workflowsの機能を使って全部まとめることにしました(現在進行形で移行中)。

aap-juce hosting fixes, and Helio Workstation now works

AAP 0.7.4をリリースしてから気づいたのですが、aap-juce-plugin-hostが何やらインスタンス生成で固まるようになっていました(自動テストしたいんだけどできないやつ)。AudioPluginHostは現状aap-juceの唯一的な実装例なので、これが動かないのでは見本として体裁が悪いので、ちゃんと調べて直すことにしました。結論からいえばAndroidContext.bindService()がメインスレッドで動くようになっていたのとJUCEのjava.lang.Threadサポートのバグのコンボだったわけですが、無事また使えるようになりました。

ホスティング実装が直ったので、この機会にと思ってHelio WorkstationAndroid版へのAAP統合をアップデートして試してみたのですが、相変わらずオーディオルーティングのところでオーディオポートが検出されないという問題が直っていなかったので、これも調査してみて、どうやらjuce::BusesPropertiesの設定を含むjuce::AudioPluginInstanceしか認識されないということがわかったので、バス設定を適当にでっち上げるようにしたら(!?)、ちゃんと実行できるようになりました。

実行はできるものの、オーディオクオリティは全然良くないので、その辺は今後の調査次第という感じですが、そもそもHelioに最初から含まれているプリセットプラグインでも音飛びが生じているので、AAPの問題とHelioの半々という感じです。まだまだ問題はありますが、AAPが見込んでいるオーディオプラグインフォーマットらしい使い方が、これでようやく示せたのではないかと思います。何とか実用できるところまでもっていきたいところですね。

Web技術とクロスプラットフォームオーディオプラグイン開発アーキテクチャの今後を検討する(2023)

最近、Web技術を利用したクロスプラットフォームのオーディオプラグイン開発アーキテクチャについて考えを巡らせている。といっても対象はWebプラットフォーム(だけ)ではない。Web以外の環境における利用を念頭に置いている。

オーディオプラグインという文脈では、従来のクロスプラットフォームWindowsMacLinuxに限られる言及が多かった。しかし2022年現在、クロスプラットフォームといったらモバイルやWebを考慮に入れるべきだし、Windows 11やChrome OSのようにLinuxコンテナやAndroidコンテナが動作する環境も登場してきている。

iOSにはAudioUnit v3があって、macOSと基本的には同一のアーキテクチャで構成できると考えても良さそうだ。Androidにはオーディオプラグイン規格がないが、筆者が開発しているAAPが実用段階に入れば(まだ待ってほしい)、あるいは他所から同様の機構が出現すれば、検討すべきものになる。WebではWAM2 = Web Audio Modules 2.0がこの分野を開拓しているが、まだ発展途上にある(AAPよりはだいぶ進んでいる)。WebAssemblyはC/C++/Rustといった言語のソースからWebアプリケーションをビルドするための基盤として確立しており、wasmランタイムによってネイティブ実行も可能になりつつある。

モバイルやWebにおける音楽制作アプリは、他の分野と比べると、デスクトップにまだまだ追いついていない。Webアプリで「ネットワークを跨いでリアルタイム演奏したい」といったデスクトップでも実現できていないような要望を叶えるのは無理があるが、今できることとできないことを整理して、どのような開発アプローチで向き合っていけるかを本稿では検討したい。

Web技術を取り込むというトレンド

筆者はオーディオプラグインとホストの開発で、昨年いくつかのプロジェクトがWeb技術を取り込もうとする動向を観測した。

これらは、必ずしも最先端のトレンドを作ろうとしている先駆者というわけではない。CmajorよりもSOULよりも前から、FAUSTではコードをWebアプリケーションとして動かすことができたし、ReaperのCockos WDLから派生したiPlug2では数年前にWebAssemblyビルドを実現しており、WAMのビルドも可能になっていた(WAM2に対応しているかは筆者は把握していない)。

また、開発用言語としてJavaScriptを使用しつつも、実際にはネイティブコードにコンパイルして動作させるElementary AudioのDSPのようなソリューションもあり(と筆者は理解しているが、使ったことはないので誤解している可能性は否定しない)、これは筆者の理解ではどちらかというとFAUSTやCmajorに近い。あるいは、ChiselでScalaから複数のパイプラインを経てネイティブコードにコンパイルするのと同じように、JSからDSPのネイティブコードを出力していると考えたほうが近いかもしれない。

Plugin UI, to and from Native and Web

Web技術をオーディオプラグインに取り込むというのと、Webにオーディオプラグインを持ち出すというのは、逆方向の概念だ。これらは整理しないと混乱を招く可能性があるので、ここで一覧にまとめておきたい。

Technology UI API/code binary platform コメント
react-juce React.js Native + JS Native ReactでUIを作ってJUCEプラグインとして動かす
Elementary JS,HTML JS, Native/WebView Web, Native ネイティブDSPはJS runtimeで動かさない
FAUST FAUST Native, wasm Native, Web オーディオDSP用言語でWebもサポート。GUIはFAUST
c-major JS,HTML Native/WebView, wasm Native, Web オーディオDSP用言語でWebもサポート。GUIはHTML
iPlug2 iPlug2/C++ Native, wasm Native, Web x-platプラグイン開発ツールがwasmに対応
DPF wasm DPF/C++ Native, wasm Native, Web x-platプラグイン開発ツールがwasmに対応
juce_emscripten JUCE/C++ wasm Web x-platプラグイン開発ツールがwasmに対応
WebAudioModule2 * wasm, JS Web WebAudio用のオーディオプラグインフォーマット

Wasm as ABI(s)

CLAP開発者の技術研究目的はオーディオプラグインのバイナリコードを再利用できる可能性に関するものであって、これは筆者の関心事と重なるところがあって興味深い。バイナリコードとしてwasmを使えるようになれば、新しいCPUアーキテクチャが登場したときにネイティブコードを新たに生成する必要がなくなり、同じwasmコードを流用できるというわけだ。新しいプラットフォームでも、DSPコードが分離していればコードの再利用が可能になるし、なんならDSPだけであればGUIなしでそのままwasmランタイム上で実行できる可能性がある。

これは世のwasmランタイムが目指している「ネイティブコードとしてのwasm」あるいは「ABIとしてのwasm」の構想をオーディオプラグインでも実現しようというものだ。C/C++/Rustコードからwasmをビルドできて、それがどのアーキテクチャのどのプラットフォームでも実行できるようになれば、あとはプラグインフォーマットの違いだけ意識すれば済むことになる。実際にはwasm32とwasm64で別々にコードを生成しなければならないだろう。これは特にLinuxデスクトップなど、機能的完全性に比べて正当に評価されていない環境での制作環境の大幅な改善に繋がりうる。

ネイティブコードとしてのWebAssemblyというのは、実のところ他の領域でも利用が検討されている。Android NDK開発チームから昨年公開された提案のひとつに、wasmをNDKのABIとして実装してみるのはどうか、というissueがある。これについてはTechBoosterから12/31に発売される「プロと読み解くモバイル最前線~アプリを支える最新技術~」に寄稿した「Android NDKはどこへ向かうのか」という記事で数ページ割いて解説したので、興味のある読者にはそちらを参照されたい。

DSPGUIのコード分離(1)ネットワーク越しのプラグイン操作

DPFの開発者であるfalktxが実験している目的は少し異なる。彼はMOD Devicesというハードウェアデバイス上に実装されたLV2プラグインを外部から制御するためにWeb GUIを利用しようとしている。つまりGUIDSPは別々のプロセスどころか別々のハードウェア上で動作しているというわけだ。実のところ、ADC22でも組み込み用Linuxでオーディオプラグインを搭載するハードウェアデバイスの開発を行っているElk Audioの開発者が、DSPとUIの分離に関するセッションを行っていて、アーキテクチャとしてはMOD Devicesに近いといえる。セッション動画をリンクしたいところだがまだ公開されていない(去年は遅いやつだと11ヶ月も待たされた動画があったしまともな時期に公開される期待値は低い)。

オーディオプラグインなのにネットワーク越しに操作するなんてナンセンスじゃないか、と考える読者もいるかもしれないが、オーディオプログラミングで最も厳密にリアルタイム性が求められるのはオーディオ処理の部分であって、GUIは一般的にはリアルタイムではあり得ない。GUIの更新は(2022年の一般的なアーキテクチャとしては)プラットフォーム上のGUIスレッド上で動作しなければならないし、それらはリアルタイムスレッドではない。

もちろんハードなリアルタイム性が要求できないからといって、いくら遅延してもかまわないということにはならず、合理的な範囲でユーザー操作が遅延なくDSPに反映されることが求められる。ユーザーによるポインターイベントの開始から実際にアプリケーション上で入力イベントがGUIアプリケーションループに乗るまでの遅延も厳しく求められる世界ではある。

DPFとFAUSTでは、複数のWeb UIがひとつのDSPをコントロールできる。これはGUIDSPのコードが適切に分離していないと実現できない。

DSPGUIのコード分離(2)プロセス分離モデル

AudioUnit v3はApp Extensionとして実装されており、DAWのアプリケーション上でプロセスを分離した上でプラグインを起動・実行できるようになっている。iOSでも、GUIDAWの上にデスクトップのオーディオプラグインのようにオーバーレイ表示できるようになっている。Android…というよりAAPの場合、GUIサポートはまだ実装されていない。Overlay WindowでDAW UIの上にプラグインUIを表示できる可能性もあるが、筆者は現時点ではプラグインから提供されるWeb UIをホスト側でロードして表示する仕組みを検討している。この場合、ホスト側のプロセスで表示しているUI上で発生したプラグインへのイベント通知は、Binder IPCの仕組みに基づいて送られる想定だ。このモデルでは、DSPGUIのコードが分離することになる。

モバイルではなくデスクトップDAWでも、Bitwig Studioのようにプラグイン毎に分けるプロセス分離モデルをサポートしていると、そのDAWではホストとプラグインの間でIPCに基づいてオーディオグラフが処理されることになるはずだ。AndroidのAAPもだが、オーディオバッファ全体をIPCのメッセージで送受信するような無駄なことはせず、共有メモリを利用することになるだろう。

これらのような環境では、プラグイン拡張機能をライブラリとしてホスト側で実行することは(少なくともインプロセスでは)できないので、プロセス境界をまたぐFFIを実現する必要があり、プラグインとホストのやり取りをAPIというよりは通信プロトコルのようなかたちで実現する必要がある。

ホットリロードを可能にするためのコード分離

FAUST、react-juce、Cmajorなどが実現しているのは、ホットリロードを前提としたRADといえる。C++やRustで開発していたら、全てを静的にビルドしなければならず、待機時間が長い。GUIをWeb技術で構成するものは、GUIのみホットリロードできるし、DSPを分離していれば、その部分だけはフルコンパイルしても大したビルド時間にならずにすむかもしれない。どちらか一方だけでも、伝統的なJUCEプラグインのように全部コンパイルするモデルに比べたら格段に速いだろう。

ホットリロードという課題は、Web技術にすれば何でも解決する問題ではないということを示すものだ。 juce_emscripten を使うと、Webブラウザ上でもJUCEプラグイン(のstandaloneアプリ)が動作するが、これはホットリロードからはほど遠い技術だ。

コード分離の境界線

オーディオプラグインのコードが設計上の制約によって分割される場合、次のような境界線が考えられる。

  • DSP: このモジュールは、主としてリアルタイム優先度をもつオーディオスレッド上で行われるオーディオ処理を担う。他のモジュールとのやり取りはatomic operationsによって行われる。
  • GUI: このモジュールは、ホストプロセス上にもロードできるようなUI処理を担う。DSPモジュールとのやり取りは(もしサポートするのであれば)atomic operationsによって行われる。リアルタイム性は期待されない。メインモジュールとのやり取りはイベントメッセージングによって行う(一般的にはunidirectionalな処理フローになろう)。
  • メインモジュール: このモジュールはプラグインプロセスでしか行えないDSP以外の処理を行う。DSPGUIからリクエストされたファイルI/OやネットワークI/O、リアルタイムでは処理できない計算処理などが想定される。(この名称はやや迷ったが、メインスレッドで動作することになるのでこれがある意味妥当だと考えることにした。)

GUIを表示するホストとDSPを処理するプラグインが別々のプロセスで動作する場合、DSPとメインモジュールは同一のコードベースでも問題ないが、メインモジュールとGUIは別々のコードベースで動作する必要があると考えられる。プラグインのアプリケーションで利用するファイルリソースにはプラグインのプロセスからしかアクセスできないので、メインモジュールはプラグイン側のプロセスで動作する必要がある。一般的にはGUIモジュールとDSP・メインモジュールの2つに分割することになると考えられる。

実はこの境界線モデルが既に確立しているプラグインフォーマットがある。LV2だ。LV2は仕組みとしてはメインのライブラリとGUIのライブラリを別々にロードすることが想定されていて、両者のやり取りはプロセス分離を前提とした考慮できるLV2 UIのAPIに基づいて行われることになっている。

※「プロセス分離を前提とした」という書き方は不正確(でわりと重要)なので訂正: LV2 UIはGUIの共有ライブラリを別途(LoadLibrary()dlopen()によって)ロードするので、コードの構造を共有しない前提にはなっていない。opaque pointerとして相互にやり取りすることは不可能ではない(当然ながらbad practiceであり仕様上は推奨されていない)。推奨されるコーディング プラクティスに沿って作られていれば、プロセスが分離していても問題ないという点は変わらないと思う(反例があるかもしれない)。

※2023/2/9追記: 上記追記のほうが間違っていた。LV2 UI仕様には次のように"MUST NOT"と明記されている: "Note that the process that loads the shared object file containing the UI code and the process that loads the shared object file containing the actual plugin implementation are not necessarily the same process (and not even necessarily on the same machine). This means that plugin and UI code MUST NOT use singletons and global variables and expect them to refer to the same objects in the UI and the actual plugin."

CLAP 1.0はこのアプローチを採用しなかったが、これは開発モデルの問題であり、CLAP 1.0の上に追加要求事項としてコードの分離を前提としたプロトコルを実装することは可能だ。もちろん同じことがVST3やAUにもいえる(AUをweb readyにするというのはちょっと考えられないが)。

ネイティブプラグインはWeb技術のラッパーで作れる?

プラグインフォーマットとは、複数のDAWと複数のプラグインを互換性を損ねることなく結びつけるための規格だ。DAWは複数プラグインフォーマットをサポートすることが期待される。プラグインフォーマットのベンダーが販売しているDAWではこれが期待できないかもしれない(AppleがLogic ProでVST3をサポートする、SteinbergがCLAPをサポートする、Bitwig StudioがLV2をサポートする、etc.)けど、そういうのは無視して、複数プラグインフォーマットで最大公約数の機能を実現することはできるだろう。それを地でやっているのがJUCEであり、iPlug2であり、DPFだ。あるいはMatlabやCmajor、Elementary Audioかもしれない。

デスクトップの全プラグインフォーマットの機能の最大公約数まで拡大しないにしても、一般的なDSP機能の範囲でクロスプラットフォームのオーディオプラグインAPIを実装することは可能だろう(もともとDSPのコードはクロスプラットフォームで実現できることが多い)。そしてGUIもWeb技術で実現できるのであれば、いっそ全てWeb技術で開発してしまって、各ネイティブプラグインAPIの境界でホストとのやり取りさえ実現できれば、複数の環境向けにコードをビルドする必要がなくなるように思える。これは実質的にプラグインラッパーを作るというのと変わらない。

これはもちろん、DSPJavaScriptで動作させようというものではない。WebAudioModules2がそうであるように、「そうすることもできる」が、そうする必要はない。DSPはwasmであればhard RT safeであり続けられるし、Elementary AudioのようにJSで書かれたコードがJSランタイム上で動作「しない」仕組みがあれば、一般的なlatencyの問題は生じない。

まとめ

2023年のオーディオプラグイン開発においては、デスクトップ、iOSAndroid、Web、組み込みなどさまざまな場面で利用できるコードがビルドできることが求められる。そのために、主にリモートプロセスのためのGUI、ホストと分離したプラグインのプロセス、ホットリロードといった機能を実現する手段としてWeb技術が模索されている。wasmとWeb UIはどの環境でも利用できるポータブルなコードになるポテンシャルがあり、これを具体的に実現する技術が新しく出てくる可能性があると思う。

追記

aikeさんのリアクションがそれなofそれなだったので言及しておきたい。

mastodon.cloud

12月の開発記録(2022)

晦日、1年分の振り返りを書く時間がなさそうなので、いつも通りひと月分だけのアップデートです。

Zrythm/DAW勉強会の下準備

12/11にはZrythm DAW勉強会を開催しました。いつも20人いれば多いほうかな?みたいなノリでやっていたところに、最終的に50人近く参加申し込みいただいて、何かえらいことになったな…!? と思いながら当日まで準備していました。 Zrythmは謎の勢いがあるプロジェクトで、いずれ調べてみようと思っていたから開催したわけで、普段使っていて詳しいから開催したと感じではありません。普段使いは強いて言えばWaveformです(が、そもそもDAWを使う作業を最近ほとんどやっていない…)。今回はalexteeせんせいをお招きしていたので、内容が間違っていても訂正はしてもらえるという期待がありましたが、当日しゃべる内容がちゃんと実態とマッチしていることは重要なので(たとえば「最先端のDAWです」みたいな話をするわけにはいかない)、動かしながら問題が発生したらバグレポートを作ったり原因を調べたりする感じでした。どちらかというと資料作成より再現性のあるバグレポートを作る作業でいっぱいいっぱいになったともいう…(資料作成はいつも通りそんなに気合を入れるつもりはなかったですし)

スライドはspeakerdeckに公開してあります。

speakerdeck.com

当日は配信やら録画やらでホスト側として失敗しすぎたのでいろいろ反省するところがあり、次はもう少しコミュニティの皆さんにヘルプをお願いして回すことになると思います。次は自分がしゃべるのではなく1回目(オフライン)のような複数本トークの集まりにしたいですね。

Android NDKはどこへ向かうのか (C101)

TechBoosterの新刊「プロと読み解くモバイル最前線~アプリを支える最新技術~」に「Android NDKはどこへ向かうのか」というタイトルで記事を1本寄稿しています。「NDK与太話」という仮題で書いていたのですが、こっちの題を思いついてしばらく悩んだ末にこっちにしました…

techbooster.booth.pm

NDKのネタを書くつもりは当初なかったのですが、Zrythm勉強会の準備で余裕がなかったのと、10月までも(M3で)文章書きタスクが多くてこれ以上開発作業から離れるのは良くないと思って、比較的ライトなネタを選びました。NDKの最新情報は多分割と地味になるので候補ではなかったのですが、そういえばGitHub issuesにはおもしろネタがたくさん転がってるんだよな…という視点で「将来」の話としてまとめています。ただしRustサポートなど、公式には「やらない」と明言しているので注意が必要です(本書でもそう言及しています)。具体的には次の3本です。

  • Rustサポート? - Rust方面の開発者以外には刺さらないと思いますが、ネイティブコード開発者としては誰もがふんわりと関心をもつトピックだと思います。筆者もRust系開発者ではないのでふんわりと調べてまとめています。ネタ元は主にissue #1742
  • wasm as IR? - WebAssemblyをIRというかABIとして活用しよう、というアイディアは個人的には想像の範囲を超えていたのでかなり刺さっています。wasmランタイムとかにふんわりと関心のある層向きです。ネタ元は主にissue #1771
  • Jetpack NDKライブラリ? - これはまあ完全に宣伝文句の問題で、単に公式ライブラリとしてPrefabを配布するよという話なのですが、公式ライブラリとしてパッケージを配布する基盤はちゃんと整備されているか?という観点で標準ライブラリ(libc++)の話を書いています。ネタ元は主にissue #1314libcxx-provider

AAP state of union updates

11月にAAP v0.7.4 / AAP-LV2 0.2.4 / AAP-JUCE 0.4.4と比較的安定したバージョンをリリースしたので、12月はひとまず外部の人向けに現状をいろいろドキュメントとしてアップデートする作業にかかりました。とりあえず2022年の更新まとめと、今できている機能のリスト、今あるプラグインとホストのリストがあります。

もう少し情報を足していかないといけない気もするので、1月にも更新が続くと思います。roadmapも2023年版が必要だし。個別にチャットとかで相談を受けたりはするのですが、来年はコミュニティ的なものを形成していったほうがいいかなあと思っています。そのためにはちゃんと複数人で開発できる体制が必要なのでコードも整備しないと…という感じでやることが芋づる式に出てきます。

まあでもこのプロジェクトを1人で続けるのはスケールしないですし(プロジェクトの立ち位置としてあまり適切な体制でもないし)、少しでもタスクをオフロードできる可能性を広げていきたいと思っています。

AAP testing updates

今月後半に主にやっていたのがテストまわりで、来月も続くと思うのですが、テストがまともに機能していないのをどうにかしようと格闘しています。個人的には割とtest firstに近いところで生きてきたのですが、AAPはクライアント/サービス + NDK環境という、割と安定解の無いところからスタートしていて(そもそも最初はJUCEプロジェクトだったし)、ものすごく治安が悪い状態です。一応gradlew connectedCheckは昔からあって、なんならaosp-atdの設定すらあるのですが、これがまともに動作したことがほとんどないし(コードの問題ではなくAGPの問題)、そもそもGitHub Actionsがnested virtualizationをmacosでしかサポートしていないので、その辺でCIへの統合はストップしている感じです。ただローカルでconnectedCheckで確認できることは増やしておきたい…と思って、ここにインスタンス生成とオーディオ処理まわりのテストを加えては期待通りに動かないのを発見してコードを直す…みたいなことをやっています。

この辺のテストを含めた開発体験が、Android Studio + NDKでは(Kotlin onlyのアプリに比べると)かなり悪く、バグレポートを作ろうと何日か格闘しても再現条件が難しい(自分のプロジェクトは複雑すぎて切り分けにも時間がかかる)…という感じで、割と時間を食わされていて、どうにかしたいなあ…とは思うところです。まあKotlinだけでも、複数スレッドが絡むとデバッグオブジェクト評価がまともに評価できない問題とかあるし、AS自体がいま割と不安定ですよね。Dolphinがそんな状態のままstableになっているので、今後もしばらく不安定なんだろうなと思っています。

その他/総括

今月はアドベントカレンダー月間だったのでこれらの他に2本記事を公開していますが、どちらもここに上がっているので言及するほどでもないでしょう(書き上げたのも先月だし)。それとは別にzennに1本Cmajor first impressionというのを出しています(先月書き溜めて中途半端だったのでそのまま忘れていた)。

…そんな感じで、12月は開発アップデートは少な目です。まあ今年は全般的にそういう月が多かったですね。来年はもう少し開発側にコミットしていきたい…(フラグっぽい気がする)

JUCE 7.0で追加されたLV2サポートについて

JUCE Advent Calendar 2022、2日目のエントリーです。

2021年の半ば頃からJUCE開発者がサポート計画を公言して以来、コミュニティで話題になっていたLV2プラグインフォーマットのサポートが、ついにJUCE 7.0のリリースで実現しました。

LV2プラグインなんて知らなかったという人向けに軽く説明すると、主にLinuxデスクトップ環境でデファクトスタンダードとして使われているオーディオプラグインフォーマットです。近年ではVST3もクロスプラットフォームで使えるようになって、Linux上でのシェアはLV2と同程度かそれ以上に大きくなりつつありますが、以前はVSTクロスプラットフォームではなかったため、「WindowsではVSTMacではAULinuxではLV2」のような状況がありました。JUCEがLinuxでVST3をサポートするようになったのも2020年にリリースされたJUCE 6.0からです。

LV2もクロスプラットフォームの仕組みであり、WindowsMacでも利用可能ですが、現実的にLV2をサポートするホストはLinuxを中心に成長してきたDAWがほとんどであり、Linux環境以外でのバイナリパッケージの配布例も多くはないでしょう。

LV2プラグインについては過去に「LV2オーディオプラグイン開発ガイド」という、世界でも他に類を見ない同人誌を書いたので、興味のある方はそちらをどうぞ(宣伝 (まあ英語では開発者直筆のlv2 bookが充実しているので不要ともいえます。)

従来のLV2サポート: コミュニティプロジェクト

JUCEはマルチプラグインフォーマットのフレームワークであり、そのエクスポーターとしてLV2を追加することは決して不自然でも不可能ではなく、実際DISTRHO/JUCEというforkではLV2プラグインのビルドが追加されていました。JUCEからLV2版プラグインを作るときはほぼこれが使われていたと思います(他にもいくつかLV2サポートのforkがありましたが、とりあえずこのforkに収束していたと考えてよいでしょう)。

一方で、JUCEはプラグインを作るだけのフレームワークではなく、プラグインホスト(DAWなど)をビルドできるフレームワークでもあります。DISTRHO/JUCEはこの実装を持っておらず、lvtk/jlv2というプロジェクトがLV2ホスティングをサポートしていました(こっちはそんなに需要が無かったのか、contributrorも自分しかいませんでした)。

JUCE7.0でのLV2サポートの追加によって、これらのプロジェクトは今後は不要になるはず…というわけにはおそらくいかず、少なくともDISTRHO/JUCEは残り続けるでしょう。オーディオプラグインのパラメーターなどは、打ち込まれた楽曲の内容に影響を与えないために、後方互換性を維持しなければなりません。DISTRHO/JUCEとJUCE7のLV2サポートで生成されるプラグインメタデータに相違があれば、後方互換性が損なわれる可能性があります。実際に内容の異なるメタデータが生成されるものなのか(= DISTRHOが後述するPatchの方式もサポートしていたりしないか)は筆者は把握していませんが、JUCE7にDISTRHOとの後方互換性を維持する動機は全く無いので、期待値はさほど高くありません。

一方でホスティングのほうは、JUCEでは(フレームワークとしての制約が無い限り)原則としてあらゆるLV2プラグインをロードできる必要があり(VST3やAUと同じことがいえます)、またホスト側には後方互換性を維持しなければならない理由は特に無いので、lvtk/jlv2はその役割を終えたといえますし、実際プロジェクトもarchivedとなっています。

LV2プラグインをビルドする

既存のオーディオプラグインプロジェクトにLV2ビルドを追加するには、CMakeのjuce_add_plugin()FORMATSオプションにLV2を加えるだけです。one liner changeです追記: もうひとつLV2URI "..."というプロパティを追加する必要があるのでtwo liner changeでした。とはいっても、JUCE 7.0以降を使う必要があるので、APIの破壊的変更には追従しないといけないでしょう。

もし今でもProjucerを使っているプロジェクトであっても、LV2サポートは追加されています。他のプラグインフォーマットと同様、プロジェクトのconfigオプションで指定できます。

LV2 export option on Projucer ホスト側のプロジェクトも、他のプラグインフォーマットと同様、juce_audio_processorsのオプションで指定できます。

LV2 hosting option on Projucer 試しに筆者がjpcima/HeraをLV2対応にしたときのパッチを置いておきます(CLAP対応も数行混ざっているのでクリーンなパッチではないです): https://gist.github.com/atsushieno/36b8963db9ba93b6e00a27008ae476ee

エクスポートされたLV2プラグインの見どころ

JUCE 7.0のLV2プラグインは、筆者の私見でいえば、2022年時点でのJUCEコードベースから構築できるLV2プラグインのベストプラクティスが実現しているといえます。その特徴をいくつか挙げます。LV2の基礎的な理解が必要な話なので、興味がない人は読み飛ばしましょう。

(1) LV2実装を実現するために、LV2のSDKとしてserd, sord, lilvがそのまま使われています。仕様上、LV2プラグインAPIを実装するために必要なのはlv2/lv2に含まれるLV2ヘッダーファイルのみですが、LV2仕様で特にserd/sordなしでTurtle Syntaxのパーサーを自前実装するのはやや無謀ですし、lilvを使わずにプラグインローダーを実装するメリットもほぼ無いでしょう。

(2) プラグインパラメーターそれぞれにPortを割り当てる伝統的な方法ではなく、パラメーターごとにLV2 Parameterを定義して、パラメーターの更新はAtom Sequence入力ポートにLV2 Patchを送ることで実現し、パラメーターの変更通知もAtom Sequence出力ポートにLV2 Patchを送るように作られています。Atom Sequenceを使うと、sample accurateなパラメーター変更/通知を実現でき、またMIDI入力と順序を維持した指示をプラグインに送信できます。CLAPのイベント入力ポートも同様の仕組みに則っているといえます(参考)。

先のHeraのLV2ビルドから生成されたdsp.ttlには、以下のようなパラメーター定義が含まれます(抜粋):

plug:VCADepth
    a lv2:Parameter ;
    rdfs:label "VCA depth" ;
    rdfs:range atom:Float ;
    lv2:default 0.5 ;
    lv2:minimum 0 ;
    lv2:maximum 1 .

plug:VCAType
    a lv2:Parameter ;
    rdfs:label "VCA type" ;
    rdfs:range atom:Float ;
...

これが後にpatch:writableおよびpatch:readableとして定義されます(数百行にもなります):

 patch:writable
        plug:VCADepth ,
        plug:VCAType ,
        ...

そしてpatch:messageを処理できるAtom Input portが後から定義されます:

     a lv2:InputPort , atom:AtomPort ;
        rsz:minimumSize 10064 ;
        atom:bufferType atom:Sequence ;
        atom:supports
            midi:MidiEvent ,
            patch:Message ,
            time:Position ;
        lv2:designation lv2:control ;
        lv2:index 2 ;
        lv2:symbol "in" ;
        lv2:name "In" ;

(3) レイテンシーを通知するControl Output Port、有効・無効を制御するControl Input Port、Free Wheelingを入力できるControl Input Portが別途作成され、一般的な共通コントローラーとして利用できます。

ベストプラクティスを実現できているのか疑問が生じる技術的選択として、LV2UIの実体はDSPと同一の共有ライブラリのバイナリになっています。LV2では「DSPGUIは分離しているべき」とされるので、ベストプラクティスに反する実装といえますが、そもそもGUIが分離していないjuce::AudioProcessorを使ってプラグインを作っている時点でコードの分離は実現しようがないので、LV2エクスポートの実装には期待できません。LV2ポートのみを経由したGUIDSPのインタラクションは実現しているので、LV2仕様が想定するバッドプラクティスのパターンには陥っていないとはいえるでしょう。(この辺りの問題は以前に少しCLAPのGUI拡張に関連して書いたことがあります。)

実例

Pianoteqは7まで従来型のLV2サポートを提供していましたが、Pianoteq 8でJUCE 7.0の標準的なLV2サポートに切り替えたと考えられます。Pianoteq 7までのパラメーターはLV2 ControlPortによるものでした。Pianoteq 8はLV2 Patchの方式に変更されています。両者はプラグインとして別々のインスタンスなので(~/.lv2/Pianoteq 7 vs. ~/.lv2/Pianoteq 8)、互換性を維持する必要がない事例ですが、Pianoteq 7のなまのLV2打ち込みデータをPianoteq 8に流用することはできなくなっているはずなので注意したほうがよいでしょう。とはいっても、大抵のDAW = ホスト側ではどちらも「パラメーター」として扱うでしょうから、ここに違いが生じることはない気もします。

ホスト側は対応できているのか

JUCEでLV2プラグインがビルドできるようになったとして、そのプラグインが正しくホストでロードできるかどうかは別の問題です。本当は別の問題であるべきではないところですが、LV2は仕様上の制約が小さい上にプラグインの数がVSTなどと比べると多くないため、ホストにとって想定外のメタデータをもつプラグインが出現する可能性が高い仕様です。

というわけでホスト側をいくつか実験したいところですが、その前にまずJUCE7のLV2プラグインを用意する必要があります。そんなわけで、上記Pianoteq 8のほか、いくつかLV2ビルドを作ってみました。

  • Monique - CMakeLists.txtjuce_add_plugin()呼び出しの部分でFORMATSLV2を足して、その下の行にでもLV2URI "(適当なURL)"を追加するとビルドできます
  • Dexed - Moniqueと同様の修正を加えるとビルド自体はできます(ただしCMakeLists.txtSource以下のもの)。ただ、少なくともJUCE 7.0.2以前では生成されるdsp.ttl「パラメーター名に含まれる.を正しくエスケープしない」問題があって、パラメーター名から.を消して回る必要があります。それが出来たら、ロードできるようになるでしょう。

というわけでプラグインが揃ったので、いよいよLV2をサポートしているホストをいくつか試してみました。Reaperはv6.71、QTractorとZrythmは11月末頃のmasterブランチ(commit hashを特定するほどでもないでしょう)、AudioPluginHostはJUCE 7.0.2に含まれているものです。

DAW Pianoteq 8 Monique Dexed
Zrythm *1 NG*1 *2
QTractor *3 NG*4 *4
Reaper OK OK OK
AudioPluginHost OK OK OK
  • *1 音は出るけど打ち込みを演奏できず https://todo.sr.ht/~alextee/zrythm-bug/1013
  • *2 ほぼ問題なく動作する。プラグインUIのウィンドウサイズが意思疎通できていないっぽい
  • *3 プラグインUIのダイアログ表示状態管理が甘いのか、頻繁にクラッシュする。クラッシュしなければ音は鳴るし演奏もできる
  • *4 音は出るけど(打ち込みプレイバックも可能)、再生速度が2倍くらいになる?

どうも実際に正常に利用できる環境とプラグインの組み合わせはまだ限られるようです。VST3も安定しない時期はこうだったんじゃないかという印象があります。

JUCE7によるLV2サポートの展望

これまでもDISTRHO/JUCEなどでLV2プラグインのビルドは可能だったといえますが、JUCE7でLV2が公式にサポートされたことによって、さらにLV2サポートの可能性が広がることが期待されます。特に期待できるのはTracktion WaveformなどJUCEを利用して構築されたDAWでLV2プラグインがついに使えるようになると見込まれるところです。Helio Workstationや、何ならZenBeatsでも使えるようになるかもしれません(Rolandのやる気次第でしょうか)。

プラグインのバイナリパッケージ配布も、前述したとおり従来はLinuxのみのものが大半でしたが、JUCEからカジュアルにビルドして配布できるのであれば、今後はWindows/Mac用LV2プラグインが公開され、プラットフォームを跨いで利用できるポータビリティの高いプラグインが増えてくるかもしれません。今後のJUCE7/LV2採用事例がたのしみですね。

VitalにCC0ライセンスのWavetableを大量に取り込む

DTMテクニック集 Advent Calendar 2022、初日が空いていたのでトップバッターをつとめることにしました。

1月にVitalのウェーブテーブルとして使えるフリー素材を大量にVital用ファイルとして取り込んだリポジトリを作ったので、その話を書きます。CC0ライセンスで誰でも使えます(大量に自作したのではなく、大量のCC0リソースを取り込んだものです)。※ウェーブテーブルのみであって、音色プリセットはありません。

github.com

※今後CC0以外のリソースを取り込んだらライセンスが変わる可能性もあります

以降は「何でこれが必要になったのか」「オープンソース互換で公開できるウェーブテーブルを生成するにはどうすればいいか」を説明します。ユーザーとして使うだけなら、すでにこのリポジトリにあるものをVitalのユーザーディレクトリ(~/.local/share/vital/Userなど)にコピーすればいいだけです(ディレクトリ構成は注意が必要です。README.mdを見てください)。

ちなみにVitalってそもそもどんな音源?という人は、この辺の日本語記事から読むと良いかもしれません。

dtmer.info

VitalのOSS版で使えるプリセットがほしい

ちょっとだけ自分のやっていることを紹介すると、オーディオプラグインフレームワークAndroidで作ろうとしていて(何しろAndroidにだけ存在しないので!)、その関係でオープンソースプラグインをいくつかAndroidに移植して遊んでいます。

去年の今頃、VitalもAndroidでビルドして動かして遊んでいたのですが、すぐに「OSSのVital(GitHubで入手出来るVitalのソースコード)には音色のプリセット(*.vitalファイル)が何も入ってない」ということに気づきました。Vitalのプロプラエタリ版はそこで商売になっているわけですね。無償版(OSS版とは違うことに注意)はその入口というわけです。コードはGPLv3、音色データは商売道具、という切り分けは、フリーソフトウェアと親和的でよくできたビジネスモデルだなと思います。

しかしAndroid版を自作して(GPLv3に基づいて)公開するのであれば、少なくとも配布データとしてプロプタエタリ音色を含めることはできないので、少なくともアプリケーションとして配布するならそれなりの音色プリセットを自作するしかありません。無償版Vitalユーザーがその音色をAndroid版に持ってきて使うのは別に問題ないのですが、わたしが配布するわけにはいかないのです。

これは大変な作業になりそうだ…でも、そういえばVitalはフリーソフトウェア界隈ではVitaliumという(ストアアクセスなどのプロプタエタリな要素を含まない)独自forkがあったはずだけど彼らはどうしているのだろう…その音色データを取り込めば解決では…?と考えましたが、結論からいえば、彼らも特に大した音色プリセットをもっているわけではない、ということでした

さらなる根本的な問題として、Vitalはウェーブテーブルシンセサイザーなので音色プリセットを作るためにはウェーブテーブル音色定義が必要になるわけですが(それをもとにさまざまなパラメーターで音色を作ることになるわけです)、VitalのOSS版にはウェーブテーブルが含まれていません(プロプタエタリなので)。ここから用意する必要があります。

Serum用のウェーブテーブルはフリー素材がたくさんある

ところで、ウェーブテーブル音源は(そういうシンセのジャンルがあることからもわかるように)Vital以外にも数多く存在しますし、その歴史も長いです。Vitalが出現する以前の代表的な音源は概ね異論なくSerumだったと言っていいでしょう。VitalはSerumの機能を数多く取り込んで実現しており、ウェーブテーブルも2048フレームまでのデータを処理できる設計になっています。

ウェーブテーブルは*.wavファイルとして配布されます。サンプリングデータ以外にもデータがいくつかあるのですが、それらはWAVファイルフォーマットの一部であるRIFFヘッダチャンクに含まれます。Serumの情報はclmというヘッダが使われるらしい、ということがあるKVR Forumのスレッドから読み取れます(Xfer Recordsの開発者が自ら書いている情報もあります)。そして、VitalではSerum用のウェーブテーブルを取り込むことができます。このclmヘッダの情報も読み取ります。clmヘッダの情報を取り込めるのはVitalだけでなく、Surgeなどでも可能なので、このジャンルでは概ね共通フォーマットと考えてもよさそうです。

ちなみにVitalのウェーブテーブルは*.vitaltableというJSONファイルになります。サンプリングデータは「JUCEのBASE64文字列」として保存されます。「」が付いているのは、これは標準的なBASE64と互換性が無いためです。

さて、Serum用のウェーブテーブルデータは、実は無料しかもオープンライセンスで大量に手に入ります。 waveeditonline.comにはいろんな人が作ってCC0ライセンスで登録したウェーブテーブルが大量にあります。

WaveEdit Online もうひとつ、kimuratato.comからも大量のウェーブテーブルを ダウンロードできます。日本語でGNU Octaveを使ったウェーブテーブルの作成方法なども公開されています。

vitaltableファイルへの自動変換

WAVファイルとclmヘッダが公知情報なら、その情報をもとにこれらの大量のWAVファイルを自動的に変換するツールを作るのは難しくないはずです…が、実際にはclmの情報をどう取り込むかはVitalの実装次第かもしれないし、前述のJUCE BASE64のような罠が他にもあるかもしれないことを考えると、自前で変換ツールを作るよりも、Vitalのコードベースを使って*.vitaltableに変換したほうが、実装としては安牌です。

そういうわけで、コンバーターは「Vitalのソースに手を加えて一括変換用のコマンドを追加する」というかたちで実装しました。つまり、Vitalのforkとなっています。

github.com

ソースの変更内容はこれだけです: https://github.com/mtytel/vital/compare/main...atsushieno:vital:batch-import-wavetables

プラグインでもよいのですが、make standalone等で実行ファイル版をビルドして起動するのが一番簡単でしょう。ビルドされるのはVialという実行ファイルであることに注意してください(README.mdを読むとわかりますが、Vitalという名称はMatt Tytelのものなので、OSSでは混乱を防ぐためにVialという名前になっています)。ビルドして起動できたら、Wavetableエディタの画面に移動して、メニューを開くと、"Batch Import Wavetables"というVital本家にはないコマンドが出現します。

Batch Import Wavetable

これを実行すると、ディレクトリ選択ダイアログが出現するので、変換元wavファイルを含むディレクトリを指定すると、その下にある*.wavから*.vitaltableが生成されます。

冒頭の繰り返しになりますが、すでにatsushieno/open-vital-resourcesリポジトリで公開されているウェーブテーブルを使用するだけであれば、ここまでやる必要はありません。リポジトリの内容をダウンロードして、Vitalのデータディレクトリ(~/.local/share/vitalなど)にしかるべきディレクトリ構成でコピーするだけです。

あと、もちろんホントにほしいのはWavetableじゃなくて音色プリセットなんですが、これはさすがに一朝一夕では大量には作れないので、どこかから湧いてこないかな〜って思っています(他力本願)

オープンリソースの可能性

最後に与太話を書きたいのですが、わたしがAndroid版をビルドしているのは「どこで音楽を打ち込んでもどこでも再生できる」ような世界を作りたいと思っていることがまあまああります。VitalだけだとOne synth challengeか??みたいな状態になってしまいますが、他にもOSSプラグインはたくさんあるので、さまざまなプラットフォーム上で使えるようになるといい世界になると思いませんか? ふとんにくるまってAndroid端末で音作りして、それをPCに持っていけたり、同じデバイス上でDAWから呼び出せたりしたいですよね。(まあそうは言ってもVitalのUIは現状モバイルでは到底使い物にならないわけですが…!)

ちなみにVitalの公式Discordでは「iOS版とAndroid版を作っている」という情報も見かけたので、別にわたしがやらなくてもそのうち出てくるかもしれません(書いていた人はコア開発者ではなく真偽は不明です)。いずれにしろAndroidにはオーディオプラグインの仕組みが存在しないので、あくまでstandaloneで使えるものということになるでしょう。

FLOSSで音楽制作ツールを作っていると、サンプルやテスト用に楽曲がほしくなるのですが、その音源データがプロプラエタリだと、リポジトリに取り込むにも抵抗があります(不可能だ、と言ってもいいのかもしれません)。そういう場合にはオープンリソースで作成した楽曲データが有用なのです。たとえばわたしは自作MMLコンパイラのサンプル楽曲で著作権の切れたオーケストラ楽曲の打ち込みデータを試験的に公開しているのですが、submoduleにはsfzのフリー音源のみが使用されていて、GitHub ActionsでMP3レンダリングまで実現しています。つまり自分のPC以外でも再現できたということです。

オープン音源リソースのエコシステムが拡大すると、こういうことをやりやすくなるので、この世界がもっと広まってくれるといいなと思っています。