ProcessorChainというクラスでいくつかのDSPプロセッサを直列につなぐことができるんですね。
いろいろなDSPクラスがあるので、ひとつづつ使い方を覚えていきたいね~
JUCEチュートリアル、「Introduction to DSP」の「Creating an oscillator」の項目を進めていきます。今回の実装を行うことで、オンスクリーンのキーボードを押すことで、次のように、アプリの下の部分に波形が表示されるようになります。
本記事は、前回のからの続きとなっています。
公式チュートリアルはこちらです。
こんな人の役に立つかも
・JUCEプログラミングを勉強している人
・JUCEチュートリアル、「Introduction to DSP」をやっている人
・JUCEのProcessorChainクラスの大まかな使い方を知りたい人
CustomOscillatorの実装
最初に作成したベースとなるプログラムのうち、今回は「CustomOscillatorクラス」を改造していきます。CustomOscillatorクラスは、「PluginProcessor.h」ファイルに記載しています。
private:
//[1]enumで以下のような値を準備します。
enum
{
oscIndex,
gainIndex
};
//[2]processorChainを準備します。
juce::dsp::ProcessorChain<juce::dsp::Oscillator<Type>, juce::dsp::Gain<Type>> processorChain;
[1]のenumは、C言語のenum(C++のenumクラスではない)で、tagとdeclarator部分を省略したものになります。そのため、単純に「oscIndex」が整数の0、「gainIndex」が整数の1となります。ここで、2つのプロセッサのインデックスとして定義します。
[2]に、ProcessorChainクラスでオシレータプロセッサと、ゲインプロセッサをまとめた一つのDSPプロセッサを定義します。
void reset() noexcept
{
processorChain.reset();
}
reset関数では、そのままprocessorChainのreset関数を呼び出します。
void prepare(const juce::dsp::ProcessSpec& spec)
{
processorChain.prepare(spec);
}
prepare関数内でもprocessorChainのprepareを呼び出します。引数として、specをそのまま渡します。ここに、サンプルレートなどの情報が含まれています。
次に、コンストラクタに以下のプログラムを追加しました。
CustomOscillator()
{
//[1]オシレータプロセッサを取得します。
auto& osc = processorChain.template get<oscIndex>();
//[2]出力波形を定義します。
osc.initialise([](Type x) { return std::sin(x); }, 128);
}
[1]は、processorChainのOscillatorクラスの参照を取得します。
これでoscがオシレータプロセッサのインスタンスを示すようになるので、出力波形を定義するOscillator::initialise関数を呼び出すことができます。[2]のように第一引数にサイン波をラムダ式で定義します。第二引数はルックアップテーブルの離散点の数です。第一引数に渡されたサイン波の128のポイントがテーブルとして保存されるようです。これで、毎回計算を行って波形のゲイン値を算出するという計算コストを削減するような形になります。
オシレータクラスのinitializeによる、ラムダ式での波形の定義については、チュートリアルの次の項目の波形の作成の段階でより詳しく見ていこうと思います。ここでは、ラムダ式で波形の定義を与えるんだ、という程度にとどめておきます。
CustomOscillatorクラス外部から、周波数を指定して設定できるように、オシレータプロセッサのsetFrequency関数を呼び出して周波数を設定できるようにしています。次のようなsetFrequency関数を追加します。
void setFrequency (Type newValue, bool force = false)
{
auto& osc = processorChain.template get<oscIndex>();
osc.setFrequency (newValue, force);
}
setFrequency関数は、dsp::Oscillatorクラスの関数です。
周波数と同じように、音量も変化させることができるように、setLevel関数を追加します。
void setLevel(Type newValue)
{
auto& gain = processorChain.template get<gainIndex>();
gain.setGainLinear(newValue);
}
今度は、[1]のように、gainIndexを指定することでprocessChainからGainのインスタンスを取得して、setGainLinear関数で音量を設定しています。setGainLinear関数はdsp::Gainクラスの関数です。
最後に、process関数内で、processChainのprocess関数を呼び出します。引数として、そのままcontextを渡しています。
template <typename ProcessContext>
void process(const ProcessContext& context) noexcept
{
processorChain.process(context);
}
実装の全体
最終的に、次のようなCustomOscillatorクラスとなります。
template <typename Type>
class CustomOscillator
{
public:
CustomOscillator()
{
auto& osc = processorChain.template get<oscIndex>();
osc.initialise([](Type x) { return std::sin(x); }, 128);
}
void setFrequency(Type newValue, bool force = false)
{
auto& osc = processorChain.template get<oscIndex>();
osc.setFrequency(newValue, force);
}
void setLevel(Type newValue)
{
auto& gain = processorChain.template get<gainIndex>();
gain.setGainLinear(newValue);
}
void reset() noexcept
{
processorChain.reset();
}
template <typename ProcessContext>
void process(const ProcessContext& context) noexcept
{
processorChain.process(context);
}
void prepare(const juce::dsp::ProcessSpec& spec)
{
processorChain.prepare(spec);
}
private:
enum
{
oscIndex,
gainIndex
};
juce::dsp::ProcessorChain<juce::dsp::Oscillator<Type>, juce::dsp::Gain<Type>> processorChain;
};