チュートリアル:プラグイン状態の保存と読み込み
プラグインパラメーターの自動管理パラメーターの保存とアクセスが簡単になり、特に効果的なユーザーインターフェースの構築がより簡単になります。
レベル:中級
プラットフォーム Windows, macOS, Linux
クラス:AudioProcessorValueTreeState, ValueTree, XmlElement,
はじめる
このチュートリアルのデモ・プロジェクトのダウンロードはこちらから:PIP | ZIP。 プロジェクトを解凍し、Projucerで最初のヘッダーファイルを開きます。
このステップでヘルプが必要な場合は、チュートリアルを参照してください:Projucerパート1:Projucerを使い始める。
また、JUCE を使ってオーディオプラグインを構築し、これをお好みのオーディオホスト(Digital Audio Workstation など)にロードする方法も知っておく必要があります。 基本的なAudio/MIDIプラグインを作る パート1:セットアップ を参照してください。 オーディオプロセッサーのパラメーターの入門として、チュートリアル:プラグインパラメータの追加 も読んでおくのが理想的です。
デモプロジェクト
このデモ・プロジェクトは、JUCE/examples/Plugins
ディレクトリにあるGainPluginプロジェクトに大まかに基づいています
このプラグインは、1つのパラメータを使って入力信号のゲインを変更します。
これに加えて、入力信号の位相を反転させる phase invert* パラメータも持っています。
ゲインプロセッサー
TutorialProcessor
クラスのコードのほとんどは、Audio Plug-In プロジェクト・テンプレートを使用したときにProjucerによって生成されるものと同じです。
簡単にするために、プロセッサーのコードを.cppファイルと.hファイルに分けるのではなく、1つの.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 サブクラスまたは RangedAudioParameterを含むAudioProcessorParameterGroupsを取ることができます。
パラメータとグループの所有権はAPVTSが持つため、パラメータとグループは std::unique_ptr
を使って渡されます。
JUCEの組み込みパラメータ型は、チュートリアルで使用したものと同じです:プラグイン・パラメータの追加で使用したものと同じものです。
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 にもパラメータが追加されます。
parameter ID は、このパラメータの一意な識別子でなければなりません。 英数字とアンダースコア、スペースは使用できません。 parameter name は、画面に表示される名前です。
ゲイン処理の実行
信号のクリックを避けるために、ゲインの変化と信号位相の変化を滑らかにします。 そのために、以前に計算したゲイン値をプロセッサに保存します[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 オブジェクトは、先ほど ValueTree オブジェクトの初期化に使用した「APVTSTutorial」というタグ名を持つ。
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()
関数によって呼び出されます:
スライダーの値域を設定する必要さえないことにお気づきでしょう。 これは SliderAttachment クラスによって自動的に行われます。 アタッチメントのコンストラクタに AudioProcessorValueTreeState 、パラメータID、アタッチする Slider オブジェクトを渡すだけです。
ButtonAttachment クラスでは、ボタン名を指定する必要があります。 (また、AudioProcessorValueTreeState::ComboBoxAttachment クラスは、ComboBox オブジェクトにアタッチできますが、ComboBox に手動で入力する必要があります)
- プラグインを2チャンネルのメインバスのみに対応するように変更し、オプションで左右のチャンネルを入れ替えることができるチャンネルスワップパラメーターを追加します。
- 再び2チャンネルのみのプラグインを使用し、左右チャンネルのレベルをバランスさせるBalanceパラメータを追加します。