【JUCEプラグイン開発】ディレイエフェクト、テンポ同期・音符の長さをコンボボックスで指定する

パンダさん

ディレイエフェクトのテンポ同期、何分音符でディレイするか選択できるものが多いですよね

コパンダ

ディレイタイムの計算で簡単に実装できそうだね~

DAWのBPMにディレイタイムを同期できるようになりましたが、一般的なディレイプラグインでは、何分音符かを選択することができます。BPMの定義は、4分音符が1分間に何個入るか、ということなので、全音符では4倍、2分音符では2倍、8分音符では1/2倍といったようにディレイタイムを変更すれば、自動的にBPMに対する音符の長さを指定できるようになります。

ということで、今回は、コンボボックスで全音符、2分音符、4分音符、8分音符を選択できるようにして、音符の長さに応じてBPMへ同期するようなディレイとしていきたいと思います。

※前回の記事で、macでのビルドで不具合がでたので、mac版とwindows版でプログラムの差異が発生しますが、基本的にパラメータの更新の計算がprocessBlock内で行われるのか、またはtimerCallBack関数内で行われるかの違いのみです。windowsで動作確認できている、timerCallback関数内での処理を記載していきたいと思います。

目次

こんな人の役に立つかも

・JUCEを勉強している人

・JUCEでディレイを実装したい人

・JUCEのディレイでBPMと同期する実装をしたい人

プロセッサ側の変更

まずは、コンボボックスで操作するためのAPVTSのパラメータが必要なので、〇〇AudioProcessor(〇〇にはプロジェクト名が入ります。)クラスのprivateなメンバとして新しく「delayNumerator」というパラメータを格納する変数を宣言しました。

class TheDelayAudioProcessor  : public juce::AudioProcessor, private juce::Timer
{
//...略...

private:
//...略...

    //[1]パラメータを追加します。
    std::atomic<float>* delayNumerator = nullptr;

//...略...

次に、タイマーのコールバック関数でのパラメータ更新処理を変更しました。

class TheDelayAudioProcessor  : public juce::AudioProcessor, private juce::Timer
{
public:
    void timerCallback() override
    {
        playHead = this->getPlayHead();
        playHead->getCurrentPosition(currentPositionInfo);

        current_bpm = currentPositionInfo.bpm;

        //[1]テンポの変更による条件を削除しました。
        //if (previous_bpm != current_bpm) {
        
        //[2]コンボボックスの値を取得します。
        float selected_delayNum = *delayNumerator;
        float delayNum = 0.0f;
        if (selected_delayNum == 0.0f) {
            delayNum = 1.0f;
        }
        else if (selected_delayNum == 1.0f) {
            delayNum = 2.0f;
        }
        else if (selected_delayNum == 2.0f) {
            delayNum = 4.0f;
        }
        else if (selected_delayNum == 3.0f) {
            delayNum = 8.0f;
        }
        float val2 = ((float)60.0 / current_bpm) * (4.0f / delayNum);

        if (val2 >= 2.0f) {
            val2 = 1.99f;
        }

        float val3 = val2;

        processorChain.get<DelayIndex>().setDelayTime(0, val2);
        processorChain.get<DelayIndex>().setDelayTime(1, val3);

        previous_bpm = current_bpm;
        //}
    }
//...略...

[1]では、以前はBPMの変更に伴ったタイミングでパラメータを書き換えするように条件を入れていましたが、今回はよりシンプルに、毎回タイマーの処理でパラメータを更新し続けるような実装としました。

コンボボックスの追加により、外部要因の変更以外に、コンボボックスの変更という条件が必要になるので、必要であれば、違うタイミングで実装を追加しようと思います。

[2]では、コンボボックスの選択によって1~4に変化する値を計算に利用する値に変更しています。この変換が必要な理由は、後のAPVTSの設定の部分で記述しています。とりあえず、コンボボックスの変更で、selected_delayNum変数に1~4が入るようになっています。そして、それに対してdelayNum変数に1、2、4、8という値が入るようになっています。

次の計算の内容としては、

((float)60.0 / current_bpm) * (4.0f / delayNum);

最初の「60/BPM」で、四分音符のディレイの時間が出ます。そのため、全音符はその後に4倍の係数を掛けたいです。delayNumの値を4から割ることで、係数としています。この計算で、次のように何分音符かでのディレイタイムが算出できます。

例)BPM120

全音符
(60 / 120) × (4 / 1) = 2秒

2分音符
(60 / 120) × (4 / 2) = 1秒

4分音符
(60 / 120) × (4 / 4) = 0.5秒

8分音符
(60 / 120) × (4 / 8) = 0.25秒

※今回は、最大ディレイタイムを2秒縛りにしていますので、実際全音符では1.99秒に変更となります。

次に、APVTSのパラメータを設定します。

TheDelayAudioProcessor::TheDelayAudioProcessor()
//...略...
            std::make_unique<juce::AudioParameterFloat>("feedback",
                                                        "Feedback",
                                                         0.1f,
                                                         0.99f,
                                                         0.5f)
                                                         ,//←忘れがち
            //[1]1~4を選択できるパラメータを作成しました。
            //  1:全音符
            //  2:2分音符
            //  3:4分音符
            //  4:8分音符
            std::make_unique<juce::AudioParameterInt>("delayNumerator",
                                                        "DelayNumerator",
                                                           1.0f,//最小値
                                                           4.0f,//最大値
                                                           3.0f)//デフォルト
        })
{
//...略...

    //[2]APVTSパラメータを変数に紐づけます。
    delayNumerator = parameters.getRawParameterValue("delayNumerator");

    startTimerHz(30);

}

