MIDI入力に関係する関数について進めていきます。
意外と複雑な処理がまっているかも
引き続き、Handling Midi Eventsのチュートリアルです。「Handling external MIDI input」と「The MIDI keyboard state and component」の部分となります。外部MIDIデバイスからのMIDIメッセージの受信と、仮想MIDIキーボードの状態を取得する点の内容となります。
チュートリアルページはこちらになります。
こんな人の役に立つかも
・JUCEプログラミングを勉強している人
・JUCEの「Handling Midi Events」チュートリアルを進めている人
・JUCEでMIDIを扱いたい人
外部MIDIデバイスからの受信
handleIncomingMidiMessage関数
MidiInputCallbackクラスの関数で、overrideしている関数です。(MainComponent.hでMidiInputCallbackを継承しています。)
この関数は、外部のMIDI入力デバイスからMIDIイベントが到達したときに、呼び出されます。優先度の高いシステムスレッドで行われる(OS側での処理)ので、この中でUIの処理などを行わないように注意します。
//外部MIDIデバイスからのMIDIメッセージを受信します。
void MainComponent::handleIncomingMidiMessage(juce::MidiInput* source, const juce::MidiMessage& message)
{
//[1]このスコープで、isAddingFromImdiInput変数をTrueにします。
const juce::ScopedValueSetter<bool> scopedInputFlag(isAddingFromMidiInput, true);
keyboardState.processNextMidiEvent(message);//[2]
postMessageToList(message, source->getName());//[3]
}
[1]の「ScopeValueSetter」で、この関数実行中だけ「isAddingInputFlag」をtrueにしています。スコープ単位で変数を代入できるような関数です。
「isAddingInputFlag」は外部MIDIデバイスからの受信メッセージである場合はTrueになるようなフラグということになります。
[2]では、kyeboardStateクラスの「processMidiEvent」関数にMIDIメッセージを与えてMIDIキーボードのノートオンノートオフの状態を変化させます。
[3]で、後程定義するPostMessageToList関数でMIDIメッセージを出力します。
MidiKeyboardStateのリスナー関数
handleNoteOn関数
この関数は、MidiKeyboardStateの状態がノートオンになったとき(processNextMidiBuffer()関数やnoteOn()が実行されたとき)に呼び出されます。音声処理スレッドで実行されるらしいので、ここでもUI処理や、処理をブロックしないように注意します。
もちろん、MidiKyeboardComponent(GUIのキーボード)はMidiKyeboardStateを可視化したのもなので、GUIキーボードのノートオンでもこのリスナーが動作します。
void MainComponent::handleNoteOn(juce::MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity)
{
if (!isAddingFromMidiInput)
{
auto m = juce::MidiMessage::noteOn(midiChannel, midiNoteNumber, velocity);
m.setTimeStamp(juce::Time::getMillisecondCounterHiRes() * 0.001);
postMessageToList(m, "On-Screen Keyboard");
}
}
isAddingFromMidiInputフラグがFalseのとき(外部デバイスからのMIDIイベントでないとき)は条件内の処理が実行されます。MIDIノートオンメッセージを作成して、タイムスタンプを格納し、postMessageToListでMIDIメッセージを出力します。
handleNoteOff関数
先ほどのnoteOn関数と同様です。ノートオフのタイミングで実行されます。
void MainComponent::handleNoteOff(juce::MidiKeyboardState*, int midiChannel, int midiNoteNumber, float /*velocity*/)
{
if (!isAddingFromMidiInput)
{
auto m = juce::MidiMessage::noteOff(midiChannel, midiNoteNumber);
m.setTimeStamp(juce::Time::getMillisecondCounterHiRes() * 0.001);
postMessageToList(m, "On-Screen Keyboard");
}
}
今回の関数の関係性
外部MIDIデバイスからのMIDIメッセージはhandleIncomingMidiMessage関数で処理されますが、その時、GUIのキーボードに状態を反映(keyboardState)したいので、processNextMidiEventにMIDIメッセージを渡します。そうすると、keyboardStateのリスナーのhandleNoteOnが動作しますので、外部MIDIデバイスのときはフラグを立ててhandleNoteOn内の処理を行わないようにしなければいけません。
ノートオンをそれぞれ外部MIDIデバイスと、GUIのキーボードから行った場合の処理を図にしてみました。
外部MIDIデバイスのときのフラグは「isAddingFromInput」で管理していて、handleIncomingMidiMessage開始時にTrueになります。そのため、この関数内部から呼び出されるprocessNextMidiEventが実行されて、handleNoteOnが処理開始されるときには、isAddingMidiInputはTrueで、handleNoteOnの処理は条件から外れて、なにもしないで終了することになります。
フラグの管理や、処理の順番が意外と複雑でした。