カテゴリー
C++ JUCE

JUCE AudioProcessorValueTreeState

JUCEの初歩tutorialシリーズではあまり取り上げられない内容ですが、Plug-Inの色々なパラメータを管理するためにAudioProcessorValueTreeState(APVTS)はとても重要です。

APVTSを理解するにはまず、APVTSを使用しない場合どのようにパラメータを登録し、取り出して使うかを理解する必要があります。登録するにはAudioProcessorのconstructorでイニシャライズし、使う時はgetParameters( )でパラメータのポインタのリストをゲットします。

#define LOWER_MIDI_BOUND_ID "lowerMidiBound"

{
  addParameter(new juce::AudioParameterInt(juce::ParameterID{LOWER_MIDI_BOUND_ID, 1}, LOWER_MIDI_BOUND, 21, 108, 21)); 

}

auto& parameters = getParameters();
float gain = parameters[0]->getValue();

この方法では数個のパラメータでは管理出来るかも知れませんが、パラメータ数が膨大になると管理が難しくなります。そこでAPVTSの出番です。

APVTSはPluginProcessorのprivate変数として宣言し、constructorでイニシャライズします。

// initializer
AudioProcessorValueTreeState (
  AudioProcessor& processorToConnectTo,
  UndoManager* undoManagerToUse,
  const juce::Identifier& valueTreeType,
  ParameterLayout parameterLayout);
// PluginProcessor.h
class ChordGeniusAudioProcessor: public juce::AudioProcessor 
{
public:

  // 省略

  juce::AudioProcessorValueTreeState& getValueTreeState() { return valueTreeState; }

private:
  juce::AudioProcessorValueTreeState valueTreeState;
}

// PluginProcessor.cpp
// constructor
ChordGeniusAudioProcessor::ChordGeniusAudioProcessor()
  : // 省略
    valueTreeState(*this, nullptr, ProjectInfo::projectName, ParameterHelper::createParameterLayout())
{}

ParameterLayoutを供給するHelperクラスを作っておけば便利です。

#define LOWER_CONTROL_ID "LOWER_CONTROL_ID"

class ParameterHelper
{
public:
  ParameterHelper() = delete;

  static juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout()
  {
    juce::AudioProcessorValueTreeState::ParameterLayout layout;
   // ParameterIDs can't contain spaces
    
    auto lowerId = std::make_unique<juce::AudioParameterInt>(juce::ParameterID{LOWER_CONTROL_ID, 1}, LOWER_CONTROL_ID, 21, 108, 21);
    
    layout.add(std::move(lowerId));
    return layout;
  }
};

公式Tutorialによると、APVTSはひとつのAudioProcessorにのみattach出来る、とあります。また、APVTSにparameterを追加すると自動的にAudioProcessorに追加したことになる、とあります。

また公式Tutorial動画によると、AudioParameterとAPVTSのAttachmentクラスを使用することで、Audio ThreadとUI Thread間のThread Safeなプログラムを書くことが出来る、とあります。SliderインスタンスはSliderAttachmentインスタンスよりもあとにdeconstructされる必要があるため、宣言の順番は重要です。

// AudioProcessorEditor.h

private:
  // 省略
  juce::AudioProcessorValueTreeState& valueTreeState;
  juce::Slider slider;

std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> sliderAttachment;

// AudioProcessorEditor.cpp
// constructor 
 : valueTreeState(p.getValueTreeState())
{
  sliderAttachment = std::make_unique<juce::AudioProcessorValueState::SliderAttachment>(valueTreeState, LOWER_CONTROL_ID, slider);
}

ちなみに、公式TutorialではJUCEでは全てのAudioParameterは[0, 1]のレンジで保存される、とあります。JUCE stores all of the parameter values in the range [0, 1] as this is a limitation of some of the target plug-in APIs. 

Host(DAW)がプロジェクトをsave/loadする時は、Plug-Inの状態(state)もsave/loadします。その際、Plug-Inのstateのload時はHostが管理するメモリ領域へserialize、save時はメモリ領域からdeserializeされます。

プロジェクトをsaveする時、Hostが getStateInformation (juce::MemoryBlock& destData)をコールし、Plug-Inのstateをゲットします。

void ChordGeniusAudioProcessor::getStateInformation(juce::MemoryBlock& destData) {
  // copying State from apvts, then create XML from it.
  if (auto xmlState = valueTreeState.copyState().createXml()) 
  {
    // dereference from xmlState and copying it to MemoryBlock
    // human readable xml will allow debugging...
    copyXmlToBinary(*xmlState, destData);
  }
}

プロジェクトをloadする時、Hostが setStateInformation (const void* data, int sizeInByte)をコールしします。

void ChordGeniusAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
  if(auto xmlState = getXmlFromBinary (data, sizeInBytes)
  {
    if (xmlState->hasTagName(valueTreeState.state.getType())) {
    const auto newTree = juce::ValueTree::fromXml(*xmlState);
    valueTreeState.replaceState(newTree);
    }
  }
}

APVTSからパラメータをゲットする時は

// returns juce::Value type object
auto param = valueTreeState.getParameterAsValue("PARAMETER_ID");

パラメータに値をセットするときは

// need to cast to juce::var type in order for Host application to save and persist the plut-in state.

param.setValue(juce::var(newValue));