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以外でも再現できたということです。

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

11月の開発記録(2022)

毎年「えっ、11月って31日まで無かったっけ?」って思ってしまう人です(時候の挨拶)

勉強会: Zrythmから読み解くDAWの作り方(12/11)

music-tech.connpass.com

10月に「あと11月のうちに1回オンライン勉強会をやっておきたいですね。」などと書いていたのですが、その頃考えていたのがこのZrythm勉強会です。Zrythmは新進気鋭のDAWで開発に勢いがあってプロジェクトが成長するのを見ているのも面白いし、開発者のalexteeせんせいは日本語もいけそうなので「もしかして日本語勉強会が出来るのでは…?」と思いついたのは天才的だったと思います(自画自賛) 「ちゃんと勉強会として提案できる内容になるかな…」といろいろ下調べをしていたら時間がかかってしまって、12月になっちゃいましたが、参加を快諾していただいたalexと、開催コミュニティのおふたりの協力をいただいて開催の目処が立てられました。

Zrythmはプラグインのサポート作業をCarlaにオフロードしているので、他の部分に注力できている一方で、GTKアプリケーションという(この分野では)割と特殊な全体構造なので、そこにあまり踏み込まずに話せるだろうか…とか、今もスライドを作りながらいろいろ落とし所を探っているところです。Zrythmに終始する勉強会にはせず、使い方の話で終わらせもせず、Zrythmへのcontributionにも道を拓くようにする…くらいの立ち位置を目指します。

ちなみに勉強会自体はMastodonTwitterFacebookでお知らせしていたら1日ちょっとで30人枠全部埋まってしまいました(今までにない流れ…!)。すでにwaitlistが出来ていて、たぶん何かしらの救済策を立てますが、これからのフル参加は難しいと思います(すみません)。

ADC2022(オンライン参加)

audio.dev

先月分を書いたときはまだ現地参加するかも…みたいなノリでしたが、その直後にオフライン参加が締め切られてしまったので、今年もオフライン参加になりました。まあ正直ハイブリッド開催におけるオフラインオンライン参加のメリットは小さいですね。gather.townに来るのは多くても100人くらいで、スピーカーなど主な参加者はオフラインにいる間はオンラインには絶対来ません(自分もよほど物足りなかったりしない限り見に行かないと思います)。セッション中にその場で質問するとか、あとはgather.townでスポンサーブースにいる人たちと会話するのが主な「参加」の楽しみ方となるでしょう。

もちろんセッションコンテンツを見られること自体も意味があると考えることはできますが、正直有料で参加していても「さっさと動画を公開してくれたほうがいい」と思います。ADC2021の動画はなかなか公開されず(youtubeにずっとunlistedで上がっていただけ)、結果的に参加したわれわれが他の人と動画鑑賞会も開催できないうちに興味/関心が薄れてセッション録画の価値が下がっただけだなー、というのがわたしの所感です。

それはさておき、今回の話題はこの辺だったかな、というのをいくつか挙げるとしたらこんな感じです:

個人的にはMIDI 2.0のセッションを聞いてアップデート情報を見て「MIDI-CIのprotocol negotiation無くなるの? マジ?」とかいいつつ、MIDI協会のブースに行って中の人としゃべったりして、ADC後にミーティングを設定してもらったりして、謎のコネクション(?)が増えました。聞いたところによると、MIDI 2.0の話とか書いたりTwitter(いつまであるかわからんけど)に書いたりしていると漏れなくヲチされるらしい…(適当) 「このブログを読んだんだけど…」と言われてここが英訳された画面が出てきたりとかして、心臓に悪かったです(これも読まれる可能性がある)。日本語もそこそこ通じたので今度はMIDI 2.0勉強会とかやったら来てくれないかな…(無理筋)

書き物

今月は「Android NDKでAPIレベルによる条件分岐を実現する」という割と長めのネタを書きました。

本文中でも触れていますが、「NdkBinderがminSdk 29で使えなくなっているんだけど…」っていうissueを春先に立てていて、(変更が複数コンポーネント = 複数チームに渡るので割と重く)ようやく解決したっぽいので、その過程で得られた知見をまとめておこうと思ったのでした。

あと、公開されるのは12月ですが、明日12/1はDTMテクニック集 Advent Calendar 2022atsushieno/open-vital-resourcesの話を、明後日12/2はJUCE Advent Calendar 2022でJUCE 7.0のLV2サポートの話を出すので、今月はそれらを書いていました。これで来月書くものは少なくなったはず…!

AAP: update plugins to work as AudioPluginService.V2 plugins

今月は結局ADCをリモート参加にしたのでその分自分の時間が取れたはずで、開発もそれなりに進んだはずなのです…が、成果を見るとそうでもないような…。AAPのパラメーターサポートの実装をやっていました。

先月はプラグインパラメーターの出入り口を(オーディオ・MIDIと同じ)Portから(独自の)Parameterに変更する調整作業を今月に持ち越していました。それはaap-lv2、aap-juceともに何とか片付いたのですが、UMPからMIDI1へのtranslatorを実装していて、「UMPのAssignable Controllerをパラメーター変更に使っているとプラグイン側でNRPNを受け取れないな?」ということに気付きました。もう少し具体的に説明すると、UMPのAssignable Controller (AC)はMIDI 1.0でいえばNRPNに相当するもので、UMP単体で見たらACはMIDI 1.0には存在しないメッセージなので既存のMIDI 1.0に基づくプラグインMIDI入力とかぶることはないのですが、MIDI 1.0とMIDI 2.0の間で変換処理を行うと、NRPN-DTEの組み合わせはMIDI 2.0ではACに変換されることになるので、やっぱりこのメッセージもMIDI 1.0用プラグイン潜在的に処理するメッセージと競合することになるわけです。

ACを使ったパラメーターの変更という設計は賢いと思っていたので、捨てるのがもったいなくてしばらく悩みましたが、これを残していたらVST3のCCと同様に使えない仕様になってしまうと考えて、やはり変えることにしました。新しいメッセージフォーマットではSysEx8を使います。SysEx8であれば今度こそ問題ないはず…と思いますが、manufacturer IDやsub IDは割と雑に00だったりするので、もしかしたらマイナーチェンジが必要になるかもしれません。ともあれ、この変更のためにcmidi2から微妙に手を入れ直して、aap-lv2もaap-juceも全体的にプロトコルを修正する作業でいっぱいいっぱいになりました。

