チュートリアル: ホワイトノイズジェネレーターの構築
このチュートリアルでは、シンプルな音声合成と音声出力について紹介します。これは、JUCEにおけるオーディオアプリケーション(およびプラグイン)の概念を理解するための鍵となります。
レベル: 中級
プラットフォーム: Windows, macOS, Linux
クラス: AudioAppComponent, AudioSourceChannelInfo, AudioBuffer, Random
はじめに
このチュートリアルのデモプロジェクトをダウンロードしてください: PIP | ZIP。プロジェクトを解凍し、Projucerで最初のヘッダーファイルを開いてください。
この手順でヘルプが必要な場合は、Tutorial: Projucer Part 1: Getting started with the Projucerを参照してください。
このチュートリアルでは、デジタルオーディオの基本原理に精通していることを前提としています。特に、サンプリングを使用して音声信号がどのように表現されるかを知っておく必要があります。また、この文脈におけるサンプルレート(サンプリングレート、サンプリング周波数、または他の類似の用語として知られている可能性があります)の概念にも精通している必要があります。
デモプロジェクト
このデモプロジェクトは、The ProjucerのAudio Applicationテンプレートを使用して作成されました。

これは、JUCE で独自のオーディオアプリケーションを作成するための便利な出発点です。このデモプロジェクトは、ホワイトノイズを合成し、ターゲットデバイスのデフォルトのオーディオハードウェアから再生します。
Audio Applicationテンプレート
このチュートリアルでは、音声出力のみを実装します。音声入力と音声入力データのリアルタイムオーディオ処理については、他のチュートリアルで説明します。Audio ApplicationテンプレートはGUI Applicationテンプレートと非常に似ていますが、以下の点が異なります:
MainContentComponentクラスは、Componentクラスではなく、AudioAppComponentクラスを継承します。- デフォルトでプロジェクトに追加される他のオーディオ関連モジュールに加えて、
juce_audio_utilsモジュールがプロジェクトに追加されます。
Audio Applicationテンプレートは、ここで提供される例のようなシンプルなアプリケーションに使用できます。また、より複雑なアプリケーション、つまりターゲットデバイスのオーディオハードウェアと直接対話する必要があるアプリケーションにもスケーラブルです。JUCEでオーディオプラグインを作成することについては、他のチュートリアルで説明します。
オーディオアプリケーションのライフサイクル
AudioAppComponentクラスは抽象基底クラスであり、派生クラスで必ず実装する必要があるオーディオアプリケーションのライフサイクルを表す3つの純粋仮想関数があります:
- AudioAppComponent::prepareToPlay(): これは、音声処理が開始される直前に呼び出されます。
- AudioAppComponent::releaseResources(): これは、音声処理が終了したときに呼び出されます。
- AudioAppComponent::getNextAudioBlock(): これは、オーディオハードウェアが新しい音声データのブロックを必要とするたびに呼び出されます。
これら3つのうち、最も重要なのはおそらくAudioAppComponent::getNextAudioBlock()です。これは、JUCEオーディオアプリケーションで音声を生成または処理する場所だからです。これがどのように機能するかを理解するには、最新のコンピュータがどのように音声を生成するかについて少し知る必要があります。オーディオハードウェアは、1秒間の音声ごとにチャンネルあたり一定数のサンプルを生成する必要があります。CD品質のサンプルレートは44.1kHzです。つまり、再生のためにオーディオハードウェアに送信される必要があるサンプルは、チャンネルあたり1秒あたり44100サンプルです。サンプルは1つずつオーディオハードウェアに渡されるのではなく、一定数のサンプルを含むバッファ(またはブロック)で渡されます。たとえば、44.1kHzでブロックサイズが441の場合、AudioAppComponent::getNextAudioBlock()関数は1秒あたり100回呼び出されます。
上記の441サンプルのバッファサイズは、説明のために算術をシンプルに保つために使用されています。実際には、441のバッファサイズは珍しいでしょう。ハードウェアバッファサイズはほぼ確実に偶数であり、2の累乗(256、512、1024など)になる傾向があります。アプリケーションが任意のバッファサイズに対応できるように準備することが重要です。サンプルレートとバッファサイズの設定の詳細については、Tutorial: The AudioDeviceManager classを参照してください。
本質的に、AudioAppComponent::getNextAudioBlock()は、オーディオハードウェアのオーディオコールバックにサービスを提供しています。この関数は別のスレッド(ほとんどの場合オーディオスレッド)から呼び出されることに注意することが重要です。
JUCEオーディオアプリケーションが正しく動作するためには、さらに2つの重要な関数があります。今回は、実装するのではなく、呼び出す必要があります:
- AudioAppComponent::setAudioChannels(): 必要な入出力チャンネル数を登録するために、これを呼び出す必要があります。通常、これはコンストラクタで行います。この関数は、アプリケーションでの音声処理の開始をトリガーします。
- AudioAppComponent::shutdownAudio(): オーディオシステムをシャットダウンするために、これを呼び出す必要があります。通常、これはデストラクタで行います。
オーディオアプリケーションの初期化
それでは、オーディオアプリケーションのライフサイクルをより詳細に調べて、ノイズジェネレーターのシンプルな実装を見てみましょう。コンストラクタでは、Componentオブジェクトのサイズを設定する必要があります(Tutorial: The main componentを参照)。また、少なくとも1つの音声出力を初期化する必要があります:
MainContentComponent()
{
setSize (800, 600);
setAudioChannels (0, 2); // no inputs, two outputs
}
前述のように、AudioAppComponent::setAudioChannels()関数の呼び出しは、オーディオシステムの起動をトリガーします。特に、prepareToPlay()関数を呼び出します:
void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override
{
juce::String message;
message << "Preparing to play audio...\\n";
message << " samplesPerBlockExpected = " << samplesPerBlockExpected << "\\n";
message << " sampleRate = " << sampleRate;
juce::Logger::getCurrentLogger()->writeToLog (message);
}
ここでは、デバッグのためにいくつかの情報を標準出力にログ出力します。AudioAppComponent::prepareToPlay()関数は、オーディオ処理の開始の準備をするための良い場所です。たとえば、音声処理に必要なリソースを割り当てることができます。
音声の表現
デジタルオーディオシステムでは、音声は通常、浮動小数点値として表現され、値±1.0で最大振幅に達します。たとえば、フルスケールのサイン波は次のようになります:

