JUCEプラグイン、ゲインプラグインをデシベル表記にする

AudioProcessorValueTreeStateのスライダーをデシベル表示にしたいです。

AudioProcessorValueTreeStateでどうやってパラメータをデシベルにするんだろうね

以前作成したゲインプラグインは、表示がデシベルではなく、より実用的なプラグインとするために、スライダーの音量表記をデシベルに変更できるか、挑戦してみました。スタンドアロンのアプリでデシベルスライダーを実装したことがありますので、その内容を流用しつつ、AudioProcessorValueTreeStateでのパラメータ管理によるデシベルへの変更の奮闘記録となります。

以前、JUCEの公式チュートリアルでゲインを調整するプラグインを作成しました。これをゲインプラグインと呼んで記事を記載していきます。本ブログでは以下の記事にて実装を行いました。

音声のゲインを変更する

プラグインパラメータの保持とゲインの滑らかな変化

XML形式でプラグイン状態を保持

位相反転を行う機能を追加する

上記が基本的なゲインプラグインの実装記事で、以下の記事にて、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は、以前チュートリアルで行ったものを流用しました。

あわせて読みたい
JUCEプログラミング、サイン波のシンプルなシンセサイザーアプリ3、デシベルのボリューム exerciseに音量追加とあったので、やってみました。 こないだやったデシベルで音量を追加したいね JUCEプログラミングチュートリアル 、Synthの「 Build a sine wa...

また、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チュートリアル記事はこちらでまとめております。

あわせて読みたい
JUCEプログラミング 【JUCEを始める際に知っておきたいと感じたこと】 JUCEアプリケーション開発を学び始める際に知っておきたいなと感じたこと1 JUCEアプリケーション開発を学び始める際...
よかったらシェアしてね!
目次
閉じる