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

JUCEプログラミング、Build a MIDI synthesiser2、音声処理などの実装

1_プログラミング

音声処理をはじめとするシンセサイザークラスの利用方法になります。

シンセサイザークラスを使うと、意外と簡単にオリジナルのシンセが作れてしまうのかも

前回に引き続き、Synthの項目の続きである、「Build a MIDI synthesiser」のチュートリアルを進めていきます。

今回は、Audioテンプレートから追加していく内容で、MainComponent.cppの内容に入りつつ、チュートリアルの音声処理関連の部分を勉強していきたいと思います。(「The SynthAudioSource class」の内容まで進みました。)

こんな人の役に立つかも

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

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

・JUCEでシンセを作成したい人

スポンサーリンク

MainComonent.cpp

キーボードのセットアップ

コンストラクタ

MainComponent.cppに実装するコンストラクタを以下のように変更しました。

[1]では、GUIのMIDIキーボード(keyboardComponent)をメンバイニシャライザで初期化しています。また、「synthAudioSource」オブジェクトへもkyeboardStateオブジェクトを渡して初期化しています。

MainComponent::MainComponent()
//[1]メンバイニシャライザの初期化を追加しました。
    : synthAudioSource(keyboardState),
    keyboardComponent(keyboardState, juce::MidiKeyboardComponent::horizontalKeyboard)
{
    //[2]GUIのキーボードを可視化します。
    addAndMakeVisible(keyboardComponent);
    setAudioChannels(0, 2);

    setSize(600, 160);
    startTimer(400);
}

[2]では、GUIのMIDIキーボードを可視化します。タイマーは、400msecという設定にします。

timerCallback関数

timerCallback関数を追加でMainComponent.cppに実装します。(位置はどこでも大丈夫ですが、一番下に追加しました。)

//タイマーのコールバック関数です。
void MainComponent::timerCallback()
{
    keyboardComponent.grabKeyboardFocus();
    stopTimer();
}

コンストラクタで、タイマーがスタートしてから400msec後に初回のコールバック関数が実行されますが、そのまま「stopTimer」関数で、初回の実行のみでストップしています。これは、キーボードのフォーカスを取得するためにアプリ起動後に一回実行したいからです。

grabKeyboardFocus関数でキーボードのフォーカスを取得するためには、GUIキーボードの可視化などが完全に完了してアプリのGUIが準備できている必要があるらしいです。

音声処理関連の関数

MainComponentクラスの3つの音声処理関連関数では、synthAudioSourceオブジェクトのそれぞれの音声処理を呼び出すように設定を行います。

prepareToPlay関数

void MainComponent::prepareToPlay (int samplesPerBlockExpected, double sampleRate)
{
    synthAudioSource.prepareToPlay(samplesPerBlockExpected, sampleRate);
}

prepareToPlayでは、引数として、そのままサンプルブロック数と、サンプルレートを渡します。

getNextAudioBlock関数

void MainComponent::getNextAudioBlock (const juce::AudioSourceChannelInfo& bufferToFill)
{
    synthAudioSource.getNextAudioBlock(bufferToFill);
}

bufferToFillをそのまま引数として渡します。

releaseResources関数

void MainComponent::releaseResources()
{

    synthAudioSource.releaseResources();
}

渡す引数はありませんので、そのまま呼び出します。

MainComponentの音声処理関数では、内容の詳細はわかりませんので、前回記事でヘッダファイルに定義した「SynthAudioSource」クラスの中身を確認します。

class SynthAudioSource   : public juce::AudioSource
{
public:
    SynthAudioSource (juce::MidiKeyboardState& keyState)
        : keyboardState (keyState)
    {
        //[2]シンセの同時発音数です。
        for (auto i = 0; i < 4; ++i)
            synth.addVoice (new SineWaveVoice());
 
        //[3]波形サンプルデータなどの設定です。
        synth.addSound (new SineWaveSound());
    }
 
