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だしいつまであるのかわからないサービスなので、いずれどこかに移転するかもしれません)。まとめきれなかったものもいくつかあるので、ちょいちょい追加するかもしれません。

勉強会参加/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コンパイラを中心とした制作環境からも使えるようにしていきたいと思っています。がそれはまた別の話かな。

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

Music Tech Meetup #1 (Night)を開催します

ここ1年くらいずっと「音楽ソフトウェア系の開発者とユーザーの両方をターゲットにした音楽技術の勉強会をやりたい〜」などと言っていたのですが、ようやく時は満ちた…! ということで来月2/6に開催します。

connpass.com

今日の正午くらいに募集開始したのですが、マニヤックな分野の勉強会にもかかわらず既に20人くらい申し込みいただいていてありがたいかぎりです。

経緯

1年も前から言っていたというのは割と本当で、去年もちょうど年末くらいから似たようなことを言っていたのでした。2年続けてロンドンのADCに参加していたので、自分たちはその知見を持ち帰って共有できるし、音楽関係のソフトウェアやSDK(JUCEとかVSTとか)などで面白いセッショントークが出来る人が何人かいたので、開催したらきっと面白いことになると思っていました。

去年はそうこうしているうちにmusic.dev勉強会が発表されたので、とりあえず自分で開催しなくてもいいや…!となったのでした。music.devは一般参加が10人しか入れなかったこと以外は理想的で、バリエーションに飛んだトークが集まって楽しく勉強になる集まりでした。

去年はさらにJUCEもくもく会みたいなことをもう少し音楽技術全般をターゲットにして開催してもいいかなと思っていたら、ちょうどMake Music Things!というまさに音楽全般もくもく会のようなものが始まったので、これまた自分で運営しなくてもいい…!となって、2019年は周りの皆さんの活動におんぶにだっこ状態でのうのうとやってこられたわけです(!)

今回は、セッションを伴う勉強会みたいなことがしばらくなかったのと、music.devは主催のケロさんが2月は東京に来られる準備で忙しそうで#2がかぶることはなさそうだったので、ついに自分で開催しようとなったわけです。

せっかくなので数十人規模で開催したいなーと思ったのですが、Make Music Things!の会場は最大10人ちょいになってしまうので、主催の小出さんと開催は一緒にやっていただいて、会場はどこかを借りてやろうとなりました。ちょうどその頃にLAPRAS社の伊藤さん(まだお会いしたことはないです)のツイートがTLに流れてきまして、

これは会場の大きさ的にも雰囲気としても割とピッタリで天佑なのでは…!?となって、ご相談してお願いすることになりました。勉強会は、いつも会場探しが一番しんどいので、ここを簡単にクリアできたのは本当にありがたいです。

勉強会の方針

今回の勉強会は、ある程度自分たちが知っている人に来てもらえる一方で、知らない人だらけだけど日頃からあるような勉強会ではない(けど興味ある)からちょっと顔を出してみよう、という方がそれなりに来られると思っています。人数もどうがんばっても50人くらいなので、なるべく会場でカジュアルに誰でも話せるようなふいんきなぜか変換できない)にしたいと思っています。

もうひとつ、IT系技術の勉強会はプログラミング言語やプラットフォーム、開発ツールなどに関連するものが多く、ほとんどが開発者「だけ」をターゲットにしているのですが、わたしみたいに開発フレームワークを提供する立場にいた人間としては、参加する「開発者」にも「ツールの開発者側」と「ツールのユーザー側」がいると思っています。両方がいて勉強会が成り立っているわけですね。これは音楽系のソフトウェアの場合でも当てはまるはずなので、この勉強会では技術やツールの開発者と同様にユーザーとしてのミュージシャンやDTMerの人たちをターゲットにする、音楽を制作するための技術や知見を広めるセッションもあります*1。ふだんこの方面のセッションを聴けることはそうそう無いと思うので、割と貴重な場になると思います。