ちなみにパラメーター変更にSysEx8を使うアプローチは実は初めてではなくて、MMLからUMP経由でTracktion Engineのパラメーター変更を実現する際にも利用していました。この頃は「音源別パラメーターってSC-88Proのインサーションエフェクトみたいなもんだからシステムエクスクルーシブでしょ」って雑に考えてやっていました。ただし、今回のフォーマットはもう少し情報を付加することになりました。MIDI 2.0のAssignable Controllerはノート別(Per-Note)パラメーターもサポートしていますし、プラグインとしてもCLAPのようなフォーマットではパラメーター変更メッセージにノートナンバーが含まれます。また、そもそもNRPNはchannel voice messageであり、チャンネル情報がa prioriに付いてきますが、SysExにはこれが無いんですね。そういうわけで、チャンネル情報とノートナンバー、あと今後ノートがkeyではなくnote IDの方式で渡される仕組みが一般化するような事態を考慮してextraフィールドを作ったので、16ビットのSysEx8単体パケットはほぼ使い切ってしまいました(いざとなればもう1パケット増やして32ビットにしてもいいのですが、パラメーターの変更ひとつに32ビットか…という気持ちは正直あります)。まあ、そんな感じでこの辺の設計にもいろいろ試行錯誤があります。

LV2のlv2:Patchサポートを除いて実装上はひと通り対応したはずなんですが、まだバグフィックスが必要になったりaap-juceを実装しているうちにaap-lv2がリグレッションを起こしたりしていて(実装してみるとわかりますが、client/serverが別アプリで非常にテストしづらいし、リポジトリを合体させるわけにもいかないのでビルドも統合できなくて、だいぶ悩ましいやつです)、めでたく12月に持ち越しです。

lv2:Patchサポートは、まだこれを必要とするLV2プラグインが無いので優先度が低いというのが正直なところですが、やらないわけにはいかないやつです。実のところJUCE 7.0のLV2ビルドではlv2:Patchが使われることになるので(この辺はJUCE Advent Calendarの記事に詳しく書きます)、それらを「LV2プラグインとして」AAPに取り込むなら必須になるのですが、JUCEプラグインならストレートにaap-juceを使えばいいじゃん…となります(!)

あとは、プラグイン単体だと機能するけどMidiDeviceServiceとして使おうとすると音が出ない、みたいな問題も大きく、今月はまだ安定的に使えるバージョンをtag打ちできないなあ、ってなっています(3ヶ月くらいできていない)。今年中に一区切りつけたいですね。

Mastodon

最近は、日常的な技術的発見の多くはmastodonで書いています(英語は @atsushieno@g0v.social 、日本語は @atsushieno@mastodon.cloud )。Twitterは概ね反応器としてしか使ってない感じです(まあイベントの宣伝とかには使うと思いますが)。可能なら正常な治安が回復してほしいですね。Twitterはほとんどリアル知り合いばかりをフォローして狭い世界を構築していたのですが、今どきそんな古臭いことをしなくてもいいだろうということでMastodonはもっとカジュアルに使っています。

10月の開発記録(2022)

M3 2022秋の出展

10/30のM3 2022秋 (50回目だったらしい)に「オーディオプラグイン研究所」として出展参加しました。技術書典13で発行した「CLAPオーディオプラグイン開発ガイド」に加筆修正を加えた「正式版」の印刷版・電子版と、「Linux DTMガイドブック」の電子版が新刊ということになります。前者はboothで取扱開始しました。技術書典のときに「紙版がほしかった」という方はこちらからどうぞ。本当は無償配布にしたいところですが、印刷代だけでも「売れば売るほど赤字になる」設定なので、ひとつよしなに…

xamaritans.booth.pm

Linux DTMガイドブックも同様に「完成版」を発行したかったのですが(たとえば、サンプラーなど「器」だけ言及していて楽器サンプルへの言及がゼロなのを何とかしたい)、時間的に厳しくあきらめました。M3では新刊の頒布に合わせてAndroidプラグインの展示も行う計画で開発を進めていて、そこまで手が回らなかったためです。(Androidプラグインの展示に合わせて最新版を安定させる計画は間に合わず、少し古いバージョンにしたのですが…!)

このLinux DTM本と既刊の「MIDI 2.0エコシステム構築術」「MML to MIDI 2.0 to DAW」も含め、展示では幻の「印刷版」を見本として用意しましたが、これらの印刷版はありません。デジタル版でお求めください。(この2冊は割と「自由研究をまとめてみた」性格の本で、現状ではそれなりに時代とともに価値が薄れていく過渡的な内容だと思っています。)

DroidKaigi2022(遊びに行っただけ)

10月上旬にはDroidKaigi2022もありましたね。今回はトーク応募すらしなかったので、単純に東京ドームシティまで遊びに行きました。開催地が遠くなってしまったこともあって、宗教上の理由で午後からの参加のみでしたが、いつものDroidKaigiのふいんきがだいぶ戻ってきていて(ランチとかコーヒーとか夜のパーティが戻ってくるのはまだ無理そう)、開催してくれてありがたい〜という感じでした。年単位で会っていなかった知人のみなさんとも久々に顔合わせできたし、新しく開発者のみなさんとも知り合いになれました。

Gradle Managed Virtual Devicesで変化するエミュレータ活用術」の外山さんに「GitHub ActionsのLinuxホストで使えるようになってほしい(KVMのnested virtualizationをサポートしてほしい)んだよ〜」とか詮無きことを言ったり、「Jetpack Composeを用いて、Canvasを直接触るようなコンポーネントを作成する方法」を見て自分がComposeで作っていたWaveform描画コードを直したり、「Android "を" ビルドしてAndroid Systemを覗いてみよう]」みたいなそこそこ低レイヤーのセッションが聞けるのうれしい〜とか言いながらAOSPアプリのビルドに手を加えるのを眺めたり、「Considerate App Update Delivery」で相変わらず多種多様な他人のアプリをビルドしないといけない仕事やべえ知見がたまりそう〜とか思いながら眺めたり、「人の声を可視化する」でオーディオAPIの話したい〜と思ってask the speakersでお話ししてみたらここの読者の方だと判明したり(そんなことあるんだ…!ってなりました)、「Android アプリの内と外をつなぐ UI」を見ながら自分のアプリのonBackPressed()onBackPressedDispatcherで書き換えたり、あと他の人と雑談していて気がついたらセッションを見逃したり()みたいないつもの体験を楽しみました。

