オーディオプラグインの動的ポートに関する覚書

最近自分がやっていることは多分もう自分以外の人にとっては完全にイミフだと思うが、後で歴史を発掘する作業も必要になってくると思うので、記録を残しておこうと思う。自分のオーディオプラグインフレームワーク最近ずっと悩みつつ進めている動的ポートのサポートについてだ。

目次

オーディオプラグインのポート

一般的なオーディオプラグインにはオーディオやMIDIの入出力チャンネルが存在し、DAWなどのホストはフィルターグラフとかオーディオグラフとかチェインとかいったものを形成して、そこにオーディオやMIDIのデータを流し込んで処理させる。プラグインにはそれぞれにさまざまなパラメーターが存在するが、一般的にはプラグインのパラメーターの数は状況に応じて増減したりはしない。

(LV2の場合はパラメーターという存在は全てポートとして処理されるので少し話がややこしくなって、ポートの数はDAWなどのホスト側の「バス」(Bus)の設定に依存してくる(ステレオとか5.1chとかambisonicとかいろいろある)のだけど、いったんバスが決まってしまえば、ポートの数はやはり普通は固定値となる。)

VSTAUでは、プラグインのパラメーターはプラグインインスタンス(ここではvst::IPluginFactoryも含むものとして考えてほしい)から取得するのだけど、LV2はこの点少し違っていてプラグインRDFマニフェストの中に記述する。たとえばGuitarixのマニフェストだとこんな感じだ(長いのでリンクにとどめる)。マニフェストに書いてあると、プラグインのプログラムを実際にロードしなくてもどんなポートが含まれるのか把握できる。ここから、たとえばMIDI入出力のあるプラグインかどうか、といった判断も可能だ(たとえばsfizzにはMIDI入力ポートがあることがわかる)。

動的ポートの存在

もう一度書くが、ポートの数は普通は固定値となる。しかし、このまわりで例外的なプラグインのやつらが存在する。ひとつの典型的なやつはプラグインの「ラッパー」と呼ばれるもの、の一種だ。ラッパーとは、本来サポート対象外である別のプラグイン規格のものをあたかも自分の実装するプラグイン規格のプログラムとして動いているかのように処理するもので、たとえばlv2vstというプログラムはLV2プラグインをまるでVSTプラグインであるかのように処理する。その実態は、ホストから受け取ったデータを単にロードしたLV2プラグインに渡して処理させ、結果をVSTホストに返すだけ、である(だけ、と書いたけど、実際には両規格の仕様には機能的な違いもあってそれなりに大変な作業だ)。ラッパーの多くは静的なものだけど、これが動的にプラグインをロードする仕組みになっていると、必要なパラメーター/ポートの数も変わらざるを得ない。

また、ラッパー以外でも動的ポートが必要になる類のプラグインが存在する。Dexedというプラグインは、往年のYAMAHA DX7などをシミュレートするプラグインだが(DX7部分はGoogleのエンジニアが作成したmusic-synthesizer-for-Androidで、詳しくは音楽ツール・ライブラリ・技術 Advent Calendar 2018の記事を参照されたい)、これはDX7Cartと呼ばれる楽器プログラムリストをロードして楽器として使うプラグインだ。そのパラメーターの数はロードしたCartによって異なってくる。Cartの問題なのか実際にサポートしている音源(DX7以外にもちょいちょい追加されているらしい)なのかは中身を追っていないのでわからないが、重要なのは状況によってポートの数が変わるという事実だ。

LV2 Dynamic Manifest拡張

LV2は事前にポートを列挙する仕組みになっているので、このままでは動的ポートのようなイレギュラーな存在をサポートすることはできない。しかしプラグインラッパーが実現できなかったりするのは不便だ。

LV2はこれを解決するために、Dynamic Manifestという拡張仕様を盛り込むことにした。本来プラグインのプログラムの一部として実装するものではないマニフェストRDFの提供を、プログラム上で動的に行ってホスト側に渡す、という、なんとも奇妙な仕様だ。ポート以外のメタデータも渡せるようだが、DAW側で既にプラグインのリストなどが生成されたりDAW上に表示されていたりするものが、いきなりプラグイン名が変わったりしたら混乱は必至だし、そもそも実現可能かもあやしい。ただ、プラグインのポートが増減する挙動は、ホスト側さえ実装できればこれで対応できる。

それにしても、ポートの数が動的であるのなら、最初からポートの情報はマニフェストに含めないほうが良かったのではないか、という気もするが、そうしないことにはメリットもある。まず、コードで表現すると、プラグインの「種類」を判別するために必要になるメタデータが際限なく増えてしまう。VST3であればPClassInfo, PClassInfo2, あるいはIPluginFactory/2/3と増えている。LV2の場合はPort Propertiesという拡張仕様にプロパティが増えるだけで、APIを増やすことはない(し、APIも拡張として提供してもよいが)。そして、ポート情報をホストとプラグインでやり取りするために、ポートをあらわす型情報が必要になってしまう。また、LV2ではポートについてはPort Propertiesという拡張仕様が規定されているが、もしAPIにしてしまうと、この拡張情報をどのようにやり取りするのか、といった問題も芋づる式に発生する。

こういった煩雑な問題が、「マニフェストのアップデート」であれば何も必要なくなるのである。LV2のDynamic Manifest拡張のAPIでホストからプラグインに渡されるのは、なんとFILE*である。そこに新しいマニフェストを書き出せ、そしたら後はホスト側でよろしく解析する、というわけだ。とんでもないやっつけAPIだな、と思ってしまうわけだけど、実はやっつけではなく、むしろ上記のようなややこしい問題を回避するためのハックなのだ。メタデータのパーサーは当然ホストにはあるはずなので(LV2ならlilvを使えばよい)、実装上のハードルは上がらないといえる。

ポートの追加はプラグインのパラメーターのやり取りの仕組みを利用すればよいのではないか、という考え方もありうるが、オーディオプラグインのパラメーターは、オブジェクト指向言語のプロパティグリッドで設定できるようなさまざまな型が用意されていることは通常はなく、全てfloat配列やbyte配列となっている。LV2のポートも同様で、複雑な構造体についてはAtomという拡張仕様が規定されている。これに既存のポート情報のやり取りを上乗せするAtom構造体を規定する苦労をするくらいなら、既にあるマニフェストのパーサーを使い回せるほうが楽だし覚えることも少なくなる。

プロセス分離モデルでの応用

わたしが開発しているのはAndroid用のフレームワークで、ホストとプラグインは別々のベンダーが開発して別々のアプリケーションとしてインストールするというモデルなので、ポート情報をやり取りする場合にはプログラム上のコードで表されたデータ構造としてそのままやり取りすることはできない(…というのは言い過ぎだが、Kotlin/JVMでもC++でも扱わないといけないので煩雑だ)。メタデータにはLV2同様にポートの情報が記述されているので、動的ポートをサポートするにはLV2のDynamic Manifest類似の拡張機能が必要そうだ。

実のところ、ポート情報をマニフェストに記載する必要はあるのか、という疑問が頭の中をぐるぐる回って、ある程度の解決に至る光明を見出すことがなかなかできなかった。LV2での実現方法とそれにまつわるメリット・デメリット、代替方法などを模索してみて、だいたい同じことをすれば良いだろうと考えるようになった。

実のところ拡張機能まわりは実装できていたと思っていたところがまだ全然足りていない側面があったこととか、LV2みたいに任意のポインタを渡してもらっても、たとえば関数ポインタとか渡されても困るから排除しないといけないということに気づかされた。おそらくプロセス分離モデルを前提とした仕様では同じような回避策を模索しなければならなくなるだろう(AUv3はApp Extensionなのでプログラム情報は共有できそうではある)。

このあたりはUI統合を実現する手法にも影響してくるので、まだ二転三転する可能性はあるのだけど、この辺は技術的に興味のある人が吸い寄せられてコメントしてくるくらい課題としてはけっこう面白いっぽいので、ちまちま実装を進めていこうと思っている。

PACEによるJUCEの買収とJUCE6開発計画が予告された件

