JUCEプログラミング、Tutorial: Wavetable synthesis5、ウェーブテーブルオシレータの改良

サイン波のオシレータの発生方法にいろいろと改良項目があるみたいです。

アルゴリズムって感じになってきたね~

JUCEチュートリアル、Synthの項目、「Wavetable synthesis」の続きです。「Wrapping the Wavetable」をやっていきます。

あわせて読みたい

公式ドキュメントからダウンロードできる該当サンプルは、「WavetableSynthTutorial_03.h」となります。

こんな人の役に立つかも

・JUCEでプログラミングの勉強をしている人

・JUCEチュートリアル「Wavetable synthesis」を勉強している人

・ウェーブテーブルの仕組みを勉強している人

目次

改良点

WavetableOscillatorクラスのgetNextSample関数の「index0」と「index1」の指定では、問題点があるようです。127から0へインデックスが戻る時、同じ値となってしまう現象です。

//...略... 
    forcedinline float getNextSample() noexcept
    {
        auto tableSize = (unsigned int)wavetable.getNumSamples();

        auto index0 = (unsigned int)currentIndex;
        auto index1 = index0 == (tableSize - 1) ? (unsigned int)0 : index0 + 1;
//...以下略...

index0が127のとき、index1は0となりますが、その時のデータ(ゲイン値)はともに0となります。

それなので、補完の計算をしても、index0が0を越えないと下の図のようになってしまい、なめらかにに先頭サンプルにつながりません。

また、現状のgetNextSample関数でのindex1でサンプルを0に戻すようにしていますが、今回の改良で、条件処理を少なくすることで、CPU負荷を減らす工夫ができるようです。

改善プログラムの実装

WavetableOscillatorクラスの変更

class WavetableOscillator
{
public:
    WavetableOscillator(const juce::AudioSampleBuffer& wavetableToUse)
        : wavetable(wavetableToUse),
        tableSize(wavetable.getNumSamples() - 1)//[2]初期化を追加しました。
    {
        jassert(wavetable.getNumChannels() == 1);
    }

    void setFrequency(float frequency, float sampleRate)
    {
        //auto tableSizeOverSampleRate = (float)wavetable.getNumSamples() / sampleRate;
        //[3]
        auto tableSizeOverSampleRate = (float)tableSize / sampleRate;
        tableDelta = frequency * tableSizeOverSampleRate;
    }

    forcedinline float getNextSample() noexcept
    {
        //auto tableSize = (unsigned int)wavetable.getNumSamples();//delete

        //auto index0 = (unsigned int)currentIndex;
        //auto index1 = index0 == (tableSize - 1) ? (unsigned int)0 : index0 + 1;
        //[4]index1は単純にindex0の次を示すように変更です。
        auto index0 = (unsigned int)currentIndex;
        auto index1 = index0 + 1;

        auto frac = currentIndex - (float)index0;

        auto* table = wavetable.getReadPointer(0);
        auto value0 = table[index0];
        auto value1 = table[index1];

        auto currentSample = value0 + frac * (value1 - value0);

        if ((currentIndex += tableDelta) > (float)tableSize)
            currentIndex -= (float)tableSize;

        return currentSample;
    }

private:
    const juce::AudioSampleBuffer& wavetable;
    const int tableSize;//[1]テーブルサイズをメンバ変数とします。
    float currentIndex = 0.0f, tableDelta = 0.0f;
};

[1]と[2]で、「tableSize」を変数として定義して、コンストラクタで最初に初期化をするように変更しました。

[3]では、今までwavetableオブジェクトからサイズを取得していたものを「tableSize」へと変更します。

[4]では、index1の設定の際の条件をなくし、単純にindex0の次のサンプルを指し示すように変更しています。

cppファイル、createWavetable関数の変更

createWavetable関数を以下のように変更しました。

void MainComponent::createWavetable()
{
    //sineTable.setSize(1, (int)tableSize);

    //[1]↑を次のように変更しました。
    sineTable.setSize(1, (int)tableSize + 1);//tableSize + 1 = 129となります。
    auto* samples = sineTable.getWritePointer(0);

    auto angleDelta = juce::MathConstants<double>::twoPi / (double)(tableSize - 1);
    auto currentAngle = 0.0;

    for (unsigned int i = 0; i < tableSize; ++i)
    {
        auto sample = std::sin(currentAngle);
        samples[i] = (float)sample;
        currentAngle += angleDelta;
    }

    //[2]最後のサンプルを最初のサンプル値と一致させます。
    samples[tableSize] = samples[0];
}

128サンプルのウェーブテーブルではなく、サンプル数を1つ増やして、129サンプルのウェーブテーブルを作成するようにします。[2]は先頭サンプルに戻る時に変なノイズとならないように、強制的に先頭サンプルと同じ値としています。

改善の概要

createWavetable関数で作成されるウェーブテーブルは「129」サンプルになります。最初に変更した「WavetableOscillator」クラスでは、ウェーブテーブルのサイズから-1した「128」サンプルを使用することで、WavetableOscillatorクラスの「getNextSample」関数でindex1を条件判断で0に戻す必要がなくなりました。

今回のアルゴリズムでは、上のように、index0が127のとき(currentIndexが127.Xのとき)は、index1が最後のサンプル(先頭サンプルと同じゲイン値)としているので、補完式ではゲイン値0に向かっていきます。index0+tableDeltaが128を越えると最後の条件で、currentIndexが0.Xの値になります。そのため、次の処理の際には、図の下のようにindex0が0、index1が1のサンプルを示すことになって、なめらかにウェーブテーブルの最後と先頭のゲイン値がつながることになります。

理解するのに結構てこずりました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次