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

JUCEプラグイン、スペクトルアナライザの描画を滑らかにする

1_プログラミング

周波数の表示がもう少しマシにならないかな〜と思っています。

描画処理を見直してみよう〜

JUCEで作成しているシンプルなハイパスフィルターのスペアナの描画処理を改善していました。

前回までは、このように帯域ごとに幅があるので、カクカクした表示になっています。

改善を試みて、次の動画のようになりました。

低い周波数の表示部分が滑らかになるように、実装を検討した備忘録になります。

シンプルなハイパスフィルターの記事一覧はこちらです。

スポンサーリンク

こんな人の役に立つかも

・JUCEチュートリアルで作成したスペクトルアナライザの描画で、低周波の描画のカクカクを改善したい人

・本ブログのシンプルなハイパスフィルターを作成している人

実装と検証

まずは、同じ音量レベルが続いている、という点を検出できるようなプログラムにしてみました。

void drawFrame(juce::Graphics& g)
{
    //[1]for文の外にもってきました。
    auto width = getLocalBounds().getWidth() - drawingOffsetX;
    auto height = getLocalBounds().getHeight() - drawingOffsetY;
    auto same_level_count = 0;

    for (int i = 1; i < scopeSize; ++i)
    {
        /*g.drawLine({ (float)juce::jmap(i - 1, 0, scopeSize - 1, 0, width) + (drawingOffsetX / 2),//20210623
                              juce::jmap(scopeData[i - 1], 0.0f, 1.0f, (float)height, 0.0f),
                      (float)juce::jmap(i,     0, scopeSize - 1, 0, width) + (drawingOffsetX / 2),//20210623
                              juce::jmap(scopeData[i],     0.0f, 1.0f, (float)height, 0.0f) }, 2.0f);*/
        //[2]↑の描画を↓に変更しました。
        //
        auto previous_level = juce::jmap(scopeData[i - 1], 0.0f, 1.0f, (float)height, 0.0f);
        auto current_level = juce::jmap(scopeData[i], 0.0f, 1.0f, (float)height, 0.0f);

        //[2-1]
        if (previous_level == current_level) {
            same_level_count++;
        }
        //[2-2]
        else {
            g.drawLine({ (float)juce::jmap(i - 1 - same_level_count, 0, scopeSize - 1, 0, width) + (drawingOffsetX / 2),
                              juce::jmap(scopeData[i - 1 - same_level_count], 0.0f, 1.0f, (float)height, 0.0f),
                      (float)juce::jmap(i,     0, scopeSize - 1, 0, width) + (drawingOffsetX / 2),
                              juce::jmap(scopeData[i],     0.0f, 1.0f, (float)height, 0.0f) }, 2.0f);

            same_level_count = 0;
        }
    }
}

まずは、今までforループ内で毎回定義しなおしていた変数、widthとheightを、[1]のように、ループの外に出しました。そして、同じ音量レベルのときに同じ音量レベルが続いている回数をカウントする変数「same_level_count」変数を追加しました。

forループのイメージとしては、次のように、描画領域を512(scopeSize分)に分けて右に向かって描画を行っていくイメージです。このiの値を描画フレームと呼ぶことにします。

iがカウントアップするにつれて、描画領域の右の方の処理になります。

[2]では、この後何回か利用する、previous_levelと、current_levelという周波数帯域の音量値をあらかじめ取得しておきます。

[2-1]で、一つ前の描画フレームの音量、previous_levelが今の描画フレームのcurrent_levelと同じ場合、same_level_count変数を増加させて、描画を行わないようにしました。

[2-2]で、描画フレームの音量が変化した場合、同じ音量が続いていた最初の描画フレームから、現在の描画フレームまでを直線で結びます。これで、カクカクした描画にならなくなります。

同じ音量が続いていた最初のフレームは、「i – 1 – same_level_count」で表現できます。

この実装で実行してみたら次のようになりました。

ずっと同じ音量の数値が続いていて、突然数値が上昇した場合、次のように一番最初のところから直線が描画されてしまうという現象が生じました。

改善1

いくつか模索した結果、次のように、同じ音量レベルが続いている回数によって条件を分ける実装をおこないました。

