サイン波のオシレータの発生方法にいろいろと改良項目があるみたいです。
アルゴリズムって感じになってきたね~
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のサンプルを示すことになって、なめらかにウェーブテーブルの最後と先頭のゲイン値がつながることになります。
理解するのに結構てこずりました。