チュートリアルサイン波シンセサイザーを作る
このチュートリアルでは、簡単なサイン波合成を紹介します。正弦波発振器の状態を管理し、オーディオ出力にデータを書き込む方法を紹介します。
レベル中級
プラットフォームWindows , macOS , Linux
クラス: AudioAppComponent,Slider,MathConstants
スタート
このチュートリアルはTutorial: Control audio levelsそれは最初に読んで理解すべきだった。
このチュートリアルのデモ・プロジェクトのダウンロードはこちらから:PIP|ZIP.プロジェクトを解凍し、最初のヘッダーファイルをProjucerで開く。
このステップにヘルプが必要な場合は、以下を参照してください。Tutorial: Projucer Part 1: Getting started with the Projucer.
デモ・プロジェクト
このデモ・プロジェクトは、Projucerのオーディオ・アプリケーション・テンプレートに基づいています。正弦波の周波数をコントロールするためのスライダーが1つ表示されます。
正弦波の生成
このチュートリアルでは、標準ライブラリ関数std::sin()
.これを使用するには、電流を保存して正弦波生成の状態を維持する必要があります。位相角と、出力サンプルごとに位相角がインクリメントされる必要がある量です。このサンプルごとの変化の大きさ(「デルタ」)は、出力のサンプル・レートと、生成したい正弦波の周波数に依存します。
ほとんどの合成アプリケーションやプラグインではstd::sin()
最も効率的な手法ではないだろう。一般的にwavetableを参照されたい。Tutorial: Wavetable synthesis.ウェーブテーブルは正弦波以外の波形も扱うことができる。
国家の維持
我々のMainContentComponent
クラスには3つのdouble
メンバー[1]:
double currentSampleRate = 0.0, currentAngle = 0.0, angleDelta = 0.0; // [1]
を更新する単純な関数がある。angleDelta
メンバーだ:
void updateAngleDelta()
{
auto cyclesPerSample = frequencySlider.getValue() / currentSampleRate; // [2]
angleDelta = cyclesPerSample * 2.0 * juce::MathConstants::pi; // [3]
}
- [2]まず、各出力サンプルに必要なサイクル数を計算します。
- [3]そして、これに正弦波1周期の長さを掛けると、次のようになる。2piラジアン
この関数が正しく動作する前に、出力サンプルレートを知る必要がある。これは、サンプルの生成頻度を知る必要があるためで、1サンプルあたりに必要な変化量を知るためです。サンプルレートはAudioAppComponent::prepareToPlay()コールバック関数:
void prepareToPlay (int, double sampleRate) override
{
currentSampleRate = sampleRate;
updateAngleDelta();
}
ここでは、サンプルレート値のコピーを保存しupdateAngleDelta()
関数は当初は
スライダーの値を使う
アプリの実行中にスライダーが動いたらangleDelta
またメンバーだ:
frequencySlider.onValueChange = [this]
{
if (currentSampleRate > 0.0)
updateAngleDelta();
};
を呼び出す前に、サンプルレートが有効かどうかをチェックする。updateAngleDelta()
関数を再び使用する。
オーディオの出力
期間中getNextAudioBlock()
コールバックで実際のサイン波を生成し、それを出力に書き出す必要がある:
void getNextAudioBlock (const juce::AudioSourceChannelInfo& bufferToFill) override
{
auto level = 0.125f;
auto* leftBuffer = bufferToFill.buffer->getWritePointer (0, bufferToFill.startSample);
auto* rightBuffer = bufferToFill.buffer->getWritePointer (1, bufferToFill.startSample);
for (auto sample = 0; sample < bufferToFill.numSamples; ++sample)
{
auto currentSample = (float) std::sin (currentAngle);
currentAngle += angleDelta;
leftBuffer[sample] = currentSample * level;
rightBuffer[sample] = currentSample * level;
}
}
出力サンプルごとに、現在の角度に対するサイン関数を計算し、次のサンプルの角度をインクリメントする。レベルを0.125
フルスケールの正弦波は非常に大きくなる!私たちは次のことができる(そしておそらくそうすべきだ)。ラップに達すると、現在の角度値をゼロに戻す。2pi.より大きな値はまだ有効な値を返すので、実際にはこの計算を避けることができる。次の画像のようなものが得られる: