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));