void drawFrame(juce::Graphics& g)
    {
        auto width = getLocalBounds().getWidth() - drawingOffsetX;
        auto height = getLocalBounds().getHeight() - drawingOffsetY;
        auto same_level_count = 0;

        for (int i = 1; i < scopeSize; ++i)
        {
            auto previous_level = juce::jmap(scopeData[i - 1], 0.0f, 1.0f, (float)height, 0.0f);
            auto current_level = juce::jmap(scopeData[i], 0.0f, 1.0f, (float)height, 0.0f);

            if (previous_level == current_level) {
                same_level_count++;
            }
            else {

                //[1]同じ音量レベルが続く場合の条件を追加しました。
                if (same_level_count > 10) {
                    //[1-1]開始点から1フレーム前までの描画です。
                    g.drawLine({ (float)juce::jmap(i - 1 - same_level_count, 0, scopeSize - 1, 0, width) + (drawingOffsetX / 2),
                            juce::jmap(scopeData[i - 1 - same_level_count], 0.0f, 1.0f, (float)height, 0.0f),
                            (float)juce::jmap(i-1,     0, scopeSize - 1, 0, width) + (drawingOffsetX / 2),
                            juce::jmap(scopeData[i-1],     0.0f, 1.0f, (float)height, 0.0f) }, 2.0f);
                    //[1-2]1フレーム前と現在フレームの描画です。
                    g.drawLine({ (float)juce::jmap(i - 1, 0, scopeSize - 1, 0, width) + (drawingOffsetX / 2),//20210623
                                  juce::jmap(scopeData[i - 1], 0.0f, 1.0f, (float)height, 0.0f),
                          (float)juce::jmap(i,     0, scopeSize - 1, 0, width) + (drawingOffsetX / 2),//20210623
                                  juce::jmap(scopeData[i],     0.0f, 1.0f, (float)height, 0.0f) }, 2.0f);
                }
                else {
                    g.drawLine({ (float)juce::jmap(i - 1 - same_level_count, 0, scopeSize - 1, 0, width) + (drawingOffsetX / 2),
                            juce::jmap(scopeData[i - 1 - same_level_count], 0.0f, 1.0f, (float)height, 0.0f),
                            (float)juce::jmap(i,     0, scopeSize - 1, 0, width) + (drawingOffsetX / 2),
                            juce::jmap(scopeData[i],     0.0f, 1.0f, (float)height, 0.0f) }, 2.0f);
                }
                
                //カウンタリセットです。
                same_level_count = 0;
            }
        }
    }

[1]のように、same_level_count変数が10より大きい場合の描画を行う処理を別にしました。

同じ音量レベルが10カウント以上続いた後の描画の場合、現在の描画フレームの1フレーム前(i-1)までの描画[1-1]と、1フレーム前と現在の描画フレームの描画[1-2]の処理を行うようにしました。

この実装に変更したら、次のようになりました。

次の赤い矩形のように、同じ音量レベルが10以上続く場合の描画として処理を分けることで、横幅10ピクセル以上の同じ音量である部分がしっかり描画されるようになります。

一方で、この場合、横軸最後のループの際、同じ音量が続いていた場合、drawLineが実行されるタイミングがありませんので、最後の線の描画が途切れます。

改善2

次の[1]の描画処理を追記しました。

    void drawFrame(juce::Graphics& g)
    {
        auto width = getLocalBounds().getWidth() - drawingOffsetX;
        auto height = getLocalBounds().getHeight() - drawingOffsetY;

        auto same_level_count = 0;

        for (int i = 1; i < scopeSize; ++i)
        {
                //...略... 
                same_level_count = 0;
            }

            //[1]横軸最後のループで、同じ音量が続いていた場合、ライン描画されない部分を描画。
            if (i == scopeSize-1) {
                g.drawLine({ (float)juce::jmap(i - same_level_count, 0, scopeSize - 1, 0, width) + (drawingOffsetX / 2),
                        juce::jmap(scopeData[i - same_level_count], 0.0f, 1.0f, (float)height, 0.0f),
                        (float)juce::jmap(i,     0, scopeSize - 1, 0, width) + (drawingOffsetX / 2),
                        juce::jmap(scopeData[i],     0.0f, 1.0f, (float)height, 0.0f) }, 2.0f);

                same_level_count = 0;
            }
        }
    }

次のように、最後が同じ音量となっていても描画されるようになりました。

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