JUCEのDSPモジュール、過去にも使いましたが、改めてシンプルにどう使うのか気になります。
シンプルなDSPモジュールを使ったプラグインを実装して確認してみよう〜
DSPモジュールのフィルターを使い、ローカットするミニマムなプラグインを作成しました。以前のJUCEチュートリアルでは、数珠つなぎするプラグインでDSPモジュールを利用したのですが、色々な要素が複雑に絡み合っていたので、現在改めて見直しても、なんとなく理解し直さなければならず、シンプルでないな、と感じましたので、再度シンプルな形で「フィルタープラグイン」を実装してみたいと思いました。
こんな人の役に立つかも
・JUCEプラグインを作成したい人
・JUCEのDSPモジュールを利用してプラグインを作りたい人
・JUCEのDSPモジュールでフィルターを実装したい人
こんな機能を実装します
できる限りシンプルにしたかったので、カットオフ周波数を1kHzと固定にし、ハイパス(ローカット)をするようなプラグインを作成しました。
JUCEのDSPモジュールを利用し、1khzをカットオフ周波数とするハイパスフィルターを使います。
GUIは実装せず、音声処理のみを実装しました。PluginProcessor側の実装のみとしています。
Projucerのプラグインテンプレートから、JUCEのDSPモジュールを使うような基本的な形を把握するという目的で実装を行いましたので、その実装備忘録を記載していきます。
プロジェクトの作成
Projucerのプラグイン、Basicのテンプレートから、「filter_plugin01」という名前をつけてプロジェクトを作成しました。
プロジェクトの作成後、DSPモジュールを追加します。
これでプロジェクトの準備ができましたので、ProjucerのXcode(windowsの場合はVisualStudio)ボタンを押してプロジェクトを開きます。
実装
今回の実装の流れと考察
今回は、音声処理部分を実装するので、プラグインのファイル構成のうち、
・PluginProcessor.h
・PluginProcessor.cpp
ファイルを触っていきます。
ローパスフィルターの機能を有するDSPのクラスは、以前チュートリアルで作成した「ProcessBase」クラスと「FilterProcessor」クラスをコピペして流用しました。
まず、PluginProcessor.hには、以下のクラスを作成します。
①AudioProcessorクラスを継承した「ProcessBase」
②ProcessBaseクラスを継承した「FilterProcessorクラス」
ProcessBaseクラスを作成する意味としては、機能追加するときに、基本的な関数を定義している「ProcessBase」クラスを一つ噛ませることで、その上に違ったDSP機能を持つクラスを「FilterProcessor」とは別に作成できるからです。
実際にどのように機能分割するかはプラグインの機能にも寄ると思いますが、前回のチュートリアルでは、このように分割してフィルターやオシレータなど、異なったDSPクラスを作成していったので、このようにしています。実際、フィルターだけ、というシンプルな構成なら、もしかするとProcessBaseと一体化したクラス一つとした方が記述量は減少するのかもしれません。
また、生成された「filter_plugin01AudioProcessor」に直接DSP処理を記載しても良いと思いますが、ProcessBaseクラスとFilterProcessorクラスを準備して記述を分割した方が、理解しやすいと感じたので、このような構成で実装を行いました。
次に、作成した「FilterProcessor」を「PluginProcessor.cpp」で初期化して利用します。
PluginProcessor.h
PluginProcessor.hに以下の[1]と[2]の2つのクラスを追加しました。
//[1]ProcessBaseクラスです。AudioProcessorクラスを継承しています。
class ProcessorBase : public juce::AudioProcessor
{
public:
//==============================================================================
ProcessorBase() {}
//==============================================================================
void prepareToPlay(double, int) override {}
void releaseResources() override {}
void processBlock(juce::AudioSampleBuffer&, juce::MidiBuffer&) override {}
//==============================================================================
juce::AudioProcessorEditor* createEditor() override { return nullptr; }
bool hasEditor() const override { return false; }
//==============================================================================
const juce::String getName() const override { return {}; }
bool acceptsMidi() const override { return false; }
bool producesMidi() const override { return false; }
double getTailLengthSeconds() const override { return 0; }
//==============================================================================
int getNumPrograms() override { return 0; }
int getCurrentProgram() override { return 0; }
void setCurrentProgram(int) override {}
const juce::String getProgramName(int) override { return {}; }
void changeProgramName(int, const juce::String&) override {}
//==============================================================================
void getStateInformation(juce::MemoryBlock&) override {}
void setStateInformation(const void*, int) override {}
private:
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ProcessorBase)
};
//[2]FilterProcessorクラスです。上で定義したProcessorBaseクラスを継承しています。
class FilterProcessor : public ProcessorBase
{
public:
FilterProcessor() {}
void prepareToPlay(double sampleRate, int samplesPerBlock) override
{
//[2-1]フィルターを作成しています。
*filter.state = *juce::dsp::IIR::Coefficients<float>::makeHighPass(sampleRate, 1000.0f);
juce::dsp::ProcessSpec spec{ sampleRate, static_cast<juce::uint32> (samplesPerBlock), 2 };
filter.prepare(spec);
}
void processBlock(juce::AudioSampleBuffer& buffer, juce::MidiBuffer&) override
{
juce::dsp::AudioBlock<float> block(buffer);
juce::dsp::ProcessContextReplacing<float> context(block);
filter.process(context);
}
void reset() override
{
filter.reset();
}
const juce::String getName() const override { return "Filter"; }
private:
juce::dsp::ProcessorDuplicator<juce::dsp::IIR::Filter<float>, juce::dsp::IIR::Coefficients<float>> filter;
};
[2-1]のところで、ハイパスを指定しています。makeHighPass関数の第二引数がカットオフ周波数になります。dsp::IIR::Coefficientsの関数で色々とフィルタータイプを指定できます。今回のはQのパラメータ指定のないハイパスですが、第3引数にQ値を指定するような関数も存在しています。
次に、プラグインの本体のクラスで、先ほど作成した「FilterProcessor」のオブジェクト「MyFilter」を定義しました。
class Filter_plugin01AudioProcessor : public juce::AudioProcessor
{
public:
//...略...
private:
//追加しました。
FilterProcessor MyFilter;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Filter_plugin01AudioProcessor)
};
PluginProcessor.cpp
コンストラクタ
コンストラクタのメンバイニシャライザでMyFilterを初期化します。
Filter_plugin01AudioProcessor::Filter_plugin01AudioProcessor()
#ifndef JucePlugin_PreferredChannelConfigurations
//...略...
#endif
,MyFilter()//追加
{
}
音声処理関連の関数
FilterProcessorはすでにProcessorBaseに継承している音声処理関数があるので、これらをそれぞれの関数から呼び出してあげます。
releaseResources関数
void Filter_plugin01AudioProcessor::releaseResources()
{
MyFilter.releaseResources();
DBG("released");
}
prepareToPlay関数
void Filter_plugin01AudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
MyFilter.prepareToPlay(sampleRate,samplesPerBlock);
DBG("prepared");
}
processBlock関数
void Filter_plugin01AudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
MyFilter.processBlock(buffer,midiMessages);
DBG("processing");
}
動作確認
今回も、Macで開発をしています。Xcodeでは、Standalone Pluginを選択すると、そのままプラグインをスタンドアロンで動作させてDBGメッセージを見ながら挙動を確認できます。
DBGに記載した文字列が出力され、音声処理はしっかりと動作している模様です。
プラグインとしてもビルドしたいので、下のように戻し、再度ビルドしました。
プラグインとして動作させて見ました。今回はLogicProXのテストオシレータでホワイトノイズを出力したものに作成した「filter_plugin01」プラグインをかけ、EQのスペアナでローカットできているかを確認しました。
まずは、プラグインバイパス状態です。
次に、プラグインをオンにしました。