今回はセッションがありませんが、音楽技術のとくにゲーム開発の分野では、音楽の制作と同時にサウンドドライバーを制作したり、古くはMMLのようなプログラム類似の言語で作曲したでしょうし(今でも商業でやっている人がいますね)、今でもスクリプトサウンドオブジェクトを制御することもあるかと思います。あるいはミドルウェアを導入したりといった知見も広く求められることがあり、この意味ではサウンドエンジニアも開発者との境界線が割と曖昧だと思います。この分野だとCRI ADX2などの勉強会も別途さかんですが、知見を共有できる人がいたらぜひ来てほしいと思っています。あとはVRなどで3Dサウンド処理もホットなトピックですよね。

この集まりは現状スポンサーがいるわけでもないので、夜の勉強会ですが軽食を用意するのもしんどいです…というのを逆手に取って、参加される方にお気に入りの食べ物などを持ち寄っていただくことにしました。開始と同時に食べ物をつまみながら話を聞いていただこうと思っています。発表が終わるのを待ち続けてフードが冷めちゃったら悲しいので、食べておいしい時に食べましょう。もちろん手ぶらでも大丈夫です。全員がたくさん食べ物を持ってきたら余るでしょうし…!

今回の勉強会では、可能な限り多くの人に抵抗感なく気配りしてもらえるようなCode of Conductを考えて設計してあります。困ったことがあったら相談してください。

セッション紹介

Audio Developers Conference 2019で見てきたオーディオ開発最新動向

わたしが勉強会を開こうと思った発端が「ADCのセッションとかで発表されているような話を紹介して、詳しく知っていそうな人とかに参加してもらってわいわい話したい」みたいな感じなので、同じくADCに2年連続で参加しているJUCE Japanのしおざわさん @COx2 と2人で漫談することになりました。(ってタイトルをわたしが勝手に付けてしまっているのですが、このへんは諸々調整する予定です…!)

ADCにはさまざまなプレイヤーが参加しています。RolandYAMAHAKORGなどの楽器メーカー、Native Instrumentsみたいなオーディオプラグインベンダー、ROLIやSteinbergのようなオーディオプラグインフレームワークベンダー、AppleGoogleあるいはELKのようなプラットフォームベンダー、音楽と機械学習のソリューションを提供するベンチャー企業など、バックグラウンドはさまざまです。2日間で行われたセッションのほとんどはyoutubeで公開されています(1日目2日目)。

現地でも見ましたがフルパワーで見ていたわけではないので、わたしはいろいろチェックして臨むつもりです。

エフェクターを作ろう

Make Music Things!を運営している小出さんによるセッションです。小出さんには他のセッション予定とかぶらなそうなトピックの狭間で決めていただいた感じなのですが(!)、結果的にハードウェア寄りでエフェクターを作るための回路設計についてお話しいただく予定です。お仕事がその方面みたいです。うらやましい…!*2

Make Music Things!の集まりは、ソフトウェアだけしかやらないわたしみたいな参加者も何人かいるし、Maker Faireとかに出展するような物を制作している参加者も何人かいるという感じで、独特のバランスでまとまっています。1人で黙々と作業することもできますし、他の人と相談やら雑談やらしながら過ごす人もいます。割と物理層の話で盛り上がったりすることもあります。

概ね毎月やっていて1/25にも第6回があるので、この辺の開発に携わっている人とわいわいやりたい人がいたらぜひ来てみてください。

変拍子兄さん & 腕毛の人 の変態音楽理論(タイトル未定)

タイトルはまだ未定なのですが、変態音楽理論アカデミーというnoteのページを公開されている変拍子兄さん @OrangeTheKeyqa と、一緒に音楽活動されている腕毛の人 @stereonoah のお二人に、独自に構築されている音楽理論に関する発表をお願いしました(もしかしたらしゃべるのは2人ではないかも)。内容はほぼ白紙でお任せしてあるのですが、たぶん微分音を活かした作曲の方法論などが内容となるのではないかと思います。

微分音というのは説明なしでは「??」となるかと思います…。ふだんわれわれが音楽を打ち込む時に使う音階は12平均律(一般的には12も抜いて単に平均律)とよばれ、周波数の比率で1オクターブの音をド〜シの12音に分割構成しています。しかし世の中には他に純正律と呼ばれる、特定の和音だけがよりきれいに響くような周波数で構成された音階などもあります。さらにはそもそも1オクターブを12で割る必要だって無いはずでは…?と考えると、1オクターブを24分割したり31分割(!?)したりといったことも出来るわけです。MIDIメッセージはどう考えても12音階なわけで、どうやってこれを乗り越えて作曲するのか…? など、いろいろ刺激的な話が聴けるかと思います(白紙委任ですが!)

ちなみに、このおふたりとは昨年の同人音楽イベントでサークルスペースが隣同士だっただけです(ゆ〜くれというこれらの音楽理論などによる音楽を発表しているこのお二人のユニットです)。普段から勉強会でセッションやっているようには見えなかったのですが、音楽理論系のセッションをお願いする場としてはピッタリだと思ったので、おっかなびっくりお願いして受けていただきました(ありがたい〜)。

ライトニングトーク

今回もう1つセッションがあるかもしれませんが、無いかもしれません。その代わりに、というわけではありませんが、LTタイムを設けてあります。おそらく自分が開発したり使っていたりするソフトウェアや楽器、あるいは音楽制作手法などについて喋りたいという方が少なからずいるのではないでしょうか。と思って募集に含めたのですが、既に申し込みいただいていて、「計画通り…!」(画像略)という顔をしています。まだ枠があるのでやりたい方はぜひ申し込んでください。

ちなみに、勉強会参加者枠を別にすることも考えたのですが、LTで何かしゃべれることあるかな…?って悩んで参加枠を迷っているうちに人数がいっぱいになって悲しい思いをすることが稀によくあるので、LT申込み枠は分けずにコメントで書いていただくという設計にしました。いろいろ考えてます…!

Music Tech Meetup #2 (Day) もやりたい

今回は1回目ということで、お試しという感じで平日の夜開催ということにしました。会場も平日限定ですし(そういうところが多いはずです)、まずは平日に開催して感触を得ておこうという腹づもりです。

まだ第1回を公開したばかりなのですが、他にもあの人やこの人にもしゃべってもらいたい…!みたいなリストを(勝手に)かかえていて、「でもこの話なら50分とかでフルタイムでお願いしたい…」みたいな状態になっているので、そのうち土日開催してフルタイムセッションを容赦なく詰め込むのも開催したいと思っています。

そういうわけで今後の展望も意識の隅におきつつ、まずは2/6の勉強会をうまいこと開催したいと思っていますので、この分野に興味のある方は(まだあまり機会が無いと思うので)ぜひ参加してください。

*1:昨年のmusic.devもそうなっていました。

*2:いや無職はひとの仕事を羨ましがらないぞ…!!

さいきょうのCode of Conductを求めて

こんど新しく勉強会を立ち上げるのだけど、2020年に新規コミュニティを立ち上げるのは、自分が今までやってきたような勉強会(Xamarin.Forms読書会とかmono meetingとか)を作ってきたようなのとはだいぶ状況が違うようだ。特に最近よく話題になるのはCommunity Code of Conduct(CCoC, CoC)に対する何かしらの意思決定を求められる、というところだろう。

今回いろいろ考えてCoCをまとめることにしたのだけど、一般的なCoCに比べてだいぶ大きな改善を施したので、それに至るまでにいろいろ検討したことを参考までにまとめておきたい。

目次

完成品

この行動規範は、コミュニティイベント参加者のコミュニティイベントに関連する行為についてのみ適用されます(被害者や視聴者がコミュニティ参加者に限定されることはありません)。

このコミュニティのイベントにおいては、参加者には、性別、性的自認・性的指向、年齢、障害、身体的特徴、人種、民族、宗教(あるいは無宗教)、技術的指向などを理由としたハラスメントの無い状態を維持すべく行動していただきます。コミュニティ主催者は、コミュニティのために、コミュニティ参加者によるいかなる種類のハラスメントも歓迎しません。性的な意図での画像等の表現は、イベント会場での発表や歓談、Twitterその他のオンラインメディアを含め、コミュニティイベントに関連するものとしては不適切であり提示しないものとします。

違反行為の態様によっては、このコミュニティでは退席を求めることもあり、また以降の参加を認めないこともあります。有償イベントの場合、払い戻しを受けることはできません。しかし、誰もが問題を意識せずに違反行為を行う可能性があります。この行動規範は問題を改善するためのものであり、実効的な改善が期待できるうちは違反を繰り返さないよう注意するに留めることを目指します。