全部かはわからないけどYouTubeセッション動画も公開されているようなので、裏番組とかで見られなかったやつを少しずつ追っかけようと思います(昨日まではさすがにそんな余裕なかった)。

ktmidi 0.4.0

今月は先月から続いていたktmidiの実装を直す作業がひと段落して、新しくバージョン0.4.0を公開しました。 Kotlin Native環境用にRtMidiのcinteropバインディングを作って、RtMidiNativeAccessという実装を追加して(JNAを使ったKotlin/JVMバインディングもあることを考えるとだいぶ二度手間感…)、Midi2Playerをネイティブでも意味あるものにしたという変更が大きいですが、UMPベースの楽曲データのフォーマットやAPIに破壊的変更を加えたからバージョンを0.1上げたというのが建前上の理由です。

楽曲データの問題はlong-standing issueで、元々トラックヘッダーにUMPの件数を指定していたのですが、これでは読み飛ばしができないのでバイト数に変更したものです。楽曲のMETAイベント…に対応するSysex8データ…も間違っていて、テンポ指定が適切に反映できなかった問題があり、今回新たに追加したUMP to MIDI1 translatorの実装と合わせて、ようやくMIDI 1.0と同等のMidi2Playerの動作がreal-worldなやつとして確認できました。mugene-ngもこれを反映しているので、MMLからMIDI 2.0楽曲を引き続き生成できます。

この過程でAPIドキュメントジェネレーターのdokkaがビルドエラーを引き起こす問題にしばらく悩まされて、結局Kotlin Slackで開発者にいろいろ助けてもらって何とかしました。自分の中でdokkaの評価がだいぶうなぎのぼりに上がりました(!?)

AAP: プロトコル変更"V2"の再設計

8月にオーディオプラグインのパラメーター変更にMIDI 2.0 UMPを使ってプロトコルを再設計する話を書いたのですが、その手助けとなるはずだった上記のMidi2Playerをプラグイン試用のアプリに組み込もうとしたものの、ネイティブコード側の実装には使えず、デバッグに使うにも微妙なので、そっちの路線は一旦留め置いて、まずmidi2プロトコルで従来どおりの固定のメッセージを処理できるPoCを(今度はUMPで)作って、それをaap-lv2やaap-juceに適用していこうと考えました。

それで、最初は従来の「midi1ポートもmidi2ポートもサポートする」方向で実装を練っていたのですが、早晩「これは開発体験が悪い」と気づきました。これまではむしろ「MIDI2ポートはいらない。UMPをサポートしたければ、MIDI1ポートでMIDI-CIのSet New Protocolを送って切り替えればよい」と考えていたのですが、プラグイン開発者が「どっちで来るかわからないから両方サポートする」ことになるのは理不尽ですし、「MIDI2を処理できるプラグインは現状ほぼ無い」にしても、「パラメーター変更はMIDI2でしか受け付けられない」、「パラメーター変更をサポートしないプラグインはほぼ無い」、「MIDI2をMIDI1に(可能な範囲で)変換するのは難しくない」…という状況を鑑みて、むしろ「MIDI2ポートのみ存在すれば良い」という考えに至りました(ここまでで何日かかかった)。

これは相互運用性の観点ではドラスティックな変更になる(完全に別物になる)ので、AAPのプロトコルのバージョンを上げて対応すべきところですが、現行バージョン0.7.4までで "V2" として作り直しが進んでいるので、そのままV2プロトコルがこの設計を反映したものになるでしょう。

一方でAndroidAudioPluginMidiDeviceServiceがUMPを前提にするわけにはいかないので、MidiDeviceServiceからの入力を8月に実装したUMP Translatorを使ってUMPに変換し、逆にプラグイン実装側ではUMPからMIDI1にダウンコンバートするTranslatorの真面目な実装を新たに作って使ったり、プラグインプレビューのアプリではパラメーター変更入力をポートへの直接入力から新しいUMPのAssignable Controllersメッセージに変更したり(これでようやくCLAPやAtom Sequenceのような新しいイベント処理のスキームに切り替えられたといえます)、同じダウンコンバーターをaap-lv2の実装にも適用したり…といったことをやっていました。実のところまだ完了しておらず、LV2 portとAAP portのマッピングどうしよう…みたいなところで現在でも泥沼にハマっています。先にaap-juceで対応したほうが早かったかも…

11月の予定

11月は正直まだどうするか決めていません。「どうする」というのは具体的には例年11月にロンドンでやっているアレです…多分オンライン参加で妥協すると思います。去年より自由になったとはいえ、まだ一生後遺症が残り得るLong-Covidの問題が続いていることに変わりはなく、現地で自己アッピール()に使えるようなソフトウェアはまだ完成していない状態なので、今行くと「オーディオクラスタにしては妙にAndroid開発に詳しいやつ」というビミョい位置付けで固まってしまう気がします(正しいのですが)。まあ何か理由があれば現地参加する可能性もあります。

あと11月のうちに1回オンライン勉強会をやっておきたいですね。ネタはあるといえばあるのですが、状況次第です。何か準備できたら改めてTwitter(いつまで使うかわからないけど)などで告知します。

9月の活動記録(2022)

9月がいつの間にか過ぎていました(忘れてた)。9月はまだ半分以上台湾で過ごしていたんですよね。いつの間にかすっかり日本で落ち着いてしまいました。9月は表立った活動としては技術書を2冊書いたのが主だったところです。

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

9月には技術書典13がありました。経験上「最終日に新刊が出ていれば大丈夫間に合う」という見込みで8月には何を出すかも決めていなかったのですが、9月になってさすがに何も計画が立っていないのはヤバい…ということで、いくつか候補を考えましたが、まず着実に書けそうなものを具体的に書き進めることにしました。その1冊目がCLAPです。

techbookfest.org

書き終わってから、サークル名を「オーディオプラグイン研究所」に変更していたことを思い出したのですが(!?)、結果的には完全にサークル名にふさわしいトピックになったと思います。なお9/11のオフライン開催には参加していません。台湾にいたし。

