##はじめに
AudioProcessorValueTreeState(以下、APVTSと省略します)クラスを利用したパラメータの追加についてまとめたいと思いましたので、こちらの記事を投稿させていただきます。
過去一年間で、ブログにて進めてきたJUCEチュートリアルの勉強で、APVTSクラスにフォーカスして記事を作成いたしました。
JUCE、C++は一年と未熟ではありますが、JUCEを利用する方のお役に立てればと思います。どうぞよろしくお願いいたします。
###こんな方の役に立てればと思います
・JUCEプラグインへのパラメータの使い方法を知りたい方
・JUCEで一般的なパラメータの作成方法があるのか知りたい方
・AudioProcessorValueTreeStateの具体的な使い方を知りたい方
AudioProcessorValueTreeStateについて
公式チュートリアル:https://docs.juce.com/master/tutorial_audio_processor_value_tree_state.html
にてAudioProcessorValueTreeStateクラスのパラメータ追加を行うページがあり、基本的な使い方がわかります。
その他、チュートリアルで断片的に紹介されていたり、JUCE Forumにて話題になっていたような内容をまとめさせていただきます。
ProjucerでのPlugin「Basic」テンプレートを利用する場合のファイル構成をもとに記載しています。プラグインのプログラム構成として、XXAudioProcessorクラス、XXAudioProcessorEditorクラスで構成されることを前提としています。「XX」には、Projucerで作成したプロジェクト名が入ります。
###メリット
音声処理クラス(XXAudioProcessor)とエディタクラス(XXAudioProcessorEditorクラス)の両方でアクセスできるパラメータが容易に定義できます。
パラメータの記述方法が一般化できる点が良いと感じます。
パラメータのアンドゥ、リドゥの機能も比較的容易に追加できます。
###デメリット
使い方に慣れが必要だと感じました。
GUIのクラスとの連携がまだ完全ではないような雰囲気があります。
また、基本的な使い方(※)以外を行おうとすると、GUIのクラスの機能と連携を取りながら、正常に動作しない部分をカバーしなければいけない点だと感じています。基本的な使い方以外に、一歩踏み込んで調査を行った部分のまとめも記載していきます。
※次の項目で記載する、「APVTSパラメータの設定および、スライダークラスへの紐づけ」までの内容を「基本的な使い方」といいました。
AudioProcessorValueTreeStateパラメータの追加手順
順番にコピペでパラメータを追加するようにできればと思います。APVTSのパラメータを追加して、スライダーに紐づけます。
###1.プロセッサー側(XXAudioProcessorクラス)の設定
####1)APVTSとパラメータの宣言
「XXPluginProcessor.h」ファイルの「XXAudioProcessorクラス」内に次のようにメンバを追加します。
private:
//AudioProcessorValueTreeStateクラスとパラメータ値を格納するポインタを準備します。
juce::AudioProcessorValueTreeState parameters;
std::atomic<float>* param1 = nullptr;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XXAudioProcessor)
};
####2)APVTSとパラメータの初期化
「XXPluginProcessor.cpp」に定義したAudioProcessorクラスのコンストラクタに追記します。
XXAudioProcessor::XXAudioProcessor()
#ifndef JucePlugin_PreferredChannelConfigurations
//省略
#endif
//[1]APVTSパラメータの追加です。
,parameters(*this, nullptr, juce::Identifier("APVTSTutorial"),
{
std::make_unique<juce::AudioParameterFloat>("param1", // ID
"Param1", // name
0.0f, // 最小値
22000.0f, // 最大値
0.0f) // デフォルト
})
//, //←[2]二個以上はカンマで区切り追加していきます。
{
//[3]APVTSパラメータをparameterに紐づけます。引数は上記指定したIDの文字列です。
param1 = parameters.getRawParameterValue("param1");
}
####3)createEditor関数の変更
juce::AudioProcessorEditor* XXAudioProcessor::createEditor()
{
//第二引数、parametersを追加します。
return new XXAudioProcessorEditor (*this, parameters);
}
この時点で、floatのパラメータがプロセッサに追加されました。
####4)任意の処理の記述
追加したパラメータの値を取得して音声処理の値として利用したい場合、例えば、次のようにして値を取得します。
void XXAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
//一度float型として受けます。
float val = *param1;
DBG("デバッグ表示:" << val);
}
###2.エディタ側(XXAudioProcessorEditorクラス)の設定
GUIのスライダーにAPVTSのパラメータを紐づけます。
####1)XXAudioProcessorEditorクラスの定義
ここから「PluginEditor.h」ファイルです。
class XXAudioProcessorEditor : public juce::AudioProcessorEditor
{
public:
//[1]コンストラクタの引数の変更
XXAudioProcessorEditor (XXAudioProcessor&, juce::AudioProcessorValueTreeState& vts);
//中略
//[2]スライダーアタッチメントクラスのtypedef
typedef juce::AudioProcessorValueTreeState::SliderAttachment SliderAttachment;
private:
//中略
//[3]以下メンバを追加します
juce::AudioProcessorValueTreeState& valueTreeState;//[3-1]APVTSへの参照
juce::Slider testSlider;//[3-2]スライダー
std::unique_ptr<SliderAttachment> testSliderAttachment;//[3-3]アタッチメント
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XXAudioProcessorEditor)
}
[1]で、エディタがプロセッサ側で定義したAPVTSクラスの参照を受け取れるようにします。
[2]で、スライダーのGUIオブジェクトへ紐づけるスライダーアタッチメントクラスというものをtypedefします。(typedefによる記載量省略用)
[3-1]で、コンストラクタでプロセッサ側から受け取るAPVTSの参照を格納するメンバを定義します。
[3-2]で、GUIのスライダーオブジェクトを準備します。
[3-3]で、スライダーオブジェクトとAPVTSパラメータを紐づけるスライダーアタッチメントのオブジェクトを準備します。
####2)コンストラクタでGUIコンポーネント等の初期化
ここから「PluginEditor.cpp」ファイルです。
//[1]コンストラクタの第二引数を追加します。
XXAudioProcessorEditor::XXAudioProcessorEditor(XXAudioProcessor& p, juce::AudioProcessorValueTreeState& vts)
: AudioProcessorEditor(&p), valueTreeState(vts) ,audioProcessor(p)
//[2]メンバイニシャライザでvalueTreeState↑を初期化します。
{
//[3]スライダーを可視化します。
addAndMakeVisible(testSlider);
//[4]スライダーひAPVTSのパラメータを紐づけます。
testSliderAttachment.reset(new SliderAttachment(valueTreeState, "param1", testSlider));
setSize (400, 300);
}
####3)GUIコンポーネントの描画
resized関数でスライダーを任意の場所に配置します。
void XXAudioProcessorEditor::resized()
{
testSlider.setBounds(10, 10, 200, 30);
}
####4)スライダー以外
プロセッサで定義したAPVTSパラメータには、GUIオブジェクトに対応するアタッチメントクラスで紐づけを行います。そのため、APVTSパラメータを紐づける先がスライダー以外の場合、それに対応するアタッチメントクラスを利用します。
APVTSクラスの公式ドキュメントを確認すると、SliderAttachment以外に、
・ButtonAttachment
・ComboBoxAttachment
が存在します。
https://docs.juce.com/master/classAudioProcessorValueTreeState.html
##パラメータの保存と読み出し
DAWプロジェクトを終了して、再度立ち上げたときにパラメータが保持されるような設定になります。
「PluginProcessor.cpp」ファイルに追加を行います。すでに存在しているgetStateInformation関数とsetStateInformation関数に追記をします。
void XXAudioProcessor::getStateInformation (juce::MemoryBlock& destData)
{
//[1]以下の内容を追加します。
auto state = parameters.copyState();
std::unique_ptr<juce::XmlElement> xml(state.createXml());
copyXmlToBinary(*xml, destData);
}
void XXAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
//[2]以下の内容を追加します。
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));
}
###スライダー以外の挙動
ComboBoxAttachmentクラスを利用した際、上記の関数だけではコンボボックスの表示初期値が反映されない現象が出ました。XXAudioProcessorEditorクラスのコンストラクタ(PluginEditor.cpp)にて、初期値をコンボボックスに初期化する必要があるようでした。
XXAudioProcessorEditor::XXAudioProcessorEditor (XXAudioProcessor& p, juce::AudioProcessorValueTreeState& vts, juce::UndoManager& um)
//省略
{
testComboBox.setSelectedId(*(vts.getRawParameterValue("param2")));
保存されたパラメータから値を取得するためには、APVTSクラスのgetRawParameterValue関数が利用できます。引数には、パラメータIDを指定します。
SliderAttachmentでは初期値が反映されますが、ComboboxAttachmenntでは反映されないという点に注意が必要でした。
##アンドゥ、リドゥマネージャ
APVTSにUndoManagerクラスのオブジェクトを設定することで、パラメータのアンドゥ、リドゥができるようになります。
アンドゥ、リドゥは、UndoManagerクラスのオブジェクトのundo関数、redo関数を呼び出すことで実行されます。そのため、エディタ側でアンドゥ、リドゥボタンを追加して、ボタンのハンドラにundo関数、redo関数を実装してアンドゥ、リドゥができるような実装を行ってみます。
###プロセッサ側の設定
PluginProcessor.hのXXAudioProcessorクラスのpublicなメンバとしてUndoManagerクラスのメンバを準備しました。
class XXAudioProcessor : public juce::AudioProcessor
{
public:
//中略
//[1]アンドゥマネージャークラスのメンバを追加しました。
juce::UndoManager undoManager;
PluginProcessor.cppのXXAudioProcessorクラスのコンストラクタ、APVTSの初期化時に、アンドゥマネージャクラスを渡します。
, parameters(*this, &undoManager, juce::Identifier("APVTSTutorial"),
//第二引数のnullptrを↑に変更します。
createEditor関数で、エディタ側にundoManagerを渡します。
juce::AudioProcessorEditor* XXAudioProcessor::createEditor()
{
//第三引数にundoManagerを渡します。
return new TheDelayAudioProcessorEditor (*this, parameters, undoManager);
}
###エディタ側の設定
PluginEditor.hファイル、XXAudioProcessorEditorクラスです。
[1]、コンストラクタで、プロセッサ側のAPVTSオブジェクトの参照を受け取ります。そして、[2]で、UndoManagerクラスの参照と、TextButtonを2つ追加します。
class XXAudioProcessorEditor : public juce::AudioProcessorEditor
{
public:
//[1]第三引数を追加しました。
XXAudioProcessorEditor (XXAudioProcessor&, juce::AudioProcessorValueTreeState& vts, juce::UndoManager& um);
//...略...
//[2]privateなメンバとしてボタンとundoManagerを追加します。
juce::TextButton undoButton, redoButton;
juce::UndoManager& undoManager;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TheDelayAudioProcessorEditor)
};
次に、PluginEditor.cppファイル、XXAudioProcessorEditorクラスのコンストラクタです。
//[1]コンストラクタの第三引数を追加します。
XXAudioProcessorEditor::XXAudioProcessorEditor (XXAudioProcessor& p, juce::AudioProcessorValueTreeState& vts, juce::UndoManager& um)
: AudioProcessorEditor (&p), audioProcessor (p), valueTreeState(vts)
//[2]テキストボタンを初期化します。
, undoButton("Undo"), redoButton("Redo")
, undoManager(um)
{
//...略...
//[3]ボタンの初期化をします。
addAndMakeVisible(undoButton);
addAndMakeVisible(redoButton);
//[4]ボタン実行時の処理をラムダ式で定義しました。
undoButton.onClick = [this] {
undoManager.undo();//[4-1]undo処理です。
DBG("undo");
};
redoButton.onClick = [this] {
undoManager.redo();//[4-2]redo処理です。
DBG("redo");
};
setSize (400, 300);
}
[3]、[4]のように、undoManagerのundo()、redo()が呼び出されると、アンドゥ、リドゥの処理が実行されます。今回は、テキストボタンを配置して、押したタイミングで実行するようにしています。
最後に、ボタンをGUIに配置しておきます。resized関数内、setBounds関数でボタンを配置しました。
void TheDelayAudioProcessorEditor::resized()
{
//省略
//任意の座標に配置します。
undoButton.setBounds(10, 50, 50, 30);
redoButton.setBounds(60, 50, 50, 30);
}
##そのほかの使い方
###スライダーの桁数と単位をAPVTSで設定する
Sliderクラスの数値の小数点以下を調整するsetNumDecimalPlacesToDisplay()関数の効果がみられなかったので、調査した結果、APVTSのパラメータ初期化時に、桁数、単位を指定できることが分かりました。
std::make_unique<juce::AudioParameterFloat>("param1", // ID
"Param1", // name
juce::NormalisableRange<float>(0.0f,22000.0f),//範囲
0.0f,//初期値
"feedback",
juce::AudioProcessorParameter::genericParameter,
//桁数、単位を指定するラムダ式です。
[](float value, int) {return juce::String(value, 1) + "Hz";}
)
AudioParameterFloatの初期化を少し変更します。最後のラムダ式で、桁数を指定して、単位の文字列を追加しています。整数の1なので、現在は小数点第一位までの表示となります。最後のdbを任意の文字列に変更できます。
また、第三引数のパラメータは最小、最大をNormalisableRangeクラスで定義しています。
###コンボボックスアタッチメントとAPVTSの関係性
コンボボックスをAPVTSに紐づけると、コンボボックスの項目数がAPVTSのパラメータの範囲に均等に割り当てられます。
参考:https://docs.juce.com/master/classAudioProcessorValueTreeState_1_1ComboBoxAttachment.html
0~100の範囲のパラメータを4項目のコンボボックスに紐づけると、パラメータの変化は次のようになります。(小数点以下は実際動作させると、四捨五入の挙動をしました)
そのため、コンボボックスの項目番号をパラメータとしたい場合は、APVTSをコンボボックスに登録する数値の範囲とすると一致します。
例として、4個の項目をもつコンボボックスを、パラメータの値が1~4となるような設定を記載します。
APVTSのパラメータのAudioParameterInt型で1~4の値となるようなパラメータを設定します。(PluginProcessor.cpp)
std::make_unique<juce::AudioParameterInt>("param3", // ID
"Param3", // name
1.0f, //最小値
4.0f, //最大値
3.0f) //デフォルト
})
※AudioProcessorIntのパラメータであっても値を格納する変数はfloatのstd::atomicで受けます。getRawParameterValueがfloatのstd::atomicを返すためです。
コンボボックスの初期化時に、項目を4つ追加(addItem関数)したとします。(PluginEditor.cpp)
addAndMakeVisible(testComboBox);
DelayNumeratorAttachment.reset(new ComboBoxAttachment(valueTreeState, "param3", testComboBox));
DelayNumerator.addItem("1", 1);
DelayNumerator.addItem("2", 2);
DelayNumerator.addItem("3", 3);
DelayNumerator.addItem("4", 4);
DelayNumerator.setSelectedId(3);//デフォルトは3を選択状態。
この設定の場合、コンボボックスの1項目~4項目目の値がパラメータの1.0f~4.0fとなり、項目番号とパラメータが一致します。
##参考リンク
パラメータの追加手順詳細
https://panda-clip.com/juce-apvts-setting/
アンドゥ・リドゥについての記事
https://panda-clip.com/juce-apvts-undomananger/
コンボボックス利用時のパラメータ保持に関する注意
https://panda-clip.com/juce-param-xml-combobox/
juce::Sliderの桁数を設定するための奮闘
https://panda-clip.com/juce-slider-decimal-places/