チュートリアル:MIDIイベントの処理
このチュートリアルでは、MIDI入力イベントの扱い方を説明します。外部ソースからMIDIデータを渡すことに加えて、オンスクリーンキーボードコンポーネントを紹介します。
レベル:中級
プラットフォーム:Windows, macOS, Linux
クラス: AudioDeviceManager,MidiMessage,MidiInputCallback,ComboBox,MidiKeyboardComponent,MidiKeyboardState,CallbackMessage,ScopedValueSetter
スタート
このチュートリアルのデモ・プロジェクトのダウンロードはこちらから:PIP|ZIP.プロジェクトを解凍し、最初のヘッダーファイルをProjucerで開く。
このステップにヘルプが必要な場合は、以下を参照してください。Tutorial: Projucer Part 1: Getting started with the Projucer.
コンピュータに外部MIDIソースを接続するのが理想的です。そうでない場合は、ある種のバーチャルMIDI音源(コンピューター上にバーチャルMIDIポートを作るもの)があると便利です。
デモ・プロジェクト
このデモ・プロジェクトでは、画面上にMIDIキーボードが表示され、コンボボックスを使ってハードウェア・デバイスのMIDI入力を1つ選択することができます。これらのソースから受信した MIDI イベントはウィンドウの下部に表示されます。これは次のスクリーンショットに示されています:
MIDI入力
このチュートリアルでは、基本的なアプリケーションで MIDI 入力を扱う方法を示します。JUCEは、接続されているハードウェアMIDIインタフェースのリストを簡単に見つけることができます。またMidiKeyboardComponentクラスのメンバ変数を見 てみよう。まず、このクラスのメンバ変数を見てみよう。MainContentComponent
クラスである:
juce::AudioDeviceManager deviceManager; // [1]
juce::ComboBox midiInputList; // [2]
juce::Label midiInputListLabel;
int lastInputIndex = 0; // [3]
bool isAddingFromMidiInput = false; // [4]
juce::MidiKeyboardState keyboardState; // [5]
juce::MidiKeyboardComponent keyboardComponent; // [6]
juce::TextEditor midiMessagesBox;
double startTime;
- [1]を使用します。AudioDeviceManagerクラスを使用して、どのMIDI入力デバイスが有効になっているかを調べます。
- [2]MIDI入力デバイスの名前をこのコンボボックスに表示し、ユーザーが選択できるようにします。
- [3]別の入力を選択した際に、以前に選択したMIDI入力の登録を解除するために使用します。
- [4]このフラグは、オンスクリーンキーボードのマウスクリックではなく、外部ソースからMIDIデータが届いていることを示します。
- [5](その)MidiKeyboardStateクラスは、現在どのMIDI鍵盤が押されているかを記録します。
- [6]これはオンスクリーンキーボードコンポーネントです。
の中でMainContentComponent
コンストラクタで初期化します。[3],[4]そして[6].また、MIDIデータのタイムスタンプを相対的に表示できるように、アプリケーションの開始時間も記録します。
MainContentComponent()
: keyboardComponent (keyboardState, juce::MidiKeyboardComponent::horizontalKeyboard),
startTime (juce::Time::getMillisecondCounterHiRes() * 0.001)
{
を通過させなければならない。MidiKeyboardStateオブジェクトを初期化する。MidiKeyboardComponentオブジェクトである。そして、これらは静的に割り当てられたオブジェクトなのでMidiKeyboardStateは、メンバー変数の最初にリストされなければならない。
MIDI入力リスト
MIDI入力のリストを含むコンボボックスは、コンピュータに接続されているMIDI入力のリストをMidiInputクラスでは、MidiInput::getDevices() 関数を使います:
addAndMakeVisible (midiInputList);
midiInputList.setTextWhenNoChoicesAvailable ("No MIDI Inputs Enabled");
auto midiInputs = juce::MidiInput::getAvailableDevices();
juce::StringArray midiInputNames;
for (auto input : midiInputs)
midiInputNames.add (input.name);
midiInputList.addItemList (midiInputNames, 1);
midiInputList.onChange = [this] { setMidiInput (midiInputList.getSelectedItemIndex()); };
// find the first enabled device and use that by default
for (auto input : midiInputs)
{
if (deviceManager.isMidiInputDeviceEnabled (input.identifier))
{
setMidiInput (midiInputs.indexOf (input));
break;
}
}
// if no enabled devices were found just use the first one in the list
if (midiInputList.getSelectedId() == 0)
setMidiInput (0);
ユーザーが選択されたMIDI入力を変更した場合、そのMIDI入力に割り当てられたラムダ関数は、そのMIDI入力に割り当てられたラムダ関数に変更されます。ComboBox::onChangeヘルパー・オブジェクトが呼び出される:
midiInputList.onChange = [this] { setMidiInput (midiInputList.getSelectedItemIndex()); };
についてsetMidiInput()
関数は、アプリケーションが選択されたデバイスのリッスンを開始するようにします。また、そのデバイスが現在無効になっている場合は有効にします:
void setMidiInput (int index)
{
auto list = juce::MidiInput::getAvailableDevices();
deviceManager.removeMidiInputDeviceCallback(list[lastInputIndex].identifier, this);
auto newInput = list[index];
if (! deviceManager.isMidiInputDeviceEnabled (newInput.identifier))
deviceManager.setMidiInputDeviceEnabled (newInput.identifier, true);
deviceManager.addMidiInputDeviceCallback (newInput.identifier, this);
midiInputList.setSelectedId (index + 1, juce::dontSendNotification);
lastInputIndex = index;
}
外部MIDI入力を扱う
を実施する。MidiInputCallback::handleIncomingMidiMessage() pure virtual関数を使う。これによりキーボードの状態が更新され(これによりMidiKeyboardComponentオブジェクト):
void handleIncomingMidiMessage (juce::MidiInput* source, const juce::MidiMessage& message) override
{
const juce::ScopedValueSetter scopedInputFlag (isAddingFromMidiInput, true);
keyboardState.processNextMidiEvent (message);
postMessageToList (message, source->getName());
}
に注目してほしい。scopedInputFlag
変数はScopedValueSetterクラスを作成します。これは次のようなものである:
- の現在の状態を保存する。
isAddingFromMidiInput
メンバーだ。 - を設定する。
isAddingFromMidiInput
メンバーをtrueに設定する。 - 関数が終了すると
isAddingFromMidiInput
メンバーを関数開始時の状態に戻す。
MIDIキーボードの状態とコンポーネント
の中でMainContentComponent
コンストラクタはMidiKeyboardComponentオブジェクトがMainContentComponent
親コンポーネントを可視化する。またMidiKeyboardStateオブジェクト(ないコンポーネント):
addAndMakeVisible (keyboardComponent);
keyboardState.addListener (this);
MidiKeyboardStateListener クラスには、次の 2 つがあります。pure virtual関数を実装しなければならない。これらはMidiKeyboardStateListener::handleNoteOn()そしてMidiKeyboardStateListener::handleNoteOff()の機能がある。
void handleNoteOn (juce::MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override
{
if (! isAddingFromMidiInput)
{
auto m = juce::MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity);
m.setTimeStamp (juce::Time::getMillisecondCounterHiRes() * 0.001);
postMessageToList (m, "On-Screen Keyboard");
}
}
void handleNoteOff (juce::MidiKeyboardState*, int midiChannel, int midiNoteNumber, float /*velocity*/) override
{
if (! isAddingFromMidiInput)
{
auto m = juce::MidiMessage::noteOff (midiChannel, midiNoteNumber);
m.setTimeStamp (juce::Time::getMillisecondCounterHiRes() * 0.001);
postMessageToList (m, "On-Screen Keyboard");
}
}