ディレイの処理、ちょっと混乱します。
時間軸で処理するから、流れを整理して理解したいね~
JUCEチュートリアルの「Create a string model with delay lines」を引き続き進めていきます。今回は、ディレイエフェクトの音声処理部分をメインに見ていきたいと思います。
公式チュートリアルはこちらの「Incorporate a delay effect」の項目からになります。
こんな人の役に立つかも
・JUCE「Create a string model with delay lines」チュートリアルを進めている人
・Projucerテンプレートで「Create a string model with delay lines」チュートリアルの実装を行なっていきたい人
・JUCEでディレイエフェクトを実装したい人
ディレイエフェクトの音声処理
reset関数
まずは、reset関数です。これもすでに実装されていますので、説明のみとなります。
void reset() noexcept
{
//[1]フィルタのリセット
for (auto& f : filters)
f.reset();
//[2]ディレイラインのリセット
for (auto& dline : delayLines)
dline.clear();
}
[1]のfは、フィルターモジュールで、privateなメンバとして準備されているフィルタモジュールの配列要素をループで取り出してひとつづつ、resetを呼び出しています。
(今回のフィルタの実装は、チャンネル数分ループを回しているので、ProcessorDuplicatorを利用したものではない、よりハードコーディングな実装になっています。フィルタモジュールの純粋な利用は、こちらの記事でもやっておりますので、ご参照ください。)
そして、[2]では、delayLineクラスを格納した配列を範囲ループにして、ディレイラインのバッファを全チャンネル分クリアしています。
prepare関数
次に、音声処理の初期化を行うprepare関数です。音声処理開始時(起動時)に実行してサンプルレートなどの情報を設定する処理です。サンプルレートなどの変更時なども一度、音声処理開始前にこの処理が実行されます。この関数もすでにデモの段階で実装されていますので、中身を見るだけになります。
void prepare (const juce::dsp::ProcessSpec& spec)
{
jassert (spec.numChannels <= maxNumChannels);
sampleRate = (Type) spec.sampleRate;
//[1]パラメータの更新を行います。
updateDelayLineSize();
updateDelayTime();
//[2]一次のローパスフィルタの係数を求めます。
filterCoefs = juce::dsp::IIR::Coefficients<Type>::makeFirstOrderLowPass (sampleRate, Type (1e3));
//[3]フィルタの初期化を行います。
for (auto& f : filters)
{
f.prepare (spec);
f.coefficients = filterCoefs;
}
}
[1]では、サンプルレートから最大遅延時間と遅延時間を求める処理を実行します。サンプルレート変更時に、これらの関数を実行して、新たにサンプル数としてのパラメータを更新します。これらの関数は前回実装した関数になります。
[2]一次のローパスフィルタの係数計算を行います。そして、[3]で各チャンネルのフィルタモジュールのprepare関数を呼び出し、[2]の係数設定をフィルタモジュールに反映させます。フィルタに関する詳細な部分は本記事では踏み込みませんが、こちらのフィルタに関する記事もご参照ください。
process関数
process関数の実装です。
デモの段階では、[2]のfor分がない状態なので、チュートリアルに沿って、for分を追加しました。
template <typename ProcessContext>
void process (const ProcessContext& context) noexcept
{
auto& inputBlock = context.getInputBlock();
auto& outputBlock = context.getOutputBlock();
auto numSamples = outputBlock.getNumSamples();
auto numChannels = outputBlock.getNumChannels();
jassert (inputBlock.getNumSamples() == numSamples);
jassert (inputBlock.getNumChannels() == numChannels);
//[1]各チャンネルの処理ループです。
for (size_t ch = 0; ch < numChannels; ++ch)
{
auto* input = inputBlock .getChannelPointer (ch);
auto* output = outputBlock.getChannelPointer (ch);
auto& dline = delayLines[ch];//[3]で利用するためのディレイライン
auto delayTime = delayTimesSample[ch];
auto& filter = filters[ch];
//[2]実装する部分は、以下のfor分になります。
for (size_t i = 0; i < numSamples; ++i)
{
auto delayedSample = dline.get (delayTime); // [3]
auto inputSample = input[i]; // [4]
auto dlineInputSample = std::tanh (inputSample + feedback * delayedSample); // [5]
dline.push (dlineInputSample); // [6]
auto outputSample = inputSample + wetLevel * delayedSample; // [7]
output[i] = outputSample; // [8]
}
}
}
[1]は、各チャンネルのループになります。
そして、[2]がチャンネルの音声ブロックの各サンプルに対するループになります。各チャンネルについて、バッファサイズ分の処理を行います。ここが今回のメインの音声処理部分になります。音声バッファについての概念についてピンとこない場合、こちらの記事で説明を書きましたので、ご参照ください。
[3]では、まずディレイラインから遅延した音声データを取得します。[4]では、入力音声データを取得します。
この[3]と[4]の2つの信号を混ぜ合わせることで、ディレイ効果を生みだします。
[5]では、「dlineInputSample」変数に、「feedback係数と遅延データを掛け合わせ」て、「inputに加算」した信号を格納します。その時に、tanh関数を通すことで、入力と遅延データの合成のクリッピングをなくし、自然な減衰となるとのことです。そして、[6]でこの作成した音声データをディレイラインにpushすることで、遅延時間後に利用する遅延データとします。
[7]では、出力音声を作成します。出力音声は、inputにwet係数と遅延データを掛け合わせたものを「outputsample」に格納します。[8]では、出力音声バッファをoutputsampleに上書きすることで出力音声とします。
前回のパラメータの記事の図が微妙にイメージに一致しそうでしたので、貼り付けます。
エフェクトのオンオフについて
Delayエフェクトは、AudioEngineクラスのProcessorChainに登録されていて、デモのプログラムでは、デフォルトで組み込まれていますので、Delayクラスの実装までで、自動的にエフェクトが反映されます。
Delayの効果を消したい場合、AudioEngineクラスの次の部分の処理を飛ばすようにします。
class AudioEngine : public juce::MPESynthesiser
{
//...略...
private:
enum
{
distortionIndex,
cabSimulatorIndex,
//delayIndex,//[1]enumからディレイを削除します。
reverbIndex
};
//[2]ProcessorChainからDelayを削除します。
juce::dsp::ProcessorChain<Distortion<float>, CabSimulator<float>,
/*Delay<float>,*/ juce::dsp::Reverb> fxChain;
上の2点を削除することで、ディレイの効果がなくなります。