    void setUsingSineWaveSound()
    {
        synth.clearSounds();
    }
 
    void prepareToPlay (int /*samplesPerBlockExpected*/, double sampleRate) override
    {
        //[4]サンプルレートを設定します。
        synth.setCurrentPlaybackSampleRate (sampleRate);
    }
 
    void releaseResources() override {}

    //[5]音声処理を定義します。
    void getNextAudioBlock (const juce::AudioSourceChannelInfo& bufferToFill) override
    {
        bufferToFill.clearActiveBufferRegion();
        //[5-1]MIDI入力をスキャンします。
        juce::MidiBuffer incomingMidi;
        keyboardState.processNextMidiBuffer (incomingMidi, bufferToFill.startSample,
                                             bufferToFill.numSamples, true);
 
       //[5-2]シンセサイザークラスで、音声データをレンダリングします。
        synth.renderNextBlock (*bufferToFill.buffer, incomingMidi,
                               bufferToFill.startSample, bufferToFill.numSamples);
    }
 
private:
    //[1]privateなメンバです。
    juce::MidiKeyboardState& keyboardState;
    juce::Synthesiser synth;
};

まず、クラス最下部で、[1]のように、MIDIキーボードの状態を表現するオブジェクト「keyboardState」とSynthesiserクラスの「synth」が定義してあります。「keyboardState」はポインタで、コンストラクタの初期化の際、MainComponentクラスから渡されますので、MainComponentクラスで定義しているkeyboardStateと、実態が同じものになります。

このsynthオブジェクトを設定して、サイン波が出力するシンセサイザーオブジェクトを作成していきます。

[2]では、(クラスの上部に戻ります)シンセの発音数を設定します。この時、ヘッダファイルに指定した「SineWaveVoice」構造体(クラス)を引数として渡します。今回は、4回繰り返して、同時発音数4のポリフォニックシンセとしていきます。Synthesiserクラスの「addVoice」関数で発音数の追加ができます。

そして、[3]では、Synthesiserクラスの「addSound」関数に「SineWaveSound」構造体(クラス)を引数として渡すことで発音サンプルを設定しています。(前回、なんとなく理解した、発音データのサンプルがこれに該当するので、なんとなく納得します。)シンセサイザーの音色を設定する、ということですね。

[4]では、prepareToPlay関数でサンプルレートを設定します。そのままMainComponentクラスから渡された設定値を設定するのみです。

[5]では、サイン波をMIDIイベントから生成するような処理を行います。

keyboardStateクラスの「processNextMidiBuffer」関数は、入力されたMIDIノートオンとオフの情報を取得して、コールバックが作成される、ということです。ちょっと言葉が複雑になってきてわかりにくいのですが、この関数で、リアルタイムにMIDIの入力を取得して、サンプルレート数分のMIDIの発音スケジュールをうまいこと処理してくれる、みたいなイメージだと思います。(MIDIバッファクラスは、MIDIの簡易なタイムスケジューラ)

最後に、[5-2]で、Synthesiserクラスの「renderNextBlock」関数で音声バッファにデータを埋めます。ここに、指定する引数として、「音声バッファ(へのポインタ)、MIDIバッファ、開始サンプル数、バッファのサンプル数」なので、指定したサウンドで、音声バッファに波形データを生成してくれる便利な関数になっていることがわかります。

Synthesiserクラスを使うと、難しいプログラミングなしに音声処理が実装できてしますんですね。

resized関数

resized関数で、GUIにMIDIキーボードを配置しておきます。

void resized() override
{
    keyboardComponent.setBounds (10, 10, getWidth() - 20, getHeight() - 20);
}

ここまでのプログラムを実行することで、とりあえずGUIのキーボードまたはPCのキーボードからの入力でサイン波が発音されるアプリが動作します。

次回からは、チュートリアルに沿って、実際、どのような内容でシンセが発音するのか見ていきたいと思います。

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