JUCEをCMakeとVSCodeで開発する


JUCEはv6.0.0から正式にCMakeを使ったビルドに対応しました。
これまでもjuce-cmakeFRUTなどコミュニティベースのサポートはあったものの、どれもそれなりに一長一短という感じでした。

これで晴れてProjucerにもXCodeにも頼ることなく開発が進められることになりました。

この記事ではまだドキュメントやチュートリアルが少ないJUCEをCMakeで使って運用する方法、またVSCodeをメインの開発環境として利用する方法についてまとめます。

CMakeを利用するメリット

サードパーティライブラリの利用が楽になる

JUCEと共にサードパーティ製のライブラリを使用する場合は、ヘッダーオンリーなライブラリはともかく、バイナリをビルドしたりリンクしたりした上で使おうとするとかなりめんどくさいです。詳細はatsushienoさんのJUCEモジュールを作って外部ライブラリを参照するを読むと分かります。

これがCMakeになると、サードパーティライブラリ自体がCMakeに対応している場合はadd_subdirectory()で追加するだけで使えるようになったりしますし、バイナリ配布されているライブラリもfind_package()target_link_libraries()などを使えば楽に使うことができます。

典型的に役立つ例としてはGoogle TestCatchのようなテストフレームワークの導入障壁が下げられます(両者ともにCMake対応)。

JUCEのバージョン管理

CMakeでJUCEを使用する場合プロジェクトの中にGitのSubmoduleとしてJUCEのリポジトリをクローンすることができます。そのため、複数のプロジェクトで異なるJUCEのバージョンを利用している際にもそれぞれのリポジトリの中で異なるリビジョンにチェックアウトして使用することでバージョンのコンフリクトを避けることができます。

開発環境の一本化

CMakeとVisual Studio Codeを利用した開発環境はmacOS、Windows、さらにはLinuxでもほぼ同等に利用することができるので、多プラットフォームでの開発のハードルがより低くできます。
また、特定のOSでのみプリプロセッサの定義やコンパイルオプションを加えたいといった時にもProjucerを開いて書き換えずCMakeの設定ファイルの中で条件分けができるので楽です。

Raspberry PiなどARM向けのアプリケーションのビルドもこれまではまずProjucerをARM向けに自分でビルドする必要がありましたが、CMakeならその手間が減らせます。

試してないですがCMake Toolchainを使えばARM向けバイナリをmacOSやWindowsからクロスコンパイルする、とかもできるはず。

参考: CMakeでARM用にクロスコンパイルする - simotin13's message

CIの管理が楽になる

CIなどコマンドラインでの実行しかできない環境ではProjucerをコマンドラインツールとして利用して、OS毎にビルドスクリプトを指定して検証などをしなければいけないですが、これをCMakeのカスタムターゲットなどを使ってCI上では共通のコマンドを叩くだけ、という形にすることでメンテナンスのコストが一段下がります。

CMakeを利用するデメリット

CMake自体の学習コストが高い

VScodeとの連携

Visual Studio CodeはCMake ToolsというMicrosoft謹製の拡張機能を使うことでXCodeやVisual Studio、CLionなどのIDEに匹敵する開発環境にすることができます。

他にもCMakeと連携して開発するためにおすすめの拡張は以下。

  • clangd(llvmベースのlanguage server。標準のIntelliSenseより補完がずっと早い)
  • CodeLLDB(デバッガ拡張。標準のデバッガよりstd::vectorのコンテナの中身が見られたりと自由度が高い)

JUCE CMake APIの基礎

JUCEをCMakeで使う上でまとまった資料は今のところ公式docsの中のJUCE CMake API以外ほぼありません。ここに必要な情報は全て揃っています。

JUCEをadd_subdirectoryする

libs以下などにgit submodule clone --recursive https://github.com/juce-framework/JUCE libs/JUCEなどでサブモジュールとしてクローンしてきます。

そして./CMakeLists.txt

cmake_minimum_required(VERSION 3.15)

PROJECT(JUCE_CMAKE_EXAMPLE
LANGUAGES CXX
VERSION 0.0.1
)
add_subdirectory(lib/JUCE)

のようにしてJUCEのリポジトリを追加すると、JUCEの用意したCMake設定用の関数が利用できるようになります。
ちなみにこれはJUCE自体をCMakeでビルドし、/usr/localなどグローバルにインストールしているとfind_package(JUCE REQUIRED)のようにしても読み込むことができます。が当然この場合はプロジェクト毎にJUCEのバージョンを変えるなどはできません。

プラグインを作る

例えばプラグインを作りたい場合はjuce_add_pluginという関数を使うことでターゲットを作ることができます。CMakeに慣れている人であればこれがadd_libraryadd_executableに相当するものだと思ってください。Projucerで指定するプラグインのオプションは基本的に全てここで設定します。

