メインコンテンツまでスキップ

チュートリアルウェーブシェイピングとコンボリューションで歪みを加える

📚 Source Page

ウェーブシェーピングでハーモニック・ディストーションを作り、シンセサイザー・サウンドに重厚さを加える。インパルス・レスポンスに含まれる音の特徴を取り出すコンボリューションの基礎を学びます。

レベル上級

プラットフォーム:Windows, macOS, Linux

プラグイン形式:VST, AU, スタンドアロン

クラス: dsp::ProcessorChain,dsp::Gain,dsp::Oscillator,dsp::Convolution,dsp::WaveShaper,dsp::Reverb,dsp::ProcessorDuplicator

警告

このプロジェクトには、C++14の機能をサポートするコンパイラが必要です。XcodeとVisual Studioの最近のバージョンには、このサポートが含まれています。

スタート

このチュートリアルはTutorial: Introduction to DSP.もしまだなら、まずそのチュートリアルを読むべきだ。

このチュートリアルのデモ・プロジェクトのダウンロードはこちらから:PIP|ZIP.プロジェクトを解凍し、最初のヘッダーファイルをProjucerで開く。

警告

このプロジェクトのPIPバージョンを使用する場合は、必ずResourcesフォルダを生成されたProjucerプロジェクトに追加します。

このステップにヘルプが必要な場合は、以下を参照してください。Tutorial: Projucer Part 1: Getting started with the Projucer.

デモ・プロジェクト

プロジェクトはプラグインとして構想されていますが、IDEで適切なデプロイメント・ターゲットを選択することで、スタンドアロン・アプリケーションとして実行することができます。Xcodeでは、以下のスクリーンショットのように、メインウィンドウの左上でターゲットを変更することができます:

Xcodeでデプロイメント・ターゲットを変更する
Xcodeでデプロイメント・ターゲットを変更する

デモ・プロジェクトでは、プラグインの上半分にMIDIキーボードが画面上に表示され、下半分にはオシロスコープを通して信号が視覚的に表示される。現在、鍵盤が押されると、プラグインはリバーブを加えた基本的なオシレーター・サウンドを出力します。

注記

でリバーブプロセッサーをコメントアウトすることで、チュートリアルの各ステップの変化を明確に聴き取ることができます。AudioEngineクラスである。

デモプロジェクトのプラグインウィンドウ
デモプロジェクトのプラグインウィンドウ
注記

MIDIコントローラーをお持ちの場合は、このチュートリアル中、画面上のキーボードを使う代わりに、MIDIコントローラーを接続することもできます。

はじめに

このチュートリアルでは、異なる方法で信号処理を可能にする2つの新しいDSPコンセプトを紹介します:ウェーブシェーピングとコンボリューションです。

このDSP用語の定義から始めよう。

ウェーブシェイピングとは何か?

ウェーブシェーピングとは、ある信号が、元の信号に適用される伝達関数によって別の信号にシェーピングされるプロセスのことである。例えば、単純な正弦波は、数学的な関数を適用することによって異なる波形に成形することができる。

ウェーブシェイパーは、特定の伝達関数を適用する際に、元の信号に高調波コンテンツを追加することで、効果的に歪みを作り出すために使用することができます。ご存知のように、例えば矩形波や三角波は基本的に正弦波に奇数倍音を加えたもので、のこぎり波は正弦波に奇数倍音と偶数倍音を加えたものです。

この事実を知っていれば、歪みを作り出す1つの方法は、正弦波の形状を矩形波に近づけることである。では、伝達関数を使ってそれを実現するにはどうすればよいのでしょうか?

例えば、単純な正弦波を考えてみよう。sin(x)これをプロットすると次のようになる:

正弦波
正弦波

符号伝達関数を正弦波に適用すると、入力された数値の符号を本質的に出力する関数になる。sgn(sin(x))これは矩形波を完璧に表現している:

シグナム機能付き方形波
シグナム機能付き方形波

しかし、この完璧な波形は、カーブのエッジが硬いため、ハードクリッピングと呼ばれる厳しい歪みを生み出すという問題がある。また、このような波形は、アナログ領域で再現するには「完璧すぎる」ため、ほとんどのアナログ・シンセサイザーで作られる矩形波のようには聞こえません。

ソフトクリッピングと呼ばれる、より穏やかな歪みを作るには、双曲線正接の伝達関数を使うことができる。tanh(sin(x))これは正弦波に近い信号を出力するが、カーブはこのように丸くなる:

tanh関数による方形波
tanh関数による方形波

そして、正方形のような形状にするために、伝達関数を適用する前に、信号をクリップするようにブーストすることができる。tanh(n*sin(x))つまり、ベル形状の頂点を切り捨て、下のようにソフトエッジの矩形波にしているのだ:

tanh関数をクリップした矩形波
tanh関数をクリップした矩形波

おわかりのように、どのような種類の伝達関数でも入力信号に適用できるため、ウェーブシェーピングの可能性は無限にある。

JUCEでは、ウェーブシェーピングはdsp::WaveShaperクラスはDSPモジュールに含まれている。