一方で、内容についてはだいぶ荒削りで、特に拡張機能まわりはここに以前書いた文章をそのまま使いまわしている部分も多いです。今回の執筆には、(コピペに手を加えた部分がそれなりに多いので)1週間くらいしかかかっておらず、途中から「なんだか初日に間に合いそうだしこのまま出せるスピードで初版を出しちゃえ」というノリになっていた側面はあります。実際、技術書典13の初日(9/10)には間に合いました。

今回は同人誌を「書き上げた」というよりは、開発に必要・有用そうな自分の過去記事を断片的に繋いでまとめた感じです。本当は30ページもいけば十分だと思っていたのですが、それで結果的に50ページ弱ものボリュームになりました。同人誌を完成させる作業のうちそれなりに悩ませられる要素である表紙のデザインについては、過去にLV2開発者ガイドを書いていたやつを色違いにして出すというやっつけ仕事で済ませました(!) 成り立ちがそんな感じですし、同人誌のちまちました収益に期待するよりは広く読んでもらったほうがいいだろうと考えて(仮に1冊1000円で100冊売れても10万円の売上にしかならんですし、現実には10冊売れるかどうかというレベルだと思います。トピックがCLAPだぞ??)、今回は無料配布しています。(前作「MML to MIDI 2.0」の本も実用性はほぼゼロの自由研究レベルだったので500円にしましたが、それと比較したら1000円でもよかったかも…?)

ついでにGitHubリポジトリも公開していますが、これは改訂版の作業を完了できたら有料販売のために閉じるかもしれません。

Linux DTMガイドブック

CLAP開発者ガイドブックは無難に書けるものを書いた(新刊として出せる安牌のネタを拾った)だけでしたが、別口でLinuxユーザー向けに「LinuxでもDTMはそんなに難しくない…!」って言えるような、DTMソフトをあれこれ紹介する本を出したいとは前々から思っていたところでした。サウンド系のコミュニティでそんな話が出てきたので、じゃあ書けそうな機運があるうちに1冊作っておこうと思って、CLAP本を出して落ち着いた後で、カジュアルに書き始めました。今度こそ技術書典最終日(9/25)が最終締切のやつ…!

techbookfest.org

最初はほぼ「Linuxで動くソフト」の紹介本のつもりで、一方で自分が特にプラグイン側の説明を滔々と書けるほどのトラックメイカーではないので(一方でDAW開発者でもプラグイン開発者でもないし、自分の立ち位置が何なのか今でもよくわからない)、大半がテキスト数行の紹介 + スクショで済むだろうと雑に目論んでいました。が、実際にDAWの使用感を説明しようと思ったらALSAやJACKをどう制御するかを全く説明しなかったら、多分DAWを全く使えないし、Wine経由でWindows用のプラグインを使える技術についても言及しておくべきだし…という感じでyabridgeの紹介を書いたり、そのために動作検証したり…といった感じで、作業量と内容が膨らんでいったところがあり、最終的にはソフトの紹介は半分ちょっとくらいになったんじゃないかと思います(数えてない)。

あと、この本のベースになった構想は草稿レベルでいくつか貯まっていて、最終的にこの本にまとめる時間がなかったネタとして「サウンドフォントやサンプラーの類をいろいろ紹介していく」などがあります。これもCLAP本と同じような感じで「最終的な完成版は改めて出す」ことにして、今回は無料で配布しています。技術書典主催の🐑も「これが無料なのはありえん」って感じで紹介してくれていましたが、ちょっとサービスしすぎたかも…まあ、仮に売れたとしても利益はせいぜい5桁なので、それよりは読まれてくれたほうがいいです。完成版は運が良ければM3 2022秋に間に合うかもしれません。

ちなみにこちらの本は技術書典13では最後の4日間だけ配布というかたちになってしまったわけですが(予定通りではある)、CLAP本は150件くらい、Linux DTM本は200件くらいダウンロードされており、トピック的にLinuxのほうが面白かったようです。CLAPが面白いって言える音楽技術関係者やプログラマーは多くないはずなので、これはまあ予想されてしかるべきところですね。

MidiPlayerのMIDI 2.0サポート用のMIDI-CI実装

8月はAAPでプラグインのサンプルクライアントを書き換えたいという話を書きましたが、そのためには現状の決め打ちのMIDI入力・オーディオ入力を渡すだけでなく任意のMIDIシーケンスを演奏できるようにしたほうが後々テストが楽になります。そのため、ktmidiのMidiPlayerを取り込んで演奏できるようにしたいと考えました。

ただ、ktmidiの特にMidi2Playerは、実際に演奏できるMIDI出力デバイスが存在しないため、実装の動作確認が何も出来ていない状態でした。Midi2Playerがきちんと動作するか確認するためには、デスクトップではMIDI2のUMPストリームからMIDI1のバイトストリームに変換して(8月にここで書いたやつです)、それを直接MidiOutputに出力するモードが必要で、その辺を実装したりしていました。AndroidではAAPのMidiDeviceServiceに垂れ流せば済むのですが、デスクトップで動作確認してからのほうが楽です。

Midi2Playerの動作確認に有用なのはMIDIプレイヤーアプリの実装なのですが、実はmanaged-midiから移行できていない最後のひとつがMIDIプレイヤーxmdspで、これをMidiPlayer基盤の動作確認が必要なレベルで作り始めるのはちとチャレンジングすぎます。そういうわけでコンソールプレイヤーのサンプルを追加したり、そのためにKotlin Multiplatformのプロジェクト構成を見直したり、あとMaven Centralへのパッケージ発行で罠にハマって(新バージョンのreleaseが失敗してもこちらに何も報告されない問題とか…)問い合わせで数日使ったり、あとサンプルデータを生成するためにmugene-ng(MMLコンパイラ)のリポジトリをビルドし直そうとして、むしろビルドを通すためのリグレッションに追われたりなど、地味な作業が進んでいるところです。