juce_add_plugin(ExamplePlugin
    # VERSION ...                                     # Set this if the plugin version is different to the project version
    # ICON_BIG ""   # ICON_* arguments specify a path to an image file to use as an icon for the Standalone
    # ICON_SMALL "${CMAKE_SOURCE_DIR}/assets/icon.png " 
    COMPANY_NAME "Tomoya Matsuura"                    # Specify the name of the plugin's author
    COMPANY_COPYRIGHT "Tomoya Matsuura"
    COMPANY_WEBSITE "https://matsuuratomoya.com/"
    COMPANY_EMAIL "[email protected]"
    IS_SYNTH FALSE                       # Is this a synth or an effect?
    # NEEDS_MIDI_INPUT TRUE/FALSE               # Does the plugin need midi input?
    # NEEDS_MIDI_OUTPUT TRUE/FALSE              # Does the plugin need midi output?
    # IS_MIDI_EFFECT TRUE/FALSE                 # Is this plugin a MIDI effect?
    # EDITOR_WANTS_KEYBOARD_FOCUS TRUE/FALSE    # Does the editor need keyboard focus?
    # COPY_PLUGIN_AFTER_BUILD TRUE/FALSE        # ビルドするたびにプラグインをインストールするか?
    PLUGIN_MANUFACTURER_CODE CCCC               # 最低一文字大文字でプラグインベンダーのIDを指定
    PLUGIN_CODE XXXX                            # 最低一文字大文字でプラグインのユニークIDを指定
    FORMATS 
    # ここでビルドしたいプラグインの種類を指定する
            Standalone 
            AU
            # AUv3 
            VST3 
            # Unity 
            # AAX           
    VST3_CATEGORIES "Fx" #複数(3つまで)列挙するとホスト上でフォルダにネストされて表示される
    AU_MAIN_TYPE "kAudioUnitType_Effect"
    # AU_SANDBOX_SAFE TRUE
    # AAX_CATEGORY ""
    # HARDENED_RUNTIME_ENABLED # macOS app settings
    # HARDENED_RUNTIME_OPTIONS
    # APP_SANDBOX_ENABLED
    # APP_SANDBOX_INHERIT
    # DESCRIPTION "" #設定するとProtoolsのプラグイン名がこのDescriptionになってしまう(バグ?)
    MICROPHONE_PERMISSION_ENABLED TRUE
    MICROPHONE_PERMISSION_TEXT "This applicaion requires a permission to use an audio input device of your computer. By Default, Built-In microphone will be used."
    PRODUCT_NAME "ExamplePlugin")        # The name of the final 

これを作ると、例えばこのExamplePluginという名前ならExamplePluginという名前のターゲットが、libExamplePlugin_SharedCode.aというプラグイン間で共通して利用されるライブラリとしてビルドされるものになります。
加えて、設定に応じてExamplePlugin_StandAloneExamplePlugin_VST3といった各フォーマット毎のビルドターゲットが作られ、libExamplePlugin_SharedCode.aはそれらの依存ターゲットとして設定されることとなります。

この他、ExamplePlugin_Allというユーティリティターゲットも定義され、これをビルドするとオプションで指定した全てのフォーマットをまとめてビルドしてくれることになります。

ソースファイルの追加

JUCE CMakeには流石にテンプレートcppファイルをを用意してくれる機能まではありません。なのでここだけProjucerを使用して初期状態のPluginProcessor.h/.cpp、PluginEditor.h/.cppのファイルを用意しました。

普通のCMakeプロジェクトではサブディレクトリ毎にadd_library(LIB_Name1 hoge.cpp fuga.cpp)のようにしてライブラリを作り、それを最後にまとめてtarget_link_libraries(Main_Target PUBLIC LIB_Name1 LIB_Name2)のようにしてリンクしていくと思いますが、JUCEを使う上では少し違う方法をとります。

target_sources(ExamplePlugin PluginProcessor.cpp PluginEditor.cpp)のように一つのターゲットに対してcppファイルのソースを追加していく形になります。これは必要なヘッダファイルなどを全て一つのJuceHeader.hで賄うという方法を取っているからなどいくつかの理由があるようです。

さらにJUCEの標準モジュールをtarget_link_librariesでリンクするように指定します。

target_link_libraries(ExamplePlugin PUBLIC
juce::juce_audio_basics
juce::juce_audio_devices
juce::juce_audio_formats
juce::juce_audio_plugin_client
juce::juce_audio_processors
juce::juce_audio_utils
juce::juce_core
juce::juce_cryptography
juce::juce_data_structures
juce::juce_dsp
juce::juce_events
juce::juce_graphics
juce::juce_gui_basics
juce::juce_gui_extra
juce::juce_opengl
juce::juce_product_unlocking
)

もちろん必要ないライブラリはリンクから外しても良いですが、リンク時間的には似たり寄ったりなのでよくわからなければ全部追加しておいてよいでしょう。3rdパーティライブラリを使用する場合は当然ここでリンクを追加しておく必要があります。

最後に、JuceHeader.hを自動生成するために以下を加えます。

juce_generate_juce_header(ExamplePlugin)

このJuceHeader.hの中には必要なモジュールのヘッダ類や後述するバイナリデータなどが全て含まれるため、JUCE関連の関数はこのヘッダを一つインクルードすれば使えるようになります。注意点としては、このJuceHeader.hはConfigure時ビルドディレクトリの中に生成されるため、Configureを行うまではIntellisenseでの補完などが効かないことになります。

