JUCEプログラミング、Looping audio using the AudioSampleBuffer class4、ゲインを追加

ループ再生アプリにゲインを追加しました。

音声の再生+ゲインという良い例になるね~

JUCEプログラミング、Audioの項目の「Looping audio using the AudioSampleBuffer class」を勉強してきました。

zipでダウンロードできるソースコードに「LoopingAudioSampleBufferTutorial_02.h」というものがありました。どうやら、音量調整(ゲイン)の機能を追加しているようなので、このプログラムを実装して勉強したいと思います。

公式チュートリアルはこちらのページです。

あわせて読みたい
JUCE: Tutorial: Looping audio using the AudioSampleBuffer class

こんな人の役に立つかも

・JUCEで音声プログラミングを学びたい人

・JUCEアプリで音声を滑らかに変更したい人

・JUCEアプリで音声処理に音量の変化の処理を追加したい人

目次

MainComponent.hへの追加

privateなメンバ変数に、スライダーオブジェクトと、「currentLevel」「previousLevel」変数を定義しました。

private:

//...略...

    juce::Slider levelSlider;
    float currentLevel = 0.0f, previousLevel = 0.0f;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};

以前の音量と現在の音量を変数に保持しておきますので、これは音量変化の際に発生するノイズを防ぐ仕組みを実装していくんですね。以前にプラグインのゲインでもやりました。

音量調整でプチプチノイズを防ぐ方法をやるんですね。

MainComponent.cppへの追加

コンストラクタ

コンストラクタでスライダーオブジェクトの初期化を追加しました。

MainComponent::MainComponent()
{
//...略...

    addAndMakeVisible(levelSlider);
    levelSlider.setRange(0.0, 1.0);
    levelSlider.onValueChange = [this] { currentLevel = (float)levelSlider.getValue(); };

    setSize(300, 200);

    formatManager.registerBasicFormats();
}

getNextAudioBlock関数

void MainComponent::getNextAudioBlock (const juce::AudioSourceChannelInfo& bufferToFill)
{
    //[1]以下の処理を追加しました。
    auto level = currentLevel;
    auto startLevel = level == previousLevel ? level : previousLevel;

//...略...

    while (outputSamplesRemaining > 0)
    {
        auto bufferSamplesRemaining = fileBuffer.getNumSamples() - position;
        auto samplesThisTime = juce::jmin(outputSamplesRemaining, bufferSamplesRemaining);

        for (auto channel = 0; channel < numOutputChannels; ++channel)
        {
            bufferToFill.buffer->copyFrom(channel,
                outputSamplesOffset,
                fileBuffer,
                channel % numInputChannels,
                position,
                samplesThisTime);

            //[2]以下の処理を追加しました。バッファーにゲインの処理を施します。
            bufferToFill.buffer->applyGainRamp(channel, outputSamplesOffset, samplesThisTime, startLevel, level);
        }

        outputSamplesRemaining -= samplesThisTime;
        outputSamplesOffset += samplesThisTime;
        position += samplesThisTime;

        if (position == fileBuffer.getNumSamples())
            position = 0;
    }

    //[3]追加しました。
    previousLevel = level;
}

[1]では、三項演算子で、現在の音量値と、前回の音量値を比較して一致しているかを見ます。levelとpreviousLevelが一致していれば真なので「level」の値がそのまま「startLevel」に入り、音量値(スライダーの値)が変化しているとlevelと「previousLevel」が一致しないので、startLevelには「previousLevel」の値が入ります。このstartLevelは、[2]で音量を滑らかに変化させる起点の音量レベルになります。

[2]では、音声にゲインの処理を施しています。ここまでで、bufferToFillのbufferには、ノイズで変調された入力音声データが入っています。この音声データに対して音量の処理を行いますので、最後のタイミングに追加されています。

AudioSampleBufferクラス(実際はAudioBuffer<float>クラス)の「applyGainRamp」関数を利用することで、任意の区間の音声サンプルに対して滑らかに音量変化させるような処理を適用することができます。

applyGainRampの仮引数:(チャンネル,サンプルのスタート位置,サンプル数,始まりの音量,最後の音量)

サンプルのスタート位置が「outputSamplesOffset 」となっています。基本的にoutputSampleOffset変数は0なのですが、音声の終わりでバッファが埋めきれないときはループして音声の頭からサンプルを読み込み、2回目のapplyRampGainの実行がありうるからです。

あわせて読みたい
JUCE: AudioBuffer< Type > Class Template Reference

最後に、[3]で、次のgetNextAudioBlockに対する前回の音量としてpreviousLevel変数にlevelの値を格納しておきます。

resize関数

音量調整用のスライダーをGUIに配置します。

void MainComponent::resized()
{
    openButton.setBounds(10, 10, getWidth() - 20, 20);
    clearButton.setBounds(10, 40, getWidth() - 20, 20);
    //スライダーオブジェクトの配置を追加します。
    levelSlider.setBounds(10, 70, getWidth() - 20, 20);
}

なんとなく変更する点のパターンが見えてきました。

よかったらシェアしてね!
目次
閉じる