【JUCEチュートリアル】Add distortion through waveshaping and convolution、ソフトクリッピングで歪みを加える

パンダさん

ソフトクリッピングを実装してどんなふうに音色が違うのかを比較していきたいと思います。

コパンダ

より音楽的な歪みに近づくことができるね~

前回の実装では、波形を一定のゲインでスパッと切るような、ハードクリッピングでの歪みを作成しました。今回は、tanhで丸みを帯びさせた波形のゲインを上げて、クリッピングさせ、ゲインを下げて波形の歪みを作るようなソフトクリッピングを実装していきます。ハードクリッピングとソフトクリッピングについては、こちらの記事で紹介しておりますので、ご参照くださいませ。

公式のチュートリアルは、こちらの「Change the transfer function」の項目の内容になります。

目次

概要

ハードクリッピングとソフトクリッピングの音声を録音してみました。

※プチプチノイズが発生しますので、音量は小さい音量でお聴きください。

前半がハードクリッピングです。後半がソフトクリッピングの音色になります。

ハードクリッピングでの波形は、スパッと一定のゲインで切れるような波形になりました。これは、前回の記事で実装した項目のウェーブシェーピングのプログラムです。

一方で、ソフトクリッピングは、丸みを帯びた感じの波形になっています。

実装

歪みの波形を確認するのにわかりにくいため、リバーブなど、いくつかの実装機能を停止してからプログラムを変更しました。

準備

キャビネットシミュレータとリバーブの停止

歪みのみの効果がわかりやすいように、デモアプリケーションのキャビネットシミュレータと、リバーブを消しました。PluginProcessor.hのAudioEngineクラスを次のように変更しました。

class AudioEngine : public juce::MPESynthesiser
{
public:
//...略...
private:
    enum
    {
        distortionIndex
        //キャビネットシミュレータと、リバーブをインデックスから削除します。
        /*,
        cabSimulatorIndex,
        reverbIndex*/
    };

    //プロセッサチェインからもキャビネットシミュレータとリバーブを削除します。
    juce::dsp::ProcessorChain<Distortion<float>/*, CabSimulator<float>, juce::dsp::Reverb*/> fxChain;
//...略...
};

LFOの停止

Voiceクラスのlfo関連のプログラムをコメントアウトしました。

class Voice : public juce::MPESynthesiserVoice
{
public:
    Voice()
    {
        //[1]
        /*lfo.initialise([](float x)
            {
                return std::sin(x);
            }, 128);
        lfo.setFrequency(3.0f);*/

//...略...
    void prepare(const juce::dsp::ProcessSpec& spec)
    {
        //[2]
        //lfo.prepare({ spec.sampleRate / lfoDownsamplingRatio, spec.maximumBlockSize, 1 });
        tempBlock = juce::dsp::AudioBlock<float>(heapBlock, spec.numChannels, spec.maximumBlockSize);
        processorChain.prepare(spec);
    }

//...略...
    void renderNextBlock(juce::AudioBuffer<float>& outputBuffer, int startSample, int numSamples) override
    {
        //[3]
        /*for (int i = 0; i < numSamples; ++i)
        {
            if (--lfoProcessingIndex == 0)
            {
                lfoProcessingIndex = lfoDownsamplingRatio;
                auto lfoOut = lfo.processSample(0.0f);
                auto cutoffHz = juce::jmap(lfoOut, -1.0f, 1.0f, 100.0f, 4e3f);
                processorChain.get<filterIndex>().setCutoffFrequencyHz(cutoffHz);
            }
        }*/
//...略...

private:
//...略...

    /*static constexpr size_t lfoDownsamplingRatio = 128;
    size_t lfoProcessingIndex = lfoDownsamplingRatio;
    juce::dsp::Oscillator<float> lfo;*/
};

これで、歪みの効果のみを見ることができるようになりました。

Distortionクラスの変更

コンストラクタを以下のように変更しました。

    Distortion()
    {
        auto& waveshaper = processorChain.template get<waveshaperIndex>();
        waveshaper.functionToUse = [](Type x)
        {
            //[1]波形データを加工する関数をtanhに変更しました。
            //return juce::jlimit(Type(-0.1), Type(0.1), x);
            return std::tanh(x);
        };
        //[2]入力ゲインを追加しました。
        auto& preGain = processorChain.template get<preGainIndex>();
        preGain.setGainDecibels(30.0f);
        //[3]出力ゲインを追加しました。
        auto& postGain = processorChain.template get<postGainIndex>();
        postGain.setGainDecibels(-20.0f);
    }

コンストラクタで、[1]〜[3]のように、DSPモジュールを設定します。[1]では、ウェーブシェイパーで波形に変更を加える式にtanhとしました。ソフトクリッピングの仕組みですね。次に、[2]で30デシベルと音量をかなり大きくします。そして、[3]で音量を出力できるゲインにまで下げることで、エッジがソフトな歪みの完成です。

次に、プロセッサチェインクラスに、入力ゲインと、出力ゲインの処理を追加しておきます。これは、privateなメンバとして定義しているProcessorChainクラスに追記をしていきます。

private:
    enum
    {
        preGainIndex,//[1]入力ゲインのインデックスです。
        waveshaperIndex,
        postGainIndex//[2]出力ゲインのインデックスです。
    };
    //[3]「入力ゲイン→ウェーブシェーピング→出力ゲイン」の処理順に定義します。
    juce::dsp::ProcessorChain<juce::dsp::Gain<Type>, juce::dsp::WaveShaper<Type>, juce::dsp::Gain<Type>> processorChain;
};

[1]〜[3]を追加することで、先ほどのコンストラクタで利用している、入力ゲインと出力ゲインを追加したProcessorChainクラスとなります。

Distortionクラス全体

template <typename Type>
class Distortion
{
public:
    Distortion()
    {
        auto& waveshaper = processorChain.template get<waveshaperIndex>();
        waveshaper.functionToUse = [](Type x)
        {
            //波形データを加工する関数をtanhに変更しました。
            //return juce::jlimit(Type(-0.1), Type(0.1), x);
            return std::tanh(x);
        };
        //入力ゲインを追加しました。
        auto& preGain = processorChain.template get<preGainIndex>();
        preGain.setGainDecibels(30.0f);
        //出力ゲインを追加しました。
        auto& postGain = processorChain.template get<postGainIndex>();
        postGain.setGainDecibels(-20.0f);
    }

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

    template <typename ProcessContext>
    void process(const ProcessContext& context) noexcept
    {
        processorChain.process(context);
    }

    void reset() noexcept
    {
        processorChain.reset();
    }

private:
    enum
    {
        preGainIndex,//入力ゲインのインデックスです。
        waveshaperIndex,
        postGainIndex//出力ゲインのインデックスです。
    };
    //「入力ゲイン→ウェーブシェーピング→出力ゲイン」の処理順に定義します。
    juce::dsp::ProcessorChain<juce::dsp::Gain<Type>, juce::dsp::WaveShaper<Type>, juce::dsp::Gain<Type>> processorChain;
};
よかったらシェアしてね!
目次
閉じる