コンボリューションとは何か?

コンボリューションとは、ある空間の残響特性を、その空間の特性を表すインパルス応答をあらかじめ録音しておき、それを使ってシミュレートすることです。このプロセスにより、インパルス応答のサンプルに対してデータの各サンプルを乗算して合成出力を作成するコンボリューションによって、入力信号にあらゆるタイプの音響プロファイルを適用することができます。

インパルス・レスポンスとは、プロファイリングする空間で短いインパルスを録音することで生成されるオーディオファイルのことですが、必ずしも実際の物理的な空間である必要はありません。例えば、このようにキャビネットを通してインパルスを再生し、その効果を録音することで、ギターアンプのプロファイルをキャプチャすることができます:

ギター・アンプのインパルス・レスポンス
ギター・アンプのインパルス・レスポンス

同じことをカセットレコーダーを通して行い、生成されたインパルス応答は以下の通りである:

カセット・レコーダーのインパルス応答
カセット・レコーダーのインパルス応答

入力信号がインパルス応答とコンボリューションされると、元のドライ信号は、両方の特性を保持したウェットな残響音に変換されます。例えば、100msの440Hzの正弦波を、上図のギター・アンプのインパルス応答でコンボリューションすると、次のような結果が得られます:

ギター・アンプのインパルス・レスポンスによるサイン波
ギター・アンプのインパルス・レスポンスによるサイン波

先に紹介したカセット・レコーダーのインパルス応答と同じ正弦波信号が、最終的にこの波形を生成する:

カセット・レコーダーのインパルス応答による正弦波
カセット・レコーダーのインパルス応答による正弦波

このように、コンボリューションの可能性は無限であり、どのようなインパルス応答でも入力信号に適用できる。

JUCEでは、コンボリューションはdsp::ConvolutionクラスはDSPモジュールに含まれている。

ウェーブシェイパーの統合

の中でDistortionクラスのテンプレート引数として、 juce::dsp::ProcessorChain に juce::dsp::WaveShaper プロセッサを追加します。[1].また、プロセッサ・インデックスを持つ enum も定義します。[2]これは、後でそのインデックスから対応するプロセスを明確に参照できるようにするためである。

private:
//==============================================================================
enum
{
waveshaperIndex // [2]
};

juce::dsp::ProcessorChain> processorChain; // [1]
};

の中でreset()関数で、プロセッサ・チェーン内のウェーブシェーパーのリセット関数を呼び出します。[3].

    void reset() noexcept
{
processorChain.reset(); // [3]
}

の中でprepare()関数を呼び出し、プロセッサ・チェイン内のウェーブシェイパーの準備関数を呼び出します。[4].

void prepare (const juce::dsp::ProcessSpec& spec)
{
processorChain.prepare (spec); // [4]
}

ここで、ウェーブシェイパーが入力信号の整形に使用する伝達関数を定義したいと思います。このチュートリアルの導入部で説明したように、まずハードクリッピング関数から始めましょう。

コンストラクタで、プロセスのインデックスを指定してWaveShaperへの参照を取得しprocessorChain.get<>()方法[5].の範囲に値を制限するラムダ関数を使ってウェーブシェイパーを初期化してみよう。-0.1 .. 0.1 [6].

public:
//==============================================================================
Distortion()
{
auto& waveshaper = processorChain.template get(); // [5]
waveshaper.functionToUse = [] (Type x)
{
return juce::jlimit (Type (-0.1), Type (0.1), x); // [6]
};
}
xfloat xDefinition juce_UnityPluginInterface.h:200

の中でprocess()関数を呼び出せば、プロセッサー・チェインのウェーブシェイパーのプロセス関数を呼び出すことができる。[7].

    template 
void process (const ProcessContext& context) noexcept
{
processorChain.process (context); // [7]
}

で上記の変更を行った後にこのコードを実行すると、次のようになる。Distortionクラスであれば、ウェーブシェイパーがオシレーター信号に与える影響を聴き取ることができるはずだ。

オシレーター信号の波形整形
オシレーター信号の波形整形
エクササイズ

の間の値を制限することによって、伝達関数に生じる制限の量を変更する。-0.5 ..0.5.音の違いを感じますか?

伝達関数を変更する

伝達関数を双曲線正接に変えて、ウェーブシェーパーのクリッピングをもう少しソフトにしてみよう。

2つ追加juce::dsp::Gainをプロセッサ・チェーンに追加した。juce::dsp::WaveShaperその間[1]に対応するインデックスを列挙し、プリゲインとして追加する。[2]およびポストゲイン[3].これらによって、ウェーブシェイパーに入る信号のレベルを調整し、ウェーブシェイパーから出てくるレベルをコントロールすることができる。

private:
//==============================================================================
enum
{
preGainIndex, // [2]
waveshaperIndex,
postGainIndex // [3]
};

juce::dsp::ProcessorChain, juce::dsp::WaveShaper, juce::dsp::Gain> processorChain; // [1]
};

コンストラクタで、伝達関数を双曲線正接に変更する。[4]このチュートリアルの冒頭で説明したように:

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]
}

