スペクトルアナライザもプラグイン化してみたいです。
周波数表示はいろいろと使えそうだね〜
チュートリアルで実装したスタンドアロンのスペクトルアナライザアプリをプラグインに移植してみたいと思います。
中身としては、以前記事にしたスペクトログラムと描画の処理が違うだけなのですが、音声処理スレッド→メッセージスレッドという実装順を追って作成して行くような記事にしたかったので、再度チャレンジです。
今回は、音声処理スレッドということで、入力音のバッファを取得するところまでを実装していきます。
音声処理スレッド、メッセージスレッドといったプログラムの構成、仕組みの概要は、以前のスペクトログラムの内容「出来上がるプラグインの機能的な考察」を参考にしていただければと思います。
また、今回、macのXcodeで開発ができるようになり、いろいろ不慣れな点もありますので、この際にXcodeでの流れも確認していきたいと思いました。
今回は、Xcodeの画面をスクショしていきます。
それでは、早速実装していきます。
JUCE公式のチュートリアルは、こちらのページを参考にしました。
※この記事は、JUCEチュートリアルのスペクトルアナライザのプラグイン化を主としますので、JUCEチュートリアルで実装するスペクトルアナライザのプログラムの内容を理解していることが前提となっています。JUCEチュートリアルのスペクトルアナライザについては以下の記事をご覧ください。
こんな人の役にたつかも
・JUCEアプリをプラグインに移植したい人
・JUCEでスペクトルアナライザのプラグインを実装したい人
プロジェクトの作成
Projucerのプロジェクトテンプレートから、「Plugin」「Basic」を選択して、ProjectNameをつけてCreateします。
今回は、「FFT_speana」というプロジェクト名をつけました。
FFTはDSPモジュールに含まれるので、Modulesni「juce_dsp」を追加しておきます。
+マークを押して、Add a modules、「Global modules juce path」の中にあります。追加すると、一覧に「juce_dsp」が表示されるようになります。
ここまでで「コマンド+S」でプロジェクトを保存しておきます。
そして、右上のXcodeのアイコンを押して、IDEを開きます。
音声処理スレッドの実装
FFT処理を行うための音声バッファデータを作成するところまでを実装していきます。
PluginProcessor.h
[1]pushNextSampleIntoFifoの追加
[2]バッファサイズの定義
[3]privateなメンバ変数などの定義
を行います。
class FFT_speanaAudioProcessor : public juce::AudioProcessor
{
public:
//...略...
//[1]追加する関数です。
void pushNextSampleIntoFifo (float sample) noexcept
{
if (fifoIndex == fftSize)
{
if (! nextFFTBlockReady)//[1-1]
{
/*juce::zeromem (fftData, sizeof (fftData));
memcpy (fftData, fifo, sizeof (fifo));*/
//2021/05/06↑2行を以下の2行としました。修正です。動作しない点失礼いたしました。
//fftDataをstd::arrayとしたので、スペクトログラムアプリのように、以下の処理にします。
std::fill(fftData.begin(), fftData.end(), 0.0f);
std::copy(fifo.begin(), fifo.end(), fftData.begin());
nextFFTBlockReady = true;
DBG("FFT data ready");
}
fifoIndex = 0;
}
fifo[fifoIndex++] = sample;//[1-2]
}
//[2]追加する定数です。
static constexpr auto fftOrder = 11;
static constexpr auto fftSize = 1 << fftOrder;
private:
//[3]追加するprivateなメンバです。
std::array<float, fftSize> fifo;
std::array<float, fftSize * 2> fftData;
int fifoIndex = 0;
bool nextFFTBlockReady = false;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FFT_speanaAudioProcessor)
};
[2]をまず、みていきます。fftOrderは、2の11畳ということで、「2048」という定数です。fftSizeは左シフト演算で、fftOrderを左に1ビットシフトさせることで「4096」という定数になります。constexprという指定をすることで、コンパイルの際に定数として固定されます。
fftOrder:2048
fftSize:4096
[3]では、privateなメンバとして、音声バッファ(floatの配列)を定義しています。fifo配列は、入力した音声バッファをひたすら溜めておく4096個の配列です。満タンになったら、fftDataに内容をコピーして自分自身は0から次のデータを溜めていきます。チュートリアルでは、floatの配列となっていますが、後ほど、このメンバにアクセスするアクセサを定義するために、std::arrayで定義しています。
fftDataは、JUCEのFFTを利用したスペクトル情報への変換する関数「performFrequencyOnlyForwardTransform」へ与えるデータです。バッファが二倍の大きさ必要なので、fftSize×2の8192個のバッファを確保しておきます。
[1]の処理は、processBlockから呼び出されて、FFTのために音声バッファデータ「fftData」配列にデータを蓄積する処理を定義しています。普段は、音声バッファ毎に、[1-2]の処理でfftDataにそのままバッファを蓄積していきます。fftDataには、たまったfifoの4096個の音声バッファデータがコピーされて、FFTで利用されるまでデータを保持します。今回は、FFTを利用する処理(GUI描画処理、メッセージスレッド)を実装しないので、fifoにたまった最初の4096個のデータのみを保持し続ける、という仕様になります。
fifoをコピー完了した段階で、DBGで「FFT data ready」と表示します。
PluginProcessor.cpp
次のように、processBlockの処理を変更しました。
void FFT_speanaAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
//内容を変更しました。
if (buffer.getNumChannels() > 0)
{
auto* channelData = buffer.getReadPointer(0);
for (auto i = 0; i < buffer.getNumSamples(); ++i)
pushNextSampleIntoFifo(channelData[i]);
}
}
processBlockからチャンネル0の音声データをpushNextSampleIntoFifo関数に渡しているだけです。プラグインの入力はステレオですが、1チャンネルしか利用していないので、モノラル仕様になりますね。
動作検証
ここまでの内容を実行して見ることにします。Xcodeでプラグインをスタンドアロンで立ち上げてデバッグするために、StandalonePluginというスキーマを指定します。
そして、「▷」でビルドとデバッグを開始します。
アプリが実行され、デバッグウィンドウに一度だけ「FFT data ready」と表示されれば成功です。