LoFiBeats・84分の作業用BGMを作成しました!

JUCEプログラミング、Tutorial: Wavetable synthesis2、ウェーブテーブルのオシレータクラス実装

1_プログラミング

ウェーブテーブルの実装について非常に気になります!

シンセではよく使う技術っぽいから、押さえておきたいね~

JUCEチュートリアル、Synthの項目、「Wavetable synthesis」を進めていきます。

今回は、「Wavetable Oscillator」の項目です。前回のサイン波オシレータを、ウェーブテーブルの仕組みを導入したオシレータに作り変えるような実装を行います。

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

前回のチュートリアルで作成したプログラムの改良となります。

こんな人の役に立つかも

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

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

・JUCEでウェーブテーブルの仕組みを実装したい人

スポンサーリンク

ウェーブテーブルの仕組みに変更

前回のサイン波オシレータクラスを変更して、ウェーブテーブルの仕組みにより、サイン波を出力する「WavetableOscillator」クラスというものを実装していきます。アプリの挙動としては、前回と同じものとなりますが、ウェーブテーブル方式にすることで、CPUの負荷などが少なくなる模様です。

MainComponent.h

MainComponentクラス

MainComponentクラスにはいくつかの変更を加えます。

[0]で、タイマークラスを継承しておきます。

class MainComponent  : public juce::AudioAppComponent,
    public juce::Timer//[0]タイマーを継承します。
{
public:
//...略...
    void createWavetable();//[1]ウェーブテーブルのサンプル作成関数です。

private:
    juce::Label cpuUsageLabel;
    juce::Label cpuUsageText;

    const unsigned int tableSize = 1 << 7;//[2]テーブルサイズです。
    float level = 0.0f;
    juce::AudioSampleBuffer sineTable;//[3]ウェーブテーブルです。
    //juce::OwnedArray<SineOscillator> oscillators;
    juce::OwnedArray<WavetableOscillator> oscillators;//[4]これから実装するオシレータクラスです。

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};

[1]で、後程cppファイルに中身を実装する「createWaveTable」関数を定義しておきます。createWaveTable関数では、サイン波のデータを作成する関数です。

[2]、[3]はウェーブテーブルで利用するための変数です。[2]は、シフト演算で1という整数を左に7回シフトして、128のビットを1とすることで整数の128になりますので、128という整数となります。ウェーブテーブルのサンプルサイズを表現します。

ここでシフト演算を利用した意味はあまりよくわかりません。

[4]は、以前SineOscillatorクラスとして定義したサイン波オシレータを今回は「WavetableOscillator」という名称で再度作成しなおすので、配列の対象となるクラスをWavetableOscillatorに変更しています。

WavetableOscillatorクラス

SinewaveOscillatorクラスと構成は似ていますが、計算方式が違うWavetableOscillatorクラスというものを新たに作成しました。ヘッダーファイルに追加します。MainComponentクラスの前に作成しました。

class WavetableOscillator
{
public:
    WavetableOscillator(const juce::AudioSampleBuffer& wavetableToUse)
        : wavetable(wavetableToUse)
    {
        jassert(wavetable.getNumChannels() == 1);
    }

    //[2]周波数とサンプルレートでtableDeltaを求める関数です。
    void setFrequency(float frequency, float sampleRate)
    {
        auto tableSizeOverSampleRate = (float)wavetable.getNumSamples() / sampleRate;
        tableDelta = frequency * tableSizeOverSampleRate;
    }

    //[3]実際に音声のゲインを計算する処理です。
    forcedinline float getNextSample() noexcept
    {
        //[3-1]ウェーブテーブルの音声のサンプル数を取得します。
        auto tableSize = (unsigned int)wavetable.getNumSamples();

        //[3-2]currentIndexで、前の処理のインデックス番号を引き継ぎます。
        auto index0 = (unsigned int)currentIndex;
        //[3-3]index1として、index0の次のサンプル番号を求めます。
        auto index1 = index0 == (tableSize - 1) ? (unsigned int)0 : index0 + 1;

        auto frac = currentIndex - (float)index0;

        //[3-4]ウェーブテーブルのサンプル値(ゲイン)を取得します。
        auto* table = wavetable.getReadPointer(0);
        auto value0 = table[index0];
        auto value1 = table[index1];

        //[3-5]現在のサンプル値を計算します。
        auto currentSample = value0 + frac * (value1 - value0);

        //[3-6]次のcurrentIndexを設定します。
        if ((currentIndex += tableDelta) > (float)tableSize)
            currentIndex -= (float)tableSize;

        return currentSample;
    }

private:
    //[1]privateなメンバを定義しています。
    const juce::AudioSampleBuffer& wavetable;
    float currentIndex = 0.0f, tableDelta = 0.0f;
};

[1]で、ウェーブテーブル方式のオシレータに利用するメンバ変数を定義します。AudioSampleBufferクラスは、32bit、floatのマルチチャンネルの音声バッファクラスです。AudioBufferクラスのfloat定義のものと同じになります。また、currentIndexは、現在のテーブルのインデックス値、tableDeltaはテーブルのサンプルの変化分とします。

[2]の「setFrequency」では、以前angleDeltaという変数を求めていたものをtableDeltaを求めるという目的に変更しています。今回は、すでに決まっているオーディオバッファサイズをサンプルレートで割ることで、準備したテーブルに対してどれだけ1サンプルで時間が進むか、という点を計算します。なので、求めるものがラジアンから、テーブルに定義したサンプルのサンプル数に変化しています。

[3]は、親コンポーネントのgetNextAudioBlockから呼び出されるオシレータの音声処理になります。

[3-1]では、ウェーブテーブルとして登録している音声のサンプル数を取得します。

[3-2]では、現在処理のウェーブテーブルの処理インデックス(サンプル番号)をIndex0という変数に取得します。この時、整数に値をキャストして取得します。currentIndexは、前回の処理インデックスの次のインデックスとしてfloatの小数値で保持されていますがここでは、ウェーブテーブルのサンプル数を取得したいので、値を整数にキャストしてIndex0とします。

[3-3]では、現在のインデックス(サンプル番号)の次のインデックスをIndex1という変数に取得します。そして、「frac」という変数として、小数のcurrentIndex変数から、それを整数にキャストしたIndex0を引いています。これで、丸められた小数値を求めることができます。

[3-4]では、ウェーブテーブルを保存しているAudioBufferクラスのポインタを取得して、実際のウェーブテーブルのインデックス(サンプル)の値のIndex0番目とIndex1番目の値を取得します。

[3-5]では、今回のサンプルの値(ゲインの値)を求めています。「value0-value1」は、ウェーブテーブルに保持されているゲインの変化量になりますが、今回は、この間のfracという変化量なので、value0〜value1の変化量のfracの割合だけゲインを変化させることになります。これを、value0に足しこむことで、ウェーブテーブルの荒い保持サンプルの間のゲイン値を補完しています。

[3-6]では、次の処理に向けてcurrentIndexを更新します。deltaTableをたして次のcurrentIndexとしたいのですが、もしウェーブテーブルのサンプル数を上回った場合は、ウェーブテーブルの全体のサンプル値(整数)を引くことで波形の頭に戻って次のサンプル数を指し示します。

WavetableOscillatorクラス、言葉だけではわかりずらいですね。できれば図式化したいです。

タイトルとURLをコピーしました