AudioProcessorValueTreeStateのスライダーをデシベル表示にしたいです。
AudioProcessorValueTreeStateでどうやってパラメータをデシベルにするんだろうね
以前作成したゲインプラグインは、表示がデシベルではなく、より実用的なプラグインとするために、スライダーの音量表記をデシベルに変更できるか、挑戦してみました。スタンドアロンのアプリでデシベルスライダーを実装したことがありますので、その内容を流用しつつ、AudioProcessorValueTreeStateでのパラメータ管理によるデシベルへの変更の奮闘記録となります。
以前、JUCEの公式チュートリアルでゲインを調整するプラグインを作成しました。これをゲインプラグインと呼んで記事を記載していきます。本ブログでは以下の記事にて実装を行いました。
上記が基本的なゲインプラグインの実装記事で、以下の記事にて、AudioProcessorValueTreeStateクラスでパラメータ管理を行うようにリファクタリングを行いました。
AudioProcessorValueTreeStateを使ったリファクタリング
AudioProcessorValueTreeStateを使ったリファクタリング2
AudioProcessorValueTreeStateを使ったリファクタリング3
ちょっと前置きが長くなってしまいました^^;
※音量を扱うので、プログラムの実行の際は、出力の音量に気を付ける必要があります。
こんな人の役に立つかも
・JUCEプラグインのスライダーをデシベル表示したい人
・ゲインプラグインをデシベル表示したい人
・AudioProcessorValueTreeStateパラメータでデシベル単位のパラメータを管理したい人
プログラムを見直した点
Processorクラスのコンストラクタ
PluginProcessor.cppのコンストラクタで、パラメータの指定方法の変更と、音声の入出力設定を追記しました。
GainPluginAudioProcessor::GainPluginAudioProcessor()
: parameters(*this, nullptr, juce::Identifier("APVTSTutorial"),
{
std::make_unique<juce::AudioParameterFloat>("gain",
"Gain",
//[1]以下のように「NormalisableRange」に変更しました。
juce::NormalisableRange<float>(-100.0f, 0.0f),
-20.0f),
std::make_unique<juce::AudioParameterBool>("invertPhase",
"Invert Phase",
false)
}),
//[2]以下の入出力設定がないとprocessBlockが動作しないので追記しました。
AudioProcessor(BusesProperties()
#if ! JucePlugin_IsMidiEffect
#if ! JucePlugin_IsSynth
.withInput("Input", juce::AudioChannelSet::stereo(), true)
#endif
.withOutput("Output", juce::AudioChannelSet::stereo(), true)
#endif
)
[1]のように、NormalisableRangeでパラメータの上限下限を与えることができますので、こちらの方法に変更してみました。また、数値は以前0~1としていたので、デシベルに合わせた数値で-100.0f~0.0fとしています。初期値も-20.0fとしています。
[2]では、以前コメントアウトしてしまっていました。これがないと、processBlock関数の音声スレッドが始まらないです。入力ステレオ、出力ステレオという設定にしました。
※パラメータをデシベルに変更して、数値が大きくなったので、プログラムの実行は、processBlockの処理を変更してからにします。
Processorクラスの変更
processBlock関数
processBlock関数の内容を以下の通り修正しました。
void GainPluginAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
auto phase = *phaseParameter < 0.5f ? 1.0f : -1.0f;
float gainParam_local = *gainParameter;//[1]decibelToGainに渡すためにここでfloatに代入します。
auto currentGain = juce::Decibels::decibelsToGain(gainParam_local) * phase;//[2]ゲインに変換して計算します。
if (currentGain == previousGain)
{
buffer.applyGain(currentGain);
}
else
{
buffer.applyGainRamp(0, buffer.getNumSamples(), previousGain, currentGain);
previousGain = currentGain;
}
//DBG("gain_local:" << gainParam_local);
DBG("currentGain:" << currentGain);//[3]数値としてゲインを確認しました。
}
今までと違い、ProcessorクラスのAudioProcessorValueTreeStateが管理するパラメータをデシベル単位として、管理するため、processBlock内でゲイン値に変換して利用する必要が出てきます。
[1]ではポインタの値「*gainParameter」をローカルのfloat変数に代入して、次の行[2]でjuce::DecibelsクラスのdecibelsToGain関数に渡すようにしています。そして、currentGainを前回同様に計算しています。
[3]は、DBG関数でcurrentGainの値を可視化してゲイン値となっているかを確認しました。
AudioProcessorValueTreeStateでパラメータを管理する場合の値の変換タイミングの現状イメージを図でまとめてみました。(わかりにくくてすいません…)
今までの作成方法では、Editorでvolumeという変数に0~1数値(ゲイン値)を保持していましたが、これをProcessorで管理するようになったので、パラメータ自体の単位をデシベルにして、processBlockで利用する際にゲインに変換するようにしています。
Editorクラスの変更
GUIにスライダーコンポーネントなどを設定するGainPluginAudioProcessorEditorクラスの実装修正です。
コンストラクタ
ここは、いくつか検証をした点もコメントアウトとして追加しておきます。
AudioProcessorValueTreeStateを利用する際、[1]のように第一引数を「GainPluginAudioProcessor& p」とすると、音声処理がうまく動作しませんでした。(リングモジュレーションみたいな音の変化となりました^^;)
//GainPluginAudioProcessorEditor::GainPluginAudioProcessorEditor(GainPluginAudioProcessor& p , juce::AudioProcessorValueTreeState& vts)
// : AudioProcessorEditor(&p), audioProcessor(p)
// valueTreeState(vts)
//[1]Projucerで生成される↑「GainPluginAudioProcessor& p」とすると、音声処理がおかしくなった。
GainPluginAudioProcessorEditor::GainPluginAudioProcessorEditor(juce::AudioProcessor& parent, juce::AudioProcessorValueTreeState& vts)
: AudioProcessorEditor(parent),
valueTreeState(vts)
{
gainLabel.setText("Gain", juce::dontSendNotification);
addAndMakeVisible(gainLabel);
addAndMakeVisible(gainSlider);
//[2]gainSlider設定の試行錯誤です。
//スライダーの範囲、初期値はparameterと紐づけることで完了しましたので、以下は不要です。
//gainSlider.setRange(-100.0, -12.0);
/*gainSlider.onValueChange = [this]
{
//以前は、スライダーのコールバックでvolume変数にデシベルからゲインに値変更。
//が、今回はcurrentGainがprocessor側なので、パラメータ自体をデシベルにして
//processBlock関数ないで利用時にゲインに変更して利用します。
//volume = juce::Decibels::decibelsToGain((float)gainSlider.getValue());
};*/
//gainSlider.setValue(-15.0f);//[2-1]この位置ではresetで初期値が上書きされます。
//===
//gainSlider.setSliderStyle(juce::Slider::Rotary);
gainAttachment.reset(new SliderAttachment(valueTreeState, "gain", gainSlider));
//gainSlider.setSkewFactorFromMidPoint(-20.0);//resetを行ってからでないと例外がでるが、skewが効いていない模様で、検証中です。
//gainSlider.setValue(-15.0f);//[2-1]reset後のここの位置だとこれで初期化がでます。
invertButton.setButtonText("Invert Phase");
addAndMakeVisible(invertButton);
invertAttachment.reset(new ButtonAttachment(valueTreeState, "invertPhase", invertButton));
setSize(400, 300);
}
[2]では、スライダーの初期化について、いろいろと試行錯誤を行いました。
AudioProcessorValueTreeStateによって、EditorクラスからProcessorクラスへのパラメータの参照が簡単にできるイメージですが、実際、スライダーの設定をどうして良いかわからなくなりました。
今回、パラメータはProcessorクラスのAudioProcessorValueTreeStateによって管理されることになったので、AudioProcessorValueTreeStateにデシベルとしてパラメータを保持して、この数値を「gainAttachment.reset」で紐づけます。そのため、スライダーの範囲設定や、初期値設定はAudioProcessorValueTreeStateに設定した値が反映されます。少し試してみたところ、reset以降に、[2-1]のようにスライダー初期値を入れると、こちらが上書きされるみたいです。
その他、スライダーの中央値をskewしたいのですが、反映されないので、ここも検証中です。
追記:スライダーのskewができましたので、こちらの記事にしました。
DecibelSliderクラスの追加
PluginEditor.hに以下のDecibelSliderクラスを実装します。スライダーオブジェクトのテキストをデシベル表記とするようなクラスです。これをコピペします。
class DecibelSlider : public juce::Slider
{
public:
DecibelSlider() {}
double getValueFromText(const juce::String& text) override
{
auto minusInfinitydB = -100.0;
auto decibelText = text.upToFirstOccurrenceOf("dB", false, false).trim();
return decibelText.equalsIgnoreCase("-INF") ? minusInfinitydB
: decibelText.getDoubleValue();
}
juce::String getTextFromValue(double value) override
{
return juce::Decibels::toString(value);
}
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(DecibelSlider)
};
DecibelSliderは、以前チュートリアルで行ったものを流用しました。
また、privateなメンバの「gainSlider」をDecibelSliderクラスに変更しました。これで、スライダーがデシベル表記されるようになります。
private:
GainPluginAudioProcessor& audioProcessor;
juce::AudioProcessorValueTreeState& valueTreeState;
juce::Label gainLabel;
//juce::Sliderを先ほど定義したDecibelSliderに変更します。
DecibelSlider gainSlider;
動作検証
JUCEでビルドしたAudioPluginHostアプリに、以前作成したオシレータ機能を持つ「CascadePlugin」でサイン波を出力して、音量の変化を確認してみました。
AudioPluginHostのビルドや準備については、こちらの記事で記載しておりますので、ご参照ください。
また、今回利用したオシレータは、チュートリアルの「Cascading plug-in effects」で実装したものを利用しています。こちらも過去の記事で実装していますので、ご参照ください。
本ブログのJUCEチュートリアル記事はこちらでまとめております。