ディレイエフェクトのテンポ同期、何分音符でディレイするか選択できるものが多いですよね
ディレイタイムの計算で簡単に実装できそうだね~
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);
}