チュートリアル:ディレイラインを使ったストリング・モデルの作成
フィジカルモデリングによるリアルなストリングスモデルの実装。ディレイ・ラインを組み込んで、ステレオ音場に複雑なエコー・パターンを作り出す。
レベル:Advanced
プラットフォーム:Windows, macOS, Linux
プラグイン形式:VST, AU, Standalone
クラス: dsp::ProcessorChain, dsp::Gain, dsp::Oscillator, dsp::Convolution, dsp::WaveShaper, dsp::Reverb
このプロジェクトには、C++14の機能をサポートするコンパイラが必要です。XcodeとVisual Studioの最近のバージョンには、このサポートが含まれています。
はじめる
This tutorial leads on from チュートリアルウェーブシェイピングとコンボリューションで歪みを加える. If you haven't done so already, you should read that tutorial first.
Download the demo project for this tutorial here: ピップ | ジップ. Unzip the project and open the first header file in the Projucer.
If using the PIP version of this project, please make sure to copy the リソース
folder into the generated Projucer project.
If you need help with this step, see チュートリアルProjucerパート1:Projucerを始める.
The demo project
プロジェクトはプラグインとして構想されていますが、IDEで適切なデプロイメント・ターゲットを選択することで、スタンドアロン・アプリケーションとして実行することができます。Xcodeでは、以下のスクリーンショットのように、メインウィンドウの左上でターゲットを変更することができます:
デモ・プロジェクトでは、プラグインの上半分にMIDIキーボードが画面上に表示され、下半分にはオシロスコープを通して信号が視覚的に表示される。現在、鍵盤が押されると、プラグインはいくつかのリバーブとディストーションを加えた基本的なオシレーター・サウンドを出力します。
Feel free to remove the effects to clearly hear the changes in each steps of the tutorial by commenting out the effects processors in the オーディオエンジン
class.
MIDIコントローラーをお持ちの場合は、このチュートリアル中、画面上のキーボードを使う代わりに、MIDIコントローラーを接続することもできます。
Introduction
このチュートリアルでは、異なる方法で信号処理を可能にする2つの新しいDSPコンセプトを紹介します:ディレイ・ラインとフィジカル・モデリングです。
このDSP用語の定義から始めよう。
What is a Delay Line?
ディレイ・ラインは、残響空間のシミュレーション、サウンド・シンセシス、フィルターの実装、ディレイ、コーラス、フェイザー、フランジャーなどの古典的なタイムベース・エフェクトなど、幅広い用途に使用できるDSPの基本ツールです。
基本的に、ディレイラインは非常にシンプルで、ある信号をサンプル数だけ遅らせることができます。複数のディレイ・ラインを使い、別々の信号を異なる間隔で合計することで、デジタル信号処理の大部分を作り出すことができます。
アナログ領域では、遅延線は、波の伝搬を遅延させるために、バネのような実際の物理的な拡張を導入することで実装されていた。デジタル領域では、遅延線は多くの場合、サーキュラー・バッファと呼ばれるデータ構造を使って実装される。
サーキュラー・バッファは、基本的に、サンプル・バッファ・ブロックのサイズに一致するサーキュラー・データ構造を作成するために、インデックスがそれ自身をラップする配列として実装することができる。これにより、前のブロックに含まれるすべてのサンプルを現在のブロックにアクセスできるように保存し、次の反復のために現在のサンプルブロックによって上書きされるようにすることができます。
このチュートリアルでは、ディレイ・ラインを実装する方法として、サーキュラー・バッファを取り上げます。
What is Physical Modelling?
フィジカル・モデリングは、サウンドを生成するために数学的および物理的モデルに依存するサウンド合成手法を示す。他の合成手法とは異なり、サンプルを出発点として使用せず、素材の研究を通じて物理的な意味で音がどのように生成されるかに焦点を当てる。
そのひとつがデジタル導波管と呼ばれるモデルで、音響波が管やパイプの中を伝搬する物理モデルに基づいている。これらの波の境界に対する反射は、遅延線を用いて効率的に計算することができ、このモデルを用いて弦楽器などの多くの楽器の音を生成することができます。
The waveguide string model
一言で言えば、導波管ストリング・モデルは、振動するストリングは、反対方向に進み、2つの端点で跳ね返る2つの波を使ってモデル化できるという概念に基づいています。この2つの波の組み合わせは、最終的に弦を弾いたときの理想的な動きをシミュレートし、2つのディレイライン(前方ディレイラインと後方ディレイライン)を使って実装することができます。
しかし、この理想的な弦のモデルは、減衰が考慮されていないため、現在の状態で静止することはない。したがって、波が境界で方向を変え、 その極性が反転するとき、波の変位を減らすために減衰係数を組み込むことができる。
このモデルで考慮すべき他の変数は、弦をはじく位置と、弦が振動する音を拾う位置である。したがって、空間上の特定の場所から音を聴くのと同じように、2つの波が伝播する弾く位置と拾う位置を統合しなければならない。
最後になるが、物理的な弦で起こる自然現象に、高い周波数の減衰が低い周波数より速いというものがある。これは、境界の一端にローパスフィルターを追加するだけで、この減衰時間の不一致をシミュレートすることができ、我々のモデルに簡単に組み込むことができる。
このチュートリアルでは、ディレイ・ラインを使ってこのデジタル導波管モデルを実装し、弦を弾いたときに弦の境界で反射する波をシミュレートします。
Implement a delay line
まず、単純なディレイ・ラインをベクターを使って円形バッファとして実装してみよう。
In the ディレイライン
class, there are a couple of self-explanatory helper functions already defined to facilitate the implementation such as サイズ
and リサイズ()
methods, a クリア
function and a バック()
function that retrieves the least recent sample in the buffer.
We first implement the プッシュ
function which adds a new sample by overwriting the least recently added sample [1] and updates the least recent index variable by wrapping the index by the size of the circular buffer [2]:
void push (Type valueToAdd) noexcept
{
rawData[leastRecentIndex] = valueToAdd; // [1].
leastRecentIndex = leastRecentIndex == 0 ? size() - 1 : leastRecentIndex - 1; // [2].
}
Then we complete the ゲット
function by returning the sample located at an offset specified by the function argument while making sure that the index wraps around the vector [3]. Notice here that we make sure the delay does not exceed the size of the buffer.
型 get (size_t delayInSamples) const noexcept
{
jassert (delayInSamples >= 0 && delayInSamples < size());
return rawData[(leastRecentIndex + 1 + delayInSamples) % size()]; // [3].
}
Next, we fill in the set()
function by assigning the sample at an offset specified by the function argument while making sure that the index wraps around the vector [4]. Here again, we make sure the delay does not exceed the size of the buffer.
void set (size_t delayInSamples, Type newValue) noexcept
{
jassert (delayInSamples >= 0 && delayInSamples < size());
rawData[(leastRecentIndex + 1 + delayInSamples) % size()] = newValue; // [4].
}
これでシンプルなディレイ・ラインの実装は完了だ。
Incorporate a delay effect
基本的なディレイ・ライン・クラスを実装したので、シグナル・チェインにステレオ・ディレイ・エフェクトを組み込んでみましょう。
In the 遅延
class, there are multiple parameters that can be tweaked to change the behaviour of our delay effect and these include delay times for individual channels, the maximum delay time allowed, the dry/wet level of the effect and the amount of feedback.
これらのパラメーターとディレイ・ラインの実装を使えば、さまざまなディレイ・エフェクトを思い通りに作ることができるが、まずはコンストラクターで定義され ているデフォルトのパラメーターから見ていこう:
{
を公開します:
//==============================================================================
ディレイ()
{
setMaxDelayTime (2.0f);
setDelayTime (0, 0.7f);
setDelayTime (1, 0.5f);
setWetLevel (0.8f);
setFeedback (0.5f);
}
これらのヘルパー関数は、主にパラメータを格納するために対応するメンバ変数を設定しますが、中にはパラメータの変更に対応するためにデータ構造のリサイズを必要とするものもあります。
One such case is the setMaxDelayTime()
function defined below which calls the updateDelayLineSize()
helper function [1]:
void setMaxDelayTime (Type newValue)
{
jassert (newValue > Type (0));
maxDelayTime = newValue;
updateDelayLineSize(); // [1].
}
Complete the following function which ensures that the circular buffers of all the delay lines are large enough to accomodate any delay time up to the max delay time by resizing the vectors [2]:
void updateDelayLineSize()
{
auto delayLineSizeSamples = (size_t) std::ceil (maxDelayTime * sampleRate);
for (auto& dline : delayLines)
dline.resize (delayLineSizeSamples); // [2].
}
Another noteworthy case is in the setDelayTime()
function of individual channels where a parameter change causes a call to the updateDelayTime()
helper function [3] as follows:
void setDelayTime (size_t channel, Type newValue)
{
if (channel >= getNumChannels())
{
jassertfalse;
を返します;
}
jassert (newValue >= Type (0));
delayTimes[channel] = newValue;
updateDelayTime(); // [3].
}
Implement this helper function that recalculates the delay times in samples for all the channels based on the new parameter change [4]:
void updateDelayTime() noexcept
{
for (size_t ch = 0; ch < maxNumChannels; ++ch)
delayTimesSample[ch] = (size_t) juce::roundToInt (delayTimes[ch] * sampleRate);
}