チュートリアル:ウェーブシェイピングとコンボリューションで歪みを加える
ウェーブシェーピングでハーモニック・ディストーションを作り、シンセサイザー・サウンドに重厚さを加える。 インパルス・レスポンスに含まれる音の特徴を取り出すコンボリューションの基礎を学びます。
レベル:Advanced
プラットフォーム:Windows, macOS, Linux
プラグイン形式:VST, AU, Standalone
クラス: dsp::ProcessorChain, dsp::Gain, dsp::Oscillator, dsp::Convolution, dsp::WaveShaper, dsp::Reverb, dsp::ProcessorDuplicator
このプロジェクトには、C++14の機能をサポートするコンパイラが必要です。 XcodeとVisual Studioの最近のバージョンには、このサポートが含まれています。
はじめる
This tutorial leads on from チュートリアルDSP入門. 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 reverb to clearly hear the changes in each steps of the tutorial by commenting out the reverb processor in the オーディオエンジン
class.
MIDIコントローラーをお持ちの場合は、このチュートリアル中、画面上のキーボードを使う代わりに、MIDIコントローラーを 接続することもできます。
Introduction
このチュートリアルでは、異なる方法で信号処理を可能にする2つの新しいDSPコンセプトを紹介します:ウェーブシェーピングとコンボリューションです。
このDSP用語の定義から始めよう。
What is Waveshaping?
ウェーブシェーピングとは、ある信号が、元の信号に適用される伝達関数によって別の信号にシェーピングされるプロセスのことである。 例えば、単純な正弦波は、数学的な関数を適用することによって異なる波形に成形することができる。
ウェーブシェイパーは、特定の伝達関数を適用する際に、元の信号に高調波コンテンツを追加することで、効果的に歪みを作り出すために使用することができます。 ご存知のように、例えば矩形波や三角波は基本的に正弦波に奇数倍音を加えたもので、のこぎり波は正弦波に奇数倍音と偶数倍音を加えたものです。
この事実を知っていれば、歪みを作り出す1つの方法は、正弦波の形状を矩形波に近づけることである。 では、伝達関数を使ってそれを実現するにはどうすればよいのでしょうか?
Consider for instance a simple sine wave sin(x) which could be plotted as follows:
By applying the signum transfer function onto the sine wave, a function that essentially outputs the sign of the inputted number, we end up with sgn(sin(x)) which perfectly represents the square wave like so:
しかし、この完璧な波形は、カーブのエッジが硬いため、ハードクリッピングと呼ばれる厳しい歪みを生み出すという問題がある。 また、このような波形は、アナログ領域で再現するには「完璧すぎる」ため、ほとんどのアナログ・シンセサイザーで作られる矩形波のようには聞こえません。
In order to create a more gentle kind of distortion called soft-clipping, we can use a hyperbolic tangent transfer function tanh(sin(x)) which outputs a signal that looks almost like a sine wave but with a rounder curve as shown here:
Then, in order to reach a square-like shape, we can boost the signal into clipping before applying the transfer function tanh(n*sin(x)), essentially truncating the top of the bell shape into a soft-edged square wave as seen below:
おわかりのように、どのような種類の伝達関数でも入力信号に適用できるため、ウェーブシェーピングの可能性は無限にある。
In JUCE, you can perform Waveshaping with the dsp::WaveShaper class included in the DSP module.
What is Convolution?
コンボリューションとは、ある空間の残響特性を、その空間の特性を表すインパルス応答をあらかじめ録音しておき、それを使ってシミュレートすることです。 このプロセスにより、インパルス応答のサンプルに対してデータの各サンプルを乗算して合成出力を作成するコンボリューションによって、入力信号にあらゆるタイプの音響プロファイルを適用することができます。
インパルス・レスポンスとは、プロファイリングする空間で短いインパルスを録音することで生成されるオーディオファイルのことですが、必ずしも実際の物理的な空間である必要はありません。 例えば、このようにキャビネットを通してインパルスを再生し、その効果を録音することで、ギターアンプのプロファイルをキャプチャすることができます:
同じことをカセットレコーダーを通して行い、生成されたインパルス応答は以下の通りである:
入力信号がインパルス応答とコンボリューションされると、元のドライ信号は、両方の特性を保持したウェットな残響に変換されます。 例えば、100msの440Hzの正弦波を、上図のギター・アンプのインパルス応答でコンボリューションすると、次のような結果が得られます:
先に紹介したカセット・レコーダーのインパルス応答と同じ正弦波信号が、最終的にこの波形を生成する:
このように、コンボリューションの可能性は無限であり、どのようなインパルス応答でも入力信号に適用できる。
In JUCE, you can perform Convolution with the dsp::Convolution class included in the DSP module.
Integrate the Waveshaper
In the ディストーション
class, add a juce::dsp::ProcessorChain with a juce::dsp::WaveShaper processor as a template argument [1]. Also define an enum with the processor index [2] to be able to clearly refer to the corresponding process via its index later on.
private:
//==============================================================================
enum
{
waveshaperIndex // [2]
};
juce::dsp::ProcessorChain> processorChain; // [1].
};
In the リセット
function, call the reset function of the waveshaper in the processor chain [3].
void reset() noexcept
{
processorChain.reset(); // [3].
}
In the prepare()
function, call the prepare function of the waveshaper in the processor chain [4].
void prepare (const juce::dsp::ProcessSpec& spec)
{
processorChain.prepare (spec); // [4].
}
ここで、ウェーブシェイパーが入力信号の整形に使用する伝達関数を定義したいと思います。 このチュートリアルの導入部で説明したように、まずハードクリッピング関数から始めましょう。
In the constructor, get a reference to the WaveShaper by supplying the index of the process and use the processorChain.get<>()
method [5]. Let's initialise the waveshaper using a lambda function that limits the values to a range of -0.1 .. 0.1 [6].
public:
//==============================================================================
Distortion()
{
auto& waveshaper = processorChain.template get(); // [5]
waveshaper.functionToUse = [] (タイプ x)
{
return juce::jlimit (Type (-0.1), Type (0.1), x); // [6].
};
}
xfloat xDefinition juce_UnityPluginInterface.h:191
In the プロセス()
function, we can call the process function of the waveshaper in the processor chain [7].
template void process (const ProcessContext& context) noexcept
{
processorChain.process (context); // [7].
}
If we run this code after implementing the above changes in the ディストーション
class, we should be able to hear the effects of the waveshaper on the oscillator signal.
Change the amount of limiting that occurs in the transfer function by restricting the values between -0.5 ..0.5. Do you perceive a difference in sound?
Change the transfer function
伝達関数を双曲線正接に変えて、ウェーブシェーパーのクリッピングをもう少しソフトにしてみよう。
Add two juce::dsp::Gain
processors to the processor chain with the juce::dsp::WaveShaper
in between them [1] and add the corresponding indices to the enum as pre-gain [2] and post-gain [3]. These will allow us to adjust the level of the signal going into the waveshaper and control the level coming out of it, thus affecting the behaviour of the transfer function.
private:
//==============================================================================
enum
{
preGainIndex, // [2]
waveshaperIndex,
postGainIndex // [3]
};
juce::dsp::ProcessorChain, juce::dsp::WaveShaper, juce::dsp::Gain> processorChain; // [1].
};
In the constructor, change the transfer function to a hyperbolic tangent [4] as described in the introduction of this tutorial:
public:
//==============================================================================
Distortion()
{
auto& waveshaper = processorChain.template get();
waveshaper.functionToUse = [] (Type x)
{
return std::tanh (x); // [4]
};
auto& preGain = processorChain.template get(); // [5]
preGain.setGainDecibels (30.0f); // [6]
auto& postGain = processorChain.template get(); // [7]
postGain.setGainDecibels(-20.0f); // [8].
}
Here we also retrieve a reference to the pre-gain processor [5] and boost the signal going into the waveshaper by 30dB [6]. Then we get a reference to the post-gain processor [7] and trim down the level coming from the waveshaper by 20dB [8].
プログラムを実行すると、また違った歪んだサウンドが得られるはずだ。
ウェーブシェイパーで使われている伝達関数をシグナム関数に変えてみてください。 音の違いを感じますか?
Adjust the filter
お気づきかもしれませんが 、ウェーブシェイパーで低域を歪ませると、歪んだ音がすぐに濁ってしまうことがあります。 ウェーブシェイパーで信号を処理する前にハイパスフィルターを導入することで、この問題を軽減することができます。
Add a juce::dsp::ProcessorDuplicator
with the juce::dsp::IIR::Filter
and juce::dsp::IIR::Coefficients
classes as template arguments [1] in order to easily convert the mono filter into a multi-channel version. To simplify the long class names, we use shorter names with the "using" keyword and add the corresponding index in the enum [2].
private:
//==============================================================================
enum
{
filterIndex, // [2]
preGainIndex,
waveshaperIndex,
postGainIndex
};
using Filter = juce::dsp::IIR::Filter;
using FilterCoefs = juce::dsp::IIR::Coefficients;
juce::dsp::ProcessorChain,
juce::dsp::Gain, juce::dsp::WaveShaper, juce::dsp::Gain> processorChain;
};
In the prepare()
function, get a reference to the filter processor [3] and specify the cut-off frequency of the high-pass filter to 1kHz by calling the makeFirstOrderHighPass()
function [4]:
void prepare (const juce::dsp::ProcessSpec& spec)
{
auto& filter = processorChain.template get(); // [3]
filter.state = FilterCoefs::makeFirstOrderHighPass (spec.sampleRate, 1000.0f); // [4].
processorChain.prepare (spec);
}
信号の低域が減衰し、よりクリアなサウンドになるはずです。
ウェーブシェイピングの前にさまざまなタイプのフィルターを試し、歪んだ信号の高調波成分がどのように変化するかに注目してください。
Implement the cabinet simulator
コンボリューションを使ってギター・キャビネットをシミュレートすることで、サウンドにさらに個性を与えてみよう。
In the キャブシミュレーター
class, add a juce::dsp::Convolution
processor to the processor chain [1] and add its corresponding index in the enum [2].
private:
//==============================================================================
enum
{
convolutionIndex // [2]
};
juce::dsp::ProcessorChainprocessorChain;
};
In the リセット
function, call the reset function of the convolver in the processor chain [3].
void reset() noexcept
{
processorChain.reset(); // [3].
}
In the prepare()
function, call the prepare function of the convolver in the processor chain [4].
private:
//==============================================================================
enum
{
convolutionIndex // [2]
};
juce::dsp::ProcessorChainprocessorChain;
};
Now we want to specify the impulse response that the convolver will use to reverberate the incoming signal. As presented in the introduction section of this tutorial, let's load the convolution processor with the guitar amp impulse response included in the リソース
folder of the project.
In the constructor, get a reference to the Convolution processor by supplying the index of the process and use the processorChain.get<>()
method [5]. Let's initialise the convolver with the guitar amp impulse response by loading the audio file from the リソース
folder [6].
CabSimulator()
{
auto dir = juce::File::getCurrentWorkingDirectory();
int numTries = 0;
while (! dir.getChildFile ("Resources").exists() && numTries++ < 15)
dir = dir.getParentDirectory();
auto& convolution = processorChain.template get(); // [5]
convolution.loadImpulseResponse (dir.getChildFile ("Resources").getChildFile ("guitar_amp.wav")、
juce::dsp::Convolution::Stereo::yes、
juce::dsp::Convolution::Trim::no、
1024); // [6]
}
Make sure that the "guitar_amp.wav" file exists in the リソース
folder of your project.
In the プロセス()
function, we can call the process function of the convolver in the processor chain [7].
template void process (const ProcessContext& context) noexcept
{
processorChain.process (context); // [7].
}
In the ディストーション
class, remove the gain trim or set its level to 0dB of attenuation [8] as the signal will get attenuated naturally through the convolution process which happens after the distortion in our signal chain:
public:
//==============================================================================
Distortion()
{
auto& waveshaper = processorChain.template get();
waveshaper.functionToUse = [] (Type x)
{
return std::tanh (x);
};
auto& preGain = processorChain.template get();
preGain.setGainDecibels (30.0f);
auto& postGain = processorChain.template get();
postGain.setGainDecibels (0.0f); // [8].
}
プログラムを実行し、どのように聞こえるか見てみよう。
Load the impulse reponse of the cassette recorder included in the リソース
folder and notice how drastically the convolved sound changes.
The source code for this modified version of the code can be found in the DSPConvolutionTutorial_02.h
file of the demo project.
概要
このチュートリアルでは、ウェーブシェーピングとコンボリューションの取り入れ方を学んだ。 特に
- ウェーブシェイピングとコンボリューションの基礎を学ぶ。
- ハードクリッピング・ウェーブシェイパーを内蔵し、歪みを作り出す。
- ウェーブシェーパーの伝達曲線を双曲線タンジェントに変更。
- 畳み込み技術を用いたキャビネット・シミュレータを実装。
Jump back to part 1 of this tutorial to understand oscillators and filters here: チュートリアルDSP入門
Check out part 3 of this tutorial to add delay lines here: チュートリアルディレイラインを使ったストリング・モデルの作成