JUCEプログラミング、サイン波のシンプルなシンセサイザーアプリ5、周波数変化のスムーズ化2

スムーズにするアルゴリズムを見ていきたいと思います。

仕組みを理解して応用できるようにしたいね

JUCEプログラミング、引き続き、チュートリアルsynthの「Build a sine wave synthesiser」を進めています。前回実装したアプリの処理内容について考察していきたいと思います。今回は、考えたこと、感じたことをそのまま文章にしますので、駄文失礼いたしますm__m

前回の記事はこちらです。

ぱんだクリップ
JUCEプログラミング、サイン波のシンプルなシンセサイザーアプリ4、周波数変化のスムーズ化1 | ぱんだクリ... 周波数スライダーを素早く動かすとちょっと音が飛ぶ感じがします。 チュートリアルではartefactsと呼んでいるやつですね JUCEプログラミング、チュートリアルsynthの「Buil...

こんな人の役に立つかも

・JUCEプログラミングを勉強している人

・JUCEの「Build a sine wave synthesiser」チュートリアルを進めている人

・周波数スムーズ化のアルゴリズムを考えている人

目次

スライダーの値をメンバ変数に

変更点の一つとして、privateなメンバ変数の「currentFrequency」にスライダーの値を保持するように変更した点が挙げられます。

スライダーの値変化で実行されるハンドラは変化時にひたすら「currentFrequency」に値を書き込みます。getNextAudioBlock関数は自分のタイミングで「currentFrequency」を見に行きます。

同じクラス(MainComponent内)のprivateなメンバ変数なので、どちらからもアクセス可能で、この二つの関数からみると、グローバルな変数のような役割を果たしています。

以前、音声処理スレッドでメッセージスレッドのことをするのはあまり良くない、とチュートリアルで学んだように、スライダークラスのgetValue関数をgetNextAudioBlock関数内で使うのではなく、一度メンバ変数に格納してから使う、という例になっています。

音声処理の内容

getNextAudioBlockで行われている処理を見ていきます。次の変更された内容についてです。

色々なFrequencyの変数が出てきますが、日本語で整理すると次のようになります。

currentFrequency:発音している周波数です。

targetFrequency:スライダーで与えられた変化後の周波数です。

localTargetFrequency:処理内部でtargetFrequencyを一時的に格納しておく変数、targetFrequencyのコピーです。

frequencyIncrement:周波数の増加分です。

このアルゴリズムでは、1サンプル毎に「frequencyIncrement」分、「currentFrequency」を変化をさせて、1ブロック分の音声処理バッファサンプル数で「targetFrequency」まで変化させようとするものです。

    auto localTargetFrequency = targetFrequency;

    //[1]一つ目のポイントです。
    if (localTargetFrequency != currentFrequency)
    {
        //[2]2つめのポイントです。
        auto frequencyIncrement = (localTargetFrequency - currentFrequency) / bufferToFill.numSamples;

        for (auto sample = 0; sample < bufferToFill.numSamples; ++sample)
        {
            auto currentSample = (float)std::sin(currentAngle);
            currentFrequency += frequencyIncrement;
           //[3]3つ目のポイントです。
            updateAngleDelta();
            currentAngle += angleDelta;
            leftBuffer[sample] = currentSample * level * volume;
            rightBuffer[sample] = currentSample * level * volume;
        }

        currentFrequency = localTargetFrequency;
    }

[1]つめのポイントでは、スライダーが変化していない時です。getNextAudioBlock関数は、一定の音声処理サンプル数毎に繰り返し実行されるので、スライダーに変化がない場合は、そのままcurrentFrequencyを出力すれば良いので、else以降の以前の処理を行います。

[2]つめに、「frequencyIncrement」変数を求める処理です。具体例を入れると考えやすいです。

500Hz→1KHzに変更する場合(ブロック数512の時)

(1000 – 500)÷512≒0.9765…

1サンプルあたり0.9765…Hz変化すれば512サンプルで500Hz目標の1kHznに到達します。

1kHz→500Hzの場合(同様にブロック数512の時)

(500-1000)÷512≒-0.9765…

1サンプルあたり-0.9765Hz変化すれば512サンプルで1kHzから目標の500Hzに到達します。

この計算式では、「localTargetFrequency – currentFrequency」の順番で計算することで、変化方向の符号がついてくることもポイントでした。

「frequencyIncrement」には変化方向の符号+1サンプル分の周波数の変化量が格納されます。

そのため、[3]でcurrentFrequency変数に+=と足し込んで上げることで、周波数の変化を起こすことができます。

1サンプル毎に周波数を変化させることで、聴感覚的に滑らかに聞こえるようにしています。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次