このような±1.0のピークレベルでは、出力レベルは非常に大きくなります。実際、これはクリッピングなしでオーディオシステムが生成できる最大の音量です。通常、この±1.0の制限を超えない音声を出力する必要があります(ただし、最終出力が低い限り、処理の中間段階がこの制限を超えても問題ありません)。
AudioSampleBufferクラス
AudioSampleBufferクラスは(非常に基本的なレベルでは)float値のマルチチャンネル配列に過ぎませんが、音声データを扱うための便利な関数セットを提供します。これらの関数の多くは後のチュートリアルで取り上げられますが、ここでは以下の関数を使用します:
- AudioSampleBuffer::getNumChannels(): これは、バッファに格納されているオーディオチャンネルの数を返します。この場合、値は、先にAudioAppComponent::setAudioChannels()関数の呼び出しで要求した出力チャンネル数と一致するはずです。(この値は常に入力チャンネル数と出力チャンネル数の最大値になります。)
- AudioSampleBuffer::getWritePointer(): これは、特定のサンプルオフセットでの
float値のバッファへのポインタを返します。
ホワイトノイズを生成するシンプルなアプリケーションでは、バッファの要求されたセクションをランダムな値で埋める必要があります。これを行うには、バッファ内のチャンネルを反復処理し、そのチャンネルのバッファ内の開始サンプルを見つけ、目的のサンプル数をバッファに書き込みます:
void getNextAudioBlock (const juce::AudioSourceChannelInfo& bufferToFill) override
{
for (auto channel = 0; channel < bufferToFill.buffer->getNumChannels(); ++channel)
{
// Get a pointer to the start sample in the buffer for this audio output channel
auto* buffer = bufferToFill.buffer->getWritePointer (channel, bufferToFill.startSample);
// Fill the required number of samples with noise between -0.125 and +0.125
for (auto sample = 0; sample < bufferToFill.numSamples; ++sample)
buffer[sample] = random.nextFloat() * 0.25f - 0.125f;
}
}
Randomクラスを使用したホワイトノイズの生成
ここでは、ランダムな値を生成するためにRandomクラスを使用します(Tutorial: The Random classを参照)。ホワイトノイズを生成するには、ゼロを中心に一様分布するランダムな数値を生成する必要があります。ここでは、まずRandom::nextFloat()関数を呼び出して、-0.125から+0.125の間のランダムな値を生成します。これにより、0から1の間の値が生成されます。次に、この結果に0.25を掛けて0.125を引きます。(このプロセスの詳細については、Tutorial: Control audio levelsを参照してください。)他のチュートリアル(Tutorial: The Random classで示されているように、共有システムRandomオブジェクトを取得するためにRandom::getSystemRandom()関数を使用しなかったことに注意してください。これは、オーディオスレッドでRandom::nextFloat()関数を呼び出しているためです。独自のRandomオブジェクトを作成する必要があります。そうしないと、他のスレッドがその共有Randomオブジェクトを使用することで値が破損する可能性があります。これを実現するために、RandomクラスのインスタンスをMainContentComponentクラスに追加します:
private:
juce::Random random;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};
オーディオアプリケーションのシャットダウン
アプリケーションが閉じられると、デストラクタが呼び出されます。この時点で、AudioAppComponent::shutdownAudio()関数を呼び出す必要があります:
~MainContentComponent() override
{
shutdownAudio();
}
AudioAppComponent::shutdownAudio()関数を呼び出すと、AudioAppComponent::releaseResources()関数が呼び出されます。これは、音声プロセスの実行中に割り当てたリソース(メモリを割り当てたり、ファイルを開いたりした場合など)を破棄するのに適した場所です。この場合、追加のリソースは必要なかったため、単純なメッセージで関数呼び出しをログに記録するだけです:
void releaseResources() override
{
juce::Logger::getCurrentLogger()->writeToLog ("Releasing audio resources");
}
演習: 音声出力の数を変更してみてください。モノラルノイズはステレオノイズとは微妙に異なって聞こえることに注意してください。マルチチャンネルサウンドカードをお持ちの場合、2チャンネル以上のノイズを生成できる可能性があります。また、生成されるノイズのレベルを変更してみることもできます。たとえば、レベル0.1でノイズを生成するには、ランダムに生成された値に0.2を掛けて0.1を引く必要があります。
まとめ
このチュートリアルでは、Audio Applicationテンプレートで使用されるAudioAppComponentクラスを使用して音声を生成する方法を紹介しました。以下のトピックを取り上げました:
- オーディオシステムの初期化とシャットダウン。
- オーディオコールバックに応答して音声データを書き込む。
- AudioSourceChannelInfo構造体とAudioSampleBufferクラスの使用。