この行動規範に反する問題のある行為を受けたり、困っていそうな人を発見した場合は、コミュニティ運営メンバーに連絡し対応を求めてください。コミュニティ運営メンバーによる違反行為についても同様に情報提供を求めますが、十分な対応が期待できない場合はTwitterなどのソーシャルメディアに助けを求めてください。ハラスメント処分について不満がある人も同様です。ハラスメントに該当するか否かは規範的な判断が不可避であるため、主催者と当事者では判断が異なることがあり得るためです。

契約書などの規約に著作権は存在しないので(船荷証券事件)、無断で自由に複製したり、自分たちのCoCに取り入れたり、好きなように使っていただいて問題ない。

CoCの本質は何か

CoCの策定について論じる前に、CoCの本質的な性格について少し前提を共有しなければならないだろう。一般的なCoCとは、実効性(処分)を伴う私的社会集団の規則として実装された法あるいは規範である。

「実効性(処分)を伴う」というのは、CoCに違反した者に対して、コミュニティへの不参加や、法的に有効に支払った参加費の返還を伴わない役務(債務)の不履行が正当化される、ということだ。当事者の合意が無いのに債務を不履行とするのは、法律的には不法行為だ。法的な不利益処分を伴うのだから、本来は「CoCは単なるガイドラインである」という(主にあいまいさに対する)釈明は通用しない。CoC違反による処分は、コミュニティ活動を違反行為によって妨げられるという損害を抑止するという理由で法的な後ろ盾を得る。

「法あるいは規範」というやや中途半端な表現を用いたのは、ローレンス・レッシグがCODEで提示した古典的な四規制力(法、規範、市場、アーキテクチャ)では自明に分類できなかったためなのだが、法律のような強制力を伴う一方で、その私的社会構成員の意思を反映しなければならないという憲法も無く、恣意的に策定される危険性がある、という理解にもとづく表現だ。これを規範に近づけると、究極的にはCoCが無い状態である(構成員の良識に100%依存する、あるいは主催者がそう言明するような事態)。

CoCを策定するのであれば、それは健全な法とならなければならず、参加者の合意(すくなくとも理解)が得られる必要がある。2020年現在は、CoCのありかたを巡って、さまざまな立場があり、それらが衝突している状態にあり、このような闘争状況においては、規範に近いルールの制定であっても、策定内容をめぐる合意について、法律と同レベルの正統性(規制根拠)が必要になるはずだからだ。

また、人の自由を拘束する、特に表現の自由を拘束するものでもあるから、法律に関する表現の自由を制限する場合の正当化に要求される二重の基準なども、適切に満たすことを目指さなければならない。

規則とは雰囲気で身勝手に規定してはならないものであり、策定者は規則の策定を拘束する規則や理念(法律であれば憲法)を遵守しなければならない。「CoCを策定しろ」と要求する者は、コミュニティの代表者に対してそれだけの覚悟を示せ、と言っているのに等しいということを自覚しなければならない。そして残念なことに(?)、現状ではその要求はどちらかといえば妥当なものであると自分は考えている。

CoCを策定することに対する批判的議論とそれらへの対応

CoCの策定にあたっては、そもそもCoCを策定すべきか否か、という意思決定がある。最近はCoCを策定しないだけで非難される・ペナルティを受ける・「差別」される*1という風潮があり、CoCを制定「しない」という決定それ自体が意味のある意思決定になっている状態だ。

CoCを「策定しない」ほうが望ましいと考える立場は、(おそらくアナーキズムにも類する議論が既に多数あるだろうが)自分が理解している範囲だと概ね次のような論理がある。

他人の行為を「禁止」できる「コミュニティ」とは一体何者なのか?

この問題を解決するためには、「コミュニティ」における活動とは何を指すのか、について明確に定義する必要がある。CoC違反に基づく処分を執行することができないのであれば、それはもはや規則ではないし、正当な権利もなく他人の行為を禁止する行為は厳しく制限されなければならない(究極的には業務妨害罪にもなり得る)。

具体的には、コミュニティ活動とは無関係なTwitter上の投稿内容が差別的であったり性的であったとしても、それを理由にコミュニティから排斥することは許されない。それはコミュニティ活動ではないからだ。コミュニティ参加の場たとえばセッショントークにおいて差別的な発言をする「おそれ」がある、と主張する場合には、その「おそれ」は具体的なものでなければ、法律における表現の自由の規制を正当化する「二重の基準」を満たしていない。

