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

チュートリアル:プラグインパラメータの追加

📚 Source Page

オーディオプラグインにパラメータを追加して、デジタルオーディオワークステーションからの制御や自動化を可能にしましょう。オーディオパラメータを使ったオーディオ処理の方法と、それらのユーザーインターフェースの作成方法を学びます。

レベル: 初級
プラットフォーム: Windows, macOS, Linux
クラス: AudioParameterFloat, AudioParameterBool, GenericAudioProcessorEditor

はじめに

このチュートリアルのデモプロジェクトをこちらからダウンロードしてください:PIP | ZIP。プロジェクトを解凍し、最初のヘッダファイルをProjucerで開いてください。

この手順でサポートが必要な場合は、チュートリアル:Projucer Part 1: Projucerを始めようを参照してください。

また、JUCEを使用してオーディオプラグインをビルドし、お好みのオーディオホスト(デジタルオーディオワークステーション --- DAWとも呼ばれます)にロードする方法を理解しておく必要があります。入門についてはチュートリアル:基本的なAudio/MIDIプラグインの作成 Part 1: セットアップを参照してください。

デモプロジェクト

デモプロジェクトはJUCE/examples/Pluginsディレクトリにある_GainPlugin_プロジェクトをベースにしています。このプラグインは、単一のパラメータを使用して入力信号のゲインを変更するだけのシンプルなものです。

The gain plug-in UI in Logic Pro X
The gain plug-in UI in Logic Pro X

ゲインプロセッサ

TutorialProcessorクラスのコードのほとんどは、Audio Plug-Inプロジェクトテンプレートを使用したときにProjucerによって生成されるものと同じです。簡略化のため、プロセッサコードを.cpp.hファイルに分割するのではなく、単一の.hファイルにまとめています。

パラメータの設定

プロセッサには、各パラメータ用のオーディオパラメータメンバーを保存する必要があります。ここでは1つだけです:

private:
//==============================================================================
juce::AudioParameterFloat* gain;

//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TutorialProcessor)
};

プロセッサは、コンストラクタでプラグインに必要なパラメータを確保し追加する必要があります。このシンプルな例では、セットアップするパラメータは1つだけです:

TutorialProcessor()
{
addParameter (gain = new juce::AudioParameterFloat ("gain", // parameterID
"Gain", // parameter name
0.0f, // minimum value
1.0f, // maximum value
0.5f)); // default value
}
ヒント

基底クラス(AudioProcessor)がパラメータオブジェクトの所有権を取得するため、派生プロセッサクラスではパラメータを格納するために生のポインタを使用します。これは、基底クラスが派生クラスの後に破棄されることが確実であるため安全です。さらに、プロセッサのエディタコンポーネントはプロセッサオブジェクトよりも先に削除されると想定できます。

_パラメータID_は、このパラメータの一意の識別子である必要があります。これは変数名のようなものと考えてください。英数字とアンダースコアを含めることができますが、スペースは使用できません。_パラメータ名_は、画面に表示される名前です。

これに加えて、AudioParameterFloatクラスでは、パラメータが表現できる値の範囲を指定できます。AudioParameterFloatクラスには、代わりにNormalisableRange<float>オブジェクトを使用できる別のコンストラクタもあります。JUCEは、_一部の_ターゲットプラグインAPIの制限により、すべてのパラメータ値を[0, 1]の範囲で保存します。上記のコードは次のように書き換えることができます:

addParameter (gain = new juce::AudioParameterFloat ("gain", // parameter ID
"Gain", // parameter name
juce::NormalisableRange<float> (0.0f, 1.0f), // parameter range
0.5f)); // default value

この例では少し意味がないように見えるかもしれません(パラメータ範囲がすでに[0, 1]の範囲にあるため!)が、NormalisableRange<float>オブジェクトを使用すると、_スキューファクター_も指定できます。これは、プラグインが周波数や時間のプロパティを表すパラメータを使用する必要がある場合に特に便利です。これらは非線形マッピングを使用して表現されることが多いためです。

ヒント

AudioParameterFloatクラスには、値をテキストに変換したりその逆を行ったりするために指定できるオプションのラムダ関数もあります。これは、パラメータの値を文字列として表示したり、ユーザーが値を入力できるようにしたい場合に特に便利です。

ゲイン処理の実行

パラメータを作成して追加したら、プラグインはこれらのパラメータオブジェクトと対話できます。ここでは、TutorialProcessor::processBlock()関数で_ゲイン_値を取得するだけです:

void processBlock (juce::AudioSampleBuffer& buffer, juce::MidiBuffer&) override
{
buffer.applyGain (*gain);
}

