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

JUCEプラグイン、シンプルなハイパスフィルター、スペアナの追加とGUI検討

1_プログラミング

市販のプラグインには、スペアナが付いていて、使いやすいです。

チュートリアルで作成したスペアナを合体させてみよう〜

シンプルなハイパスフィルターを色々と改造しています。シンプルなハイパスフィルターは、前回までの記事で実装したものを利用しています。今回大きく変更した点としては、GUIにスペクトルアナライザを追加した点です。

追加したといっても、以前プラグイン化したスペクトルアナライザのプログラムをほぼコピペして、一部調整した程度です^^;

スクショ画像は、ホワイトノイズジェネレータでホワイトノイズを入れた時の様子です。

スポンサーリンク

こんな人の役にたつかも

・シンプルなハイパスフィルターを実装している人

・JUCEでスペクトルアナライザをプラグインに組み込みたい人

・スペクトルアナライザのスケーリング調整を行いたい人

スペクトルアナライザの追加

シンプルなハイパスフィルタープラグインに、以前作成したスペクトルアナライザのプログラムを追加しました。

音声処理スレッド編

GUI、メッセージスレッド編

「PluginProcessor.h」のプラグインの本体クラス「●●AudioProcessor」クラスに以下のプログラムを追加しました。

※●●は任意のプロジェクト名が入ります。