ktmidiのこれまでのMIDI2サポートは、基本的にMIDI-CIで調整された後のストリームの送受信を前提としていたり、あるいはMIDI-CIのSet New Protocolメッセージを盲目的に受信して切り替えられるようにしていましたが、本来ならそこにMIDI-CIにおけるsource/destination MUIDのチェックやTest New Protocolのやり取りが必要なわけで、その辺も実装しておかないと他のMIDI 2.0システムとの相互運用は難しいところでしょう。これは先月たまたま発見したのですが、ALSAでもMIDI 2.0サポートに目が向き始めている様子があるので、MIDI-CIも少し整備しておきたいところです。そういうわけで、MIDI-CIのプロトコルに基づく状態管理に使えるクラスを作ったりしていました。ただ、まだプリミティブな実装になっていて、同じMIDI-CIを実装しているAppleのCoreMIDIのAPIと比べると使いにくいです。HTTP/3をリクエスト/レスポンス別々にユーザーが扱わなければならないような状態なので、たぶんKotlinのsuspendを使った非同期APIにすると思います。

作っていて気付いたのですが、MIDI 2.0における3つのPと言われているやつのひとつProperty Exchangeは、最新のCoreMIDIでもサポートされていませんでした。JSONを使ってプロパティをやり取りするやつで、これをサポートするにはJSONリアライザーを統合しなければならないわけですが、その辺でどういうAPIにするのが適切なのか、たぶんAppleも決めあぐねているところがあるのでしょう。わたしにもいいアイディアが無いというか現状そこまで考えるメリットがなさすぎるので(この仕様もしかしたらGeneral MIDI 2みたいに使われずに死ぬんじゃない?くらいの印象)、とりあえず様子見です。

10月の予定

9月末からずっとゲームばかりやっていて忙しくて他のことに手を付けていられず、今週もDroidKaigiオフラインを眺めに行く予定なので、まだ見通しが立たないところですが、9月の積み残しのMidi2Playerを片付けて、それをもとに8月の積み残しのAndroidプラグインクライアントを片付けて、あと↑の同人誌の穴埋めが出来たらM3 2022秋に少数部でも紙版とか持っていきたいところではあります。最近の新刊はずっと電子版ばかりでカタログ上の存在しかなかったので、ちょっとグッズを持っていきたい気持ちがあるんですよね。個人的にはもう紙版と電子版があったら後者しか買っていないのですが。まあそんな感じです。

8月の開発記録 (2022)

ちょっとだけ台湾生活のようす

台湾で日常生活を送りながら日暮しPCに向かいて過ごしています。日本も医療崩壊がひどいことになっていますが、安全に過ごせる度合いは台湾とどっこいどっこいなところがあるなあと感じています。公共交通機関でマスクを外す人間は皆無に近いので(これはきちんと規制があるためでしょう)、日本とは違って安全にMRTやバスで移動できています(日本では2ヶ月に1回くらいしか載っていなかった)。MRTはもともと飲食禁止で、日本みたいにホームに自販機も売店も設置されていないですし。

最初の1ヶ月はそこそこ他人と会うことがあったのですが、最近は外出する理由もなく、ほとんどYouBike(貸自転車)しか使っていません。いつの間にかYouBike 2.0とやらができて、大抵500mも歩かないうちにバイクスポットが見つかるくらい増えているので、ちょっとGoogle Mapで茶店(作業場)を探して空いていたら(空いているかどうかはざっくりGoogle Mapsアプリでわかる)行ってみるか…みたいなことは簡単に出来ています。昔は短時間なら完全無料でしたが、今もほぼ毎回5NTしか払ってないしほぼ無料です。昔は自転車を買っていましたが今は買わなくてもいいな…となっています。町中でもYouBike以外の自転車をだいぶ見なくなった気がします。

安全なのは移動くらいで、食・住は日本のほうが安全だなあと思います。もともと日本人は家庭持ち以外はたいがい一人暮らしだと思いますが、こっちではたいがい複数人でルームシェアリング(正確にはflat sharing)で暮らしていますし。わたしも7月は本当はそうなる一人暮らしになる予定だったのですが、airbnbのホストとのやり取りで完全に騙されて、いざ行ってみたら他の住人が…みたいなことになっていて、しかも初日から隣の部屋から止まらない咳が聞こえてくる…みたいな恐怖体験で、もう自室に入るたびにアルコール消毒するストレスフルな生活でした。家族や自分の同意のある知人ならともかく、どこで何をしているかもわからんどこの馬の骨とも知れぬ輩から伝染されるのはさすがに…。今は完全に自分1人しかいない部屋を借りているのですが、日本の自宅並みに安全です。ただ当然ながら家賃が高くて、今は日本並みに払っています。これはサステナブルではない…

食が安全ではないと思うのは、特に国レベルで圧倒的に自炊に向いていないためです。キッチンのない部屋も多いし(たとえば591で部屋探しの検索条件に「可開伙」 = 料理可能 を追加するとそこそこ絞り込まれます)、食材は日本以上に高いものも多いし、外食したほうが総合的に安いと言っていいでしょう。そんなわけで多くの人にとっては外食が基本になりますし、飲食店は混んでいます。持ち帰りが簡単にできるのはいいところですが、料理を作っている側も日本に比べたら感染対策は緩いですね。店員は休憩中に店内でマスクを外して過ごしていることが多く、何なら調理中も…みたいな様子を、日本より頻繁に見かけます。弁当類は東京のコンビニ同様に台北のコンビニでも買えるので、それで毎日過ごせるというのであれば、こっちは十分安全でしょう。

引越し前は住居付近に便當の店がたくさんあって毎日ローテーションで健康的なものを買えたのですが(共用のキッチンなんて使う気になれなかったし)、今はほとんどなくて代わりに夜市が近くにあるので、たまに油断してその辺で買って帰るか、数少ない便當の店まで足を延ばすか、ランチだけ外でちょっとお高いブランチの類(大抵200NT超え)を食うか、チェーン店(バーガーキングとか)で済ませるか、自宅にある大同電鍋やIHホットプレートを使ってできることだけやる(ただし調味料が何もない)…みたいな生活です。日本では最近ほぼ毎日自宅で食事を作っていたので、天地の差です。

AAP: port-config拡張(未了)と自動定義ポート

6月頃から始めたAAPクライアントの再構築(ネイティブコード中心化)を皮切りに、state拡張をextension化して、7月にはAIDLによる初期化プロセスに破壊的変更を加えましたが、8月にもまずこれを応用したプラグインインスタンスメタデータによらないポートの生成を実現しました。これはport-config拡張機能として実装される予定ですが、現状では大したことをやっておらず、この拡張機能が使われていたら<ports>要素をプラグインメタデータres/xml/aap_metadata.xmlに書かなくてもよろしくやってくれる、というものです。

