更新版はこちらです↓↓↓
この記事は新歓ブログリレー2022 / 40日目の記事です
こんにちは、19Bのカシワデです。普段はオーケストラ系の曲を作ってます。
TwitterやSoundcloudもよろしくね!!!!!!
さてさて、皆さまいかがお過ごしでしょうか。近くスプリングセールやサマーセールなども行われるであろう中、DTMerの敵である円安はめちゃめちゃに進み、とてもじゃないがプラグインは買えないよ~~~と言ってる頃かと思います。
こんなことを思ったことはありませんか。
「あぁ…もしも自分が理想とするプラグインを無料で作れたら、こんなにも散財することはないのになぁ」
その思いを叶えてしまおう、というのが本記事の目的となっています!
さて
今回メインに扱うのはJUCEと呼ばれるフレームワークです。
オーディオ関連のソフトウェアを開発することに特化したフレームワークで、比較的簡単にVSTプラグインやVSTプラグインホスト、普通のアプリケーションを作ることが出来ます。
かなり使いやすいフレームワークなのですが、初学者がチュートリアルを見ながら進めるには少々チュートリアルが雑だったり、情報が散ってたり、ドキュメントをみようにも説明不足のところがあったり...などと、ちょっと進めにくいなぁと思うところが何点かありました。
というわけで今回の記事ではJUCEの基本的な使い方を浚いつつ、自力でゼロから、作ってみたいプラグインを完成に持って行けるような状態を目指せるように進めていきます。
最終的にはこんな感じのプラグイン(GainとPanがついたやつ)ができる予定です。
対象
- JUCEで自作プラグインを作りたい
- 簡単なプログラミングの経験がある。
- Windowsを使っている
- 筆者がMacを持っていないのでXcodeを使った開発やAUでのコンパイルはわかりません...
- VSTeを作りたい
- 筆者はVSTeしか作ったことが無いので今回の記事ではVSTeについての説明になります。
JUCEのダウンロード
下記のページからJUCEをダウンロードしましょう。
とりあえずはPersonalで大丈夫です。JUCEを使って作ったもので$50k稼ぐつもりのある人は商用ライセンス版への移行を考えましょう。
自身のOSにあったものを選んでください。
ダウンロードしたら展開しましょう。
多分展開したら以下のディレクトリ構成になるんじゃないかと思います。
- JUCE
├---- .github
├---- docs
├---- examples
├---- extras
├---- modules
├---- .gitignore
├---- .gitlab-ci.yml
└---- ....
このJUCEフォルダ以下をわかりやすい位置に移動しておきましょう。自分はC:\直下に置きました。
どこでもいいと思います。
Visual Studioのダウンロード
後述するVSCodeは必須ではないのですが、WindowsでJUCEを使って開発をする場合Visual Studio(MacならXcode)は必須です。インストールしましょう。
https://visualstudio.microsoft.com/ja/free-developer-offers/
ダウンロードしたら実行して進めていきましょう。途中下記のようにインストールするワークロードを選択するところが出てくるはずです。ここでは『C++によるデスクトップ開発』を選択して進めましょう。
正しくインストールできたか確認
インストールが成功したかを確認するためにデモプロジェクトをビルドしてみましょう。
今回ビルドするデモプロジェクトはJUCE/extras/AudioPluginHostにある『AudioPluginHost』というソフトウェアで、自分が制作したプラグインを簡単に実行してテストができるソフトウェアになっています。
手順
JUCEフォルダーからProjucer.exeを起動します。
ProjucerはJUCEプロジェクトを管理するアプリケーションです。これから長らくお世話になるので仲良くしておきましょう。
『Open Existing Project...』をクリックして
先ほどJUCEをインストールしたフォルダを開いて『extras』フォルダを開き、
更にその下にある『AudioPluginHost』を開いて、
『AudioPluginHost.jucer』を選択して『開く』をクリック。
Projucerがこんな感じになれば大丈夫です。
右上のVisual Studioのアイコンをクリックしてください。
Visual Studioが起動したら、上部のメニューバーから『ビルド』→『ソリューションのビルド』をクリックしてください。
これでビルドが始まります。
画面下側に
========== ビルド: 1 正常終了、0 失敗、0 更新不要、0 スキップ ==========
が表示されたらビルド成功です!
生成物の起動
この記事の通り、JUCEをC:\直下においている場合、生成物は下記のパスにあります。
C:\JUCE\extras\AudioPluginHost\Builds\VisualStudio2022\x64\Debug\App
そこにある『AudioPluginHost.exe』をダブルクリックしてこうなったら完璧です!
(環境によって緑色のやつの本数が違ったりするかもしれません)
右クリックすることでプラグインを追加できます。
今後はデバッグにこの『AudioPluginHost.exe』を利用するので、これのショートカットを作成してJUCEフォルダにおいておくと便利です。
簡単なプラグインを作ってみよう!
プロジェクトの作成
というわけでいよいよプラグインを作っていこうと思います。
今回作るのは、GainとPanだけを持った簡単なものです。簡単なものですがJUCEにおけるプラグイン作成の肝は抑えているので今後さまざまに応用できます。
-
Projucerの上部メニューバーから『File』→『New Project』をクリックするとこんな画面になると思います。
-
左側のメニューバーから『Plug-In』→『Basic』を選んで、『Project Name』に適当に入力した後『Create Project』をクリックします。
-
そうするとエクスプローラが表示されるので、わかりやすいところにフォルダを作って『フォルダーの選択』を押しましょう。
Document以下にJUCEフォルダーを作って、それを選択するのがわかりやすくて良いと思います。 -
こんな画面になったら成功です
パラメータの追加
まずは必要なパラメータの検討を行います。
今回製作するプラグインが持つ機能はGainとPanで、信号処理部分の実装を楽するためにdsp::Gainとdsp::Pannerモジュールを使いたいと思います。
ドキュメントを見るとdsp::Gainはデシベル単位で音量を調整できるようなので、Float型のパラメーターgain
を用意することにしましょう。
dsp::Pannerはパンルールも選択できるようです。DAWによってデフォルトのパンルールも違う可能性があるので、プラグインのパンルールも選択できた方が嬉しいかもしれません。ChoiceできるようなパラメーターpanRule
を用意します。
そしてパンの度合いについては、-1~1の範囲で指定できるようです。Float型のパラメーターpanAngle
を用意しましょう。ただ実際に操作するときにパンの値が小数で表示されるのはちょっと慣れないですね。どうにかする必要がありそうです。
-
いよいよコーディングを行っていきます。右上の『Visual Studio』をクリックしましょう
-
パラメータの追加にはAudioProcessorValueTreeStateクラスを用いると便利なのでそれを使います。
右側のソリューションエクスプローラからGain-Pan-tutorial_SharedCode
->Gain-Pan-tutorial
->Source
->PluginProcessor.h
を選択します
-
PluginProcessor.h
のプライベート変数を宣言します(省略) void getStateInformation (juce::MemoryBlock& destData) override; void setStateInformation (const void* data, int sizeInBytes) override; private: juce::AudioProcessorValueTreeState parameters; // 追加 std::atomic<float>* gain = nullptr; // 追加 std::atomic<float>* panRule = nullptr; // 追加 std::atomic<float>* panAngle = nullptr; // 追加 //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GainPantutorialAudioProcessor) };
-
PluginProcessor.cpp
にてパラメータの中身を書いていきます。parameters
クラスのコンストラクタに初期引数を渡してるというイメージです。右側のソリューションエクスプローラからPluginProcessor.cpp
を選択してパラメータを追加しましょう。(省略) #endif , parameters(*this, nullptr, juce::Identifier("tutorial"), // 追加 {// 追加 std::make_unique<juce::AudioParameterFloat>( "gain", // 追加 "Gain", // 追加 juce::NormalisableRange<float>(-100.0f, 10.0f), // 追加 0.0f), // 追加 std::make_unique<juce::AudioParameterChoice>( "panrule", "Pan Rule", juce::StringArray("linear", "balanced", "sin3dB", "sin4p5dB", "sin6dB", "squareRoot3dB", "squareRoot4p5dB"), 1), std::make_unique<juce::AudioParameterFloat>( "panangle", // 追加 "Pan Angle", // 追加 juce::NormalisableRange<float>(-100.0f, 100.0f),// 追加 0.0f),// 追加 })// 追加 { } GainPantutorialAudioProcessor::~GainPantutorialAudioProcessor() (省略)
今回使ったAudioParameterFloatとAudioParameterChoiceの指定方法について説明したいと思います。
基本的にはドキュメント見た方が早いです。- AudioParameterBool
- Bypassスイッチとかに使える
- AudioParameterChoice
- リスト形式でパラメータを指定するときに使える
- AudioParameterFloat
- 一般パラメーターに使える
- AudioParameterInt
- 整数型のパラメーターに仕える。モジュールの順番を持つようなときに使える
- AudioParameterBool
-
パラメーターの紐づけを行います。パラメータの値をただ読み取りたいだけなら、
parameters.getParameterAsValue("gain").getValue()
とやるよりもここで紐づけを行う*gain
の値を読んだ方が簡単で速いです。(省略) std::make_unique<juce::AudioParameterFloat>( "panangle", "Pan Angle", juce::NormalisableRange<float>(-100.0f, 100.0f), 0.0f), }) { gain = parameters.getRawParameterValue("gain"); // 追加 panAngle = parameters.getRawParameterValue("panangle"); // 追加 panRule = parameters.getRawParameterValue("panrule"); // 追加 } (省略)
-
UI側にもparametersを渡しておきたいので渡します。この時点ではエラーが出ても大丈夫です。
(省略) juce::AudioProcessorEditor* GainPantutorialAudioProcessor::createEditor() { return new GainPantutorialAudioProcessorEditor (*this, parameters); // parametersを追加 } (省略)
-
UI側のコードをいじります。まずは右側のソリューションエクスプローラから
PluginEditor.h
を開いてコンストラクタに引数を追加します。(省略) class GainPantutorialAudioProcessorEditor : public juce::AudioProcessorEditor { public: GainPantutorialAudioProcessorEditor (GainPantutorialAudioProcessor&, juce::AudioProcessorValueTreeState& vts); // juce::Audio~vtsを追加 ~GainPantutorialAudioProcessorEditor() override; //============================================================================== void paint (juce::Graphics&) override; void resized() override; (省略)
-
UI要素(スライダーとか)を追加します。
typedef
で長いクラス名を省略してます。(省略) private: // This reference is provided as a quick way for your editor to // access the processor object that created it. GainPantutorialAudioProcessor& audioProcessor; typedef juce::AudioProcessorValueTreeState::SliderAttachment SliderAttachment; // 追加 typedef juce::AudioProcessorValueTreeState::ComboBoxAttachment ComboBoxAttachment; // 追加 juce::AudioProcessorValueTreeState& valueTreeState; // 追加 juce::Slider gainSlider; // 追加 std::unique_ptr<SliderAttachment> gainSliderAttachment; // 追加 juce::Slider panAngleSlider; // 追加 std::unique_ptr<SliderAttachment> panAngleSliderAttachment; // 追加 juce::ComboBox panRuleBox; // 追加 std::unique_ptr<ComboBoxAttachment> panRuleBoxAttachment; // 追加 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GainPantutorialAudioProcessorEditor) };
-
PluginEditor.cpp
を開いて、コンストラクタの引数を追加したり、内部クラスのコンストラクタに引数わたしたり、UIとパラメーター要素をくっつけたりします(省略) GainPantutorialAudioProcessorEditor::GainPantutorialAudioProcessorEditor ( GainPantutorialAudioProcessor& p, juce::AudioProcessorValueTreeState& vts) // valueTreeStateを追加 : AudioProcessorEditor (&p), audioProcessor (p), valueTreeState(vts) // valueTreeStateを追加 { gainSliderAttachment.reset(new SliderAttachment(valueTreeState, "gain", gainSlider)); // 追加 addAndMakeVisible(gainSlider); // 追加 panAngleSliderAttachment.reset(new SliderAttachment(valueTreeState, "panangle", panAngleSlider)); // 追加 addAndMakeVisible(panAngleSlider); // 追加 panRuleBox.addItemList( juce::StringArray("linear", "balanced", "sin3dB", "sin4.5dB", "sin6dB", "sqrt3dB", "sqrt4.5dB"), 1); // 追加 panRuleBoxAttachment.reset(new ComboBoxAttachment(valueTreeState, "panrule", panRuleBox)); // 追加 addAndMakeVisible(panRuleBox); // 追加 // Make sure that before the constructor has finished, you've set the // editor's size to whatever you need it to be. setSize(400, 300); } (省略)
(図の方はちょっとコードをミスってます)
addAndMakeVisible(gainSlider);
をしないとUIに表示されません。
今後何か自分でプラグインを作ってて「あれ~表示されないな??」ってなったら真っ先にこれを疑ってください。
僕は以前、別の所を疑ってしまい時間を溶かした経験があります。 -
UIを配置します。とりあえず適当においてみます
(省略) void GainPantutorialAudioProcessorEditor::resized() { // This is generally where you'll want to lay out the positions of any // subcomponents in your editor.. gainSlider.setBounds(10, 10, 200, 30); // 追加 panAngleSlider.setBounds(10, 40, 200, 30); // 追加 panRuleBox.setBounds(10, 70, 200, 30); // 追加 }
とりあえずこれでパラメータの実装(信号処理部分はまだ)とUIへの配置が完了しました!
ビルドしてみましょう!
ビルド
メニューバーから『ソリューションのビルド』をクリック!
画面下側に
========== ビルド: 3 正常終了、0 失敗、0 更新不要、0 スキップ ==========
みたいなものが表示されたらビルド成功です!
起動
以前作った『AudioPluginHost.exe』で確認します。
- まずは起動しましょう
- 上部メニューバーから『Options』→『Edit the List of Available Plug-ins』をクリック
- 出てきた画面の一番下にある『Options...』から『Scan for new or updated VST3 plug-ins』をクリック
- 出てきた画面から、『+』ボタンをクリックしてプロジェクトフォルダを追加しましょう
- 追加したら『Scan』をクリック
- Scanが終わったら元の画面にもどって任意のポイントで右クリック。メニューから『yourcompony』→『Gain-pan-tutorial』をクリックしましょう
- こんな感じで読み込まれるはずです
- ダブルクリックでプラグインが表示されます!!
- プラグインを右クリックして『Show all parameters』をクリックしましょう。パラメータが見えます
- 連動して動くはずです
今...何が起こった???
気が付いたらパラメーターが追加されてなんかそれっぽい見た目になってしまいました。まだ信号処理の部分は実装してないので見た目だけの状態ですが、「なんか色々したらパラメーターができた」って感じてる人も多いのではないでしょうか。この段落では、何が起こったのかを説明したいと思います。
とりあえず先に進めたい人は、次の「パラメーターの保存」まで読み飛ばしちゃってください。
JUCEでのパラメータの追加はざっくり下記のような流れで行います。
juce::AudioProcessorValueTreeState parameters;
をPluginProcessor.h
で定義。- AudioProcessorValueTreeStateクラスをオブジェクト名
parameters
で生成します。AudioProcessorValueTreeStateクラスはプラグインが持つ状態を一括で管理し、GUIとの紐づけやパラメータの種類、パラメータの保存等に便利なすごいクラスになっています。このクラスはプラグイン当たり1つしか生成することが出来ません。また、このクラスはコンストラクタとして初期化情報(パラメータの種類や初期値等)が必要になります。この初期化情報は後でPluginProcessor.cpp
で書かれることになります。
- AudioProcessorValueTreeStateクラスをオブジェクト名
std::atomic<float>* hogehogeParam = nullptr;
をPluginProcessor.h
で定義。- パラメータの読み取り専用のポインタを作ります。信号処理の際にパラメータの値を読み取る時に使います。実はパラメータを読み取る別の方法として、
parameters.getParameterAsValue("hogehogeParam").getValue()
もあるのですが、parameters.getParameterAsValue()
で得られるValueクラスがスレッドセーフでなかったり、冗長だったり、ほんの少しだけ遅かったりするので、ここで定義したポインタを読み取った方が良かったりします。読み取り専用なので、例えば*hogehogeParam = 500
といった形で新たな値を代入したとしてもパラメータは更新されません。パラメータを更新させたいときはparameters.getParameterAsValue("hogehogeParam").setValue(500)
としましょう。
- パラメータの読み取り専用のポインタを作ります。信号処理の際にパラメータの値を読み取る時に使います。実はパラメータを読み取る別の方法として、
PluginProcessor.cpp
にparameters
の初期化情報を与える。ValueTreeState
の肝になる部分です。詳しくは是非クラスリファレンスを見て欲しいのですが、「元に戻すボタン」を作る際に有用なUndoManagerとのリンクはココで行います。また、第4引数のParameterLayout
で使うであろうAudioParameterBool、AudioParameterChoice、AudioParameterFloat、AudioParameterIntについては絶対にリファレンスを1度は見ておいた方が良いです。また、AudioParameterFloat
で使うNormalisableRangeは非常に有用なのでこれも絶対に見てお来ましょう。NormalisableRangeはパラメータの上限・下限を設定するだけでなく、値の間隔(0.5刻みとか)やskew(EQの周波数ノブとか。値が小さい時と大きい時で値の変動幅を変化させることができる)も設定できるなどかなり必須級です。リファレンスをみよう。
hogehogeParam = parameters.getRawParameterValue("hogehoge");
のようにPluginProcessor.cpp
で値を渡す。- 前述の読み取り専用の値が入るポインタに、値を入れます。
createEditor()
にてAudioProcessorEditorにparameters
を渡す。return new hogehogeAudioProcessorEditor(*this, parameters);
のように、GUI側でもValueTreeStateの値を読み取れるように渡しておきます。この段階ではまだhogehogeAudioProcessorEditor()
が定義されているPluginEditor.h
に第二引数が宣言されていないのでエラーが出るはずです。
PluginEditor.h
にてAudioProcessorEditorのコンストラクタの第二引数にjuce::AudioProcessorValueTreeState& vts
を追加。- 前述のエラーを解消します。ただこの段階ではまだ
juce::AudioProcessorValueTreeState& vts
に渡されたものがクラスのどこに入るのかを書いてないのでまた別のエラーが出ます。
- 前述のエラーを解消します。ただこの段階ではまだ
PluginEditor.h
にてAudioProcessorEditorのPrivate変数としてjuce::AudioProcessorValueTreeState& valueTreeState;
を追加。juce::AudioProcessorValueTreeState& vts
に渡されたもの将来的に代入される箱を用意します。「この箱に代入しろ!」という命令はまだ書いてないのでエラーが出ます。
PluginEditor.cpp
にてjuce::AudioProcessorValueTreeState& vts
に渡されたものを前述の箱に代入するよう指示します。- 下記に今回作るプラグインでの該当部分を示します。このように書くことでGUI側でValueTreeStateを扱うことが出来ます。
GainPantutorialAudioProcessorEditor::GainPantutorialAudioProcessorEditor ( GainPantutorialAudioProcessor& p, juce::AudioProcessorValueTreeState& vts) // valueTreeStateを追加 : AudioProcessorEditor (&p), audioProcessor (p), valueTreeState(vts) // valueTreeStateを追加
- 下記に今回作るプラグインでの該当部分を示します。このように書くことでGUI側でValueTreeStateを扱うことが出来ます。
PluginEditor.h
にてGUIのアタッチメントのクラス名を省略する。- この操作はやってもやらなくてもいい操作なんですが、スライダーを追加する度に
juce::AudioProcessorValueTreeState::SliderAttachment hogehogeSlider
と書くのは補完があってもつらいので、typedef
を使って省略を定義するのがお勧めです。typedef juce::AudioProcessorValueTreeState::SliderAttachment SliderAttachment;
- この操作はやってもやらなくてもいい操作なんですが、スライダーを追加する度に
PluginEditor.h
にてスライダー等のUI要素を定義します。- こんな感じです。クラス名を省略したので可読性が向上しています。
juce::Slider hogehogeSlider std::unique_ptr<SliderAttachment> hogehogeAttachment;
- こんな感じです。クラス名を省略したので可読性が向上しています。
PluginEditor.cpp
にてGUIの設定と紐づけを行う。- 下記のコードで、見えるようにして(addAndMakeVisible())パラメータとリンク(hogehogeAttachment.reset())をしてます。addAndMakeVisible()をしないと画面に表示されないので注意しましょう。その他操作や見た目の設定もここで行います。詳しくはUIクラスのリファレンスを見ましょう(例 : Slider)。GUIの見た目については後述するLookAndFeelを使って大胆にカスタマイズできます。
addAndMakeVisible(hogehogeSlider); hogehogeAttachment.reset(new SliderAttachment(valueTreeState, "hogehoge", hogehogeSlider));
- 下記のコードで、見えるようにして(addAndMakeVisible())パラメータとリンク(hogehogeAttachment.reset())をしてます。addAndMakeVisible()をしないと画面に表示されないので注意しましょう。その他操作や見た目の設定もここで行います。詳しくはUIクラスのリファレンスを見ましょう(例 : Slider)。GUIの見た目については後述するLookAndFeelを使って大胆にカスタマイズできます。
PluginEditor.cpp
にてGUI要素を表示する座標を設定する。hogehogeSlider.setBounds(10, 10, 100, 100);
のようにすることで座標を指定できます。後述しますが、座標を数値で設定するのは避けた方がよいです。
パラメーターの保存
今のままだと、DAWを閉じたときにパラメータの設定画全てリセットされてしまいます。そこで、DAW側が正しくプラグインのパラメータを保存できるようにしましょう。
ValueTreeStateを用いることでこの部分は簡単に実装できてしまいます。
PluginProcessor.cpp
を開いて下記を追加してください。
(省略)
//==============================================================================
void GainPantutorialAudioProcessor::getStateInformation (juce::MemoryBlock& destData)
{
// You should use this method to store your parameters in the memory block.
// You could do that either as raw data, or use the XML or ValueTree classes
// as intermediaries to make it easy to save and load complex data.
auto state = parameters.copyState(); // 追加
std::unique_ptr<juce::XmlElement> xml(state.createXml()); // 追加
copyXmlToBinary(*xml, destData); // 追加
}
void GainPantutorialAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
// You should use this method to restore your parameters from this memory block,
// whose contents will have been created by the getStateInformation() call.
std::unique_ptr<juce::XmlElement> xmlState(getXmlFromBinary(data, sizeInBytes)); // 追加
if (xmlState.get() != nullptr) // 追加
if (xmlState->hasTagName(parameters.state.getType())) // 追加
parameters.replaceState(juce::ValueTree::fromXml(*xmlState)); // 追加
}
(省略)
終わりです!これでパラメータの保存までできてしまいます。
信号処理の実装
このままだと、パラメータを変えても音量やパンニングがかわりません。なぜなら信号処理の部分を実装してないからです。というわけで信号処理の部分を実装しましょう。
本当だったら、Gain部分はサンプルごとにn倍したり、Pan部分はLRチャンネルに重みづけをしたりするのですが、このレベルの簡単な信号処理だったらJUCEのdsp::Gainとdsp::Pannerモジュールを使うことが出来ます。JUCEはかなりの数の信号処理機をデフォルトで持っているので、余程のこだわりが無ければこれらのモジュールを使ってしまうのが便利です。
またこれらのモジュールは自作もできますし、既存のものを少し編集したりなども簡単です。是非やってみてください。
- まずはプロジェクトにJUCEモジュールを追加します。Projucerから下記のようにして
juce_dsp
をクリックして、プロジェクトに追加してください。そのあとVisualStudioを一旦閉じてもう一度Projucerから起動してください。
PluginProcessor.h
にて、使用するモジュールの定義を行います。今回はとりあえず処理精度がfloat(単精度浮動小数点演算)のものを作るので<float>
としています。juce::dsp::ProcessSpec spec;
はjuce::dspモジュールが動作前の準備にて必要となる情報が格納される変数です。(省略) std::atomic<float>* panAngle = nullptr; juce::dsp::Gain<float> gainDSP; // 追加 juce::dsp::Panner<float> pannerDSP; // 追加 juce::dsp::ProcessSpec spec; // 追加 //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GainPantutorialAudioProcessor) };
PluginProcessor.cpp
にてjuce::dspモジュールの動作前準備を記述します。サンプルレートと1ブロック当たりのサンプル数、そしてブロックが持つオーディオチャンネル数を渡します。void GainPantutorialAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) { // Use this method as the place to do any pre-playback // initialisation that you need.. spec.maximumBlockSize = samplesPerBlock; // 追加 spec.numChannels = 2; // 追加 spec.sampleRate = sampleRate; // 追加 gainDSP.prepare(spec); // 追加 pannerDSP.prepare(spec); // 追加 }
PluginProcessor.cpp
に信号処理部分を記述します。処理はprocessBlock()
関数内に書きます。この関数の引数としてDAWから入力のサンプルブロックが渡されて、そのサンプルブロックをそのまま編集することでエフェクトをかけるということになります。初めに読み取り専用のパラメータポインタから値を読み取ってjuce::dspにセットし、次にjuce::AudioBufferをラップしてjuce::dspに渡して処理してもらいます。(省略) void GainPantutorialAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages) { juce::ScopedNoDenormals noDenormals; auto totalNumInputChannels = getTotalNumInputChannels(); auto totalNumOutputChannels = getTotalNumOutputChannels(); // In case we have more outputs than inputs, this code clears any output // channels that didn't contain input data, (because these aren't // guaranteed to be empty - they may contain garbage). // This is here to avoid people getting screaming feedback // when they first compile a plugin, but obviously you don't need to keep // this code if your algorithm always overwrites all the output channels. for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i) buffer.clear (i, 0, buffer.getNumSamples()); gainDSP.setGainDecibels(*gain); // 追記 pannerDSP.setRule(static_cast<juce::dsp::PannerRule>((int)*panRule)); // 追記 pannerDSP.setPan(*panAngle / 100); // 追記 juce::dsp::AudioBlock<float> audioBlock(buffer); // 追記 juce::dsp::ProcessContextReplacing<float> context(audioBlock); // 追記 gainDSP.process(context); // 追記 pannerDSP.process(context); // 追記 } (省略)
- これで信号処理部分は完成です。ビルドして『AudioPluginHost.exe』で読み込んでみましょう。
- このままだと音が意図通りに加工されているのかわからないので、付属のシンセサイザーを起動します。画面上を右クリックして「Sine Wave Synth」を起動し下記のように結線しましょう。
- 作ったプラグインの画面を表示し、パラメータを適当にいじったあと鍵盤を押して音を鳴らしてみましょう!意図したとおりに音量やパンが変化していたら成功です!
UIを何とかする
プラグインとして最低限の機能ができました。ここで完成!としちゃってもいいのですがせっかくなのでUIも何とかしてみましょう。はじめに断っておくのですが、JUCEにおいてUIを頑張るのはとてもとても大変です。何か別のライブラリを作るとかした方が時間の節約になると思います。
ここではあえてJUCEの機能だけを使って頑張ってUIをいい感じにしたいと思います。
ここからしばらくPluginEditor.cpp
とPluginEditor.h
しかいじりません。
どのファイルのコードをいじっているのかは、スクリーンショットの情報から確認してください。
-
とりあえず画面の縦横を何とかしたいと思います。
PluginEditor.cpp
のコンストラクタ内にあるsetSize(400, 300)
をいじれば簡単に変えられるのですが、ここを数値で直打ちすると後々困るので、プライベート変数として画面の横幅と縦幅を決めたいと思います。// PluginEditor.h int width = 150; int height = 550;
// PluginEditor.cpp setSize(width, height);
-
UI要素の配置は数字を直打ちして決めることもできるのですが、とても大変なので、UIエリアの四角形を用意してその中の相対座標で決めていくと良いです。とりあえず下記のような感じでエリアを2つ作ります。
// PluginEditor.h juce::Rectangle<int> GainAria{ width, height * 4 / 5 }; juce::Rectangle<int> PannerAria{ 0, height * 4 / 5, width, height / 5 };
// PluginEditor.cpp g.setColour (juce::Colour::Colour(46, 52, 64)); g.fillRect(GainAria.toFloat()); // エリアをわかりやすくするために一部を塗った
(↓コードが一部間違ってます)
-
Pannerは横長のスライダーじゃなくてノブっぽい感じにしたいのでそうします。
// PluginEditor.cpp (省略) gainSliderAttachment.reset(new SliderAttachment(valueTreeState, "gain", gainSlider)); addAndMakeVisible(gainSlider); panAngleSliderAttachment.reset(new SliderAttachment(valueTreeState, "panangle", panAngleSlider)); // ↓スライダーのスタイルを回転ノブ型にして、操作方向は垂直にする panAngleSlider.setSliderStyle(juce::Slider::SliderStyle::RotaryVerticalDrag); // ↓テキストボックスはノブの↓に表示し高さと幅を設定する panAngleSlider.setTextBoxStyle(juce::Slider::TextEntryBoxPosition::TextBoxBelow, false, width * 2 / 3, PannerAria.getHeight() / 6); addAndMakeVisible(panAngleSlider); panRuleBox.addItemList( (省略) gainSlider.setBounds(10, 10, 200, 30); // ↓PannerAriaの四角形から5px内側の四角形をノブの領域として与える panAngleSlider.setBounds(PannerAria.reduced(5)); panRuleBox.setBounds(10, 70, 200, 30); (省略)
-
ここまでの操作でこんな感じになります。
-
ところで、Pan RuleのComboBoxの存在を完全に忘れていたのでPannerAriaを上に上げて下に余白を作ることにします。
// PluginEditor.h // ↓修正 juce::Rectangle<int> GainAria{ width, height * 4 / 5 - 30 }; // ↓修正 juce::Rectangle<int> PannerAria{ 0, GainAria.getBottom(), width, height / 5}; // ↓追記 juce::Rectangle<int> PannerRuleAria{ 0, PannerAria.getBottom(), width, height - GainAria.getHeight() - PannerAria.getHeight()};
// PluginEditor.cpp g.setColour (juce::Colour::Colour(46, 52, 64)); g.fillRect(GainAria.toFloat()); // ↓追記 g.fillRect(PannerRuleAria.toFloat());
-
ComboBoxを移動します
// PluginEditor.cpp void GainPantutorialAudioProcessorEditor::resized() { // This is generally where you'll want to lay out the positions of any // subcomponents in your editor.. gainSlider.setBounds(10, 10, 200, 30); panAngleSlider.setBounds(PannerAria.reduced(5)); panRuleBox.setBounds(PannerRuleAria.reduced(width / 6, 4)); // 追記 }
-
Gainフェーダーを何とかします。
// PluginEditor.cpp (省略) gainSliderAttachment.reset(new SliderAttachment(valueTreeState, "gain", gainSlider)); // ↓スライダーの種類を縦方向のスライダーにする gainSlider.setSliderStyle(juce::Slider::SliderStyle::LinearVertical); // ↓テキストボックスの位置をスライダーの下にして、高さと幅を調整する gainSlider.setTextBoxStyle(juce::Slider::TextEntryBoxPosition::TextBoxBelow, false, width * 2 / 3, PannerAria.getHeight() / 6); addAndMakeVisible(gainSlider); (省略) // ↓修正 gainSlider.setBounds(GainAria.reduced(5)); panAngleSlider.setBounds(PannerAria.reduced(5)); panRuleBox.setBounds(PannerRuleAria.reduced(width / 6, 4));
-
色を塗る指示のところを消して完成です!お疲れさまでした!
もっとUIを何とかする
大分まともになりましたし、個人で作る分には問題ないレベルになっています。しかしながらプラグインの見た目ってDTMのモチベに影響するのでカッコいいほうが良いに決まっています!!!
というわけで何とかしていきましょう。
JUCEのUI要素はLookAndFeelクラスによって定義されています。つまりこのクラスにおいてスライダーやコンボボックスの描画をしている部分を上書きしてしまえば、お望みの見た目に簡単に変えることができるということです!
しかしLookAndFeelクラスに関してのドキュメントは皆無に近く、本家のコードを読みながら実装していくことになります。頑張るぞ~~~
前準備
-
回転ノブの見た目を何とかしていこうと思います。目標はこんな感じのノブです。パンナーなので中心から変化量が伸びる感じにしていきたいですよね。
-
ソースコードを読むとdrawRotarySlider()という関数が描画を担っているようです。中身を読むと引数の意味は下記のような感じになっていそう。
Graphics& g // 描画の主体 int x // スライダー要素に与えられた領域のx座標 int y // スライダー要素に与えられた領域のy座標 int width // スライダー要素に与えられた領域の横幅 int height // スライダー要素に与えられた領域の縦幅 float sliderPos // スライダーの全長を1とした時のスライダーの現在値(0.0~1.0) const float rotaryStartAngle // スライダー周りの円弧の開始位置 const float rotaryEndAngle // スライダー周りの円弧の終了位置 Slider& slider // スライダーそのもの
-
juce::sliderは内部に色情報を持っています。LookAndFeelで見た目をいじる際にはこの色から参照しておくと後々のカスタマイズ性が良いのでお勧めです。
パラメータ名 内容 backgroundColourId 直線スライダーの軌道の背景色。回転ノブのスライダーだとよくわからない。 thumbColourId ノブの現在地を示す丸いやつの色。 trackColourId 直線スライダーの開始位置から現在位置までを塗りつぶす色。回転ノブのスライダーだとよくわからない。 rotarySliderFillColourId 円弧の開始位置から現在位置までを塗りつぶす色。 rotarySliderOutlineColourId 円弧の現在位置から末端位置までを塗りつぶす色。 textBoxTextColourId テキストボックス内の文字色。 textBoxBackgroundColourId テキストボックスの背景色。 textBoxHighlightColourId テキストボックス内の文字を選択したときの色。 textBoxOutlineColourId テキストボックスの外枠の色。
コーディング
-
これで必要な情報は手に入れたことになるので、実際にコードを書いていきます。
juce::LookAndFeel_V4
クラスを継承してCustomLookAndFeel
クラスを用意し、その中のdrawRotarySlider()
を上書きします。// PluginEditor.cpp #include "PluginProcessor.h" // ↓ここから追記↓ #include "PluginEditor.h" #define _USE_MATH_DEFINES #include "math.h" void CustomLookAndFeel::drawRotarySlider(juce::Graphics& g, int x, int y, int width, int height, float sliderPos, float rotaryStartAngle, float rotaryEndAngle, juce::Slider& slider) { // 色情報の取得 auto outline = slider.findColour(juce::Slider::rotarySliderOutlineColourId); auto fill = slider.findColour(juce::Slider::rotarySliderFillColourId); auto background = slider.findColour(juce::Slider::backgroundColourId); auto thumb = slider.findColour(juce::Slider::thumbColourId); // 回転ノブを描画する領域を用意。与えられた領域から10px小さい長方形を描画領域とします。 auto bounds = juce::Rectangle<int>(x, y, width, height).toFloat().reduced(10); // 回転ノブの半径は先ほど用意した描画領域の縦辺と横辺の内短い方の半分の長さとします。 auto radius = juce::jmin(bounds.getWidth(), bounds.getHeight()) / 2.0f; // ノブの現在値を示す角度を計算します。 auto toAngle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle); // ノブの周りの線の幅です。 auto lineW = juce::jmin(4.0f, radius * 0.5f); // 線を描くときに使う円の半径です。 auto arcRadius = radius - lineW * 0.5f; juce::Path backgroundArc; backgroundArc.addCentredArc(bounds.getCentreX(), bounds.getCentreY(), arcRadius, arcRadius, 0.0f, rotaryStartAngle, rotaryEndAngle, true); g.setColour(outline); g.strokePath(backgroundArc, juce::PathStrokeType(lineW, juce::PathStrokeType::curved, juce::PathStrokeType::rounded)); juce::Path valueArc; valueArc.addCentredArc(bounds.getCentreX(), bounds.getCentreY(), arcRadius, arcRadius, 0.0f, 2 * M_PI, toAngle, true); g.setColour(fill); g.strokePath(valueArc, juce::PathStrokeType(lineW, juce::PathStrokeType::curved, juce::PathStrokeType::rounded)); radius = radius - lineW * 2; g.setColour(background); g.fillEllipse(bounds.getCentreX() - radius, bounds.getCentreY() - radius, radius * 2, radius * 2); juce::Path p; auto pointerLength = radius * 0.5f; auto pointerThickness = 2.4f; p.addRectangle(-pointerThickness * 0.5f, -radius, pointerThickness, pointerLength); p.applyTransform(juce::AffineTransform::rotation(toAngle).translated(bounds.getCentreX(), bounds.getCentreY())); g.setColour(thumb); g.fillPath(p); }
プラグイン開発においてはやや非本質なのでこのコードの解説は省きますが、下記の図とコードを見比べてみると何をやっているのかわかりやすいと思います。
-
新たに用意した
CustomLookAndFeel
クラスをスライダーに適応させます。// PluginEditor.h (省略) private: CustomLookAndFeel customLookAndFeel; // 追記 (省略)
// PluginEditor.cpp (省略) panAngleSliderAttachment.reset(new SliderAttachment(valueTreeState, "panangle", panAngleSlider)); panAngleSlider.setSliderStyle(juce::Slider::SliderStyle::RotaryVerticalDrag); panAngleSlider.setTextBoxStyle(juce::Slider::TextEntryBoxPosition::TextBoxBelow, false, width * 2 / 3, PannerAria.getHeight() / 6); panAngleSlider.setColour(juce::Slider::ColourIds::backgroundColourId, juce::Colour::Colour(236, 239, 244)); // 追記 panAngleSlider.setColour(juce::Slider::ColourIds::rotarySliderOutlineColourId, juce::Colour::Colour(46, 52, 64)); // 追記 panAngleSlider.setColour(juce::Slider::ColourIds::rotarySliderFillColourId, juce::Colour::Colour(136, 192, 208)); // 追記 panAngleSlider.setColour(juce::Slider::ColourIds::thumbColourId, juce::Colour::Colour(46, 52, 64)); // 追記 panAngleSlider.setLookAndFeel(&customLookAndFeel); // 追記 addAndMakeVisible(panAngleSlider); (省略)
-
ビルドしてどのようになったか見てみましょう!かなりそれっぽくなっています。
-
背景色とテキストボックスの色を調整しました。全体的に統一感が出てきています。
// PluginEditor.cpp (省略) panAngleSlider.setColour(juce::Slider::ColourIds::thumbColourId, juce::Colour::Colour(46, 52, 64)); panAngleSlider.setColour(juce::Slider::ColourIds::textBoxOutlineColourId, juce::Colour::Colour(114, 122, 140)); // 追記 panAngleSlider.setColour(juce::Slider::ColourIds::textBoxTextColourId, juce::Colour::Colour(236, 239, 244)); // 追記 panAngleSlider.setColour(juce::Slider::ColourIds::textBoxHighlightColourId, juce::Colour::Colour(129, 161, 193)); // 追記 panAngleSlider.setLookAndFeel(&customLookAndFeel); (省略) // (Our component is opaque, so we must completely fill the background with a solid colour) g.fillAll(juce::Colour::Colour(59, 66, 82)); // 修正 (省略)
-
同じようにしてComboBoxの見た目にも少し手を入れてみましょう。デフォルトだと矢印がおおきすぎますもんね
// PluginEditor.h (省略) class CustomLookAndFeel : public juce::LookAndFeel_V4 { void drawRotarySlider(juce::Graphics&, int x, int y, int width, int height, float sliderPosProportional, float rotaryStartAngle, float rotaryEndAngle, juce::Slider&) override; void drawComboBox(juce::Graphics& g, int width, int height, bool, int, int, // 追記 int, int, juce::ComboBox& box) override; // 追記 }; (省略)
// PluginEditor.cpp (省略) g.setColour(thumb); g.fillPath(p); } // ↓以下追記 void CustomLookAndFeel::drawComboBox(juce::Graphics& g, int width, int height, bool, int, int, int, int, juce::ComboBox& box) { juce::Rectangle<int> boxBounds(0, 0, width, height); g.setColour(box.findColour(juce::ComboBox::backgroundColourId)); g.fillRect(boxBounds.toFloat()); g.setColour(box.findColour(juce::ComboBox::outlineColourId)); g.drawRect(boxBounds.toFloat(), 1.0f); juce::Rectangle<int> arrowZone(width - 15, 0, 10, height); juce::Path path; path.startNewSubPath((float)arrowZone.getX() + 2.0f, (float)arrowZone.getCentreY() - 1.0f); path.lineTo((float)arrowZone.getCentreX(), (float)arrowZone.getCentreY() + 2.0f); path.lineTo((float)arrowZone.getRight() - 2.0f, (float)arrowZone.getCentreY() - 1.0f); g.setColour(box.findColour(juce::ComboBox::arrowColourId).withAlpha((box.isEnabled() ? 0.9f : 0.2f))); g.strokePath(path, juce::PathStrokeType(2.0f)); } (省略) panRuleBox.addItemList( juce::StringArray("linear", "balanced", "sin3dB", "sin4.5dB", "sin6dB", "sqrt3dB", "sqrt4.5dB"), 1); panRuleBoxAttachment.reset(new ComboBoxAttachment(valueTreeState, "panrule", panRuleBox)); panRuleBox.setLookAndFeel(&customLookAndFeel); // 追記 panRuleBox.setColour(juce::ComboBox::ColourIds::backgroundColourId, juce::Colour::Colour(59, 66, 82)); // 追記 panRuleBox.setColour(juce::ComboBox::ColourIds::textColourId, juce::Colour::Colour(236, 239, 244)); // 追記 panRuleBox.setColour(juce::ComboBox::ColourIds::arrowColourId, juce::Colour::Colour(236, 239, 244)); // 追記 panRuleBox.setColour(juce::ComboBox::ColourIds::outlineColourId, juce::Colour::Colour(114, 122, 140)); // 追記 addAndMakeVisible(panRuleBox); (省略)
-
ビルドするとこんな感じになるはずです。
-
最後にフェーダーを何とかしましょう。フェーダーのつまみもJUCEのグラフィック機能を用いてゴリゴリ書くこもできるのですが、めんどくさいのでそのまま画像を表示してもらうことにしましょう。適当にそれっぽい画像を作ったのでダウンロードしてください。
-
ダウンロードした画像をProjucerの「File Explorer」にドラッグ&ドロップします。
-
VisualStudioを閉じて、Projucerからもう一度起動してください。
-
スライダーの描画は
LookAndFell_V4
クラスのdrawLinearSlider()
が扱っています。今回は簡単のために縦型のスライダーに絞って実装します。関数の引数はいくつかありますが今回はつまみが1つしかないタイプなのでminSliderPos
とmaxSliderPos
は使いません。その他の引数の意味については下記の画像を見てください。
-
実装します。画像の読み込みは
juce::ImageCache::getFromMemory(BinaryData::fader_png, BinaryData::fader_pngSize);
で行うことができます。// PluginEditor.h (省略) class CustomLookAndFeel : public juce::LookAndFeel_V4 { void drawRotarySlider(juce::Graphics&, int x, int y, int width, int height, float sliderPosProportional, float rotaryStartAngle, float rotaryEndAngle, juce::Slider&) override; void drawComboBox(juce::Graphics& g, int width, int height, bool, int, int, int, int, juce::ComboBox& box) override; void drawLinearSlider(juce::Graphics& g, int x, int y, int width, int height, // 追記 float sliderPos, float minSliderPos, float maxSliderPos, // 追記 const juce::Slider::SliderStyle style, // 追記 juce::Slider& slider) override; // 追記 }; (省略)
// PluginEditor.cpp (省略) // ↓以下追記 void CustomLookAndFeel::drawLinearSlider(juce::Graphics& g, int x, int y, int width, int height, float sliderPos, float minSliderPos, float maxSliderPos, const juce::Slider::SliderStyle style, juce::Slider& slider) { auto track = slider.findColour(juce::Slider::trackColourId); auto background = slider.findColour(juce::Slider::backgroundColourId); auto bounds = juce::Rectangle<int>(x, y, width, height).toFloat(); float lineW = 10.0f; g.setColour(background); g.fillRect(juce::Rectangle<float>(bounds.getCentreX() - lineW * 0.5, bounds.getY(), lineW, bounds.getHeight())); g.setColour(track); g.fillRect(juce::Rectangle<float>(bounds.getCentreX() - lineW * 0.5, sliderPos, lineW, bounds.getBottom() - sliderPos)); juce::Image knob = juce::ImageCache::getFromMemory(BinaryData::fader_png, BinaryData::fader_pngSize); float knobWidth = knob.getWidth() * 0.15; float knobHeight = knob.getHeight() * 0.15; g.drawImage(knob, juce::Rectangle<float>(bounds.getCentreX() - knobWidth * 0.5, sliderPos - knobHeight * 0.5, knobWidth, knobHeight)); } (省略) gainSliderAttachment.reset(new SliderAttachment(valueTreeState, "gain", gainSlider)); gainSlider.setSliderStyle(juce::Slider::SliderStyle::LinearVertical); gainSlider.setTextBoxStyle(juce::Slider::TextEntryBoxPosition::TextBoxBelow, false, width * 2 / 3, PannerAria.getHeight() / 6); gainSlider.setColour(juce::Slider::ColourIds::backgroundColourId, juce::Colour::Colour(46, 52, 64)); // 追記 gainSlider.setColour(juce::Slider::ColourIds::trackColourId, juce::Colour::Colour(136, 192, 208)); // 追記 gainSlider.setColour(juce::Slider::ColourIds::textBoxOutlineColourId, juce::Colour::Colour(114, 122, 140)); // 追記 gainSlider.setColour(juce::Slider::ColourIds::textBoxTextColourId, juce::Colour::Colour(236, 239, 244)); // 追記 gainSlider.setColour(juce::Slider::ColourIds::textBoxHighlightColourId, juce::Colour::Colour(129, 161, 193)); // 追記 gainSlider.setLookAndFeel(&customLookAndFeel); // 追記 addAndMakeVisible(gainSlider); (省略)
-
ほぼ完成です!だいぶそれっぽくなりましたね。
-
最後に操作感について微調整を行います。Gainのスライダーは、大きい値なら細かく調整でき小さな値ならざっくり調整できるべきであるのでSkew Factorで調整しました。ついでに値の変動間隔も調整することで小数点表示を改善しました。
// PluginProcessor.cpp ←←注意 (省略) , parameters(*this, nullptr, juce::Identifier("tutorial"), { std::make_unique<juce::AudioParameterFloat>( "gain", "Gain", juce::NormalisableRange<float>(-100.0f, 10.0f, 0.01, 4.5), // 修正 0.0f), std::make_unique<juce::AudioParameterChoice>( "panrule", "Pan Rule", juce::StringArray("linear", "balanced", "sin3dB", "sin4p5dB", "sin6dB", "squareRoot3dB", "squareRoot4p5dB"), 1), std::make_unique<juce::AudioParameterFloat>( "panangle", "Pan Angle", juce::NormalisableRange<float>(-100.0f, 100.0f, 1), //修正 0.0f), }) (省略)
-
Gainスライダーには単位を表示して欲しいので"dB"を表示させるようにしました。
// PluginEditor.cpp (省略) gainSlider.setLookAndFeel(&customLookAndFeel); gainSlider.setTextValueSuffix(" dB"); // 追加 addAndMakeVisible(gainSlider); (省略)
-
あたり判定が大きすぎるので少し小さくしました。これ以上小さくするとテキストボックスも小さくなってしまうので何とかする必要があります。
// PluginEditor.cpp (省略) void GainPantutorialAudioProcessorEditor::resized() { // This is generally where you'll want to lay out the positions of any // subcomponents in your editor.. gainSlider.setBounds(GainAria.reduced(width / 6, 5)); // 修正 panAngleSlider.setBounds(PannerAria.reduced(width / 6, 5)); // 修正 panRuleBox.setBounds(PannerRuleAria.reduced(width / 6, 4)); }
-
ComboBoxはスクロールホイールで選択できると便利なのでそのようにしました。
// PluginEditor.cpp (省略) panRuleBox.setColour(juce::ComboBox::ColourIds::outlineColourId, juce::Colour::Colour(114, 122, 140)); panRuleBox.setScrollWheelEnabled(true); // 追加 addAndMakeVisible(panRuleBox); (省略)
-
0.0dBの所には目印があった方が良さそうだったので線を引きました。
// PluginEditor.cpp (省略) auto track = slider.findColour(juce::Slider::trackColourId); auto background = slider.findColour(juce::Slider::backgroundColourId); auto bounds = juce::Rectangle<int>(x, y, width, height).toFloat(); float lineW = 10.0f; // ↓ここを追記 juce::Path line; auto lineMain = slider.findColour(juce::Slider::textBoxTextColourId); line.startNewSubPath(bounds.getCentreX() - 30, 136.86); line.lineTo(bounds.getCentreX() + 30, 136.86); g.setColour(lineMain); g.strokePath(line, juce::PathStrokeType(1.0f)); // ↑ここまで g.setColour(background); g.fillRect(juce::Rectangle<float>(bounds.getCentreX() - lineW * 0.5, bounds.getY(), lineW, bounds.getHeight())); g.setColour(track); g.fillRect(juce::Rectangle<float>(bounds.getCentreX() - lineW * 0.5, sliderPos, lineW, bounds.getBottom() - sliderPos)); (省略)
-
今度こそ完成です!お疲れさまでした。
-
プラグインを配布するときはここを「Release」にしたうえでビルドしましょう
Tips / Q&A
本編では紹介できなかったTipsを紹介します。
デバッグのやり方
「プラグインを読み込んだら落ちた」とか、「思った挙動をしない」というときにはデバッグを行います。
- 赤枠の部分が「Debug」となっていることを確認したうえでビルドします。
- Juce Plu-in Hostを起動しますが、まだプラグインは読み込まないでください。
- 「デバッグ」→「プロセスにアタッチ」をクリック
- 「AudioPluginHost.exe」を探して選択し、「アタッチ」をクリック。
- デバッグが始まるので、Juce Plu-in Hostにてプラグインを読みこむなどしてください。
- 例えば今回、プラグインを起動するとソフトが落ちるというバグがある状態でデバッグを行いました。呼びだし履歴を見ていると、どうやら
processBlock()
の動作が原因で落ちたようです。このようにしてバグの原因を見つけていきます。
- 「出力」というタブに文字列を出力したいときは、プログラム中に
DBG("hogehoge");
のように書くと出力することができます。
ComboBoxに初期値が表示されない
ドキュメントを見ると「setSelectedId()
とかsetText()
で初期値を設定する必要があるよ」って書いてありますが、ValueTreeStateを使えばその必要はありません。但し注意すべき点として、PluginEditor.cpp
のコンストラクタにて、ComboBoxにアイテムを追加した後にValueTreeStateとリンクする必要があるという点があります。
具体的には、下記のような書き方だとプラグインの初回起動時に規定値がComboBoxに表示されません。
panRuleBoxAttachment.reset(new ComboBoxAttachment(valueTreeState, "panrule", panRuleBox));
panRuleBox.addItemList(
juce::StringArray("linear", "balanced", "sin3dB", "sin4.5dB", "sin6dB",
"sqrt3dB", "sqrt4.5dB"),
1);
下記のような書き方なら、初回起動時に規定値が表示されます
panRuleBox.addItemList(
juce::StringArray("linear", "balanced", "sin3dB", "sin4.5dB", "sin6dB",
"sqrt3dB", "sqrt4.5dB"),
1);
panRuleBoxAttachment.reset(new ComboBoxAttachment(valueTreeState, "panrule", panRuleBox));
倍精度(double)で演算させたいんだけど
初期の状態だとPluginProcessor.cpp
のprocessBlock
の所が
void GainPantutorialAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
ってなってると思います。倍精度で演算するためにはここの<float>
を<double>
に変えればよいです。
void GainPantutorialAudioProcessor::processBlock (juce::AudioBuffer<double>& buffer, juce::MidiBuffer& midiMessages)
今どき大部分のDAWは64bit処理に対応しているはずなのですが、まれに32bit処理にしか対応してないDAWやホストが存在するので、proseccBlockは単精度と倍精度の両方を用意しておく方が良いです。但し、同じものを2回書くのは面倒なので、テンプレートクラスを用いたプログラムを書くことをおすすめします。
プロジェクトファイル配布
今回作ったプログラムをGithubに上げておきます。最終的なコードを見たい人は御覧ください。