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

周波数スライダーを素早く動かすとちょっと音が飛ぶ感じがします。

チュートリアルではartefactsと呼んでいるやつですね

JUCEプログラミング、チュートリアルsynthの「Build a sine wave synthesiser」を進めています。今回は、「Somoothing frequenct changes」の項目を進めていきます。

現段階でのアプリは、スライダーを素早く動作させると、R〇D2のように少しだけ音程変化がピロピロします。これは、素早く動作させたとき、スライダーの数値変が飛ぶことや、また、getNextAudioBlock関数が音声ブロック毎の処理なので、更新タイミングが離散的で、いきなり数値が変化することで、音程の変化がピロピロするのです。このピロピロはこれで味があってよいのですが、今回のチュートリアルでは、周波数の変化がなめらかになるような処理を追加します。

今回は、プログラムの実装をメインに行います。このチュートリアルに該当するプログラムは、ダウンロードできる「SineSynthTutorial_02.h」の内容になります。

こんな人の役に立つかも

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

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

目次

プログラムの実装

前回まで作成したサイン波のシンセサイザーアプリをベースに、改良していきます。

MainComponent.h

privateなメンバ変数に「currentFrequency」と「targetFrequency」を追加しました。


//...略...
private:
    juce::Slider frequencySlider;
    double currentSampleRate = 0.0, currentAngle = 0.0, angleDelta = 0.0; 

    float volume = 0.0f;
    DecibelSlider volumeSlider;

    //追加しました。
    double currentFrequency = 500.0, targetFrequency = 500.0;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};

MainComponent.cpp

コンストラクタ

コンストラクタの内容は、次の2点を追加、変更しました。

MainComponent::MainComponent()
{
    addAndMakeVisible(frequencySlider);
    frequencySlider.setRange(50.0, 5000.0);
    frequencySlider.setSkewFactorFromMidPoint(500.0);
    frequencySlider.setValue(currentFrequency, juce::dontSendNotification);//[1]初期値を追加しました。
    frequencySlider.onValueChange = [this]
    {
        /*if (currentSampleRate > 0.0)
            updateAngleDelta();*/
        //[2]ハンドラの内容を以下のように変更しました。
        targetFrequency = frequencySlider.getValue();//changed
    };

初期値として、[1]に、currentFrequencyを与えました。また、[2]のスライダーの値変化で実行されるハンドラの内容を、currentFrequencyを上書きするように変更しました。

updateAngleDelta関数

updateAngleDelta関数を以下のように書き換えました。

void MainComponent::updateAngleDelta()
{
    //auto cyclesPerSample = frequencySlider.getValue() / currentSampleRate;
    auto cyclesPerSample = currentFrequency / currentSampleRate;//currentFrequencyで計算するように変更しました。
    angleDelta = cyclesPerSample * 2.0 * juce::MathConstants<double>::pi;
}

getNextAudioBlock関数

getNextAudioBlock関数は以下のように条件を追加していくつかの処理を追加しています。

void MainComponent::getNextAudioBlock (const juce::AudioSourceChannelInfo& bufferToFill)
{
    auto level = 0.125f;
    auto* leftBuffer = bufferToFill.buffer->getWritePointer(0, bufferToFill.startSample);
    auto* rightBuffer = bufferToFill.buffer->getWritePointer(1, bufferToFill.startSample);

    //[1]以下の部分を変更しました。
    auto localTargetFrequency = targetFrequency;

    if (localTargetFrequency != currentFrequency)
    {
        auto frequencyIncrement = (localTargetFrequency - currentFrequency) / bufferToFill.numSamples;

        for (auto sample = 0; sample < bufferToFill.numSamples; ++sample)
        {
            auto currentSample = (float)std::sin(currentAngle);
            currentFrequency += frequencyIncrement;
            updateAngleDelta();
            currentAngle += angleDelta;
            leftBuffer[sample] = currentSample * level * volume;
            rightBuffer[sample] = currentSample * level * volume;
        }

        currentFrequency = localTargetFrequency;
    }
    else
    {
        //[2]以下の内容は以前の内容と同じです。
        for (auto sample = 0; sample < bufferToFill.numSamples; ++sample)
        {
            auto currentSample = (float)std::sin(currentAngle);
            currentAngle += angleDelta;
            leftBuffer[sample] = currentSample * level * volume;
            rightBuffer[sample] = currentSample * level * volume;
        }
    }
}

[1]以下の内容を丸ごと追加しました。[2]の内容が以前の音声処理と同様のものです。

とりあえず、この実装でピロピロしなくなりました。

次回、この処理の詳細を追っていきたいと思います。

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