4月になってからJUCE方面の動きが大きい。今日はついにPACE社がJUCEを買収したというニュースが出てきた。それなりにセンシティブな話題で、ある程度JUCEにコミットメントがある人には書きにくい話もあると思うので、完全に無責任な立ち位置で書いておこうと思う。

目次

PACEによる買収とjuce-framework/JUCEへの移行

JUCEは(ここでは何度も書いているので今さら感あるけど)クロスプラットフォームでクロスオーディオプラグイン規格の開発フレームワークで、今日まではROLI社が所有する製品だった。商用ライセンスとGPLv3で公開されている。

今回はROLI社が買収されるのではなく、JUCEの開発事業がPACEに買収されるらしい。JUCEはもともとJulian Storerという開発者がJule'sなんちゃら(忘れた)という自分の名前を冠して作ったライブラリで、彼はROLIに所属しているのだけど、最近はSOULというAPL(オーディオプログラミング言語)まわりの仕事をしていて、SOULは事業譲渡の対象ではないようだ。つまりJUCEはPACEのものになるけど、Juleは付いてこないということになる*1。ROLI社は楽器ハードウェアを販売している会社なので(Apple StoreなどでSeaboardやBLOCKSといったROLI製品が販売されているのを見たことがある人もいることだろう)、JUCE以外にもやるべきことはいっぱいある。

買収するほう、PACE社が分かる人がどれくらいいるか、多くはないんじゃないかと思うけど、iLokという製品を聞いたことがある/使っている人はDTMやってる人ならそれなりにいるかもしれない。USBドングルを売っているライセンシング・ソリューションを提供する会社だ。iLokは自分もADCで2年連続で見ているはずなのだけど、会社名までは全く覚えていなかった。最初 IP management company みたいな文言で書かれているのを見て「JUCE完全に身売りしないといけなくなったのか…」と暗い気持ちになったのだけど、製品開発にはノータッチノーサポートでIPだけ吸い取るような買収ではないようだ。

ROLIは実際のところかなり資金難に苦しんでいて、JUCE開発チームも2人か3人くらいで回している状態だった。JUCEにはいわゆる外部コントリビューターのようなものがいない。現時点ではROLIのポリシーなのかJUCEチームのポリシーなのかわからないが、当初はそもそもJuleの個人ユーティリティで彼は要望を受け付けてコードを取り込むのを嫌がって拒否していたようだ。つまりオープンソースではあるがオープン開発ではない。

ごく少人数で回しているJUCE開発は、実のところさまざまな方面で停滞していた。個人的に関心のある範囲だけでも、Projucerはビルドを全面的に面倒見るための機構なのに、痒いところに手が届かず、細々とした改善が求められていた。SteinbergがVST2 SDKを入手できなくしたので全面的なVST3サポートが必須だったのに、Linux版はVST3を全くサポートしなかった。

閑話休題、そこに現在のコロナ禍である。ROLI単独で状況が改善される見込みはほぼ無かった。資金難という話は昨年には出ていたので(昨年はNative Instrumentsなども大規模リストラを行っており、オーディオ開発ソフトウェアの世界は割としんどい年だったようだ)、PACE以外からもいくつか買収提案を受けていたようだ。PACEはJUCEからのシームレスなiLokサポートを実現できれば既成品の販売が強化できるので、それだけでもJUCEを手元に置いておく理由にはなるかもしれない。実のところiLok利用ベンダーとJUCEユーザーはある程度かぶっていたようだ。JUCEチームとしては資金の心配なく開発が継続できれば良いということだろう。JUCEユーザー(音楽ソフト開発者)としてはそれで得られる安心感は確かにある。

