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

JUCEプログラミング、Create MIDI data、MIDIを扱うアプリ3

1_プログラミング

MIDIのノートオフや、MidiBufferについて学びます。

ノートオンだけだとあまり良くないよね〜

JUCEのチュートリアル、MIDIの「Create MIDI data」チュートリアルを進めています。このチュートリアルでは、ZIPでダウンロードできる「MidiMessageTutorial_02.h」の内容に相当します。また、チュートリアル内の項目の「The MidiBuffer class」の内容となります。

進めているチュートリアルはこちらです

また、この記事は、以前のチュートリアルで作成したアプリを元にしています。

MIDIを扱うアプリ1

MIDIを扱うアプリ2

こんな人の役に立つかも

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

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

・JUCEでMIDIを扱いたい人

スポンサーリンク

アプリの改良の概要

前回のアプリでは、MIDIノートオンメッセージを発生させていましたが、ノートオフメッセージを投げていないので、よくありません。ということで、MIDIノートオフを一定の時間後に発生させる、という仕組みを作成していきます。

次の図のように、ノートオンを発生させるボタンを押すと、0.1秒後にノートオフメッセージが発生します。

MidiBufferクラス

MidiBufferクラスを利用して、簡単なMIDIのスケジューリングを行うことができるようです。

タイムスタンプをもつMidiMessageオブジェクトをMidiBufferクラスのオブジェクトに登録して、Timerで一定期間毎に発生させるMIDIメッセージを確認することで実現できます。(今回はタイマーでやっていますが、getNextAudioBlockなど、一定間隔で繰り返すような処理に組み込んだりもできるようです。一定間隔で確認、という点がポイントみたいです。)

プログラムの実装

MainComponent.h

まずはMainComponent.hファイルで、以下の点を変更しました。

//[1]Timerクラスを継承し、Componentクラスの継承に変更しておきます。
class MainComponent : public juce::Component,//: public juce::AudioAppComponent
    private juce::Timer
{
public:
    MainComponent();
    ~MainComponent() override;

    //[2]今回は、音声処理を消しておきます。
    //void prepareToPlay(int samplesPerBlockExpected, double sampleRate) override;
    //void getNextAudioBlock(const juce::AudioSourceChannelInfo& bufferToFill) override;
    //void releaseResources() override;

    void paint(juce::Graphics& g) override;
    void resized() override;

private:
    static juce::String getMidiMessageDescription(const juce::MidiMessage& m);
    void setNoteNumber(int noteNumber);
    void logMessage(const juce::String& m);
    void addMessageToList(const juce::MidiMessage& message);

    //[3]新たに次の2つのメンバ関数を追加しました。
    void timerCallback() override;
    void addMessageToBuffer(const juce::MidiMessage& message);

    juce::TextButton bassDrumButton;
    juce::TextButton snareDrumButton;
    juce::TextButton closedHiHatButton;
    juce::TextButton openHiHatButton;

    juce::Label volumeLabel;
    juce::Slider volumeSlider;

    juce::TextEditor midiMessagesBox;

    int midiChannel = 10;
    double startTime;

    //[4]新たに次の3つのメンバ変数を追加しました。
    juce::MidiBuffer midiBuffer;               // [4-1]
    double sampleRate = 44100.0;        // [4-2]
    int previousSampleNumber = 0;       // [4-3]

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainComponent)
};

[1]では、音声処理を停止するため、ProjucerのGUIテンプレートと同様になるようにComponentクラスの継承に変更しています。Audioのテンプレートから音声処理を削除する点につきましては、次の記事でも記載していますので、ご参照ください。

また、今回は、タイマーを利用するので、Timerクラスの継承も行っておきます。

複数継承すると、カンマを忘れがちなんですよね^^;

[2]では、音声処理を停止するので、音声処理に関する関数をコメントアウトしました。

[3]で、後ほど実装していく今回のメンバ関数を追加しています。

[4]では、今回利用するメンバ変数を定義します。サンプルレート値について、今回は音声処理がないので、44100と決め打ちして利用していくようです。

MainComponent.cpp

cppファイルに加える変更について見ていきます。

音声処理の削除部分

以前のチュートリアルで作成したアプリは、Projucerの「Audio」テンプレートで作成したので、今回は、以下の音声処理の部分をコメントアウトしました。

MainComponent::~MainComponent()
{
    //shutdownAudio();
}

/*
void MainComponent::prepareToPlay (int samplesPerBlockExpected, double sampleRate){}

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

void MainComponent::releaseResources() {}
*/

コンストラクタ

[1]のように、スライダーのコールバック関数の関数をMidiBufferへ値を追加する処理に変更しました。addMessageToBufferは後ほど実装する関数になります。また、[2]で、今回は、タイマーが出てきますので、タイマーを開始させます。

MainComponent::MainComponent()
    : startTime(juce::Time::getMillisecondCounterHiRes() * 0.001)
{
//...略...
    volumeSlider.onValueChange = [this]
    {
            auto message = juce::MidiMessage::controllerEvent (10, 7, (int) volumeSlider.getValue());
            message.setTimeStamp (juce::Time::getMillisecondCounterHiRes() * 0.001 - startTime);
            addMessageToBuffer (message);//[1]変更しました。
    };
 //...略...
    setSize(800, 300);
    startTimer(1);//[2]タイマーの初期化を追加しました。
}

タイマーは、1msecで動作させています。また、これはメッセージスレッドで動作させていますので、実際には、あまり精度がよくはないということです。

今回は、チュートリアルをシンプルにするために、このような形でタイマーを使ってMidiBufferオブジェクトを動作させるようです。ほとんどの場合、MIDIを使うと音声も使うことになるので、音声処理スレッド(getNextAudioBlock関数内)で使うとより良い結果になりそうです。

アプリによって、適した方法で一定間隔にMidiBufferの処理を実行する方法を考える必要はありそうですね。

今回はここまでとして、次回も実装の続きを行っていきます。

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