MIDIの入力が画面でもできる、よく見る雰囲気も出てきました。
プログラムも少しづつ複雑になってきたね~
JUCEチュートリアル、MIDIの「Handling Midi Events」の項目を進めていきます。だんだんプログラムが複雑になってきました。Projucerの「Audio」テンプレートをもとに、チュートリアルでダウンロードできる「HandlingMidiEventsTutorial.h」の内容を実装していきます。今回は、MainComponent.hを作成するところまでを進めていきます。
こんな人の役に立つかも
・JUCEプログラミングを勉強している人
・JUCEの「Handling Midi Events」チュートリアルを進めている人
・JUCEでMIDIを扱いたい人
チュートリアルで作成するデモアプリ
このチュートリアルでは、次のようなアプリが作成できます。
①赤枠のコンボボックスで、MIDIコントローラが選択できます。
②オレンジ枠のように、画面上での仮想的なMIDI入力装置を設置します。
③以前のチュートリアルと同様に、MIDIメッセージをテキストとして表示します。
前回は、プログラムでMIDIを発生させましたが、今回はMIDI入力を受け付けるようになります。
プログラムの実装
今回、プログラムをProjucerの「Audio」テンプレートで作成しました。Midi入力に関する機能は、「juce_audio_utils」のモジュールに存在しているようなので、「Audio」テンプレートで作成して、デフォルトでモジュールが読み込まれているものを利用するためです。「GUI」テンプレートを利用して、ビルドできないときは、モジュールの追加部分を見直すのが良いと思います。
MainComponent.h
ヘッダファイルには、MainComponentクラスに、継承するクラスの追加と、結構多めなprivateなメンバを追加します。
class MainComponent : public juce::AudioAppComponent,
//[1]継承するクラスを追加します。
private juce::MidiInputCallback,
private juce::MidiKeyboardStateListener
{
public:
//...略...
まずは、[1]で、次のクラスを継承しています。
このクラスは、MIDIコントローラなどの外部MIDI入力装置からMIDIメッセージを受け取ります。
MidiKeybouardStateListenerはMidiKeyboardState::Listenerクラスのusingとして定義されています。
MidiKeyboardState::Listenerは、MidiKeyboardStateクラスのオブジェクトからMIDIイベントを受け取ります。
この二つのクラスがどのように動作してMIDIメッセージを受信するようにしているのかを、実装を通して確認していきたいと思います。
次に、privateなメンバに追加した項目です。[2]のように、IncomingMessageCallbackというクラスを定義します。
private:
//[2]MIDI入力をメッセージスレッドにディスパッチするクラスです。
class IncomingMessageCallback : public juce::CallbackMessage
{
public:
IncomingMessageCallback(MainComponent* o, const juce::MidiMessage& m, const juce::String& s)
: owner(o), message(m), source(s)
{}
void messageCallback() override
{
if (owner != nullptr)
owner->addMessageToList(message, source);
}
Component::SafePointer<MainComponent> owner;
juce::MidiMessage message;
juce::String source;
};
privateなメンバ関数も次のように定義します。
//[3]メンバ関数を追加しました。
static juce::String MainComponent::getMidiMessageDescription(const juce::MidiMessage& m);
void MainComponent::logMessage(const juce::String& m);
/*[3-1]選択したMIDI入力装置のMIDI受信開始をするための関数です。*/
void MainComponent::setMidiInput(int index);
//[3-2]外部MIDIコンと仮想MIDIコンのコールバック処理を扱う関数です。
void MainComponent::handleIncomingMidiMessage(juce::MidiInput* source, const juce::MidiMessage& message) override;
void MainComponent::handleNoteOn(juce::MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override;
void MainComponent::handleNoteOff(juce::MidiKeyboardState*, int midiChannel, int midiNoteNumber, float /*velocity*/) override;
void MainComponent::postMessageToList(const juce::MidiMessage& message, const juce::String& source);
void MainComponent::addMessageToList(const juce::MidiMessage& message, const juce::String& source);
「getMidiMessageDescription」と「logMessage」関数は、前回チュートリアルにも出てきた機能です。
[3-1]のsetMidiInputは、コンボボックス(MIDIコンの選択用)のハンドラとして登録される関数です。この関数で、選択したMidi装置からのMIDIの受信を開始するような処理を実装していきます。
[3-2]では、MIDIコントローラから入力されたMIDIメッセージに対するハンドラを定義します。説明だけだと理解しづらい点がありましたので、実際にどのようなときにどの関数が実行されるかなどを追う必要がありそうです。
//[4]次のprivateなメンバを追加しました。
juce::AudioDeviceManager deviceManager; // [4-1]
juce::ComboBox midiInputList; // [4-2]
juce::Label midiInputListLabel;
int lastInputIndex = 0; // [4-3]
bool isAddingFromMidiInput = false; // [4-4]
juce::MidiKeyboardState keyboardState; // [4-5]
juce::MidiKeyboardComponent keyboardComponent; // [4-6]
juce::TextEditor midiMessagesBox;
double startTime;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainComponent)
};
[4-1]AudioDeviceManagerクラスを利用して、外部MIDI入力装置を見つけます。AudioDeviceManagerについては、こちらの記事でもチュートリアルをおこないましたので、ご参照ください。
[4-2]は、MIDI入力を選択するためのコンボボックスです。
[4-3]は、setMidiInput関数で利用されます。MIDIデバイスの番号を保持しておくための変数のようです。
[4-4]では、仮想のMIDIコントローラ(オンスクリーンのMIDIコン)か、外部MIDI入力装置(USBなどで接続した外部コントローラ)からのMIDIメッセージなのかを判別するフラグです。
[4-5]のMidiKeyboardStateクラスで、現在どのMIDIキーが押されているかを追跡するためのクラスです。
[4-6]は、オンスクリーンの(GUIに表示する)MIDIキーボードコンポーネントです。