本来はこれでオーディオポート生成も自動調整できることが期待されるのですが、ひとまずはこれでポートの定義を自動生成中心に置き換えることを目論んでいます。今後「ポートが自動生成されるように作成されたプラグインのであれば、新しいポート設計にも自動で対応する」ような変更を加えやすくなりますし(現状ではポートをハードコードすることになるので、コード上の後方互換性は間違いなく維持できませんが)。

port-configに関連する話でいうと、これ自体はオーディオバス(ポート)設定の自動調整を主眼においたものなのですが、プラグインのオーディオ処理の中心process()やその前段階としてのprepare()に渡すオーディオバッファポインターの集合体AndroidAudioPluginBuffer構造体の構成についても、再設計すべきか否か常日頃から悩んでいて、結論が出ません。CLAPのようにdata32data64を分けたりports x channelsの論理単位を分ける設計が魅力的なので模倣するのも良いと思っています。特にportごとにlatencyを設定できるのが美しいですね。ただこのノリでstrongly-typed APIを導入してコロコロ破壊的に変更し始めると多分癖になって良くない…という警戒心があります。これまでAAPは2年近くプラグインAPIレベルで破壊的変更を加えてこなかったので。6月からどんどん崩していますが(今だけ…というつもり)。

AAP UMP based parameters

今月はこの流れでいよいよパラメーター変更を1つのMIDIポートからすべて処理する仕組みを実装しました。LV2でいえばAtomポート、CLAPでいえばclap_process_tevents_inevents_outに相当するやつです。これでOdin2みたいに数百個のパラメーターがあるやつが数百個のパラメーターポートを生成してそれぞれにバッファが付く、みたいなことをする必要が原理的には不要になります。

原理的に…というのは、実のところこれがまだaap-juceやaap-lv2には適用されていないというのと、メタデータによらないポート生成との食い合わせが悪く、プラグインプレビュー( = テスト)のActivityでMIDIシーケンスを再生できていないためです。現状、MIDIシーケンスはMIDI1で生成されていて、MIDI1イベントとして送信されていますが、新しいプラグインパラメーター変更はMIDI2のAssignable Controllerを利用しており(bank/indexでパラメーター番号、値はfloatのビットパターンを32bit intとしてそのまま反映)、MIDI1に変換すると情報落ちが発生します。floatの列挙値で不一致が発生することを考えるとこれは容認できません。

MIDI1ポートとMIDI2ポート(あるいはパラメーター変更などに特化したポート)を並立させることも考えられますが、パラメーター変更イベントはもともとtimestampedでなければならないことを考えると、MIDI1ポートと並立していることは大きなデメリットになります。これはCLAPのWebサイトにあるMIDI & Event Queuesというページにある解説が詳しいですが、特にVST3のパラメーター変更とノートを別々のイベントキューに分岐するやり方ではイベントの処理順序が維持されないという問題があります。これを回避するためにsample accurateだったはずのイベントを1サンプルずつずらすみたいなdirty hackが用いられるわけです。

そういうわけで、MIDIサポートは、ホスト-プラグイン間のプロトコルとしては全て1本のMIDI2ポートでやらざるを得ない気がしています(プラグインAPIとしてはUMPのみ用いて、MIDI1への変換はプラグイン開発サポートライブラリみたいなものを作って対応する)。現行のMIDI1サポートもSMFのtimecodeをそのまま送るようなやっつけ仕様で、プラグイン側が複数のtimecode decoderを持つのは負担にしかならないはずですし。

最近CLAPのコミュニティでやり取りしていて知ったのですが、CLAPではMIDIなどのイベントポートを複数使えないわけではないんですね。ただホストとプラグインを繋ぐ論理ポートは1本でなければならず、その中で流れる各種イベントにはこれとは別の概念であるportが存在するようです。たとえば、Kontaktのport 1〜port 8に、あるいはSC-88ProのPort AとPort Bに(めっさ古い例えだ…)、同時にイベントを送信できるわけです。追記: イベント構造体にport_indexとして指定するスタイルです(clap_plugin_note_ports_t.get()で渡すindexに対応するはず)。

プラグインパラメーターの変更とプラグインからの変更通知に関してはUIが絡む場合も考えなければならず、特にAAPの設計ではホストからの変更(ホストプロセスUI)の他に、プラグイン本体からの変更(インプロセスUI)と、「プラグインUIアプリ」からの変更()純粋なアウトプロセスUI?)についても検討していたので、設計をいろいろ検討…というか再検討…する必要がありました。それで「オーディオプラグインのパラメーター変更と通知に関する覚書」を書いたりもしていました。

MIDI 2.0 UMP仕様書日本語版(未遂)

7月にCOSCUP 2022でMIDI 2.0のセッションをもったわけですが、MIDI 2.0の情報ってまだあまり無いんですよね。わたしは最大の癌はMMAの「ログインしないと仕様書がダウンロードできない」時代遅れの組織であることが原因だと思っていますが、共同使用策定者であるAMEIのサイトに行くとログインなしでダウンロードできます…という話をそのセッションでもしてきました。

それで「そういえば割と長いこと待っているけど日本語仕様書出てこないな…?」「AMEIにはMIDI 2.0の日本語情報ページがあるんだけど半年くらい更新されていないな…?」となっていたのを思い出して、「UMP仕様書とか短いから2, 3日あれば本体(Appendix以外)は訳せるだろうし、適当に作ってみるか」と思って、8月のはじめの頃に3日くらいで結局全部(Appendixも含めて)全部翻訳しました。

翻訳できたはいいのですが、これどうしようかな…勝手に公開というわけにもいかんだろうし、とりあえずAMEIのMIDI規格委員会のページにはこうあるのを見てとりました。

私たちは、共にMIDI関連の楽器やプログラム、ガジェットの開発/研究を行っていただける仲間を求めています。音楽に関わるお仕事をされてている方々、MIDIに興味をお持ちの学生の方で「個人情報の取り扱いについて」に同意いただけましたら、お名前、メールメールアドレスを記入の上、ご意見、ご要望をお送り下さい。尚、コメント欄は空欄でも結構です。MIDI関連のイベント等について情報を発信すると共に、今後の活動に役立ててまいります。