なお、CoCを遵守すべき主体はコミュニティ参加でありその守備範囲であるイベント等に付随する場合に限られるが、言及される対象はコミュニティ参加者に限られない。これは現時点で参加していない人の新規参入を妨害してはならないためである。

参加者が合意していないものを強制できるのか?

民主主義社会において、法律は主権者たる国民を代表する国会(日本国の実装の場合)が策定する(そして国会議員は憲法に拘束され、その立法内容は憲法に反してはならない)。法律の場合、国民に法律を遵守する義務があるのは、それが間接的には自らが定めたためである、という理屈で正当化される。憲法学には憲法制定権力は何であったか、という論点で議論がなされるし、刑法で言えば、罪刑法定主義明確性の原則といったかたちで実装されている。

CoCにはこれが無く、あくまでコミュニティ運営者が身勝手に策定しているものだから、少なくとも参加にあたって事前に十分に提示して同意してもらう必要がある。

強制力があるかないか、という問題で言えば、CoCを「執行」出来る場面は限られている、ということは言える。限られてはいるが無いわけではない、という程度のものなので「それならば無いほうがマシ」という再反論は有効だ。

一方で「CoCは憲章であり違反に対応する手続きを定めていることで差別等への抑止力になる」という指摘はあり、この点では具体的な処分が機能しなくても意味があるといえる。

CoCに合意できない一部のメンバーを積極的に排除するために規定するものなのではないか?

CoCを策定する行為そのものが差別的な意図に基づいていないことは必要条件だ。特定個人を狙い撃ちで排除するために定めるCoCの条項は原則として無効になるべきものだ。排除された者が「差別的だ」と感じたら、その主張をきちんと取り上げて判断する公正な機関が必要だし、無ければSNS上でコミュニティが非難されることになる。

ここには三権分立の機能していないコミュニティという組織に特有の問題があり、コミュニティ運営者が自ら定めたCoCおよびそれに基づく処分について、自らが違反であると判断することはおそらく稀なので、公正な判断機関に準ずるものとしてSNS等における「裁判」が機能する余地がある。SNSポピュリズムで動く側面があり公正であるとは言いがたいが、少なくとも別の視点をもたらし得る存在ではある。排除される者がSNSに助けを求められるということを、少なくとも明記しておくことは、より公正なルールを担保することに繋がる。

善良なコミュニティには「ルール」を規定する必要はない。「真面目な」生徒が大半を占める中学校・高校には校則がほとんど無いではないか?

これには部分的に正しい側面があるが、試験等で生徒をフィルターしてきた学校とは異なり、CoCを規定しなくても問題がないというような議論が出てくるくらい一般的なコミュニティはほぼ「差別的であってはならない」だろうし、どんな参加者でも最初は歓迎する義務があるはずだ。となると、「善良な」構成員しか集まらない、という主張は、一般的には論理として成り立たないといえる。

違反の判断基準が恣意的ではないか?

正当な批判であり、100%対策されているので問題ないと反論することは論理的に不可能であると考えられる。

これは刑事法では「規範的」判断といわれるもので、たとえばわいせつ物陳列罪における「わいせつ」の範囲は何か、といった、明確な答えのない問題になる(そのため同罪には根拠が無いという批判も有力だ)。CoCの場合は、単に「差別はいけません」のような言い方では明確性の原則に反するので、少なくともどういう項目で差別該当性が判断されるかを列挙することが望ましい。

ただ限定列挙にするほど厳密にするのは難しいかもしれない。理想をいえばCoCは常に更新されるべきであるが、現実には追いつかないだろう。フィクションを前提にした規則は、空洞化し悪用される可能性が上がる。

いずれにしても、問題となりうる「曖昧さ」を可能な限り縮小して、CoCが無いことによる不利益と比較衡量して納得してもらうしかないだろう。

処分の有無の判断基準が恣意的ではないか?

正当な批判であり、100%対策されているので問題ないと反論することは論理的に不可能であると考えられる。