ここでは、プリゲイン・プロセッサーへのリファレンスも取得している。[5]で、ウェーブシェイパーに入る信号を30dBブーストする。[6].次に、ポストゲイン・プロセッサーへの参照を得る。[7]ウェーブシェイパーからのレベルを20dB下げる。[8].

プログラムを実行すると、また違った歪んだサウンドが得られるはずだ。

双曲線正接関数による波形整形
双曲線正接関数による波形整形
エクササイズ

ウェーブシェイパーで使われている伝達関数をシグナム関数に変えてみてください。音の違いを感じますか?

フィルターの調整

お気づきかもしれませんが、ウェーブシェイパーで低域を歪ませると、歪んだ音がすぐに濁ってしまうことがあります。ウェーブシェイパーで信号を処理する前にハイパスフィルターを導入することで、この問題を軽減することができます。

追加juce::dsp::ProcessorDuplicatorを持つ。juce::dsp::IIR::Filterそしてjuce::dsp::IIR::Coefficientsクラスのテンプレート引数[1]モノ・フィルターを簡単にマルチ・チャンネル・バージョンに変換するためだ。長いクラス名を簡略化するために、usingキーワードで短いクラス名を使用し、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;
};

の中でprepare()関数で、フィルター・プロセッサーのリファレンスを取得する。[3]を呼び出し、ハイパスフィルターのカットオフ周波数を1kHzに指定する。makeFirstOrderHighPass()機能[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);
}

信号の低域が減衰し、よりクリアなサウンドになるはずです。

ウェーブシェイパーの前のフィルタリング
ウェーブシェイパーの前のフィルタリング
エクササイズ

ウェーブシェイピングの前にさまざまなタイプのフィルターを試し、歪んだ信号の高調波成分がどのように変化するかに注目してください。

キャビネット・シミュレーターの実装

コンボリューションを使ってギター・キャビネットをシミュレートすることで、サウンドにさらに個性を与えてみよう。

の中でCabSimulatorクラスにjuce::dsp::Convolutionプロセッサからプロセッサ・チェーンへ[1]に対応するインデックスを追加する。[2].

private:
//==============================================================================
enum
{
convolutionIndex // [2]
};

juce::dsp::ProcessorChain processorChain;
};

の中でreset()関数を呼び出して、プロセッサー・チェーン内のコンボルバーのリセット関数を呼び出す。[3].

    void reset() noexcept
{
processorChain.reset(); // [3]
}

の中でprepare()関数を呼び出したら、プロセッサチェイン内のコンボルバーの準備関数を呼び出す。[4].

private:
//==============================================================================
enum
{
convolutionIndex // [2]
};

juce::dsp::ProcessorChain processorChain;
};

ここで、コンボルバーが入力信号の残響に使用するインパルス応答を指定します。このチュートリアルの導入セクションで説明したように、コンボリューション・プロセッサーに、ギターアンプのインパルス応答をResourcesフォルダにある。

コンストラクタで、プロセスのインデックスを指定してコンボリューション・プロセッサへの参照を取得しprocessorChain.get<>()方法[5].からオーディオ・ファイルをロードして、ギター・アンプのインパルス応答でコンボルバーを初期化しよう。Resourcesリーフレット[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]
}
警告

に "guitar_amp.wav "ファイルがあることを確認してください。Resourcesフォルダに保存する。

の中でprocess()関数を呼び出せば、プロセッサー・チェインでコンボルバーのプロセス関数を呼び出すことができる。[7].

    template 
void process (const ProcessContext& context) noexcept
{
processorChain.process (context); // [7]
}

の中でDistortionクラスでは、ゲイントリムを外すか、そのレベルを 0dB の減衰に設定してください。[8]というのも、信号チェーンの歪みの後に起こるコンボリューション・プロセスによって、信号は自然に減衰するからだ:

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]
}

プログラムを実行し、どのように聞こえるか見てみよう。

コンボリューションによるギター・アンプ・シミュレーション
コンボリューションによるギター・アンプ・シミュレーション
エクササイズ

に同梱されているカセットレコーダーのインパルス応答を読み込む。Resourcesフォルダに保存し、コンボリューションされたサウンドがどれほど劇的に変化するかに注目してください。

注記

この修正版のソースコードはDSPConvolutionTutorial_02.hファイルにある。

概要

このチュートリアルでは、ウェーブシェーピングとコンボリューションの取り入れ方を学んだ。特に

  • ウェーブシェイピングとコンボリューションの基礎を学ぶ。
  • ハードクリッピング・ウェーブシェイパーを内蔵し、歪みを作り出す。
  • ウェーブシェーパーの伝達曲線を双曲線タンジェントに変更。
  • 畳み込み技術を用いたキャビネット・シミュレータを実装。
注記

このチュートリアルのパート1に戻って、オシレーターとフィルターについて理解しましょう:Tutorial: Introduction to DSP

このチュートリアルのパート3で、ディレイラインを追加する方法をご覧ください:Tutorial: Create a string model with delay lines

こちらも参照