AudioSampleBuffer::applyGain()関数は、バッファ内のすべてのチャンネルのすべてのサンプルにゲイン値を適用します。

これは、オーディオパラメータクラスを使用する際のイディオムを示しています:パラメータへのポインタを逆参照してパラメータ値を取得します。この場合、AudioParameterFloatを使用しているため、floatが取得されます。

他のAudioParameter_XXX_クラスも同様に動作します:

パラメータの保存と取得

オーディオ処理のルーチンを提供することに加えて、プラグインの_全体の_状態をメモリブロックに保存および取得するメソッドも提供する必要があります。これにはすべてのパラメータの現在の値が含まれる必要がありますが、必要に応じて他の状態情報も含めることができます(例えば、プラグインがファイルを扱う場合、ファイルパスを保存することがあります)。

シンプルなゲインプラグインでは、保存するものは1つだけです:ゲイン値そのものです。これを保存するには、浮動小数点値をバイナリ形式で書き込むだけです:

void getStateInformation (juce::MemoryBlock& destData) override
{
juce::MemoryOutputStream (destData, true).writeFloat (*gain);
}

AudioProcessor::getStateInformation()コールバックは、プラグインの状態を保存する必要があるときに呼び出されます。例えば、ユーザーがDAWプロジェクトを保存したり、プリセットを保存したりするとき(一部のDAWでは)に発生します。この関数に渡されるMemoryBlockオブジェクトには、好きなものを入れることができます。

AudioProcessor::setStateInformation()関数は逆のことを行う必要があります:メモリ位置からデータを読み取り、プラグインの状態を復元します:

void setStateInformation (const void* data, int sizeInBytes) override
{
*gain = juce::MemoryInputStream (data, static_cast<size_t> (sizeInBytes), false).readFloat();
}
注記

演習:ゲインパラメータをバイナリ形式ではなく文字列として保存してみてください。

ゲインプロセッサの改善

このゲインプロセッサには、いくつかの改善を加えることができます:

  • ゲインを変更すると信号に不連続が生じ、ゲインを素早く変調するとクリックノイズとして聞こえることがあります。
  • XMLを使用するとプラグインの状態の保存がより便利になります。

ゲイン変更のスムージング

AudioSampleBufferクラスを使用すると、バッファのブロックサイズ全体にわたってゲイン変更を簡単にランプできます。これを行うには、_前回の_オーディオコールバックからのゲインパラメータの値を保存する必要があります。まず、TutorialProcessorクラスにメンバ変数を追加します[1]:

private:
//==============================================================================
juce::AudioParameterFloat* gain;
float previousGain; // [1]

//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TutorialProcessor)
};

次に、この値がTutorialProcessor::preparePlay()関数で初期化されることを確認します:

void prepareToPlay (double, int) override
{
previousGain = *gain;
}

最後に、TutorialProcessor::processBlock()関数を変更してゲインランプを実行します:

void processBlock (juce::AudioSampleBuffer& buffer, juce::MidiBuffer&) override
{
auto currentGain = gain->get();

if (juce::approximatelyEqual (currentGain, previousGain))
{
buffer.applyGain (currentGain);
}
else
{
buffer.applyGainRamp (0, buffer.getNumSamples(), previousGain, currentGain);
previousGain = currentGain;
}
}

ここでは、値が変更されていない場合は単純に一定のゲインを適用し、値が変更されている場合はゲインランプを適用してから、次回のためにpreviousGain値を更新していることがわかります。

ヒント

このプラグインの修正版のソースコードは、デモプロジェクトのAudioParameterTutorial_02.hファイルにあります。

注記

演習:スムージングアルゴリズムを処理ブロックサイズに依存しないように変更してください。

XMLを使用したプロセッサ状態の保存

プラグインの状態をバイナリ形式で保存すると、メモリとストレージスペースの使用量が少なくなります。しかし、XMLやJSONなどの形式を使用する方が便利なことが多いです。これによりデバッグが容易になり、保存された状態情報をプラグインの将来のバージョンと互換性を持たせることも簡単になります。特に、XMLを使用すると以下のことが容易になります:

  • 情報ブロックに見つからないパラメータをデフォルト値に設定する
  • プラグインの異なるバージョン間の前方互換性と後方互換性を処理するために、情報ブロックにバージョン情報を含める

ゲインプラグインの状態をXMLで保存するには、次のようにします:

void getStateInformation (juce::MemoryBlock& destData) override
{
std::unique_ptr<juce::XmlElement> xml (new juce::XmlElement ("ParamTutorial"));
xml->setAttribute ("gain", (double) *gain);
copyXmlToBinary (*xml, destData);
}