訳文はAMEIの訳語統一方針とか何も知らない状態で作ってあるので(調べようと思えばMIDI 1.0仕様書から推論できるといえばできますが)、とりあえず仮訳とか参考訳とか、もし何も無ければベースに使えなくはないだろうと思ってprivate gistにmarkdownを貼って送りつけてみました。3週間後に返事が返ってきて(夏季休暇の時期だし「定例会があれば議題に取り上げてもらえるだろう」くらいの推測でひと月は完全に反応待ちのつもりだったので、適切に対応してもらえたと理解しています)、翻訳作業そのものがMMAと示し合わせて進んでいるような状態なので静観してほしい、という内容だったので、訳文は非公開のまま成果物が出てくるのを待とうと思います。

MIDI 1.0 to 2.0 Translator in cmidi2 and ktmidi

仕様書を翻訳していて気付いたのですが、UMPのMIDI 1.0 from/to MIDI 2.0の変換を規定するDefault Translationに対応する実装がまだktmidiでもcmidi2でも出来ていなかったので、前述のAAPポートのイベント変換まわりを実装するときにも必要になるなと思って、とりあえず1.0から2.0への変換機能を追加しました。2.0のUMPを1.0のバイトコードに変換する機能は、以前から部分的に実装してあったので、そこはそのままです(sysexだけ複雑になるのでやっていなかった)。cmidi2はsingle header, inline functionのみの実装を想定していたので、この辺までやってしまうとだんだんktmidi並に複雑になってしまうのでやりたくない…と思っているのですが、まあ変換くらいならいいでしょう。

この辺をがっつりやるのであれば、本来ならMIDI 1.0メッセージからMIDI 2.0 UMPへの変換だけでなく、MIDI 1.0 UMPからMIDI 2.0 UMPへの変換もできたほうがよいのでしょうが、MIDI 1.0 UMPのストリームを使う場面がまだ自分のユースケースで出てこないので、後で考えよう…となっています。JUCEだとこの辺をByteStreamInputHandlerU32InputHandlerで分けている感じで、これくらいなら複雑になってもいいか…と思わなくはないですが、なるべくシンプルで直感的なAPIにしたいところではあります。(まあJUCEのUMPのAPIがわかりにくいのは、この場合は過剰な共通化ではなく単なるドキュメント不足だと思いますが。)

9月の予定(のつもり)

パラメーターポートまわりの次世代実装のProof of Concept実装が使えそうだとわかったので、現在はPluginPreview(プラグインのテストプレビュー実装)とそのUI (Activity) まわりを改造しています。現状でもlv2applyの入力を固定したやつみたいな感じですが、もう少しMIDI playerに近いものを作りたいところです。

あと9月には技術書典13があって10月にはM3 2022秋もあるので(出展予定)、新刊を用意しないと…(技術書典は最終日が締切日くらいに思っているところがある) …というわけでコーディングは少なめになるかもしれません。

オーディオプラグインのパラメーター変更と通知に関する覚書

最近自分のプラグインフレームワークプラグインパラメーターのサポートを設計しているのだけど(パラメーターのサポート無かったの!?と思われそうだけど、これまでは古いLV2と同じでパラメーター毎に「ポート」を用意するスタイルで実現していたのを再設計している)、特に変更通知まわりは割と面倒なところがあるなと思ったのでちょっとまとめておきたい。

パラメーター操作の分類

オーディオプラグインのパラメーターを操作する主体は2つ、あるいは3つある。

(1) ホスト(DAW)のシーケンサープラグインprocess()とかrun()とかに相当するオーディオ処理関数が呼び出されたときに、MIDI…に類するイベント…としてパラメーター設定命令が(理想をいえばタイムスタンプ付きでsample accurateに)そのprocess()あるいはrun()のサイクルの分をまとめて渡される。データの元がMIDIマッピングされたMIDIシーケンスであろうと、オートメーションであろうと、APIとしてはこれらの関数に行き着く。これらの関数はMIDI…に類するイベント…のアウトプットも取得でき、ホストはこれを現在処理中のフィルターチェインにある次のプラグインに渡すことができる(これはおそらくは一度MIDIに類する抽象イベントに変換することになる…というのは、たとえば、VST3のチェインの次にAUが繋がっていたらVST3イベントそのものを渡すことはできないからだ)。

(2) プラグインのUI操作。一般的に、GUIはリアルタイムで処理できないので、リアルタイムのオーディオ処理ループからは切り離されていて、したがってスレッドコンテキストも分離しているので、GUI上で生じたパラメーター変更のイベントは、いったんプラグインの内部でキューに溜め込まれて、次のprocess()run()の中で(理想をいえばタイムスタンプ付きでsample accurateに)処理される。ホスト側でプラグインのパラメーターの状態(現在値)を把握しておきたいことを考えると、UIによるパラメーター変更はホスト側に通知されるのが理想的で、したがってプラグインがホストに通知イベントを送信する仕組みが必要になる。

(3) オーディオ処理そのもの。これはパラメーター変更の入力ではなく出力 = 通知の話になるが、パラメーターには他のプラグインパラメーターの変更によって計算される性質のものがあり得る。これらはオーディオ処理に直接影響するとは言い難い(その関連パラメーターの変更は上記のいずれかですでに渡されている)が、これによって生じたパラメーターの再計算は、その変更を通知されるホストにとっては、プラグイン上にに生じたパラメーター変更と同義だ。

ただ、これら(1)〜(3)の処理モデルは、リアルタイムのオーディオ処理ループが稼働していることを前提としている。もしオーディオループが回っていなければ、UIによる変更を反映する次のprocess()run()は回ってこないので、それに伴って生じるべき変更通知はずっとやってこない。オーディオループが回っていない状態でもイベント通知が滞りなく届くようにするには、オーディオ処理が回っていない状態でもパラメーター変更が処理される仕組みが必要だ。そうしないとプラグインGUIで設定したパラメーターの値が、ホストで必要な箇所に反映されなくなってしまう。

UIイベントをプラグインに蓄積させるAPIプラグインにイベントを処理させるAPI

上記のうち(2)に関しては、一般的にはGUIプラグインのプロセスの中で動作しているので、GUIが発生させるイベントは同じプラグインの内部でイベントキューを保持しておいて、オーディオ処理が走るたびに内部で(1)のイベントストリームとマージして処理することになると考えられる。この場合、外部にAPIとして表出させることはない。