これは刑事法でも同様なのだが、犯罪に対して処罰が固定されることは通常はあり得ず*2、通常は情状を酌量される。また通常は初犯に対しては執行猶予がつく。法律に恣意的な運用の問題が無いわけではないが、法律でも同程度の問題が生じているので、少なくともCoC固有の問題ではない(規則を定めれば定めるだけ恣意的な処分が世の中に増えるということになるのだから、「法律もそうだから問題ない」という反論は成り立たない)。

一方で「公平性を担保するため、常に退会処分とする」といった運用に傾くと、同じ違反行為を犯すならより悪質な行為に走るということにもなる(饅頭を盗み食いしても殺して財布を奪っても死刑なら、後者のほうが利益が大きい)。「注意」のような処分すなわち執行猶予がつくほうがコミュニティ全体としての安心感が上がるはずだ。

こういった対策で問題の生じる可能性をなるべく縮小して、CoCが無いことによる不利益と比較衡量して納得してもらうしかないだろう。

CoCで策定されるべき事項とそうでない事項を検討する

2020年時点で議論されている「CoCは必要である」という議論に一般的に欠落しているのが、次のような問題に対する真剣な検討である。

  • CoC適用の公平性担保
  • 正当な理由のない禁止の禁止

これらはいずれも「コミュニティ運営を拘束し負担を大きくする」ものである。自己中心的なコミュニティ運営者がこれらの問題や議論を避けるとしてもそれは本能だが、正当化される本能ではない。これらの議論を回避することで被害を受けるのはコミュニティ参加者なのである。

CoC適用の公平性担保、特にSNS等に訴えかけられるという次善策の提示

三権が分立した法律の世界のイデアにおいては、立法者と司法者は別個の機関であり、これによって法の公平な執行に対する期待値が上がる。CoCの対象であるコミュニティには通常そのような体制は無い。しかし、コミュニティ運営者が「差別はいけません」などと言いながら、一方では自分の嫌いな参加者に対して差別的に待遇していたら、CoCは正当化しえない。こういう場合に備えて、まず運営者もCoCに違反する可能性があることや、そのような場合に被害者がどのような対応を取ることが出来るのか、提示しておくことは重要だ(もっとも「SNSで訴えかけると良い」というくらいしかできないが)。

SNSで訴えかけることが出来る」と言明しておくことにはもうひとつの効果がある。「SNSで公に指摘するべきではなく、運営に相談すべきである」という言い分を否定することができるのだ。SNSで指摘するのは本人がしたいからそうするのであって、それを強制的に隠蔽するべきではない。もちろん運営が適切に対処していて、被害を受けたという主張が正しくなさそうであれば、虚偽を拡散したという非難を免れることはできないが、それは本人の責任だし、「被害を100%立証できなければ公に発言すべきではない」という主張は、個別の事実関係なしには語れない以上、妥当ではない。被害者が被害を受けたという事実を、本人の意思に反してコミュニティ運営者が隠蔽するような状態を、コミュニティ運営が正当化することはありません、という意思表明だ。

もちろん、運営が相談されたら対応するのは当然のこととして、CoCに規定する。

正当な理由のない禁止の禁止と「非技術的なノイズ」問題

CoCは強制力のある法としての側面があるのだから、正当な理由のない禁止条項は排除されなければならない。一方で、CoCは行為規範として容姿等に言及する行為を禁止対象として列挙するのが一般的だ。

この禁止を正当化する理由としてよく挙げられるのは「外見・容姿に関する言及は非技術的な評価であり、(技術的な勉強会においては)ノイズであるから有害である」という立て付けの議論だ。もうひとつ「外見・容姿に関する言及は身体的特徴に関する差別的なものである」という考え方がある。自分は、一般的には後者を否定する余地はなく、禁止は妥当であると考えている。前者については、状況に応じて禁止すべき時とそうでない時があると考えている。

非技術的な情報が技術勉強会においてノイズになることは間違いない。しかし「ノイズであれば排除して良い」ということは必ずしも言えない。二重の基準論で考えれば、ノイズを排除しなければ他の利益が損なわれる(損なわれた)など、表現を規制するに足る正当な理由が必要になる。外見に(非差別的に)言及する*3参加者がいた結果、まともに勉強会として参加できる雰囲気ではなくなった、という事態があれば、それは禁止に値する有害なノイズであったと言える。