AudioProcessor::copyXmlToBinary()関数は、XMLをバイナリblobに変換する便利なヘルパー関数です。状態を取得するには、逆のことを行います:

void setStateInformation (const void* data, int sizeInBytes) override
{
std::unique_ptr<juce::XmlElement> xmlState (getXmlFromBinary (data, sizeInBytes));

if (xmlState.get() != nullptr)
if (xmlState->hasTagName ("ParamTutorial"))
*gain = (float) xmlState->getDoubleAttribute ("gain", 1.0);
}

AudioProcessor::getXmlFromBinary()関数は、AudioProcessor::copyXmlToBinary()関数で作成されたバイナリデータをXMLに変換します。

重要なのは、ここでエラーチェックが行われていることです。情報ブロックがXMLではない場合、関数は何もしません。また、タグ名「ParamTutorial」をチェックし、この名前が見つかった場合のみ処理を続行します。gainパラメータが見つからない場合、ゲイン値はデフォルトで1.0になります。バージョン情報を追加するには、この目的のための別の属性を追加するだけです。そうすれば、さらなるエラーチェックにより、状態情報の異なるバージョンを処理できます。

ヒント

このプラグインの修正版のソースコードは、デモプロジェクトのAudioParameterTutorial_03.hファイルにあります。

位相反転パラメータの追加

ゲインプラグインに_位相反転_パラメータを追加しましょう!

ブールパラメータの追加

まず、TutorialProcessorクラスにAudioParameterBool*メンバを追加します[2]:

private:
//==============================================================================
juce::AudioParameterFloat* gain;
juce::AudioParameterBool* invertPhase; // [2]

float previousGain;

//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TutorialProcessor)
};

次に、TutorialProcessorコンストラクタでパラメータを確保して追加する必要があります[3]:

TutorialProcessor()
{
addParameter (gain = new juce::AudioParameterFloat ("gain", "Gain", 0.0f, 1.0f, 0.5f));
addParameter (invertPhase = new juce::AudioParameterBool ("invertPhase", "Invert Phase", false)); // [3]
}

もちろん、ブールパラメータには指定可能な範囲はなく、デフォルト値のみです。TutorialProcessor::getStateInformation()関数を更新する必要があります[4]:

void getStateInformation (juce::MemoryBlock& destData) override
{
std::unique_ptr<juce::XmlElement> xml (new juce::XmlElement ("ParamTutorial"));
xml->setAttribute ("gain", (double) *gain);
xml->setAttribute ("invertPhase", *invertPhase); // [4]
copyXmlToBinary (*xml, destData);
}

そしてTutorialProcessor::setStateInformation()関数も更新します[5]:

void setStateInformation (const void* data, int sizeInBytes) override
{
std::unique_ptr<juce::XmlElement> xmlState (getXmlFromBinary (data, sizeInBytes));

if (xmlState.get() != nullptr)
{
if (xmlState->hasTagName ("ParamTutorial"))
{
*gain = (float) xmlState->getDoubleAttribute ("gain", 1.0);
*invertPhase = xmlState->getBoolAttribute ("invertPhase", false); // [5]
}
}
}

オーディオ処理コードを追加する必要があります:

void processBlock (juce::AudioSampleBuffer& buffer, juce::MidiBuffer&) override
{
auto phase = *invertPhase ? -1.0f : 1.0f; // [6]
auto currentGain = *gain * phase; // [7]

if (juce::approximatelyEqual (currentGain, previousGain))
{
buffer.applyGain (currentGain);
}
else
{
buffer.applyGainRamp (0, buffer.getNumSamples(), previousGain, currentGain);
previousGain = currentGain;
}
}

ここで注意すべき点:

  • [6]:invertPhaseパラメータの状態に応じて+1または-1を選択します。
  • [7]:これにgainパラメータの値を掛けます。
  • この関数の残りのコードは、スムージングテクニックを含めて同じです。

最後に、previousGain値をTutorialProcessor::prepareToPlay()関数で初期化する必要があります:

void prepareToPlay (double, int) override
{
auto phase = *invertPhase ? -1.0f : 1.0f;
previousGain = *gain * phase;
}

まとめ

このチュートリアルでは、AudioProcessorクラス内でオーディオパラメータを使用することについて学びました。特に以下について探求しました:

  • プロセッサの可変パラメータを表すAudioParameterFloatオブジェクトの作成。
  • AudioParameterFloatオブジェクトの値を使用したオーディオ処理の制御。
  • プロセッサの状態情報へのパラメータデータの保存と取得。
  • オンまたはオフの状態にあるパラメータを表すAudioParameterBoolオブジェクトの使用。

関連項目