とはいえ…iLokである。もともとユーザーにとっては邪魔でしかないUSBドングルのライセンシングソリューションの会社でサポートの評判もいまいちらしく、全く歓迎されるべき事態には見えない。JUCE forumでこの話題が上がったのはこのスレッドだけど、リンク先のgearslutz.comの掲示板には残念がるコメントが散見される。まずタイトルがPace Ilok has bought the Juce Platform :-(である。散見ではあるけど、匿名掲示板でも大規模掲示板でもないことを考えると、十分「大きな」懸念が上がっていると思う。

PACEの買収はどんな影響をもたらすだろうか? 私見としては3つくらい今後の筋書きが考えられる。

  1. 何も変わらない。これが一番ありそうなシナリオだと思う。PACEはiLokの販路が拡大できれば十分なので、JUCEにiLokサポートを追加するような動きは見せるが、JUCEのライセンスモデルを変更したりすることはない。juce_product_unlockingモジュールはもしかしたらiLokサポート用に別モジュールが出来て消滅するかもしれない(が、そのためにはiLokがLinuxをサポートしなければならず、それ無しで移行するとは考えにくい)。
  2. 商用ライセンスに変更が加えられ、商用ライセンス版では特にソフトウェアプロテクションまわりでiLok以外の選択肢を排除するために独自の改良を加えられなくなる可能性が懸念される。こうなると独自にLV2サポートを追加するような変更が加えられなくなる。
  3. GPLv3ライセンスでの提供が廃止される。外部コントリビューションを受け付けていなかったので、PACEはJUCEのライセンスを恣に変更できる。もちろん、これまでにリリースされたバージョンは従前の商用ライセンスとGPLv3で利用できるが、誰かがOSSとしてforkしてメンテナンスする必要がある。幸い(?)本家の開発リソースは小さいのでそこまで劣勢にはならないだろう。ただLGPLで利用できないライブラリはライセンス的に使い勝手が悪いし、商用ユーザーにとってはそっちに協力してもメリットがないので、かなり閉じた世界になりそうではある。

(3)は一番問題のあるシナリオだけど、可能性は限りなく小さいと思う。競合製品を出しているわけでもないPACEにそんなことをするメリットは、悪意の深読みスペシャリストであるわたしにもちょっと思いつかない。JUCEの市場は小さくないけど、独占的とまで言えるほどではない。

…とまあ、懸念事項はあるのだけど、現時点で言えることとしては、少なくともJUCEチームにはいくつかの選択肢があったうえで一番よさそうなものを選んだのだということだ。PACEは少なくとも開発者にとっては親和的であるようだ。JUCEチームによる公式アナウンスには developer-focused company と書かれている。ただそれにしてはgithub organizationsも無いような会社なので、このコメントへはわたしは50%くらいは(良く言えば)リップサービスだなと思っている。

JUCEはROLIのorganizationからは外れて、新しくjuce-frameworkというorganizationの下で公開されることになったようだ。github teamsが価格改定されたこともあるし、タイミングとしては割とラッキーだったのかもしれない。

JUCE6の予告

ともあれ、資金的な困難が解消したこともあってか、JUCEチームは大規模リリースに向けて本格稼働を始めた(あるいはそれを公開するようになった)ようだ。今から4日前、JUCE6のリリースが告知された。個人的には嬉しい事だらけの内容だ。

forum.juce.com

JUCE6は現在すでにある程度コードがgithub上でjuce6というブランチで公開されている。以下の項目以外にもいくつか改善があるので、詳しくは上記アナウンスやgithub上のコミットを眺めてみてほしい。

CMakeサポート

JUCE6最大の変更はProjucerからCMakeへの全面的な移行だ。CMakeサポートはもともと「CLionサポート (beta)」というかたちで実装されていたわけだけど、あくまでビルドが可能というレベルで再利用性は低く、ファイル追加などはProjucer上で行うことが前提となっていた。JUCE6ではこれを全プラットフォームで全面的に使うということを意味している。Projucerは、なくなるわけではなく、CMakeLists.txtを生成するための補完的なツールとして生き残り続けるようだ(そもそも他にもいくつか機能がある)。

昨年12月にJUCE Advent Calendarでわたしは以下のように書いたのだけど、完全にその通りになってくれた。

わたしの個人的な感想としては「もうProjucerのビルドシステムはあきらめてCMakeを使おう」なのですが(パッケージ参照もちゃんと解決できるしVisual StudioでもAndroid StudioでもCLionでも開けるんですよコレ)

完全に余計な話だけど、CMakeサポートはJUCEのオリジナル開発者Julian Storerがやりたがらなかった仕事のひとつなので(「CMakeが嫌だからJUCEProjucerを作ったんだ」)、彼が組織上は居なくなるタイミングでCMakeサポートが追加されるというのは何とも象徴的ではある(タイミングの問題であって、CMakeが理由で決別ってことはまあないだろう)。PACEの買収のポジティブな側面として、CMakeサポートは新しくコミュニティからスカウトされてJUCEチームの一員となった開発者が書いている。

これを書いている時点では、examples/CMake/README.mdに詳しいドキュメントがまとめられている。「ドキュメントはわかりやすい場所に置いてほしい」というユーザーフィードバックが開発者に届いていたので、そのうち移動するかもしれない*2

Run Headless (Linux)

JUCEアプリケーションがLinux上ではheadlessでGUIなしで動作するようになった。GUIが無くても良いということは、CIサーバ上で非常に動かしやすくなったということだ。CircleCIを使う記事を去年書いたときにも言及したけど(CIまわりはわたしが自分で書かないと英語圏でも誰も書いていないような状況だった)、これまではX server相当の何かをセットアップしないと何も出来なかった。

atsushieno.hatenablog.com

GUIコードが一切存在しなくても、Projucerでファイルを生成するためにはJUCEアプリであるProjucerが実行できなければならなかったし、これがheadlessで動作しなかった。ビルドファイルをコミットする覚悟があるプロジェクトでも、プラグインのルックアップすらjuce_eventsなしにはできなかったし、juce_eventsだけ使ってjuce_gui_basicsを使わないコードを書くことはほぼ無かっただろう。headless化が具体的にどこまで基盤を変更することになったのかはわからないけど(ソースツリー差分で調べようと思えば調べられそうではある)、juce_events依存部分が整理されたことを期待している。

なおLinuxのみらしい。CIサーバ上でheadlessのメリットがあるのはLinuxくらいなので、これで十分だ(GUIなしのWindowsmacOSがあれば話は別だけど)。

Linux VST3サポート

ついにLinux上でもVST3がサポートされるようになった。だいぶ前にホスト側だけでもと思ってパッチを作ってpull requestを送っていて、最近も「VST3対応しなさそうだから自分で作って公開するか…?」となっていたのだけど、公式対応が出てきたのでこれでひと安心だ(まだリリースされたわけではないが)。

JUCEのアイデンティティクロスプラットフォームでオーディオプラグイン等をビルドできることであり、VST3サポートが無い問題はLinux系音楽開発クラスタにとっては致命的だった。VST2が利用できなくなった結果、VST3という出口のない状態で、JUCEのAPIを使って書いたコードがLinuxでは何の価値も無くなってしまったのだ。LADSPA v1サポートはホストだけだし、そもそもv1にはMIDIサポートが無い。そしてLV2サポートも無い。

これが理由でかつてJUCEを使っていたLinux系のオーディオプロジェクトのいくつかが本家JUCEを捨ててLV2サポートのあるforkやDISTRHOに切り替えたり(dexedやADLplugなど)、あるいはJUCEを捨ててLV2に移行する(sfizzなど)といったトレンドが発生していた*3し、わたしも別のものに移行するかJUCEでないものを作るしか無いと思っていた*4

なお、昨日の時点で関連コードがjuce6ブランチに追加されている。

DSPのビルディングブロック

DSPにはディレイ、コンプレッサー、フェイザーなどいくつかの定番フィルターがあって、それらはもちろん単体でもオーディオプラグインとして機能しうるものだけど、実際のオーディオプラグイン製品とくに楽器の類では、これらをオプションとして最初から組み込んでいるものが多い。そういったアプリケーションを簡単に作れるように、DSPのビルトイン部品が追加された…ということだろう。

WebViewまわりの刷新

最新のWebViewが各プラットフォームで利用できるようになるらしい(一度も使ったことがないのでわからない)。blueprintなど、JUCEでも最近のアプリケーションのGUI部分をjuce_gui_basicsではなく独自に作成するトレンドもあり、WebViewサポートによっていわゆるガワネイティブ的な使い方も出てくるようになる可能性がある。

総括

PACEによる買収は、まだ今後どうなるか分からないのである程度注意しながら様子を見ていきたいけど、とりあえず資金が確保できたことで開発が先に進むようになった側面があるので(OracleによるSun買収もそうだったとは思う)、短期的にはJUCE6のポジティブな計画をたのしみに待ちたい。

*1:まあそうは言っても、JUCEはもともとTracktion社から出てきたもので、彼はtracktion_engineにもcommitしていたりするので、今後もそんな関係かもしれない

*2:わたしが眺めているtheaudioprogrammer discordにbuild-systemというチャンネルがあって、そこで観測した

*3:これはJUCEをLinux方面で使っていないと滅多に目にすることはなかったと思う。iPlug2などLinuxサポートの無いライブラリに比べたらJUCEは比較的マシなほうなのだけど、それでは不十分だったということだ

*4:今でもちょっと思っている節はある

3月の開発記録

4月ももう1/3くらい過ぎているのに3月かよ?ってなりますが、PCを修理に出したり、そこに至るまでそもそもPCの不調にやられたりでいろいろ止められていた感じです。

3月からいよいよ世間がコロナ一色になって落ち着かないですね。わたしは世間一般でようやく自分の在職時代の日常だった在宅勤務が普及するようになったのを横目で眺めながら、家でゲームしたりしていました(半分くらいは↑のせい)。3月はどちらかといえば生産少なめです。

目次

aap-juce

先月も書いていたAndroid用オーディオプラグインのプロジェクトにJUCEを統合するやつ、あまりにも手作り作業が多かったので、これを誰でも手順通りにやれば作れるように作業フローをまとめてCIできるところまでもっていこう、というhigh意識でちょいちょい作業しました。

github.com

Android用に組み込むと言いながら、実際にはAndroid用とemscripten用の2つをビルドしていて、これはだいぶ面倒なことではあります。ひとつには、先月書いたjuce_emscripten app gallaryのページもCIでリリースしたいという希望があったのと、もうひとつは後で詳しく書くUI統合で利用する目論見があったためです。juce_emscriptenのデモは、2月にやりたいと書いていたtracktion_engine統合の実験も兼ねて移植したStepSequencerDemoも追加してあります(統合サンプル自体は未着手)。tracktion_engineの開発者もTLで見つけたらしく、質問されたりしていました。

juce-demos.atsushieno.dev

それはさておき、ビルド自動化にあたって一番問題だったのはやっぱりProjucerが生成するMakefileemscripten用)を手作業で加工していた部分なので、ここは諦めてProjucerに手を加えました。

その過程で気づいたのですが、juce_emscripten自体はソースツリーがJUCEからのforkになっていなかったものの、実際には本家からのforkとして公開するのも全然難しくはありませんでした。なので現在はそういう構成でJUCEの独自forkとして発展させています。(juce_emscriptenの本家Dreamtonicsは現在進行中の製品開発で忙しいようなので、この辺はあっちが落ち着いたら連携して統合する予定です。)

結果的に以下の5つを移植してビルド自動化して、半分くらいはまだ謎クラッシュしたり音が出なかったりといったところまではやってある感じです。

  • AudioPluginHost
  • andes
  • SARAH
  • dexed
  • Magical8bitPlug2

それぞれどんなものなのかは、(Androidではありませんが)juce_emscripten app gallaryで見ることができます。

ちなみにビルドはGitHub Actionsで行っています。これは3月になってようやくactionsのAndroid SDKコンポーネント不足が解消されることによって実現しました。Android Studio 4.0の想定するAndroid Gradle Plugin 4.0.x系列が使用するCMakeが3.6から3.10.2にバージョンアップしたのですが、去年12月に試したときは、GitHub ActionsのVM (ubuntu-18.04) ではAndroid SDKに含まれるcmake 3.10.2がインストールされていなかったので、actionsに直接プルリクを送りつけて対応を求めたのですが全然反応が無く、2月になって他のユーザーも問題に気づき始めてコメントされるようになってから、ようやく3月にマージされました。

また、GitHub Actionsで1つのサンプルをビルドするのにかかる時間は20〜30分、それが5本あるので、1ビルド150分くらいかかっています(!) 1つのアプリをビルドすると500MBくらい消費してしまうので、intermediatesを削除して回ることでようやく可能になっています… 500MBのfree tierでもビルドできるようにしたいのですが(今はProの1GBを使えている状態)、500MB削るのは困難そうです。

UI統合の再検討

aap-juceでjuce_emscriptenを使っている主な理由は、UI統合のスキームとして使えるんじゃないかという目論見があったためでした。ロジックとなるオーディオ処理はプラグイン側のアプリケーションで処理するが、UIはjuce_emscriptenで生成したHTMLをホスト側のアプリケーションでWebViewとしてロードする、という設計が最も合理的なのではないかと思ったためです。

ただ、実際にjuce_emscriptenのアプリをAndroid WebViewで実行してみると、途中でエラーが出て止まってしまいます。ChromiumのDevToolsにあるremote devicesの機能を使ってデバッグしてみると、emscriptenが内部的に使用しているAtomicsAndroidでサポートされていないことがわかりました。GUI部分にatomicを使う必要はないはずなのですが2020/6/1追記: GUIコードもオーディオプラグインパラメーターを直接いじる時にはatomicsを使う必要がある場面があります、これがJUCEの膨大なコードベースから呼び出されるlibc等のどこで使われているかは、emscriptenの標準ライブラリに踏み込まないとわからなそうなので(juce_emscripten自体はJSのAtomicsを参照していない)、ちょっとこれは無理ゲーの香りがして中断しています。

UIをHTMLで実現するというアイディアはjuce_emscriptenでなくても可能なので、このアプローチ自体は引き続き考慮しています。たとえば、JUCE方面ではUIをReactで記述するblueprintというプロジェクトがあります。

sfizzからARIAまわりを探る

2月にはtracktion_engineを移植して音楽を鳴らせるようにしたい、という話を書いていましたが、音楽を鳴らすためには再生エンジンの他に楽器として使えるプラグインが必要です。JUCE方面ではjuicysfpluginというFluidsynthを使ってサウンドフォントベースのinstrumentプラグインがあって、最新のFluidsynthがAndroidでも動くことはよく知っているのですが(何しろ自分が移植したコードが公式なので)、これ自体はgtkを使ったりしていて、移植するより自前で作ったほうが早いかな…でもfluidsynth相当ならMIDIプレイヤーでもいいんだよな…みたいなことを考えていました。

それでふとsfzならkey switchとかもサポートしているし、もう少し高機能なんじゃないかと思って、OSSの合成エンジンを探して(何しろLinux方面では一番ポピュラーなLinuxSamplerが非OSSなので)、まずはJUCE統合もあるSFZero-Xを見つけました(実際にはSFZeroを見つけてその中で最も新し目のforkを見つけたわけですが、細かいことはおいといて)。それで、sfzformat.comからリンクされているsfzを試してみたのですがサッパリ期待通りに鳴らない。どういうことだろうと調べてみて、どうやらSFZeroはkey switchサポートも含めていろいろ機能が足りないということがわかりました。それで他にもいろいろ試した結果、OSSではsfizzが良さそうだということになりました。

sfizzはLV2をサポートしていて、QTractorで試しに使っている範囲ではちゃんと音が出るようなので、Androidに移植するのも現実的であるように思えます。ただ、sfzによってはSforzandoの独自拡張として定義されているARIAというUIマークアップW3Cとは無関係、仕様も未公開)もサポートしているものがあって、これをまずsfizzでサポートしたい気持ちがちょっとあります(ほぼ未着手)。ARIAは単なるXMLマークアップで内容もシンプルそうなので、SVGあたりに変換すればWebViewで表示できそうです…というあたりでさっきのUI統合の話と繋がります(!)

ちなみに、sfzで移植したら面白いことになりそうだと思っているのは、sfzformat.comからもリンクされているこの辺のギター音源とかです。keyswitchやvelocityいかんで各種奏法を切り替えられるし音色も使い勝手が良さそうです。

unreal-instruments.wixsite.com

あと、関連してLV2をVSTとして使うlv2vstというプロジェクトがあって、コレがあるとTracktion WaveformのようなLV2をサポートしていないLinuxDAWでもLV2音源が使えるのですが、LV2 featureまわりの解決に問題があってsfizzがロードできなかったので、その辺を修正するパッチを書いて取り込んでもらいました。最新版ではsfizzもロード出来ますが、lv2vstはnon-X11 UIをサポートしておらず、sfizzがファイル名を受け取るためのAtomポートにファイル名をバイト列として渡せるルートが無いので、sfizz側に手を入れないと実質使えない状態です。わたしは諦めてsifzzを組み込んだJUCEプラグインをちょっとだけ作ってその後放置しています…

guitarixとそのUIを眺める

これはどちらかといえばデスクトップ側でsfizzとギター音源を組み合わせてからの話なのですが、ギター音源があってもそれ単体だとありがたみがありません。本格的なオーバードライブやアンプを組み込んで音を作りたくなるはずです。Collectiveとかで適当にプリセット音色をいじっているときは組み込みエフェクトだけでもそれなりの音が作れますが、Androidには移植できないのでどこかから持ってくる必要があります。

Native InstrumentsのKompleteで遊んでいた時はGuitar Rigっていう製品があったけどああいうものはLinuxには無いのか…と思って探したら、ありました。知ってました。Guitarix。今でもsourceforgeで(!)開発されていて、LV2もサポートしています。これなら移植しがいがありそうです。

しかもソースを覗いてみるとなにげにwebuiなんてディレクトリまであります。コレもHTML UIの仕組みに乗っけやすそうなやつなので、もう少し深入りしてみようと思っています。ただ、enyo.jsとかいうやや古そうなフレームワークなのと、今guitarix自体がUIをgtkなどからx11に移植しつつあって、これはもしかしたらemscriptenで直接サポートできるUIになるのではないかという期待もあり、とりあえず移植が終わるまで待っています。

Androidネイティブバイナリビルドの動向

sfizzにしろguitarixにしろ、LV2プラグインなので、LV2がプラグインフレームワーク開発の出発点であるわたしとしては着手しやすいものです…となるはずなのですが、実際にはLV2サポートの根幹となるネイティブライブラリのビルドを整備して、aap-juceのリポジトリと同じくらい手順フローを整備してプラグインを簡単に移植できるようにしたいところです。

そんな中、AndroidオーディオチームがOboeのパッケージを新しく"prefab"フォーマットのaarで配布するよ、という話が流れてきました。prefabというのはAndroid Studio 4.0(用のGradle Plugin)で新しくサポートされる形式です。

google.github.io

詳しくは今回は省きますが、これまでのaarとは、拡張子こそ同じだけど根本的に違う内容になっています。そしてこのprefabをビルドするための仕組みとして、Googleはndkportsという仕組みを併せて公開しました。

android.googlesource.com

1つ以上のネイティブライブラリのソースtarballとビルド記述ファイルとなるport.ktsがあれば、後はndkportsのビルドスクリプトがNDK各種ツールチェインを駆使してaarまでビルドしてくれるという代物です。という目標はいいのですが、現実にはまだまだ機能が全然足りない感じです。さいわいprefabはndkportsが無くてもビルドできるので(自前でzipアーカイブすれば!)、ある程度の省力化・ビルド自動化はできそうです。

もっともこれでは開発がスムーズになる気がしないので(フレームワークやLV2ビルドに手を入れたらアプリケーションをAndroid StudioからF9一発でデバッグできるようにはならない)、パッケージング以外の場面ではまだ取り込めないかもしれません。いずれにしろ、4月はこの辺に手を出すタイミングなのかなと思っています。

2月の活動記録

早くも冬が終わりそうで、基本的に冬型人間のわたしとしてはこのままずっと冬が続いてほしいし皆さんには永遠に春が来ないでほしいと思っているのですが、いかがお過ごしでしょうか。(時候の挨拶)

というわけで2月の活動記録です。

目次

Music Tech Meetup #1

