チュートリアル:プラグイン状態の保存と読み込み
プラグインパラメータの自動管理。パラメータの保存とアクセスが簡単になり、特に効果的なユーザーインターフェースの構築がはるかに容易になります。
レベル: 中級
プラットフォーム: Windows, macOS, Linux
クラス: AudioProcessorValueTreeState, ValueTree, XmlElement
はじめに
このチュートリアルのデモプロジェクトをこちらからダウンロードしてください:PIP | ZIP。プロジェクトを解凍し、最初のヘッダファイルをProjucerで開いてください。
この手順でサポートが必要な場合は、チュートリアル:Projucer Part 1: Projucerを始めようを参照してください。
また、JUCEを使用してオーディオプラグインをビルドし、お好みのオーディオホスト(デジタルオーディオワークステーションなど)にロードする方法を理解しておく必要があります。入門についてはチュートリアル:基本的なAudio/MIDIプラグインの作成 Part 1: セットアップを参照してください。オーディオプロセッサパラメータの入門として、チュートリアル:プラグインパラメータの追加も読んでおくことをお勧めします。
デモプロジェクト
デモプロジェクトはJUCE/examples/Pluginsディレクトリにある_GainPlugin_プロジェクトを大まかにベースにしています。このプラグインは単一のパラメータを使用して入力信号のゲインを変更します。これに加えて、入力信号の位相を反転させる位相反転パラメータも持っています。
ゲインプロセッサ
TutorialProcessorクラスのコードのほとんどは、Audio Plug-Inプロジェクトテンプレートを使用したときにProjucerによって生成されるものと同じです。簡略化のため、プロセッサコードを.cppと.hファイルに分割するのではなく、単一の.hファイルにまとめています。プロセッサのエディタはGenericEditorクラスにあります。
プラグインのパラメータ管理にAudioProcessorValueTreeStateクラスを使用することには、いくつかの利点があります:
- ValueTreeクラスは本質的にアンドゥサポートを提供します。
- ValueTreeオブジェクトは、すでに(XMLへの)シリアライズとデシリアライズをサポートしています。
- ValueTreeオブジェクトにはリスナーをアタッチできます。これにより、AudioProcessorValueTreeStateクラスはスライダーやボタンにほぼ自動的に接続して、UIとプロセッサの状態をスレッドセーフな方法で最新の状態に保つことができます。
AudioProcessorValueTreeStateオブジェクトを使用するには、プロセッサクラスに1つ保存できます:
private:
//==============================================================================
juce::AudioProcessorValueTreeState parameters;
AudioProcessorValueTreeStateオブジェクトを別の場所に保存することもできますが、各AudioProcessorValueTreeStateオブジェクトが以下の条件を満たすよう注意する必要があります:
- 1つのプロセッサにのみアタッチされている
- プロセッサと同じライフタイムを持つ(相互に依存関係があるため)
AudioProcessorValueTreeStateオブジェクトをプロセッサクラスに保存すると、これらの要件を満たしていることを確認しやすくなります。
AudioProcessorValueTreeStateコンストラクタには、アタッチされるAudioProcessorサブクラスへの参照、UndoManagerオブジェクトへのポインタ、ValueTree用のIdentifier、および管理するパラメータを含むAudioProcessorValueTreeState::ParameterLayoutが必要です。
AudioProcessorValueTreeState (AudioProcessor& processorToConnectTo,
UndoManager* undoManagerToUse,
const juce::Identifier& valueTreeType,
ParameterLayout parameterLayout);
この場合、このチュートリアルではアンドゥサポートを実装しないため、UndoManagerオブジェクトにはnullptr値を使用します。nullptr値は、アンドゥサポートを使用しないことを示します。
パラメータの設定
AudioProcessorValueTreeStateのAudioProcessorValueTreeState::ParameterLayoutパラメータには、プラグインのパラメータが含まれます。AudioProcessorValueTreeStateはRangedAudioParameterから派生した任意のパラメータを管理でき、AudioProcessorValueTreeState::ParameterLayoutコンストラクタは可変数のRangedAudioParameterサブクラスまたはRangedAudioParametersを含むAudioProcessorParameterGroupsを受け取ることができます。
パラメータとグループはAPVTSが所有権を取得するため、std::unique_ptrを使用して渡されます。
JUCEの組み込みパラメータ型、つまりチュートリアル:プラグインパラメータの追加で使用したものと同じものはRangedAudioParameterのサブクラスであるため、ここでも使用できます。
TutorialProcessor()
: parameters (*this, nullptr, juce::Identifier ("APVTSTutorial"),
{
std::make_unique<juce::AudioParameterFloat> ("gain", // parameterID
"Gain", // parameter name
0.0f, // minimum value
1.0f, // maximum value
0.5f), // default value
std::make_unique<juce::AudioParameterBool> ("invertPhase", // parameterID
"Invert Phase", // parameter name
false) // default value
})
{
パラメータをAudioProcessorValueTreeStateに追加すると、アタッチされたAudioProcessorにも自動的に追加されます。
_パラメータID_は、このパラメータの一意の識別子である必要があります。これは変数名のようなものと考えてください。英数字とアンダースコアを含めることができますが、スペースは使用できません。_パラメータ名_は、画面に表示される名前です。
ゲイン処理の実行
信号のクリックを避けるため、ゲインの変化と信号位相の変化をスムーズにします。これを行うには、プロセッサに前回計算されたゲイン値を保存します[1]:
private:
//==============================================================================
juce::AudioProcessorValueTreeState parameters;
float previousGain; // [1]
std::atomic<float>* phaseParameter = nullptr;
std::atomic<float>* gainParameter = nullptr;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TutorialProcessor)
};
コンストラクタの最後でパラメータへのポインタを保存し、後で参照解除できるようにします:
phaseParameter = parameters.getRawParameterValue ("invertPhase");
gainParameter = parameters.getRawParameterValue ("gain");
変更はTutorialProcessor::prepareToPlay()関数で初期化されます:
void prepareToPlay (double, int) override
{
auto phase = *phaseParameter < 0.5f ? 1.0f : -1.0f;
previousGain = *gainParameter * phase;
}
ここでは、位相反転係数(+1または-1)を計算し、これをゲインで乗算して、最初の処理コールバックに備えます。AudioProcessorValueTreeState::getRawParameterValue()関数を使用して、パラメータ値を表すfloat値へのポインタを取得していることがわかります。これを参照解除して実際の値を取得します。処理はTutorialProcessor::processBlock()関数で実行されます:
void processBlock (juce::AudioSampleBuffer& buffer, juce::MidiBuffer&) override
{
auto phase = *phaseParameter < 0.5f ? 1.0f : -1.0f;
auto currentGain = *gainParameter * phase;
if (juce::approximatelyEqual (currentGain, previousGain))
{
buffer.applyGain (currentGain);
}
else
{
buffer.applyGainRamp (0, buffer.getNumSamples(), previousGain, currentGain);
previousGain = currentGain;
}
}
ここでは、値が変更されていない場合は単純に一定のゲインを適用し、値が変更されている場合はゲインランプを適用してから、次回のためにpreviousGain値を更新していることがわかります。
パラメータの保存と取得
オーディオ処理のルーチンを提供することに加えて、プラグインの_全体の_状態をメモリブロックに保存および取得するメソッドも提供する必要があります。これにはすべてのパラメータの現在の値が含まれる必要がありますが、必要に応じて他の状態情報も含めることができます(例えば、プラグインがファイルを扱う場合、ファイルパスを保存することがあります)。
AudioProcessorValueTreeStateオブジェクトを使用してプラグインの状態を保存すると、ValueTreeオブジェクトはXMLとの間で簡単に変換できるため、これが非常にシンプルになります。
AudioProcessor::getStateInformation()コールバックは、プラグインに状態をMemoryBlockオブジェクトに保存するよう要求します。ValueTreeオブジェクト経由でXMLを使用してこれを行うコードは以下のようにシンプルです:
void getStateInformation (juce::MemoryBlock& destData) override
{
auto state = parameters.copyState();
std::unique_ptr<juce::XmlElement> xml (state.createXml());
copyXmlToBinary (*xml, destData);
}
作成されるXmlElementオブジェクトは「APVTSTutorial」という_タグ名_を持ち、これは先ほどValueTreeオブジェクトの初期化に使用しました。
XMLからの状態の復元もほぼ同様に簡単です:
void setStateInformation (const void* data, int sizeInBytes) override
{
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));
}
ここでは安全のためにエラーチェックを含めています。また、XML要素の_タグ名_を検査することで、ValueTreeで生成されたXMLがプラグインに対して正しいValueTree_タイプ_であることを確認しています。
ゲインエディタ
プロジェクトのGenericEditorクラスを見てみましょう。GenericEditorクラスの宣言が非常にシンプルであることに気づくかもしれません:
class GenericEditor : public juce::AudioProcessorEditor
{
public:
スライダーとボタンの操作に応答するために、Slider::ListenerクラスとButton::Listenerクラスを継承する必要があると思うかもしれません。しかし、これもまたAudioProcessorValueTreeStateクラスを使用する利点の1つです。代わりに、AudioProcessorValueTreeStateクラス内の_アタッチメント_クラスを使用できます。
コンポーネントアタッチメント
実際、これらのクラスの名前は非常に長くなる可能性があるため、必要な各アタッチメントクラスに対してtypedefを含めています:
typedef juce::AudioProcessorValueTreeState::SliderAttachment SliderAttachment;
typedef juce::AudioProcessorValueTreeState::ButtonAttachment ButtonAttachment;
GenericEditorクラスには、スライダー、トグルボタン、およびこれらのアタッチメントオブジェクトを含むいくつかのメンバーが含まれています:
private:
juce::AudioProcessorValueTreeState& valueTreeState;
juce::Label gainLabel;
juce::Slider gainSlider;
std::unique_ptr<SliderAttachment> gainAttachment;
juce::ToggleButton invertButton;
std::unique_ptr<ButtonAttachment> invertAttachment;
};
また、AudioProcessorValueTreeStateオブジェクトを参照する必要があるため、それへの参照も保持しています。
GenericEditorクラスのコンストラクタはこれらのオブジェクトをセットアップします:
GenericEditor (juce::AudioProcessor& parent, juce::AudioProcessorValueTreeState& vts)
: AudioProcessorEditor (parent),
valueTreeState (vts)
{
gainLabel.setText ("Gain", juce::dontSendNotification);
addAndMakeVisible (gainLabel);
addAndMakeVisible (gainSlider);
gainAttachment.reset (new SliderAttachment (valueTreeState, "gain", gainSlider));
invertButton.setButtonText ("Invert Phase");
addAndMakeVisible (invertButton);
invertAttachment.reset (new ButtonAttachment (valueTreeState, "invertPhase", invertButton));
setSize (paramSliderWidth + paramLabelWidth, juce::jmax (100, paramControlHeight * 2));
}
これはプロセッサのTutorialProcessor::createEditor()関数によって呼び出されます:
juce::AudioProcessorEditor* createEditor() override { return new GenericEditor (*this, parameters); }
スライダーの値範囲を設定する必要がないことに気づくかもしれません。これはSliderAttachmentクラスによって自動的に行われます。_アタッチメント_コンストラクタにAudioProcessorValueTreeState、パラメータID、およびアタッチするSliderオブジェクトを渡すだけです。
ButtonAttachmentクラスでは、引き続きボタン名を提供する必要があります。(また、ComboBoxオブジェクトにアタッチできるAudioProcessorValueTreeState::ComboBoxAttachmentクラスでは、ComboBoxを手動でポピュレートする必要があります。)
演習:プラグインを2チャンネルメインバスのみをサポートするように変更し、左右のチャンネルをオプションでスワップできるチャンネルスワップパラメータを追加してください。- 同様に2チャンネルのみのプラグインを使用して、左右のチャンネルレベルをバランスするバランスパラメータを追加してください。
プログラムによるパラメータの追加
AudioProcessorValueTreeStateに対してaddを呼び出すことで、プログラム的にパラメータ(またはAudioProcessorParameterGroups)を追加することもできます。以下にその方法の例を示します:
juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout()
{
juce::AudioProcessorValueTreeState::ParameterLayout params;
for (int i = 1; i < 9; ++i)
params.add (std::make_unique<AudioParameterInt> (String (i), String (i), 0, i, 0));
return params;
}
YourAudioProcessor()
: parameters (*this, nullptr, "PARAMETERS", createParameterLayout())
{
//...
非推奨メソッド
JUCEバージョン5.4以前では、AudioProcessorValueTreeStateにパラメータを追加する唯一の方法は、現在は非推奨となっている多くの関数パラメータを持つcreateAndAddParameterメソッドを使用することでした。
以前は次のようなコード
createAndAddParameter (paramID1, paramName1, ...);
は次のように書き換えられます
using Parameter = juce::AudioProcessorValueTreeState::Parameter;
createAndAddParameter (std::make_unique<Parameter> (paramID1, paramName1, ...));
しかし、このチュートリアルで説明した新しいAudioProcessorValueTreeStateコンストラクタを使用する方がはるかに良いアプローチです:
using Parameter = AudioProcessorValueTreeState::Parameter;
YourAudioProcessor()
: apvts (*this, nullptr, "PARAMETERS", { std::make_unique<Parameter> (paramID1, paramName1, ...), std::make_unique<Parameter> (paramID2, paramName2, ...), ... })
まとめ
このチュートリアルでは、AudioProcessorValueTreeStateクラスを紹介し、以下のことに役立つ方法を示しました:
- プラグインパラメータの管理。
- XMLを使用したプラグイン状態の保存と取得。
- スレッドセーフな方法でプラグインパラメータをボタンやスライダーに接続。