juce_emscripten: 最新のJUCE on WebAssembly

English version

目次

2020年元旦からわれわれの界隈には刺激的なニュースが出てきました。

www.dtmstation.com

この記事で触れられているDreamtonics社がgithubで公開しているというのは、このjuce_emscriptenというJUCEのforkプロジェクトです。emscriptenを活用してwasmおよび周辺ファイルを生成します。

github.com

ちょっとだけ(本当にちょっとだけ)開発の手伝いをしていたので、詳しい解説をまとめようと思います。

Dreamtonics/juce_emscriptenは、JUCEオリジナルからのforkではなく、もともと5年くらい前にemscriptenでJUCEのWeb版を作ろうとしていた先人のプロジェクトのコードに基づくものです。これはかなり古いJUCEのバージョンに基づいているので、Dreamtonics/juce_emscriptenは最新版のJUCEにアップデートして、自社プロダクト(完全にJUCEで作られたアプリケーション)をビルド出来るようにした、ということになります。

(JUCEアプリケーションがWebブラウザで動作する先例としては、WebAudioModules (WAMs)を使用したWebDexedやWebOBXDといったアプリケーションがありました。WAMについては https://qiita.com/COx2/items/1f19d045936eebc3b82d でもう少し詳しく触れられているので、これを読むと良いと思いますが、WebAudioModules APIemscriptenを使用してJS上とネイティブコードの相互運用のごく基本的な部分だけを規定して、あとはC/C++のアプリケーションをそのまま移植できるようにしたものといえます。これらを実際にJUCEと統合した部分のソースコードWebOBXDのリポジトリで見られます。)

この最新のjuce_emscriptenがどれだけ実用的になっているか、まだ試していない人はここで試してみてください。

synthesizerv.com

juce_emscriptenを体験する

juce_emscriptenでは、JUCE本家のサンプルであるexamples/DemoRunnerがビルドできるように設定されています(後で言及しますが、juce_emscriptenはProjucerが対応していないので、Emscriptenでビルド出来るように調整されたMakefileが用意されています)。わたしが確認した限りではLinuxMacでビルドできます。WindowsならWSLでもできるでしょう。

emsdkがローカルで使える状態になっていない場合は、まずemmakeを使えるようにセットアップする必要があります。juce_emscriptenREADME.mdにあるビルドの説明が簡単です。

$ git clone https://github.com/emscripten-core/emsdk.git
$ cd emsdk
$ ./emsdk install latest
$ ./emsdk activate latest

emsdkがセットアップできたら、Emscriptenのツールチェインを呼び出せるように環境変数を設定します。インストールは1回で十分ですが、環境変数はその後いつでもemmakeを呼び出す前にシェル上で設定しておく必要があります。

$ source ./emsdk_env.sh

あとはemmakeでビルドするだけです。Emscripten用のビルドはexamples/DemoRunner/Builds/Emscriptenディレクトリ上にあります。生成済みのものがあるので、Projucer --resaveで生成する必要はありませんし、実のところ上書きするとビルドできなくなるので注意が必要です。

$ cd /path/to/juce_emscripten/examples/DemoRunner/Builds/Emscripten
$ emmake make

ビルドしたWebアプリケーションはローカルのChrome/Chromiumで実行できます。公式ドキュメントではcd build && python -m SimpleHTTPServerですが、わたしはnpx http-server buildで動かしています。HTTPサーバなら何でも良いです。

f:id:atsushieno:20200101003632p:plain
DemoRunner sshot

2021.12.30追記: このエントリーを書いてから、SPECTRE対策でSharedArrayBufferは無効化されるようになり、この手順では使えなくなりました。2021年現在、SharedArrayBufferはサイト側でCORS対策していないと利用できません。npx http-serverだと対応できない(--corsオプションだと足りない)ので、この辺のやり方を真似するとよいでしょう。

JUCEのプラットフォームバックエンドの基本

一体何をどうやったらデスクトップアプリケーション向けのフレームワークであるJUCEがWebブラウザで動作するようになるのでしょうか? 普段からクロスプラットフォームライブラリの実装を追いかけている人から見れば自明のことですが、どのクロスプラットフォームのライブラリも、プラットフォーム固有の部分を個別に実装して、(一般的には)共通のAPIのみを安定APIとして公開する、というアプローチが採られます。

JUCEの場合、本家がすでにWindows, Mac, Linux, iOS, Androidをサポートしており、そのプラットフォーム固有部分はJUCE本体の各モジュールで実装されています。JUCEはわれわれがよく目にするReact Native, Xamarin, Flutterといったクロスプラットフォーム開発フレームワークとは異なり、オーディオアプリケーション開発が主眼にあり、オーディオ/MIDI IO部分もクロスプラットフォームで実現する必要があります。オーディオ/MIDI IOはjuce_audio_devicesというモジュールで、GUIjuce_gui_basicsというモジュールで、主に実現しています。

また、GUIは主にメインスレッド上で動作するメッセージループのコールバックに基づいて動作する仕組みがどのOSにも存在しており、これはjuce_eventsというモジュールに独立して存在しています。メッセージループのコールバックの仕組みはオーディオI/Oでも用いられており、このjuce_eventsjuce_audio_basicsにとっても依存モジュールのひとつです。GNOMEデスクトップ開発に慣れている人であれば、gtkとglibが分かれていることを知っているかもしれませんが、juce_eventsはjuce_coreと合わせるとglibのような位置付けになります。

これらのモジュールのソースコードの中に、プラットフォーム固有の実装が含まれています。juce_audio_devicesの場合はjuce_audio_devices/nativeに、juce_gui_basicsの場合はjuce_gui_basics/nativeにあります。それぞれのディレクトリにAndroidだのiOSだのwin32だのといったファイルが含まれていることがわかるでしょう。

そして、プラットフォーム固有の実装は、自分で追加することもできるのです(そういえば今年のJUCE Advent Calendar1日目の記事もそういう内容でしたね)。juce_emscriptenは、ここに新しくemscripten / wasm用の実装を追加するものです。

自分のプロジェクトをjuce_emscriptenでビルドする際の注意点

juce_emscriptenは、条件が合えば自分のプロジェクトでも利用することが可能です。DemoRunnerとSynthesizer Vが動くというだけではもったいないので、他のアプリケーションも動くようにしたいですよね(!?)

ただし、現状ではjuce_emscriptenはSynthesizer Vが動作するために必要最低限のコードが実装されているものであって、まだいろいろ足りない部分があり、またビルドしたアプリケーションが動作するまではいくつかの手作業が必要になります。以下で少しずつ詳しく説明していきます。

missing modules

まず、README.mdのStatusセクションを見ると、どのモジュールがサポートされていないかがわかります(流動的に増えるかもしれないのでリンクだけ示しておきます)。

特によく引っかかりそうなのは、JUCEでよく使われていそうなオーディオプラグインサポートのためのモジュールjuce_audio_plugin_clientで、これはまだサポートされていません。juce_audio_plugin_clientを使っているアプリケーションはstandaloneでもビルドに失敗します。

もしサポートされていないものを使っている場合は、そのモジュールの機能を使わないように改造するか、ビルドエラーをもとに自分で実装する(!)しかありません。ゼロから自分でGUIもオーディオも実装するよりは、はるかに楽になっているはずですし、移植のための知見も本家との差分から調べやすくなっているとは思います。

missing Projucer builder type

また、現状ProjucerでプロジェクトファイルをEmscripten用に生成することはできません。Emscriptenの一般的な使い方と同様に、ProjucerでいったんLinuxMakefileを生成して、その内容に手を加えることになるでしょう。DemoRunner.jucerには、Emscripten用の出力ターゲットが追加されているので、これを再利用するのが一番手っ取り早いです。

注意すべきは、Projucerで出力したMakefileは、そのままでは使えないということです。DemoRunner.jucerには次のようなコメントがあります:

After resaving, do the following by hand:
Remove $(shell pkg-config ...) from the Makefile
Change JUCE library path to juce_emscripten
Add .html extension to target app
Optionally, add -s SAFE_HEAP=1 -s ASSERTIONS=1 to JUCE_CFLAGS for Debug build
Optionally, add -s WASM_OBJECT_FILES=0 to JUCE_CFLAGS and
 --llvm-lto 1 to JUCE_LDFLAGS for Release build.

重要なのは(1)JUCEモジュールのパスをProjucerの指定するmodulesのパスからjuce_emscriptenリポジトリmodulesに置き換え、(2)pkg-configを呼び出している部分を全部消すことと、(3)JUCE_TARGET_APPに拡張子.htmlを追加することです(こうすることでemmakeのビルド結果が.html、.js、.wasm等のセットになります)。これはProjucerで保存するたびに行う必要があります。

2020/02/02追記: これらに加え、JUCE_CPPFLAGSに -I/usr/include/freetype2 を追加してやる必要もありそうです。(本当は/usr/以下のパスを指定するのは良くないような気もしますが…)

LinuxMakefileのビルドファイルを手作業で編集するのが面倒になったら、Projucerに手を加えるような変更をcontributeすると良いかもしれません(!)

ちなみに、Projucerで新規GUIアプリケーションを作成して生成されたプロジェクトは、実のところビルドできません。利用モジュールの中にサポートされていないjuce_openglが含まれているためです。Projucer上でこのモジュールを外すと良いでしょう。

missing resources

もうひとつEmscriptenの制限でちょっと面倒なのは、フォントファイルをはじめとする各種リソースをemccの--preload-fileオプションで組み込まなければならないということです。実のところフォントを組み込まないとシンプルなASCII文字列すら描画してくれません。先のDemoRunnerでは、Linuxビルドが参照しているX11のフォントリソースを取り込んでビルドするようになっています。

DemoRunnerのビルダーを使い回す場合は(使いまわしたほうが楽だと思います)、DemoRunner固有の--preload-fileオプションは外しても大丈夫ですが、X11R6のフォントはコピーした上で--preload-fileを指定するのを忘れないようにしましょう。

足りない機能は自分で移植できる

いかがでしたか?(キュレーションサイト風) juce_emscriptenを使うと、既存のJUCEアプリケーションをWebブラウザ上にもってくることが、だいぶ現実的に可能になってきたことが見て取れたのではないでしょうか。

ただ、JUCE本体は幅広いツールチェインでサポートするプロジェクトの種類も多様です。おそらく"GUI Application" 以外の種類のプロジェクトを移植するには、juce_emscripten本体をちまちまとハックする必要があるかもしれません。とはいえ、おそらく最も面倒であろうGUIとオーディオI/Oのコア部分は、オリジナル開発者とDreamtonicsによって移植されているので、足りないところは問題にぶち当たった時にちまちまと実装してcontributeしていくと、完成度が上がっていくのではないでしょうか。

おまけ