まず2/6に無事開催できたMusic Tech Meetup #1。今さら書くのはかなり遅いのですが、参加してくださった皆さん、その他関係者の皆さんありがとうございました。今月は後半になって勉強会やカンファレンスの類が全滅に近い状態ですが、こちらは結果的にはだいぶギリギリのタイミングで開催できた感じになりました。これもひとえに日頃の行い…! (今ほとんどのイベント運営者を敵に回した気がする)

初開催だったこともあって、公開前には「10人くらいになるかもしれないし50人埋まるかもしれないけど、どんな人数でも対応できるようにしておこう…」くらいの気持ちで開催したのですが、翌日にはwaitlistになってしまうくらいの申込みをいただいてテンションが上がりました。

当日のついったらんどの様子はtogetterでまとめておきました。

togetter.com

また、最初のADC2019振り返りセッションについては動画を公開しました。動画上げる前に一応チェックしなきゃな…となって後回しにしてしまって、いざチェックしてみたら音量とかがイマイチで加工を繰り返すことに…

www.youtube.com

また微分音セッションについては、時間制約のないかたちで再演されている動画が公開されているので、こちらを見てください。これはお二人にセッションをお願いして大正解だったと思っています(自画自賛

www.youtube.com

以下KPT

50人規模の勉強会を自分たちで回すのは初めてだったので、わからないことがそれなりにあったりしました。今回は特に他のスピーカーを探してきてお願いするというタスクがあり、イベント全体の時間と帳尻を合わせたり、内容のバランスをどうするかで悩んだりしました。LTで本編に足りなかった成分がある程度補給できたこともあって、結果的にはとてもバランスがとれて良かったと思っています。可能なら次回もバランスよくいきたいです。

タイムキープ。3セッションにしたので余裕があるつもりだったのですが、逆に全体的にざっくりやっても大丈夫だろうと慢心した結果、あろうことか自分たちのセッション時間が伸びて余裕がなくなるという恥ずかしい事態になっていまい、大いに反省しています…(他の登壇者の皆さんにはご迷惑をおかけしました) 今度平日夜に開催するなら、30分トークx3、5分LTx3、30分くらい懇親会、くらいの流れにしたいと思います。

予算も無い草の根勉強会なので、フードはセルフで…!という方式にしましたが、実際には用意していった15人分くらいのお菓子が切れた時点で終わったようでした。まあこれはやむなし…とはいえお菓子ならまあそんなに高くつかなさそうなので次回もこうするかもしれません。ピザとか注文すると1人3000円とかになっちゃうので、当日なんとかするのもやめました。一方で飲み物はソフトドリンクをArt Teknikaさんにご提供いただけたので大変助かりました。

当日までwaitlistがほとんど解消せず、何件か「自分は参加できそうか?」と訊かれたのですが、当日キャンセルの見込みが全くわからず、会場キャパとしても50人はギリギリという認識だったので(多分合ってる)、無理そう…ゴメンナサイ…という感じで応対していました。結局15人を超える事前処理なしの無断キャンセルがあって、入ろうと思えば全員入れた計算になります。さすがに無断キャンセルを無条件でスルーするのは、入れなかった皆さんには心象が悪いと思うので、無断キャンセル勢の皆さんは次回申し込んでもwaitlistが出来たら直前にこちらからキャンセル処理する処置をとる予定です*1。このサンクションの設計は他の勉強会でも有効な措置なんじゃないかなと思うので公開で書いておきます。

次回も開催したいと思っていますが(セッションをお願いしたい人リストはけっこう溜まっているので…!)、数カ月は先とするつもりです。もともとそのつもりだったのですが、COVID19の流行もあって、今後数カ月はイベントの企画が厳しくなりそうだと思っています。今度は土日祝に半日開催としたいのですが、会場を貸してくださる会社でも外部の人への土日の会場貸出はやっていないことが多く、なかなか難しそう…(!?)

ADCセッションメモ

Music Tech Meetupのセッションに先駆けて、数多のセッション動画を眺めていた時に、ある程度メモ書きを残しておいて公開したほうが有益そうだなと思ったので、見ながらちょいちょいまとめていました。まとめたものはとりあえず demo.codimd.org に公開しています(名前もdemoだしいつまであるのかわからないサービスなので、いずれどこかに移転するかもしれません)。まとめきれなかったものもいくつかあるので、ちょいちょい追加するかもしれません。

追記: codimdのサーバ(今はhedgedocのサーバ)はあくまでデモインスタンスなので(ずっと残してくれてはいるけど)、zenn booksに移行しました。

zenn.dev

勉強会参加/LT発表未遂

今月は自分としては珍しく小咄のネタがいくつかあったので、後半にWebAssembly Nightやshibuya.apkでLT登壇するつもりで登録していたのですが、全てイベント自体がキャンセルとなってしまいました。さらにDroidKaigiの中止と技術書典の中止が重なって、今月は完全に自宅警備員モードということになってしまいました(あれ…平常運転では…)。今回は技術書典もほとんど手伝っていなかったのですが、仕掛かりの作業を停止して積みゲーの消化に勤しんでいました(!?)

現時点ではだいぶ知見が広まったと思いますが、この種の大規模なイベントは安易にキャンセルできるものではなく、実施しても中止しても大損害が生じるので、個人的には税金で運営でもしているんでない限りは中止であれ開催であれ支持する立場です。M3は何とか開催されそうなので、楽しみにしています。本来だったらまさかの技術書典8とかぶって参加できないはずだったので…(!)

もしイベント運営者の事情をきちんと知りたいという人がいたら、DroidKaigiについて運営メンバーの人たちがかなり詳しく語っている動画が公開されているので、見てみると良いでしょう。

www.youtube.com

発表するつもりだったネタは主にjuce_emscriptenまわりやAndroid NDKまわりだったのですが、まあ鮮度が落ちるというほどのこともないので、いずれ内容を最新情報に改めて喋ろうと思っています。

juce_emscriptenでオーディオプラグインをWebAssemblyで動かす

今月はちょいちょいjuce_emscriptenに手を加えて、まずJUCEで一番よく使われているであろうAudio Plug-inのプロジェクトのStandaloneアプリケーションをjuce_emscriptenでも動かせるように手を加えました。最新のmasterに取り込まれています。

これまでは実質的に "GUI Application" しかビルドできなかったので、ごく一部のアプリケーションだけがwasm化できただけのように見えましたが、オーディオプラグインのプロジェクトはgithub等に少なからずあるので、これまでに比べるとだいぶ気軽に楽しめるというか実験の成果が出やすくなったと思います。とりあえず成果をいろいろtwitterに上げていたので引っ張り出しておきます。

RtMidiとjuce_emscriptenにWeb MIDI APIサポートを追加

juce_emscriptenではMIDIサポートが実装されていません。なので、MIDI入力を前提としたオーディオプラグインのStandaloneプロジェクトでは、実行できたとしても音を出す手立てがありません。EmscriptenならWeb MIDI APIを使うことも出来るはずだし、実装はそんなに難しくはないのではないかと思っていました。(まあEmscriptenを使ったことは例によって数日しか無いのですが)

C++クロスプラットフォームMIDIをサポートするのによく使われるのはportmidiやRtMidiですが、これらにもWeb MIDI APIサポートはありません。どうせならいったんRtMidiでWeb MIDI APIをサポートするようにして、それをもとにjuce_emscriptenでWeb MIDI APIサポートを実装するようにすれば、今後のメンテナンスは半分くらいはRtMidiコミュニティに投げられるのではないか(!?)と思って、RtMidi経由で実装しています。

RtMidi単体でemscriptenを活用して動かせるMIDIアプリケーションは何一つ知らないので、juce_emscriptenはいいネタになったと思います。

最終的には繋がるようになったので、仮想ポートを自動生成するxmmkからjuce_emscriptenで動くオーディオプラグインのstandaloneアプリにMIDI入力メッセージを送りつけて、(臨むなら)それをローカルで起動しているfluidsynthにMIDI出力メッセージとして送る、なんてこともできます。(画像は入力のみ)

JUCEオーディオプラグインAndroid上でプラグインとして動かす

1月までの時点でJUCEアプリケーションで自分のAndroidプラグインフレームワークをサポート出来るようになっていたので、今月は逆にJUCEを使ったオーディオプラグインを自分のフレームワークで動作するプラグインとしてビルド出来る仕組みを構築していました。そもそもJUCEはAndroid用にAudio Plug-inプロジェクトをサポートしてこなかったので、そこから手さぐりでJUCE本体には手を入れずにサポートを追加する方法を模索しました。この辺の話は、実装が出来た段階で先日のエントリーでまとめてあります。

JUCEオーディオプラグインプロジェクトはいろいろあるのですが、Androidでも動作する比較的簡単なものとして、とりあえずandesSARAHを移植してあります。関連して、他にもADLplugなどいくつかのオーディオプラグインAndroidで動かしています(自作フレームワーク統合はまだ)。

この辺は、先のjuce_emscriptenのサポートと合わせて、自分のforkとしてビルドすることが多くなりそうなので、いずれWebサイトを作ってまとめておこうと思います。特にwasmビルドはWeb上にデプロイできるわけですし。

emscriptenはさておき、Androidで自前のプラグインフレームワークでJUCEプラグインが動くようになると、こういうことが出来るようになります。

今回はどちらもJUCEプロジェクト、別々のAndroidアプリケーションインスタンスです(まあ先月もLV2プラグインを動かしていたわけで、それも別々のアプリケーションインスタンスですが)。

What's next?

次は…いよいよtracktion_engineあたりを動かしますかね。tracktion_engineが動いたら自作MML環境で作った音楽がAndroid上で再生できる用になる日も近い…?

それと別に、UI統合についてそろそろ真面目に検討しようと思っています。ホスト部分はtracktion_engineが動けばコンセプトとしては十分に目的を達成できていると思うので、足りない部分を継ぎ足していきたいですね。

*1:再申込拒否まではしない

JUCEベースのオーディオプラグインを自分のフレームワークに輸入する

こちらは二部構成の後編になる。前編はこちら。

atsushieno.hatenablog.com

タイトルは対照的ではないけど、今度は「JUCEでカスタムAudioPluginFormatをホストする」の反対側、すなわちJUCEを使って自分のプラグインフレームワーク用のオーディオプラグインをビルドできるようにするために、JUCEモジュールを作成する。

JUCEの本質的な特徴のひとつは、1つのコードベースで複数フォーマットのオーディオプラグインを簡単にビルドできることだ。すなわちオーディオ処理の部分はコード共有できるので、自分が独自にオーディオプラグインフレームワークを開発しているときは、これを活用できるようにしたい。

ホスト側をサポートするだけでは、DAWの基盤で自分のフレームワークをサポートすることは出来ても、オーディオプラグインのほうは実例が増えないので、JUCEをプラグイン作成のほうでサポートできるようにするのが手っ取り早いやり方だ。

目次

JUCEでサポートで必要になる機能追加

JUCEは、ごく大まかに言えば、フレームワークライブラリと、ビルドシステムであるところのProjucerの2つから成り立っている。オーディオプラグインが単一の共有ライブラリで完結していることはおそらく多くなく、一般的には何らかのバンドル(パッケージ)になっている。ライブラリを利用してコードを実装できるのはフレームワークの部分だけであって、パッケージングの部分ではビルドシステムが重要になる。これらは切り離して考えたほうがよい。つまり:

  • オーディオ処理コードの自家製フレームワークへの繋ぎ込み → フレームワーク側の拡張
  • リソースの生成とパッケージング(バンドル作成) → Projucerのビルドシステムの拡張

この2つのうち、Projucerの拡張は、あまり推奨できないし、自分でもやらなかった。ただそれがなぜなのかを説明するためには、フレームワークの方から先に説明する必要がありそうだ。

JUCE本体に手を加えずに機能を拡張する

どんなライブラリやフレームワークも、いったん安定版をリリースして広く使われるようになったら、可能な限りAPI互換性を維持するようになる。互換性が破壊されると、自分が書き換えられないユーザーのコードがビルドできなくなるからだ。開発者はある日突然自分が使っていたライブラリのアップデートのせいでビルドが通らなくなると、アップデートしなくなるかもしれない。つまりユーザーが減る。これはユーザーベースが無くても良い開発者の場合は問題にならないが、そんなケースは相当まれだろう。

Androidや.NETの開発者としてのキャリアが長い自分から見ると、JUCEは比較的APIを破壊的に変更しているほうだと思うが(基本的には良い特徴ではない*1)、それでも破壊的変更は原則として不必要には行われないはずだ。

JUCEに何かしらの機能を追加する場合によく見られるのが、JUCE本体をforkして、必要なコードに手を加えることだ。これは実のところあまり持続性が無いやり方で、可能な限り避けたほうが良い。いったんforkを維持する方針を採ってしまうと、ROLIが新しいバージョンをリリースするたびに、コードの変更からコンフリクトを解決しなければならなくなる(これは変更が大きければ大きいほど問題になる)し、実行時の意図しない挙動の変更によって問題が生じる可能性も広がる*2

もっともJUCE本体にかかるさまざまな特徴*3が、これを困難にしている。だからJUCEのforkは他のフレームワークに比べたらずいぶん多い。どれも開発者にとっての関心の対象外になった時点で追従しなくなっている。

JUCEはなるべくforkせずに、基本的にはJUCE APIに沿って、破壊的に変更されないAPIを使ったモジュールやライブラリとして実装したほうがよい。

Projucerサポートの部分をあきらめる

さて、ここでもうひとつの本題であるところのProjucerに戻ろう。非破壊的変更の原則はフレームワークにしか適用されない。生成されるビルドスクリプトMakefileなど)には常に破壊的な変更が入りうる。だからこれに依存する仕組みは入れられない。

