目次
- juce_emscriptenを体験する
- JUCEのプラットフォームバックエンドの基本
- 自分のプロジェクトをjuce_emscriptenでビルドする際の注意点
- 足りない機能は自分で移植できる
- おまけ
2020年元旦からわれわれの界隈には刺激的なニュースが出てきました。
この記事で触れられているDreamtonics社がgithubで公開しているというのは、このjuce_emscriptenというJUCEのforkプロジェクトです。emscriptenを活用してwasmおよび周辺ファイルを生成します。
ちょっとだけ(本当にちょっとだけ)開発の手伝いをしていたので、詳しい解説をまとめようと思います。
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 APIはemscriptenを使用してJS上とネイティブコードの相互運用のごく基本的な部分だけを規定して、あとはC/C++のアプリケーションをそのまま移植できるようにしたものといえます。これらを実際にJUCEと統合した部分のソースコードはWebOBXDのリポジトリで見られます。)
この最新のjuce_emscriptenがどれだけ実用的になっているか、まだ試していない人はここで試してみてください。
juce_emscriptenを体験する
juce_emscriptenでは、JUCE本家のサンプルであるexamples/DemoRunner
がビルドできるように設定されています(後で言及しますが、juce_emscriptenはProjucerが対応していないので、Emscriptenでビルド出来るように調整されたMakefileが用意されています)。わたしが確認した限りではLinuxとMacでビルドできます。WindowsならWSLでもできるでしょう。
emsdkがローカルで使える状態になっていない場合は、まずemmakeを使えるようにセットアップする必要があります。juce_emscriptenのREADME.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サーバなら何でも良いです。
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
というモジュールで、GUIはjuce_gui_basics
というモジュールで、主に実現しています。
また、GUIは主にメインスレッド上で動作するメッセージループのコールバックに基づいて動作する仕組みがどのOSにも存在しており、これはjuce_events
というモジュールに独立して存在しています。メッセージループのコールバックの仕組みはオーディオI/Oでも用いられており、このjuce_events
はjuce_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していくと、完成度が上がっていくのではないでしょうか。
おまけ
新しいjuce_emscripten、一番面白いのは afc1543 で-s PROXY_TO_PTHREADを有効にしてオーディオ処理をメインスレッドに置きつつGUIはMAIN_THREAD_EM_ASMで呼び出す方式にして、glitchの出るAudioWorkletを使わずに処理を最適化しているところ。こういうコードを毎日のように書いてるのヤバい。
— Atsushi Eno (@atsushieno) January 1, 2020