メインコンテンツまでスキップ

チュートリアル:プラグイン状態の保存と読み込み

📚 Source Page

プラグインパラメーターの自動管理パラメーターの保存とアクセスが簡単になり、特に効果的なユーザーインターフェースの構築がより簡単になります。

レベル:中級

プラットフォーム 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 オブジェクトへのポインタ、 ValueTreeIdentifier 、管理するパラメータを含む AudioProcessorValueTreeState::ParameterLayout が必要です。

AudioProcessorValueTreeState (AudioProcessor& processorToConnectTo,
UndoManager* undoManagerToUse,
const juce::Identifier& valueTreeType,
ParameterLayout parameterLayout);

このチュートリアルではアンドゥを実装しないので、UndoManager オブジェクトにはnullptr値を使います。nullptr値は、アンドゥのサポートを使用しないことを示します。

パラメータの設定

AudioProcessorValueTreeStateAudioProcessorValueTreeState::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 オブジェクトを渡すだけです。

注記

私たちはまだ Slider オブジェクトの所有権を保持しています。 アタッチメントクラスが Slider オブジェクトと同じライフタイムを持っていることを確認してください。

ButtonAttachment クラスでは、ボタン名を指定する必要があります。 (また、AudioProcessorValueTreeState::ComboBoxAttachment クラスは、ComboBox オブジェクトにアタッチできますが、ComboBox に手動で入力する必要があります)

エクササイズ
  • プラグインを2チャンネルのメインバスのみに対応するように変更し、オプションで左右のチャンネルを入れ替えることができるチャンネルスワップパラメーターを追加します。
  • 再び2チャンネルのみのプラグインを使用し、左右チャンネルのレベルをバランスさせるBalanceパラメータを追加します。

プログラムでパラメータを追加する

また、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を使用したプラグイン状態の保存と取得。
  • スレッドセーフな方法でプラグインパラメータをボタンやスライダに接続する。

関連項目