アプリケーション開発者に公開されていて破壊的変更が無いことを暗黙的に約束されているのは.jucerファイルにおける設定部分のみだ。それ以外ではProjucerにはAPIは無いので、外部から利用する手立てがない。自前フォーマットのサポートがProjucerに入っているはずはないので*4、Projucerで行われている処理に機能を追加するためにはforkするしかない。しかしforkしたら持続性が無いことは説明したとおりだ。

ではどうすべきか? Projucerで行われている部分はもうあきらめて、自分でパッケージバンドル作成ユーティリティを作ったほうがよいだろう。幸いなことに、われわれ(誰)に必要なのは、多様なプラットフォーム向けにさまざまなパッケージングを求められるVST3のようなフォーマットに手を加えることではなく、あくまで自分のフォーマットに沿ったパッケージングだ。

「理想的な」ビルドシステムへの繋ぎこみを考えるのであれば、ProjucerはCMakeをサポートしていて(CLionサポートなどと称しているが、要するにCMakeサポートである。JUCEは伝統的にCMakeを毛嫌いしていたので正面から認めたくないだけだ)、CMakeは現状では最も幅広くクロスプラットフォームでサポートされているビルドシステムなので、CMakeモジュールとして自分のパッケージフォーマットをビルドする仕組みを作るのが良いかもしれない。

ProjucerのCMakeサポートはそこそこやっつけ仕事感があるので(たとえばファイルパスをLinuxMakefileから取り込む部分が雑でCMakeではビルドできないなんてことがざらにある)、話半分で聞いておいたほうがいい話かもしれない(「理想的な」というのはエクスキューズだ)。

次節からコードに踏み込んで解説することになるのだけど、Projucerでやっている仕事をどうやって実現するかについても後ほど改めて触れていく。

プロジェクト構成 - Audio Plug-inプロジェクトの概要

さて、コードの話に踏み込むまでにすっかり説明が長くなってしまった。JUCEのオーディオプラグインプロジェクトの「コードを」取り込む話に入っていこう。

最初に、そもそもJUCEのオーディオプラグインプロジェクトについて書いておこう。JUCEのアプリケーション テンプレートには、GUI ApplicationやOpenGL Application、Audio Applicationなどと並んでAudio Plug-inというものがある。JUCEでオーディオプラグインを開発する人はほとんどこれを利用しているはずだ。

プロジェクト テンプレートによって何が異なるかというと、Projucerで表示され.jucerで保存されるオプションが変わってくる。Audio Plugin-inの場合は、ここにプラグインの基本情報(名前とかベンダーとか)やVSTAU・AAXなどに固有のオプションが入ってくる。これが生成される.vcxprojや.xcodeproj、Makefileなどにも影響するというわけだ。