{
public:
//...略... 
   //スペクトルアナライザに関するコードの追加です。
    void pushNextSampleIntoFifo (float sample) noexcept
    {
        if (fifoIndex == fftSize)
        {
            if (! nextFFTBlockReady)//[1-1]
            {
                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]
    }
   
   //アクセサです。 
  bool getFFTReady(){
        return nextFFTBlockReady;
    }

    void setFFTReady(bool b) {
        nextFFTBlockReady = b;
    }

    float* getFFTDataPtr() {
        return fftData.data();
    }

    float getFFTDataSample(int sample) {
        return fftData[sample];
    }

    //FFTのパラメータです。
    static constexpr auto fftOrder  = 11;
    static constexpr auto fftSize   = 1 << fftOrder;
    
private:
    //スペクトルアナライザに関するprivateな変数です。
    std::array<float, fftSize> fifo;
    std::array<float, fftSize * 2> fftData;
    int fifoIndex = 0;
    bool nextFFTBlockReady = false;
//...略...

「PluginProcessor.cpp」のprocessBlock関数に以下のプログラムを追加しました。

void ●●AudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
    //ハイパスフィルター直列の処理です。
    MyFilter.processBlock(buffer,midiMessages);
    if(*slope >= 1.0f && *slope <2.0f){
        MyFilter2.processBlock(buffer,midiMessages);
    }
    else if(*slope >=2.0f){
        MyFilter2.processBlock(buffer,midiMessages);
        MyFilter3.processBlock(buffer,midiMessages);
    }
    
    //[1]以下が今回追加したスペクトルアナライザの処理です。
    if (buffer.getNumChannels() > 0)
    {
        auto* channelData = buffer.getReadPointer(0);

        for (auto i = 0; i < buffer.getNumSamples(); ++i)
            pushNextSampleIntoFifo(channelData[i]);
    }
}

[1]が追加したプログラムですが、スペクトルアナライザの処理を、ハイパスフィルターの後に入れることで、フィルター処理された音声を処理することができます。

次にGUI側の実装です。

「PluginEditor.h」へタイマーを追加しました。

class ●●AudioProcessorEditor  : public juce::AudioProcessorEditor, private juce::Timer
{
public:
    //タイマーのコールバック関数を追加します。
    void timerCallback() override
    {
        auto flg = audioProcessor.getFFTReady();
        if (flg)
        {
            drawNextFrameOfSpectrum();
            audioProcessor.setFFTReady(false);
            repaint();
        }
    }
//...略...

続けてpublicなメンバに以下の関数と変数を追加しました。

    void drawNextFrameOfSpectrum()
    {
        window.multiplyWithWindowingTable (audioProcessor.getFFTDataPtr(), audioProcessor.fftSize);
        forwardFFT.performFrequencyOnlyForwardTransform (audioProcessor.getFFTDataPtr());

        auto mindB = -100.0f;
        auto maxdB =    0.0f;

        for (int i = 0; i < scopeSize; ++i)
        {
            auto skewedProportionX = 1.0f - std::exp (std::log (1.0f - (float) i / (float) scopeSize) * 0.2f);
            auto fftDataIndex = juce::jlimit (0, audioProcessor.fftSize / 2, (int) (skewedProportionX * (float) audioProcessor.fftSize * 0.5f));
            auto level = juce::jmap (juce::jlimit (mindB, maxdB, juce::Decibels::gainToDecibels (audioProcessor.getFFTDataSample(fftDataIndex))
                                                               - juce::Decibels::gainToDecibels ((float) audioProcessor.fftSize)),
                                     mindB, maxdB, 0.0f, 1.0f);

            scopeData[i] = level;
        }
    }
    
    void drawFrame (juce::Graphics& g)
    {
        for (int i = 1; i < scopeSize; ++i)
        {
            auto width  = getLocalBounds().getWidth();
            auto height = getLocalBounds().getHeight();

            g.drawLine ({ (float) juce::jmap (i - 1, 0, scopeSize - 1, 0, width),
                                  juce::jmap (scopeData[i - 1], 0.0f, 1.0f, (float) height, 0.0f),
                          (float) juce::jmap (i,     0, scopeSize - 1, 0, width),
                                  juce::jmap (scopeData[i],     0.0f, 1.0f, (float) height, 0.0f) });
        }
    }
    
    static constexpr auto scopeSize = 512;

privateなメンバにもFFT関連の変数などを追加しておきます。

private:
    //スペクトルアナライザの変数です。
    juce::dsp::FFT forwardFFT;
    juce::dsp::WindowingFunction<float> window;
    float scopeData [scopeSize];

    //[1]スペクトルアナライザで利用するためのAudioProcessorオブジェクトです。
    ●●AudioProcessor& audioProcessor;
//...略...

上記の[1]は、シンプルなハイパスフィルターを作成する過程でコメントアウトまたは削除した●●AudioProcessorクラスのオブジェクトを指し示すものです。今回はスペクトルアナライザのプログラムで利用するので、復活させています。

「PluginEditor.cpp」に以下の変更を加えました。

[1]のメンバイニシャライザに、FFT関連の初期化、audioProcessorのアドレス取得のための初期化、[2]に、スペアナ初期化、主にタイマーを30Hzで動作させます。[3]には、今回背景を500×300としたので、合わせました。

Filter_plugin01AudioProcessorEditor::Filter_plugin01AudioProcessorEditor (Filter_plugin01AudioProcessor& p, juce::AudioProcessorValueTreeState& vts)
    : AudioProcessorEditor (&p), valueTreeState(vts)
    //[1]以下のメンバイニシャライザを追加しました。
    ,audioProcessor (p)
    ,forwardFFT (p.fftOrder)
    ,window (p.fftSize, juce::dsp::WindowingFunction<float>::hann)
{
//...略...    
    //[2]スペアナ用初期化です。
    setOpaque (true);
    startTimerHz (30);
    //[3]GUIに合わせてウィンドウサイズを以下のサイズにしました。
    setSize (500, 300);
}

ここまでで、次のようになりました。

周波数が低い部分をもう少し中央寄りに広くしないと使いづらいので、次に、スペアナのスケーリングを調整していきたいと思います。

スペクトルアナライザの調整

低周波成分のスケーリング

以前実装したスペクトルアナライザは、高い周波数の表示部分が広く、カットオフ周波数の調整が行いにくかったです。市販のEQプラグインをみてみると、大抵中央が1Khzくらいなので、中央の線が1kHzになるように適当に調整してみました。

といっても、今回は、感覚で調整しただけなので、実際に、微調整を行う点は、次回以降の課題です。

PluginEditor.h」のdrawNextFrameOfSpectrum関数内のfftDataIndex変数を求める計算の係数を「0.5f」から「0.08f」に変更しました。

auto fftDataIndex = juce::jlimit (0, audioProcessor.fftSize / 2, (int) (skewedProportionX * (float) audioProcessor.fftSize * 0.08f));

この係数を調整することで、周波数のスケーリングを変更することができます。

ただ、ここで問題となる点があります。

今のFFTの設定では、低周波成分が次のように、荒くなってしまいます。

分解能の調整

低い周波数を広く表示することで、フーリエ変換の周波数の分解能が引き伸ばされて荒く見えるようになりました。

ここで、「PluginProcessor.h」に設定したfftOrderの定数を「11」から「12」に変更しました。

static constexpr auto fftOrder  = 12;

これで、FFTの分析結果の周波数の分解能が二倍に細かくなりました。(その代わり、より多くのデータバッファ、言い換えると、より長い時間の音声データが必要となるので、FFTの応答速度が遅くなります。)

FFTの性質上、一定間隔の周波数成分を出力しているので、FFT設定より小さな周波数単位は表示することができません。このFFTの仕組みなどについては、以前のスペクトルアナライザJUCEチュートリアルの実装記事で考察していますので、ご参照ください。

1KHzが中央ではないので、周波数を細かくしたFFTでも少し荒くなっています。これは、もう少しスケーリングをしっかりとすることで、なんとかできそうな気がします。(1kHzが右のほうすぎるので、もう少し左になるように調整すると、低い周波数の頂点が四角くならなくなっていきます。)

その他の調整

背景の追加

次の画像を背景として貼り付けました。中央の線は1kHzにしたいという願望の線です。この線に合わせてスペアナの調整を行なっていきたいです。

プラグイン背景を画像にする方法は、以前の記事をご参照ください。

スライダーの非表示化

カットオフ周波数の操作など、スライダーコンポーネントでない方がいいと感じましたので、テキストボックスの数値のみになるように変更しました。

数値のみのスライダーとする設定は、以前のスライダーコンポーネントに関する記事に新たに追記しましたので、ご参照ください。

スライダーのカスタマイズ記事の「数値ボックスのみの表示」

改善点と目標

1)周波数表示のポイントで背景の線を描画したいです。

スケーリング調整を行なって微調整をすればできそうな気がします。

2)GUIをかっこよくしたいです。

プラグインの視覚的要素はやっぱり、使う人のテンションを上げたりそういった部分が大きいと思いますので、出来るだけカッコ良いGUIを目指していきたいです。

3)市販のプラグインのように、スペアナ上で横軸周波数、縦軸音量みたいに操作できるようなGUIを作りたいです。

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