何がノイズになり、何がノイズにならないかを事前に予測するのは、多くの場合は容易ではないが、一方で法の執行には「予見可能性」が無ければならない。独自の視点で「この発言は差別的ではないか」と気付く参加者もいるかもしれないし、言われれば同意する参加者もいることだろうが、発言者にそのような意思が無かった可能性をきちんと考慮し、まずは配慮を促すという対応が必要である。「望ましい」ではなく「必要である」とする根拠は「予見可能性」だ。人は自らが予見できなかった行為・結果について責任を問われることはない。故意が認められないためだ。

また発言者も含め他の参加者が不同意を表明する自由は、必ず守らなければならない。

「非技術的なノイズ」の問題は、参加者がSNSなどで外見・容姿について言及する場合だけでなく、講演者が自ら非技術的な興味を引く目的で外見を調整する行為にも当てはまる。これを「どこまで許されるか」という議論にすると答えの見えない問題になるが、視点を変えて、たとえば外見を整えるために参加者の手を借りたり「いい写真」の撮影を依頼するような行為があれば、それは非技術的なノイズを増やす故意があると言える。

同様に、外見・容姿にかかる言及は「優遇」であることも多く、「被害者の同意」がある場合もある。これは他の講演者や参加者と相対的に比較する状況や側面があれば明確に有害だし、そうでない場合でも参加者が自らの容姿・外見等を意識させられるような状況が生じるのであれば、それは差別的言及である。特定メンバーに対する外見的な優遇は外見的な差別であることを意識すべし。

いずれにしても、容姿・外見等に関する言及があった場合は、当人らがネガティブに感じるようなものであれば十分にCoC違反の条件を満たすし、そうでない状況でも当人の故意の有無などをきちんと個別具体的に把握して、実害のあるものは排除するよう「毅然と」=公正に対応する必要がある。単に積極的に科罰的に振る舞うことを「毅然とした対応」と勘違いしてはならない

障害からの回復という目的を忘れない

人は誰でも失敗するし、これはCoC違反についても同様だ。CoCは違反者を糾弾するために存在するのではなく、問題を是正し再発を抑止するために存在する。CoCに違反した人は(その判断に同意するなら)反省等を示して再発を防ぐよう気をつければ良い。「気をつける」以外の対策がなければそれは単なる精神論であり再発する可能性があるのだから、具体的な改善案が無いのなら再発を弾劾してはならない。

ここまでの項目ではコミュニティ運営者により大きな責任が課せられるような内容が続いたが、運営者も万全ではないし、CoCはコミュニティ運営者が間違えることを想定していなければならない。判断ミスの回復に務めるべきではあるが、単に判断を誤ったことを理由に直ちに退任させられたらそれは運営者に対する不正なサンクションである。クビは改善が期待できなくなってからで十分だし、処分の公平性は運営者に対しても妥当する。

カジュアルに失敗し、カジュアルに問題を指摘し、カジュアルに改善することが、CoCを据えるコミュニティが目指すべき状態である。

さいきょうを目指して実装されたCoC

以上のようなことを考えながら、最初は3年前に自分が翻訳したcommunitycodeofconduct.comのやつをコピって多少いじるだけにしようと思っていたのだけど、冒頭に挙げたもののように、だいぶ書き換わることになった。CoCは法の世界のように憲法と法律が分離していることはなく、すべてが単体で完結しているので、その中に憲法的な「代表を縛る」ための条項を公平性の担保としていくつか追加することになった。実体法と手続法も一体化しているが、これは一般的なCoCも同様だろう。

近々公開する勉強会でこれが採用されるかどうかは未定だが(コミュニティは自分が作るとしても自分が恣意的に全て決めて良いものではない)、採用されなくても誰かしらの指針になると良いなと思っている。

最強のルールとは、みんなに気持ちよく遵守してもらえるようにフェアに設計され、違反行為にもインクルーシブに対応できるようなソフトローであると自分は考える。

追記: フィードバック

内容の語句にはいろいろ選択肢があるはずなので、自分のスタイルに合わせて調整するのが良いでしょう。

*1:「差別を受けたと感じた者がそう表明した」場合は、少なくともその気持ちをリスペクトすることが求められる。差別に配慮する人間ならそう考えるはずだ。

*2:是非はともかくとして外患誘致罪のような例外がある

*3:差別的に言及する行為が容認されないことは書くまでもないだろう。