一方で、LV2 UIのように、プラグイン規格そのものがGUIをきちんと分離して、外部から入力をデータとして渡す仕組み担っている場合、あるいはその他のプラグインでもUIをきちんと分離する仕組みにしている、あるいはプラグイン本体とプロセス分離せざるを得ない場合(AAPがUIをホスト側で生成するとそうなる)、イベントキューイングのためのAPIプラグイン側から表出している必要がある。LV2 UI拡張のLV2UI_Port_Subscribe LV2UI_Write_Functionはこれを体現したAPIと考えてよいだろう。このAPIはイベントのキューイングのためのAPIであって、イベントを処理するためのAPIではない。

一方で、前述の通り、リアルタイムのオーディオ処理が停止している状態でもパラメーター変更が処理される仕組みが必要で、これはprocess()run()と同様に、イベント出力を生成する必要がある。このAPIの具体例と言えるのがCLAPのparams拡張に含まれるclap_plugin_params_tに含まれるflush()だ。LV2のGUIに変更を反映させるための仕組みとは性格が異なる。(このflush()の位置付けは実のところ曖昧だというissueも立っているが、本質的には書き方の問題であって仕様の設計がぼやけているということではないはずだ。)

別ベクトルの解決策として、そもそもprocess()run()をリアルタイム処理中以外でも呼び出せるようにすれば良いのではないか、そうすればイベント処理の実装はこれらのオーディオ処理関数で一元化できるのではないか、とも考えられるが、それはそれできちんと入力を処理できないプラグイン(すなわちオフラインレンダリングに対応できないプラグイン)が存在する可能性があって、この路線を押し通すには事前に調整されて然るべきということになる(たとえばAAPではまだ仕様が策定中なのでそういう前提を作れる)。

これが多分難しいのは、DSPの処理がリアルタイム前提で時間計算するようになっていたら、オフラインレンダリングには対応できないということと、DSPは特定のプラグインフォーマットから独立して実装されていることが多く、既存のDSPコードがオフラインレンダリングに対応できていない場合、process()run()がオフラインレンダリング対応を前提にしていると、そのDSPそのものが利用不可能になってしまうかもしれない、とかもしれない、といったことが考えられる。

非同期イベントとしての通知出力と、同期イベントとしての通知出力

リアルタイムのオーディオ処理のバックグラウンドで動作するUIを経由したパラメーター変更は、一般的には非同期イベントとなる。これに対して、オーディオ処理を経てのパラメーター変更通知が非同期となるべきかどうかは議論の余地がある。一般的には、UIコードがパラメーター変更のAPIを呼び出しても、前述の通りいったんキューイングされるだけだ。一般的にはキューイングするだけの処理の中で、パラメーター変更通知に値するプラグインの処理が発生するとは考えられず、一般的にはオーディオ処理の「結果」として通知に値するイベントが生成されるはずだ。キューイングの時点で通知イベントが生成されることを期待するようなAPIにしていると、何がしたいのかよくわからないぼやけたAPIになってしまう。

前述のclap_plugin_params_tに含まれるflush()を取り上げると、この関数はリアルタイムオーディオ処理が走っていないときに呼び出されてclap_output_events_tにイベント出力を格納することが想定されており、これは同期的なAPIだ。逆に、LV2 UI拡張にあるLV2UI_Write_Functionport_eventのようなAPIでは、その関数呼び出しから何らかの通知イベントが生成されることは期待されていない非同期的なAPIだ。そもそもGUIからプラグインを経ずにイベントを生成することは推奨されない。

CLAPのparams拡張のような同期的にイベント処理を行う方式については、もうひとつ言及しておくことがある。CLAPの場合、パラメーターの変更はプラグイン側で発生するイベントだが、プラグイン側からホスト側に対してイベント出力を直接送信する仕組みにはなっていない。その代わり、プラグインはホストから事前に渡されているホスト側のコンテキストであるclap_host_params_tのメンバーrequest_flush()を呼び出して、あくまでホスト側からclap_plugin_params_tflush()を呼び出すことを期待している。こうすることで、通常のオーディオ処理とイベント通知による処理のモデル(特にスレッディングモデル)を統一できる。オーディオ処理がリアルタイムで動作している状態であれば、request_flush()を呼び出さなくても、次のサイクルで通知イベントを出力するだけでよい。

パラメーター拡張APIは誰のためのものか (8/31追記)

前回はパラメーター変更と通知のAPIの類型について解説したが、特にLV2とCLAPでの位置付けがだいぶ異なることが見て取れた。なぜそのような違いが生じるのかを、少し踏み込んで考えたい。

一番根本的な違いは、それらのAPIを「誰が」使うのかを明確にすると見えてくるだろう。

  • CLAPの場合、拡張プラグインAPIを呼び出すのは常にホストであり、拡張ホストAPIを呼び出すのは常にプラグインだ(と自分は理解している)。パラメーターのflush()はホストが呼び出すものだし、プラグイン側はプラグインAPIを呼び出すことはない。プラグインGUIコードがプラグインDSPコードで利用するパラメーターを設定するために使用するAPIは存在しない。それはCLAPのスタンスでいえば、プラグイン本体が内部的に解決(提供・実装)すべきものであって、プラグインとホストのインタラクションに影響しないなら規格化されない。オーディオプラグインデザインパターンのようなものは、CLAP全体として標準化されない傾向があるように見える。
  • LV2の場合、LV2 UIの実装はLV2 DSPの実装とは別の共有ライブラリになり、パラメーターの変更と通知は接続されたポート経由で行う。LV2 UIの実装とLV2 DSPの間でインターフェースすなわちAPIが必要になる。

ちなみに、この一連の覚書でCLAPに関してparams拡張の話だけしてgui拡張の話を一切しないのは、CLAPのgui拡張は本当にGUIを扱っているだけで、GUIから発生するであろうパラメーターの変更イベントに関しては一切関知していないからだ。この辺CLAPの切り分けは美しくできている。最初に見たとき「拡張機能が"UI"ではなく"GUI"なのはセンス無いな…」と思ってしまったものだけど、この拡張は本当にGUIのためのものであって、たとえばUIとDSPの分離などを目的としてはいない。