オーディオプラグインプロジェクトではstandaloneアプリケーションをビルドすることもできる。プラグイン実装のロジックをデバッグする時には大抵これを使うことになるだろう。繋ぎこみの部分にバグが入ってくることは通常はあまり考えなくてよいし、それは基本的にはJUCE本家の開発者がやるべきことだ。もっとも、われわれ(?)は独自のフレームワークへの繋ぎこみを実装する立場なので、この意味ではJUCE開発者のポジションに近い。

独自のライブラリへのglueコードを構築する手段として一番便利そうなのは共有ライブラリで、ProjucerではStatic LibraryやDynamic Libraryのプロジェクトも作成できるが、これではプラグインメタデータ生成が行えないし、standaloneプロジェクトをテンプレートから自動生成してくれるAudio Plug-inのほうが便利だ。何より既存のプラグインを取り込んでサポートしたいのであれば、こちらをサポートする必要がある。

AudioProcessorの"Wrapper"を作る

Audio Plug-inプロジェクトを新規作成すると、コードとしてはjuce::AudioProcessorの派生クラスが定義される。これを自分で実装することになる。API自体はそんなに難しくはない。ホストを実装する時に出てきたjuce::AudioPluginInstanceprocessBlockと同じだ。そもそもAudioPluginInstanceAudioProcessorから派生しているので、同じ関数をプラグインを作るときにもoverrideするということだ。

Audio Plug-inプロジェクトのコードの部分は大半がこの実装になるので、実装すべきコードについての説明はもうほぼ終わりだ(!)。あと他にAudioPluginEditorを実装している場合もあるが、これを繋ぎこむ処理が必要になる場合については話がややこしくなる(そもそも繋ぎこむ先のプラットフォームGUI固有の話になる)ので、基本的には何もしなくてもJUCE側でよろしくやってくれることを期待することにする。

自分のフレームワークに繋ぎこむためには、むしろ自分のフレームワーク側でどのようなプラグインのエントリーポイントを用意すべきか、どんなコードでオーディオ処理を行うべきか、といったところから考えなければならない。これはしかし自分のフレームワーク向けのプラグインを開発するのと、基本的には何も変わらないだろう。自分のフレームワークには自分なりのオーディオバッファデータ構造があるはずだが、概ねfloat配列などに帰着するだろうから、多くの場合はフォーマットの変換処理までは必要ないだろうし、なるべくコストのかからない方式で変換してやると良いだろう。実行時にメモリ確保しないようにすることが重要だ。もちろんMIDIメッセージに関してはこれ単純変換で済むという話は当てはまらない。

JUCEがVSTやらAUやらのサポートをビルドするときに何をしているかというと、基本的には.jucerで指定されたオプションに基づいて、C++コードに何を有効にしているかを宣言する#defineを追加して、その状態次第で各プラグイン向けのコードがコンパイル対象になる。たとえばVST3の場合はJucePlugin_Build_VST3が有効になるとjuce_VST3_Wrapper.cppのコードがコンパイル対象になる。この中にVST3プラグインで必要になるGetPluginFactory()が定義されているので、VST3用にビルドされたコードはVST3プラグインのコード部分として機能する、というわけだ。

これらは公開APIになっている必要はないので、juce_audio_plugin_clientのAPIリファレンスを眺めても、プラグイン固有のAPIは何も出てこないというわけだ。なのでコードも別にWrapperという名前を付けなくても良いのだけど、伝統的に他のプラグインフレームワークAPIの処理を自分たちのAPIでwrapする感じになるのでwrapperという名前がよく使われるようだ。

ちなみにJUCE側にはwrapperの基底クラスとして使うべきものは何もないが、wrapperを実装するためにはプラグインプロジェクトで作成されるAudioProcessorインスタンスを取得出来る必要がある。これはcreatePluginFilter()という、Projucerが自動的に生成する非公開の関数を呼び出すことで使えるようになる。

作ったwrapperはJUCEモジュールとして再利用できるようにしておくと、プラグインを移植する際にモジュールを追加するだけで済む…可能性が上がる。実際に可能かどうかは、次節で説明する残りの作業の内容次第だ(プラグインフォーマットによって変わる)。

Projucerに代わってメタデータをコードから生成する

さて、ここまでコードの実装を頑張ったところで、このプラグインについてビルドして利用できるのはStandaloneビルドのアプリケーションとSHARED_CODEと呼ばれるstaticライブラリのみだ。自分のプラグインフォーマットのためのビルドスクリプトでは、これをリンクして共有ライブラリなどを完成させなければならない。前述の通りProjucerは使えないので、これは自前で実装する必要がある。

もう一つ、残されているのは一般的にプラグインに必要とされるメタデータ生成だ。個別のプラグインAPIでは、コード中のAPIとして実行時に取得可能になっている可能性もあるが、JUCEではそのようなAPIは用意されていないので、ユーザーもそのようなコードを実装してはいない。

ではどうやってVST3やAUメタデータが生成されているかというと、.jucerファイルで指定された作者情報などは、ProjucerがC++#defineディレクティブに変換していて、各wrapperではこれを利用しているのである。juce_audio_plugin_clientの公開APIには含まれていないので、あくまで自分のモジュール内で、該当するディレクティブを見つけて、その内容を処理してメタデータを生成するコードを実装するしかない。

Projucerはもちろん独自プラグイン形式のメタデータを生成してくれないので、この枠組みの中で自分にもできることは、wrapperコードの一環としてメタデータを生成する関数を定義して、これを利用する外部ツールをビルドすることだ。ビルドされたSHARED_CODEのstaticライブラリをリンクすれば、このコードを呼び出せる。

このアプローチを採用しているのが、ADLplugというプラグインのプロジェクトで使われているJUCEのforkに含まれるLV2サポートの実装*5。このコードで定義されているLV2リソース生成のためのコードは、プラグイン本体としては全く実行されないが、LV2のTurtle形式のメタデータを生成する際に使われる。JUCEでLV2をサポートするためにはこういうアプローチが採用されている。

まとめ

ここまでの流れを踏まえて、Audio Plug-inプロジェクトを移植する全体的な作業をまとめる。

  • もしまだ作っていなければ、独自プラグインフレームワークへ繋ぎこむモジュールを実装する
  • 移植対象のプラグインのソースの.jucerをProjucerで開いて、そのモジュールを追加する
  • ビルドする
  • もし必要かつまだ作っていなければ、繋ぎ込みモジュールにメタデータリソース生成のためのコードを実装して外部から呼び出せるようにする
  • メタデータリソース生成(無理がなければパッケージバンドル生成)のためのツールを用意する(エントリーポイントからその関数を呼び出すようにして、Audio Plug-inのライブラリにリンクするだけで十分)

これでいけるはずだ。出来てしまえば難しいことはあまり無いだろう。

*1:ゼロ・トレランスで破壊的変更を拒絶するAPIは成長に対するコミットメントが無く未来もないことが多いので、「基本的には」という留保を付けている。C++ライブラリがそもそもそういうものかもしれない。

*2:挙動に変更に起因するバグは外部モジュールにしていても当然発生するけど、意識のすり合わせのない「変更」ではその可能性がぐんと上がる

*3:伝統的に外部モジュールによる機能拡張をあまり歓迎しなかったC++コード基盤とか、多様な環境をより簡単にサポートするためにソースから全てビルドするがゆえにバイナリ配布によるAPIを考慮してこなかったとか、そもそも「Juleの個人的なユーティリティだから改造したいやつは好きに使え」だった歴史とか

*4:ちなみに今回コードを書いていて発見したのだけど、公式のProjucerでLV2サポートを追加しようとしていた痕跡が今でもコードベースにある

*5:これ自体はDPFという別のフレームワークの開発者による実装かもしれない

JUCEでカスタムAudioPluginFormatをホストする

そろそろ忘れそうなので備忘録的に書いておく。

自分で独自のオーディオプラグインフレームワークを開発していて課題になるのは、自分でフレームワークからアプリケーションまで全てを開発するのは不可能だということだ。そういうわけで可能な限り既存のリソースを使い回すためにJUCEのバックエンドとしてサポートを追加しようと考えるのは割と自然なことだろう*1

