MIDIが使えると世界が広がりそうですね。
MIDIコンからの操作ができるようになりたいね
JUCEプログラミング、MIDIの項目の「Create MIDI data」チュートリアルをやっていきます。前回までは、SineWaveシンセを作成していました。次のステップでは、Midiのシンセチュートリアルとなっていたのですが、MIDIのチュートリアルが基礎項目に上がっていたため、MIDIの項目を進めることにしました。
チュートリアルのページはこちらです。
今回は、チュートリアルで作成するアプリの概要と、Projucerからアプリを新規作成で実装していくところまでを行います。
こんな人の役に立つかも
・JUCEプログラミングを勉強している人
・JUCEの「Create MIDI data」チュートリアルを進めている人
・JUCEでMIDIを扱いたい人
チュートリアルのアプリの概要
チュートリアルのプログラムでは、Projucerの「GUI」のテンプレートから作成しているようです。
今後、音声処理も含めてMIDIを扱いたいので、あえて「Audio」のテンプレートで作成して、「GUI」と「Audio」テンプレートの違いも確認していきます。
作成するアプリ
次のようなGUIのアプリが作成できます。
ボタンクラスでノートオンメッセージを発生させます。スライダークラスを使ってCC7(音量)のMIDIを発生させます。
このアプリでは、音はならず、右のテキストボックスに発生したMIDIメッセージを表示するようにします。
Projucerでプロジェクトの作成
Projucerの「Audio」テンプレートで新規プロジェクトを作成しました。
※今回のチュートリアルは音声処理を利用しないので、GUIテンプレートで作成できます。「Audio」と「GUI」テンプレートの差分は後の項目で検証していきます。
実装
MainComponent.h
ヘッダーファイルです。privateなメンバを追加しています。
class MainComponent : public juce::AudioAppComponent
{
public:
MainComponent();
~MainComponent() override;
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:
//次の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);
//今回はGUIにボタン4つとラベルとスライダー、テキストエディタを使います。
juce::TextButton bassDrumButton;
juce::TextButton snareDrumButton;
juce::TextButton closedHiHatButton;
juce::TextButton openHiHatButton;
juce::Label volumeLabel;
juce::Slider volumeSlider;
//GUIの右部分のテキスト出力を行うテキストエディタです。
juce::TextEditor midiMessagesBox;
int midiChannel = 10;
double startTime;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};
privateなメンバとして、MIDIを操作する独自の関数4つと、GUIのオブジェクトを追加しました。また、MIDIチャンネルを10としました。startTime変数も後ほど利用しますので、定義しておきます。
MainComponent.cpp
setNoteNumber関数
cppに次のsetnoteNumber関数を作成します。
この関数は、ノートナンバーを与えることでnoteOnのMIDIメッセージを発生させてGUIの右部分のテキストに出力します。
void MainComponent::setNoteNumber(int noteNumber)
{
//[1]キーダウン(ノートオン)メッセージの作成です。
auto message = juce::MidiMessage::noteOn(midiChannel, noteNumber, (juce::uint8) 100);
message.setTimeStamp(juce::Time::getMillisecondCounterHiRes() * 0.001 - startTime);
addMessageToList(message);
}
「juce::MidiMessage」クラスは、MIDIのメッセージを操作するのに便利なクラスです。
[1]では、MidiMessageクラスの「noteOn」関数でキーが押されたというMIDIメッセージ(MidiMessageクラスのインスタンス「message」として取得)を作り出すことができます。
noteOn関数には、引数として(MIDIチャンネル,ノートナンバー,ベロシティ)を与えます。ベロシティの値は、float型とjuce::uint8型の2種類の関数がありますが、今回はuint8型の整数でベロシティを与える方をつかいますので、このようにキャストしておかないとコンパイルエラーとなります。
[2]では、MidiMessageクラスのインスタンスmessageにタイムスタンプを設定しています。タイムスタンプは任意で設定できます。
引数に与えている「getMillisecondCounterHiRes」関数は、システムの起動からの時間が取得できるようです。(パソコン起動時からの時間)
startTimeの変数は、同様にgetMillsecoundCounterHiRes関数を使った時間取得でコンストラクタにて初期化されていますので、startTimeには、アプリが稼働を開始を始めた時間が入ります。そのため、今回のMIDIメッセージにはアプリが起動してからの時間がnoteOnのMIDIメッセージに付加されることになります。
タイムスタンプは、シーケンサなどで利用できそうですね。
最後のaddMessageToList関数は、後程定義するGUIの右のテキスト表示用関数です。
addMessageToList関数
addMessageToList関数は、MidiMessageクラスのオブジェクトのポインタを引数として受け取り、MIDIメッセージのタイムスタンプを時間表示に変更、そしてMIDIメッセージの内容と一緒にテキストとして出力する機能です。
void MainComponent::addMessageToList(const juce::MidiMessage& message)
{
//MidiMessageからタイムスタンプを取得します。
auto time = message.getTimeStamp();
//タイムスタンプは秒単位ですので、次の計算式で時分秒、ミリ秒に変換します。
auto hours = ((int)(time / 3600.0)) % 24;
auto minutes = ((int)(time / 60.0)) % 60;
auto seconds = ((int)time) % 60;
auto millis = ((int)(time * 1000.0)) % 1000;
auto timecode = juce::String::formatted("%02d:%02d:%02d.%03d",
hours,
minutes,
seconds,
millis);
//GUIの右部分にテキスト出力を行います。
logMessage(timecode + " - " + getMidiMessageDescription(message));
}
最後の「getMessageDescription」と「logMessage」関数はこれから実装します。
juce::MidiMessageクラスについて
ちょっと実装面から外れるのですが、C++について新しく勉強しましたので、メモです。
JUCEでは、MidiMessageクラスの静的メンバ関数を利用して、MIDIメッセージを生成したりできます。
C++では、クラスの関数をstaticなメンバ関数とすることで、クラスからインスタンスを作成して利用する一般的な利用方法ではなくなり、クラスからそのまま関数を利用できます。静的メンバ関数と呼ばれるものとなります。
juce::MidiMessageクラスにはstaticでpublicなメンバ関数が定義されているので、インスタンスを作成することなく、MIDIメッセージを生成したりします。
公式ドキュメントの「Static Public Member Functions」にその一覧が見られます。
MidiMessageのstatic publicな関数は、グローバルに定義された関数みたいに使えるというイメージですかね。
次回も引き続き、実装を行なっていきます。