ビルドする

CMake Toolsがインストールされている状態でプロジェクトを開くと、初回は画像のようにどのコンパイラを使うか聞かれます。macOSの場合は/usr/bin/clang++を指しているやつを、WindowsではVisual Studio Community 2019 Release - amd64などのVisual Studioの提供するコンパイラをアーキテクチャに合わせて適当に選びましょう。

CMake Toolsがインストールされていれば左のメニューにCMakeのタブができているので選択します。左から2番目のConfigureボタンを押すと、正常にConfigureができていれば画像のようにターゲットが追加されているはずです(初回はConfigure時にProjucer相当の機能を提供するツールjuceaideをビルドするため少し時間がかかるかも)。

ここがOKなら右隣のBuild Allでビルドします。

build/src/ExamplePlugin_artefacts/Debugのなかにビルドしたプラグインやアプリケーションが生成されているはずです。

デバッグする

VSCodeでCMakeのターゲットをデバッグするにはいくつかの方法があります。一つは先ほどのExamplePlugin_Standaloneのようなターゲット名を右クリックし、Debugをクリックするとデバッガが起動します。
ただしこれは標準のgdbデバッガしか利用することができません。

より高機能なデバッガであるlldbを利用するには次のようにします。

同じようにターゲットを右クリックし、Set as Launch/Debug Targetを選択します。

.vscode/launch.jsonか、yourproject.code_workspaceの中の"launch":{}プロパティに以下のように追記します。


        "configurations": [
            {
                "type": "lldb",
                "request": "launch",
                "name": "CMake Debug",
                "program": "${command:cmake.launchTargetPath}",
                "args": [],
                "cwd": "${workspaceFolder}"
            },
        ]

これで左のメニューからデバッグタブを選ぶと、CMake DebugというConfigurationが表示されていると思うので、Runボタンから立ち上げることができます。

また、独自にユニットテストなどExecutableを追加している場合は別ですが、そもそもStandAlone以外は直接起動することができないので、同様のJSONファイルにconfigurationを追加してprogramにホストアプリケーションのパスを指定するか、ホストを起動してからCmd+Shift+Pでコマンドパレットを開き、LLDB:Attach to Processを検索して出し、アプリケーションの名前を検索して指定し途中からAttachします。(例えばUnityのNative Audio Pluginの開発とかだと、Unityを直接programに指定しても実際のエディタが立ち上がる時には別プロセスに切り替わっていたりするので、attach以外の方法が取れないこともあります。)

バイナリファイル(アセット)の追加

JUCEではProjucerやBinary Builderを用いて画像ファイルやwavファイルをC++のソースファイルに変換し、アプリケーションに埋め込むことができますが、これをCMake経由で行うこともできます。例えばJUCEのロゴのsvgファイルをassets以下に保存した場合、以下のようにして読み込みます。


juce_add_binary_data(ExamplePluginBinaries
SOURCES 
${CMAKE_SOURCE_DIR}/assets/juce-logo.svg
)

target_link_libraries(ExamplePlugin PUBLIC
ExamplePluginBinaries #ここに追加する
juce::juce_audio_basics
...
)

これを例えば次のようにして読み込むことができます。


auto svgimg =  juce::Drawable::createFromImageData(BinaryData::jucelogo_svg,
                                              BinaryData::jucelogo_svgSize);

ファイル名からドットなど使えない文字をアンダーバーに変換したもの(BinaryData::jucelogo_svg)がデータの先頭を表すポインタ、それにSizeを追加した変数(BinaryData::jucelogo_svgSize)の2つを組み合わせて使う形になります。
これはwavファイルやフォントでも使い方は同じで、大体読み込みにこの2つをセットで指定するメソッドが用意されています。

というわけで、空のプラグインにJUCEロゴを追加したのはこんな感じに表示できたりしました。

プロジェクトテンプレートを作りました。

ここまで説明したようなことをテンプレートリポジトリの形で公開しておきました。

juce_cmake_vscode_example.code-workspaceをVisual Studio Codeで開くと、足りないプラグインがある場合はrecommendしてくれるような設定になっていますし、デバッグのjson設定も既に含まれていますので、XcodeやVisual Studioなどコンパイラ類が用意されていればすぐにでも使えるようになっています。興味がある人はぜひ試してみてください。

ちなみに、VSCode向けの設定はないですがC++20,CMake,Catch2,Github Actionsなどモダンなワークフローを詰め込んだ2020年向けテンプレートリポジトリのPamplejuceというのもあるようです。

既知のバグ

プラグインのビルドをStandAlone,VST,AU,AAXなど、組み合わせによって時々CMake Toolsのタブでターゲット一覧が表示されなくなるバグがあるようです。CMakeのビルドそのものに支障はないのでBuild Allからビルドすれば良いのですが、エラーメッセージがタブを開くたびに出て来て邪魔だったりするので一時的にどれかのビルドを外して対処したりしています。原因不明。