AudioProcessorValueTreeStateでのパラメータ追加手順


##はじめに
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にて話題になっていたような内容をまとめさせていただきます。

:::note info
ProjucerでのPlugin「Basic」テンプレートを利用する場合のファイル構成をもとに記載しています。プラグインのプログラム構成として、XXAudioProcessorクラス、XXAudioProcessorEditorクラスで構成されることを前提としています。「XX」には、Projucerで作成したプロジェクト名が入ります。
:::

###メリット
音声処理クラス(XXAudioProcessor)とエディタクラス（XXAudioProcessorEditorクラス）の両方でアクセスできるパラメータが容易に定義できます。
パラメータの記述方法が一般化できる点が良いと感じます。
パラメータのアンドゥ、リドゥの機能も比較的容易に追加できます。

###デメリット
使い方に慣れが必要だと感じました。
GUIのクラスとの連携がまだ完全ではないような雰囲気があります。

また、基本的な使い方（※）以外を行おうとすると、GUIのクラスの機能と連携を取りながら、正常に動作しない部分をカバーしなければいけない点だと感じています。基本的な使い方以外に、一歩踏み込んで調査を行った部分のまとめも記載していきます。

:::note info
※次の項目で記載する、「APVTSパラメータの設定および、スライダークラスへの紐づけ」までの内容を「基本的な使い方」といいました。
:::

## AudioProcessorValueTreeStateパラメータの追加手順
順番にコピペでパラメータを追加するようにできればと思います。APVTSのパラメータを追加して、スライダーに紐づけます。
###１．プロセッサー側(XXAudioProcessorクラス)の設定

####1)APVTSとパラメータの宣言
「XXPluginProcessor.h」ファイルの「XXAudioProcessorクラス」内に次のようにメンバを追加します。

```cpp
private:
    //AudioProcessorValueTreeStateクラスとパラメータ値を格納するポインタを準備します。
    juce::AudioProcessorValueTreeState parameters;
    std::atomic<float>* param1 = nullptr;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XXAudioProcessor)
};
```

####2)APVTSとパラメータの初期化
「XXPluginProcessor.cpp」に定義したAudioProcessorクラスのコンストラクタに追記します。

```cpp
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関数の変更

```cpp
juce::AudioProcessorEditor* XXAudioProcessor::createEditor()
{
    //第二引数、parametersを追加します。
    return new XXAudioProcessorEditor (*this, parameters);
}
```

この時点で、floatのパラメータがプロセッサに追加されました。

####4)任意の処理の記述
追加したパラメータの値を取得して音声処理の値として利用したい場合、例えば、次のようにして値を取得します。

```cpp
void XXAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
    //一度float型として受けます。
    float val = *param1;
    DBG("デバッグ表示:" << val);
}
```

###２．エディタ側(XXAudioProcessorEditorクラス)の設定
GUIのスライダーにAPVTSのパラメータを紐づけます。

####1)XXAudioProcessorEditorクラスの定義
ここから「PluginEditor.h」ファイルです。

```cpp
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」ファイルです。

```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関数でスライダーを任意の場所に配置します。

```cpp
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関数に追記をします。

```cpp
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)にて、初期値をコンボボックスに初期化する必要があるようでした。

```cpp
XXAudioProcessorEditor::XXAudioProcessorEditor (XXAudioProcessor& p, juce::AudioProcessorValueTreeState& vts, juce::UndoManager& um)
//省略
{
     testComboBox.setSelectedId(*(vts.getRawParameterValue("param2")));
```

保存されたパラメータから値を取得するためには、APVTSクラスのgetRawParameterValue関数が利用できます。引数には、パラメータＩＤを指定します。

SliderAttachmentでは初期値が反映されますが、ComboboxAttachmenntでは反映されないという点に注意が必要でした。

##アンドゥ、リドゥマネージャ
APVTSにUndoManagerクラスのオブジェクトを設定することで、パラメータのアンドゥ、リドゥができるようになります。
アンドゥ、リドゥは、UndoManagerクラスのオブジェクトのundo関数、redo関数を呼び出すことで実行されます。そのため、エディタ側でアンドゥ、リドゥボタンを追加して、ボタンのハンドラにundo関数、redo関数を実装してアンドゥ、リドゥができるような実装を行ってみます。
###プロセッサ側の設定
PluginProcessor.hのXXAudioProcessorクラスのpublicなメンバとしてUndoManagerクラスのメンバを準備しました。

```cpp
class XXAudioProcessor  : public juce::AudioProcessor
{
public:
//中略
    //[1]アンドゥマネージャークラスのメンバを追加しました。
    juce::UndoManager undoManager;
```

PluginProcessor.cppのXXAudioProcessorクラスのコンストラクタ、APVTSの初期化時に、アンドゥマネージャクラスを渡します。

```cpp
    , parameters(*this, &undoManager, juce::Identifier("APVTSTutorial"),
    　　//第二引数のnullptrを↑に変更します。
```

createEditor関数で、エディタ側にundoManagerを渡します。

```cpp
juce::AudioProcessorEditor* XXAudioProcessor::createEditor()
{
    //第三引数にundoManagerを渡します。
    return new TheDelayAudioProcessorEditor (*this, parameters, undoManager);
}
```

###エディタ側の設定
PluginEditor.hファイル、XXAudioProcessorEditorクラスです。
[1]、コンストラクタで、プロセッサ側のAPVTSオブジェクトの参照を受け取ります。そして、[2]で、UndoManagerクラスの参照と、TextButtonを２つ追加します。

```cpp
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クラスのコンストラクタです。

```cpp
//[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関数でボタンを配置しました。

```cpp
void TheDelayAudioProcessorEditor::resized()
{
//省略

    //任意の座標に配置します。
    undoButton.setBounds(10, 50, 50, 30);
    redoButton.setBounds(60, 50, 50, 30);
}
```

##そのほかの使い方
###スライダーの桁数と単位をAPVTSで設定する
Sliderクラスの数値の小数点以下を調整するsetNumDecimalPlacesToDisplay()関数の効果がみられなかったので、調査した結果、APVTSのパラメータ初期化時に、桁数、単位を指定できることが分かりました。

```cpp
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の範囲のパラメータを４項目のコンボボックスに紐づけると、パラメータの変化は次のようになります。（小数点以下は実際動作させると、四捨五入の挙動をしました）

![fig1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2241114/9abd3765-87b1-aafa-690f-0af93654e3c5.png)


そのため、コンボボックスの項目番号をパラメータとしたい場合は、APVTSをコンボボックスに登録する数値の範囲とすると一致します。

例として、４個の項目をもつコンボボックスを、パラメータの値が１～４となるような設定を記載します。

APVTSのパラメータのAudioParameterInt型で１～４の値となるようなパラメータを設定します。（PluginProcessor.cpp）

```cpp
std::make_unique<juce::AudioParameterInt>("param3",  // ID
                                          "Param3",  // name
                                          1.0f,  //最小値
                                          4.0f,  //最大値
                                          3.0f)  //デフォルト
        })
```

※AudioProcessorIntのパラメータであっても値を格納する変数はfloatのstd::atomicで受けます。getRawParameterValueがfloatのstd::atomicを返すためです。

コンボボックスの初期化時に、項目を４つ追加（addItem関数）したとします。（PluginEditor.cpp）

```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);//デフォルトは３を選択状態。
```

この設定の場合、コンボボックスの１項目～４項目目の値がパラメータの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/