AudioProcessorクラスのコンストラクタでAPVTSのコンボボックス用のパラメータを追加しました。[1]のように、コンボボックスの数となるようなパラメータを追加します。これは、今回の検証で判明したのですが、コンボボックスアタッチメントクラスでAPVTSにパラメータが紐づけされるとき、コンボボックスのAPVTSのパラメータを均等に割ったように割り振られるので、コンボボックスの要素の数、とするようにしました。ここで、パラメータ計算に合わせて最大値を8とかにすると、1、3.3、5.6、8のように均等に割り当てられてくるので、ちょっと困惑します^^;

この割り当てを非線形にするような方法もあると思いますが、今回は、APVTSのパラメータを4と、コンボボックスにあわせることで、コンボボックスの数が整数として出てくるようにしました。そのため、パラメータを利用する際に、値を必要な数値に変換して利用する必要があります。

エディタ側の変更

コンボボックスを利用するために、アタッチメントクラスを準備します。[1]のように、コンボボックスアタッチメントを準備して、[2]のようにコンボボックスのクラスと、アタッチメントクラスを準備します。

この点は、APVTSのパラメータの作成をまとめた記事に詳しく記載しましたので、ご参照ください。

class TheDelayAudioProcessorEditor  : public juce::AudioProcessorEditor
{
public:
//...略...
    //[1]コンボボックスアタッチメントのtypedefをしました。
    typedef juce::AudioProcessorValueTreeState::ComboBoxAttachment ComboBoxAttachment;

private:
//...略...

    //[2]コンボボックスをメンバとして追加します。
    juce::ComboBox DelayNumerator;
    std::unique_ptr<ComboBoxAttachment> DelayNumeratorAttachment;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TheDelayAudioProcessorEditor)
};

次に、コンストラクタでコンボボックスを初期化します。

TheDelayAudioProcessorEditor::TheDelayAudioProcessorEditor (TheDelayAudioProcessor& p, juce::AudioProcessorValueTreeState& vts)//add second param
    : AudioProcessorEditor (&p), audioProcessor (p), valueTreeState(vts)
{
//...略...
    //[1]コンボボックスの初期化を行います。
    addAndMakeVisible(DelayNumerator);
    DelayNumeratorAttachment.reset(new ComboBoxAttachment(valueTreeState, "delayNumerator", DelayNumerator));
    DelayNumerator.addItem("Whole", 1);
    DelayNumerator.addItem("2", 2);
    DelayNumerator.addItem("4", 3);
    DelayNumerator.addItem("8", 4);
    DelayNumerator.setSelectedId(3);

    setSize (400, 300);
}

[1]のコンボボックスの初期化では、アタッチメントのreset関数でAPVTSのパラメータを紐づけ、addItem関数で項目をひとつづつ追加していきました。第一引数に、コンボボックスで表示する項目名、第二引数にID番号です。

最後にresized関数でGUI上にコンボボックスを配置しました。

void TheDelayAudioProcessorEditor::resized()
{
//...略...

    //[1]コンボボックスを配置しました。
    DelayNumerator.setBounds(10, 160, 200, 30);
}
よかったらシェアしてね!
目次
閉じる