ローパスフィルターも追加したいです。
音声処理でフィルターを追加してみよう~
JUCEディレイプラグインへローパスフィルターを追加しました。プラグイン自体には、ハイパスフィルターとローパスフィルターの2つのフィルターを接続して、それぞれをハイ、ローからカットできるようになりました。今回は、この2つのフィルターをDelayクラスへ追加していく備忘録となります。
作成しているディレイプラグインの過去の備忘録は、こちらをご参照ください。
Delayクラスの変更
ディレイ音を付加する音声処理はDelayクラス内で行っていますので、Delayクラス内の音声処理にフィルタモジュールを追加してローパスフィルターの効果が反映されるようにしていきます。
メンバの準備
新たに2つ目のフィルタモジュールのためのメンバを準備します。
private:
//...略...
//以下のフィルタモジュールに必要なprivateなメンバを準備します。
std::atomic<Type>* LoPassParam_filter = nullptr;
std::array<juce::dsp::IIR::Filter<Type>, maxNumChannels> filters2;
typename juce::dsp::IIR::Coefficients<Type>::Ptr filterCoefs2;
JUCEのDSPモジュール、Filterを利用するために、fliter2とfilterCoefs2を定義します。filter2は、std::arrayを利用して、配列でチャンネル数分のフィルタを準備しています。filterCoefs2は、フィルタの係数を格納するためのメンバです。フィルタのみの実装については、シンプルなハイパスフィルターの記事でも記載しておりますので、ご確認いただければと思います。
「LoPassParam_filter」はプラグイン本体で後程定義するカットオフ周波数を格納する数値へのポインタとして利用します。プラグイン本体側では、APVTSのパラメータと紐づけて、GUIで操作できるようにします。このAPVTSパラメータの数値を格納している変数へのポインタを定義しておきます。
「LoPassParam_filter」 はprivateなメンバとしていますので、外部から値をセットできるように、次のようにpublicなメンバとしてアクセサを追加しました。
public:
//...略...
//アクセサでパラメータ値を受け取ることができるようにします。
void setLoPassParam(std::atomic<float>* param) {
LoPassParam_filter = param;
}
後程、プラグイン本体で、prepareToPlay関数でパラメータのポインタを受け取り、セットすることで、プラグイン本体のパラメータ値を参照できるようにします。
音声処理
ディレイ音の生成に関する処理は、Delayクラスのprocess関数内で行われますので、ここにフィルターの処理を追加していきます。
void process(const ProcessContext& context) noexcept
{
//...略...
Type freq = *cutoffParam_filter;
filterCoefs = juce::dsp::IIR::Coefficients<Type>::makeFirstOrderHighPass(sampleRate, freq);
//[1]パラメータの計算です。
Type freq2 = *LoPassParam_filter;
filterCoefs2 = juce::dsp::IIR::Coefficients<Type>::makeFirstOrderLowPass(sampleRate, freq2);
for (auto& f : filters)
{
f.coefficients = filterCoefs;
}
//[2]フィルターの係数を反映させます。
for (auto& f2 : filters2)
{
f2.coefficients = filterCoefs2;
}
for (size_t ch = 0; ch < numChannels; ++ch)
{
auto* input = inputBlock.getChannelPointer(ch);
auto* output = outputBlock.getChannelPointer(ch);
auto& dline = delayLines[ch];
auto delayTime = delayTimesSample[ch];
auto& filter = filters[ch];
//[3]追加しました。
auto& filter2 = filters2[ch];
for (size_t i = 0; i < numSamples; ++i)
{
auto delayedSample = filter.processSample(dline.get(delayTime));
//[4]ローパスフィルターを通します。
delayedSample = filter2.processSample(delayedSample);
auto inputSample = input[i];
auto dlineInputSample = std::tanh(inputSample + feedback * delayedSample);
dline.push(dlineInputSample);
auto outputSample = inputSample * dryLevel + wetLevel * delayedSample;
output[i] = outputSample;
}
}
}
[1]と[2]は1つ目のフィルタと同じ処理を行います。[1]では、makeFirstOrderLowPass関数で1次のローパスフィルタのための係数を計算してfilterCoefs2に格納しています。フィルタはarray配列でチャンネル数分ありますので、[2]のようにfor文でチャンネル数分のフィルタに係数を反映させます。
[3]では、filter2配列に含まれる、チャンネル番号のフィルタを取得しています。
[4]では、フィルタの処理を反映させます。delayedSampleには、一度1個目のフィルタの処理が反映されたディレイ音の音声データが格納されています。このデータにさらに2つ目のフィルタ(今回作成したフィルタ)のprocessSample関数を通すことで、2つ目のフィルタの効果を反映させることができます。
APVTSパラメータの追加
プラグイン本体クラスのプロセッサクラス(●●AudioProcessorクラス)のprivateなメンバに、以下のパラメータ格納メンバを追加しました。
std::atomic<float>* loPassParam = nullptr;
次に、cppファイルの方で、APVTSの設定と、紐づけをコンストラクタで行います。APVTSパラメータの追加設定については、こちらの記事
//以下の設定のパラメータを追加します。
std::make_unique<juce::AudioParameterFloat>("lopassParam", // ID
"LoPassParam", // name
juce::NormalisableRange<float>(1.0f,22000.0f),
22000.0f, //default
"lopassParam",
juce::AudioProcessorParameter::genericParameter,
[](float value, int) {return juce::String(value, 1) + "Hz";}
)
})
{
//...略...
//パラメータを紐づけます。
loPassParam = parameters.getRawParameterValue("lopassParam");
//...略
prepareToPlay関数でパラメータ値へのポインタをDelayクラスに渡します。
void TheDelayAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
processorChain.prepare({ sampleRate, (juce::uint32) samplesPerBlock, 2 });
processorChain.get<DelayIndex>().setCutoffParam(cutoffParam);
//setLoPassParam関数を呼び出してパラメータへのポインタを渡しました。
processorChain.get<DelayIndex>().setLoPassParam(loPassParam);
GUIの追加
ここからは、エディタ側のプログラムになります。GUIのスライダーを設置して、ローパスフィルターのカットオフ周波数を操作できるようにしていきます。
まずは、プラグイン本体のエディタクラス「●●AudioProcessorEditor」クラスのprivateなメンバに、スライダーコンポーネントと、アタッチメントを作成します。
private:
//...略...
//スライダーを追加します。
juce::Slider LoPassSlider;
std::unique_ptr<SliderAttachment> LoPassAttachment;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TheDelayAudioProcessorEditor)
};
次に、PluginEditor.cppファイルのコンストラクタ定義部分に、ローパスフィルターのスライダーの初期化を行います。
//...略...
{
//スライダーの初期化を行います。
addAndMakeVisible(LoPassSlider);
LoPassAttachment.reset(new SliderAttachment(valueTreeState, "lopassParam", LoPassSlider));
LoPassSlider.setSliderStyle(juce::Slider::Rotary);
LoPassSlider.setLookAndFeel(&dialLookAndFeel);
LoPassSlider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 64, 30);
LoPassSlider.setColour(juce::Slider::textBoxTextColourId, juce::Colours::white);
LoPassSlider.setColour(juce::Slider::textBoxOutlineColourId, juce::Colour::fromRGBA(0,0,0,0));
LoPassSlider.setRange(1.0f,22000.0f);
LoPassSlider.setSkewFactorFromMidPoint(1000.0f);
//LoPassSlider.setValue(22000.0f);//20220204更新、コメントアウト
addAndMakeVisible関数で、スライダーの可視化を行います。次に、アタッチメントクラスのreset関数で、スライダーをAPVTSパラメータと紐づけます。
※20220204追記:setValue関数不要でした。これを入れてしまうと、GUI表示の毎に値が初期化されてしまいます。
[1]のsetLookAndFeel関数でカスタマイズしたLookAndFeelクラスを指定しているので、デストラクタで次のようにLookAndFeelをnullptrとする必要があります。
TheDelayAudioProcessorEditor::~TheDelayAudioProcessorEditor()
{
//...略...
LoPassSlider.setLookAndFeel(nullptr);//追加しました。
}
最後に、resized関数で、以下の場所に、スライダーを設置しました。
void TheDelayAudioProcessorEditor::resized()
{
delayTimeLSlider.setBounds(232, 120, 64, 94);
delayTimeRSlider.setBounds(323, 120, 64, 94);
wetLevelSlider.setBounds(60, 205, 64, 87);
dryLevelSlider.setBounds(60, 93, 64, 87);//20211231
feedbackSlider.setBounds(495, 100, 64, 94);
cutoffSlider.setBounds(675,100,64,94);
LoPassSlider.setBounds(675,200,64,94);
//...略...