JUCEではオーディオプラグインをホストする機能はjuce_audio_processorsというモジュールでコア機能とVST, AU, LADSPAのホスティングをサポートしている。幸いJUCEに独自のオーディオプラグインを追加するのは難しくない。今回はこれをざっくり解説する。

念のため明記しておくが、今回の主題はホスト側のみであって、プラグインを作る側ではない。プラグイン側は後編としてこちらにまとめた。

atsushieno.hatenablog.com

目次

自分のAudioPluginFormat派生クラスを作る

JUCEでは、ホストする対象となるオーディオプラグイン仕様は、juce::AudioPluginFormatから派生するクラスとして定義する。VST3PluginFormatとかAudioUnitPluginFormatといったクラスがこのモジュールの中でも具体的に定義されている。juce_audio_processors内部に自分のクラスを追加する必要はないので、自分のモジュールを作ってメンテナンスするのが一番楽だろう*2。モジュールの作り方は以前に解説してある。

atsushieno.hatenablog.com

AudioPluginFormatクラスにはいくつかオーバーライドすべきメンバーがあるが、このクラスの基本的な役割は2つだ:

  • (主に)システムにインストールされているオーディオプラグインを検索する
    • そのために必要な検索パスのリストを指定する
    • 対象パスからプラグインを含むファイルを絞り込む
    • 対象ファイルから含まれているプラグイン(群)の情報をPluginDescriptionクラスのインスタンスとして格納する
  • 指定されたPluginInstanceオブジェクトからAudioPluginInstanceインスタンスを生成する

前者はより具体的に書くと

  • getDefaultLocationsToSearch()でデフォルト検索パスを設定し、
  • searchPathsForPlugins()プラグインを含む可能性のあるファイル群をリストアップして、
  • fileMightContainThisPluginType()で引数ファイルにプラグインが含まれている可能性があるか判断し、
  • findAllTypesForFile()で指定されたファイルに含まれるプラグイン(群)を取得する

といった流れで実装する。ファイルベースのOSでない等の事情でこの一連の流れが面倒な場合は、その環境でどういう流れになるべきか、少し挙動を調査・検討する必要があるだろう。

後者はcreatePluginInstance()で実装する。この関数は非同期で戻り、生成したインスタンスは引数にあるPluginCreationCallbackに渡して呼び出すことになる。

PluginDescriptionにプラグインメタデータを格納する

AudioPluginFormat::findAllTypesForFile()を実装する時点で、プラグインメタデータPluginDescriptionというクラスのインスタンスとして返す必要がある。OwnedArrayの引数に結果を追加するので、メモリ解放を心配する必要はない(解放できるメモリのポインタを渡す必要がある)。PluginDescriptionは派生して定義するものではない。プラグインフレームワークでメモリ管理すべきものをここに含めるのは適切ではないからだ。

個々のプラグインPluginDescriptionのメンバーの値で識別できる必要がある。fileOrIdentifieruidが有用だろう。またKnownPluginListクラスなどで検索結果をローカルにキャッシュする仕組みを活用することから、識別できる値はプロセスごとに変わってもいけない。

自分のAudioPluginInstance派生クラスを作る

メタデータ処理が終わったらいよいよjuce::AudioPluginInstanceクラスを派生させてプラグインインスタンスを生成する。このクラスはjuce::AudioProcessorの派生クラスで、実際にはオーディオ処理の大半はこちらで行われている。

生成されたインスタンスは次のような流れでオーディオ処理を走らせることになる。いずれもアプリケーションからコールバックされる前提で実装する。

  • prepareToPlay()プラグインをオーディオを処理できる状態にする
  • processBlock()でオーディオバッファとMIDIバッファを処理する
  • releaseResource()で最初に準備したリソースを解放する

またエディタUIを表示したりUIで編集したデータを読み書きすることもある

  • createEditor()でエディタUIを表示する
  • getStateInformation()でstateをプラグインから取得する
  • setStateInformation()でstateをプラグインに反映する
  • getNumPrograms(), getCurrentProgram(), getProgramName()などでプログラム(プリセットなど)を取得する(set...()で設定もできる)

最低限のプラグインフォーマットのサポートは、このクラスの純粋仮想関数を全部実装するだけでいける。ただ純粋でない仮想関数の中にも絶対に実装すべき重要なものが含まれている可能性は多分にある。Busの設定やチャンネルレイアウトの変更通知などが純粋仮想関数になっていない。いったん定義してしまった抽象クラスに後から純粋仮想関数を追加するとAPI互換性を破壊することになるので、後からメンバーが追加される時は必須であるべきものであっても純粋仮想関数にならない。

ちなみにprocessBlockMIDIメッセージを処理するにはタイムスタンプを付加し考慮する必要がある。一般的なMIDIアプリケーションであればあまり問題にならないかもしれないが、オーディオプラグインではオーディオ処理のためのと合わせてMIDIメッセージも流れてくるのが一般的だ。なぜなら関数呼び出しというのはコストが比較的高いので、何回も頻繁に呼び出せるものではないからだ。しかしオーディオバッファはそれなりの大きいチャンクに分けられて呼び出されるので、MIDIメッセージにタイムスタンプがないと、長い場合は数百ミリ秒のレベルでしかイベントが処理されないことになる。これでは音楽にならないため、MIDIイベントにはタイムスタンプが付加されて、プラグインがこれをよろしく処理する、というのが一般的だ。

自分のAudioPluginParameter派生クラスを作る

ここまでのタスクをこなすだけでも、JUCEアプリケーションからオーディオ処理を呼び出して実行できるようにはなっている。ただしプラグインのパラメーターを調整しようと思って、たとえばプラグインパラメーターを調整するUI*3を表示しても何も表示されない。パラメーターの定義はAudioPluginInstanceに追加してやる必要があるのだ。

言い換えれば、パラメーター情報のクエリはメタデータのレベルでは不可能で、インスタンス化しないと出来ない、ということでもある。これはVSTのような仕様でサポートされていないということだろう。

いずれにせよ、AudioPluginInstanceインスタンスを生成するたびに、AudioPluginParameterというパラメーター情報と操作の両方を実装するクラスのインスタンスを生成して追加してやらなければならない。一般的には自分のAudioPLuginInstance派生クラスのコンストラクターで実装することになるだろう。

このクラスではgetValue()setValue()getName()などを実装するだけで、難しいことは特に無い。もっとも、「パラメーター」のセマンティクスは必ずしもオーディオプラグイン仕様によっては存在しないので(たとえばLV2にはportの概念しかなく、portはデータを送信するためにあるのであって値を取得することは前提となっていない)、仕様次第では何らかの調整が必要になる可能性はある。

自分のAudioPluginFormatをアプリケーションから使う

JUCEはアプリケーションを全てソースからビルドするような仕組みになっている。JUCEのフレームワークを直接参照しているプロジェクトに後付けで自分のプラグインフォーマットをサポートさせることはできない。既存のアプリケーションで自分のプラグインフォーマットをサポートしようと思ったら、アプリケーションのコードにサポート追加のためのコードを書かなければならない。

幸い、独自プラグインをサポートするためのAPIは用意されている。AudioPluginFormatManager::addFormat()を呼び出すだけだ。AudioPluginFormatManagerは一般的にはオーディオプラグインを扱うアプリケーションごとに生成されているはずなので、その部分に1行追加するだけで足りる。

*1:JUCE以外のプラグイン開発フレームワークでもアプリケーション資産があれば対応を考えるところだ

*2:一旦内部に作ってしまうと自分のforkを延々とメンテし続けることになってしまって維持するのがしんどくなるだろう

*3:自分で実装する必要はない。JUCEにはパラメーター定義から設定コントロールをシンプルにリストアップできるクラスがある

1月までの開発記録

前哨戦

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

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

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

techbooster.booth.pm

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

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

atsushieno.hatenablog.com

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

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

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

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

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

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

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

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

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

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

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

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

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

github.com

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

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

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

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

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

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

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

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

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

www.youtube.com

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

これからどうするか

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

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

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