前回に引き続き、Handling Midi Eventsのチュートリアルを進めていきます。チュートリアルの「Midi Input List」の部分を中心に進めていきます。コンストラクタと「setMidiInput」関数の部分を中心に実装していきます。
チュートリアルページはこちらです。
こんな人の役に立つかも
・JUCEプログラミングを勉強している人
・JUCEの「Handling Midi Events」チュートリアルを進めている人
・JUCEでMIDIを扱いたい人
コンストラクタ
少し長くなるので、機能毎に分割して表記しています。
MainComponent::MainComponent()
//[1]keyboardCompnentの初期化です。
: keyboardComponent(keyboardState, juce::MidiKeyboardComponent::horizontalKeyboard),
startTime(juce::Time::getMillisecondCounterHiRes() * 0.001)
{
setOpaque(true);
//コンポーネントの描画を最適化します。
[1]は、メンバイニシャライザでMidiKeyboardComponentクラスの「keyboardComponent」(GUI上部に配置する仮想MIDIキーボード)を初期化しています。初期化するときは、MidiKeyboardStateクラスのオブジェクトを第一引数に、第二引数にキーボードの描画方向を指定します。Orientationはこちらのページで確認できます。
startTimeは、以前のチュートリアルと同様、アプリ開始時の時刻を保存します。そして、Componentクラスの「setOpaque」という関数でコンポーネントの描画を最適化するようです。(この関数は、もう少し調べないと理解しきれない状態です。)
コンボボックス関連の設定
ここから、コンボボックス関連の設定です。
[2-1]では、「MIDI Input:」のテキストを表示するためのラベルオブジェクトを初期化します。「attachToComponent」関数は、指定したオブジェクトの左にくっつくような関数です。コンボボックスの左にくっつくような配置になります。
//[2]MIDI入力選択コンボボックス関連の初期化です
//[2-1]コンボボックス左のラベルです。
addAndMakeVisible(midiInputListLabel);
midiInputListLabel.setText("MIDI Input:", juce::dontSendNotification);
midiInputListLabel.attachToComponent(&midiInputList, true);
[2-2]では、コンボボックス自体を設定していきます。「setTextWhenChoicesAvailable」関数は、有効な項目がないときにボックスをクリックしたときに表示するメッセージを設定しています。
[2-3]で、コンボボックスにMIDI入力デバイスの項目を追加します。まず、「getAvailableDevices関数」利用して有効なMIDIデバイスを「midiInputs」に取得します。それとは別に「midiInputNames」というStringArrayを準備します。
範囲forループでmidiInputsをループすると、MIDI入力デバイスの数分ループしますので、midiInputNamesにその名称を取得して「.add」関数で追加します。midiInputNamesにはMIDIデバイスの名称が登録されることになります。
//[2-2]コンボボックスの設定です。
addAndMakeVisible(midiInputList);
midiInputList.setTextWhenNoChoicesAvailable("No MIDI Inputs Enabled");
//[2-3]Midi入力を取得して、StringArrayに格納します。
auto midiInputs = juce::MidiInput::getAvailableDevices();
juce::StringArray midiInputNames;
for (auto input : midiInputs)
midiInputNames.add(input.name);
次に、[2-4]で取得したMIDIデバイスをコンボボックスに追加します。「addItemList」関数で追加します。第二引数の1は、最初の項目番号のオフセットです。そして、つづけてコンボボックスの項目が変更されたときに実行するハンドラをラムダ式で定義しています。後程実装する「setMidiInput」関数を呼び出すようにしています。
//[2-4]コンボボックスに項目を追加して、ハンドラを追加します。
midiInputList.addItemList(midiInputNames, 1);
midiInputList.onChange = [this] { setMidiInput(midiInputList.getSelectedItemIndex()); };
//[2-5]項目で一番最初の有効なデバイスを選択します。
for (auto input : midiInputs)
{
if (deviceManager.isMidiInputDeviceEnabled(input.identifier))
{
setMidiInput(midiInputs.indexOf(input));
break;
}
}
//上記で有効なデバイスがない場合は0にします。
if (midiInputList.getSelectedId() == 0)
setMidiInput(0);
[2-5]ではMIDIデバイスの数分ループをさせ、最初に有効なMIDIデバイスをデフォルトの入力MIDIデバイスと設定しています。すべてのループが抜けても有効なデバイスがない場合、最後のif文で、0を指定しています。
その他の初期化
[3]では、仮想MIDIキーボードの可視化を行い、keybordStateにリスナーを設定しています。前回のヘッダで、今回のMainComponentクラスに「juce::MidiKeyboardStateListener」(実際はMidiKeyboardState::Listenerクラス)を継承させているので、リスナー関数を紐づけることができます。このMidiKeyboardState::Listenerのリスナー関数は、「handleNoteOn」「handleNoteOff」というものがありますので、overrideで実装していきます。
//[3]キーボードコンポーネントの可視化などです。
addAndMakeVisible(keyboardComponent);
keyboardState.addListener(this);
//[4]前回チュートリアルと同様TextEditorオブジェクトの初期化です。
addAndMakeVisible(midiMessagesBox);
midiMessagesBox.setMultiLine(true);
midiMessagesBox.setReturnKeyStartsNewLine(true);
midiMessagesBox.setReadOnly(true);
midiMessagesBox.setScrollbarsShown(true);
midiMessagesBox.setCaretVisible(false);
midiMessagesBox.setPopupMenuEnabled(true);
midiMessagesBox.setColour(juce::TextEditor::backgroundColourId, juce::Colour(0x32ffffff));
midiMessagesBox.setColour(juce::TextEditor::outlineColourId, juce::Colour(0x1c000000));
midiMessagesBox.setColour(juce::TextEditor::shadowColourId, juce::Colour(0x16000000));
setSize(600, 400);
}
[4]は、前回のMIDI入力アプリと同様、テキストエディタにMIDIメッセージを表示するためのオブジェクトになります。
setMidiInput関数
選択したMIDIデバイスのMIDIメッセージの受信を開始する関数です。現在設定中のMIDIデバイスからリスナーを取り除き、コンボボックスで新たに選択したMIDIデバイスにリスナーを設定する、という機能です。コンボボックスの項目を変更した際にハンドラ(と、コンストラクタの初期化の際)で呼び出されます。
void MainComponent::setMidiInput(int index)
{
//[1]MIDIデバイスを取得します。
auto list = juce::MidiInput::getAvailableDevices();
//[2]選択中のMIDIデバイスのリスナーを取り除きます。
deviceManager.removeMidiInputDeviceCallback(list[lastInputIndex].identifier, this);
//[3]新しく選択されたデバイス番号のMIDIデバイスを取得します。
auto newInput = list[index];
//[4]AudioDeviceManagerでMidiデバイスの状態を有効にします。
if (!deviceManager.isMidiInputDeviceEnabled(newInput.identifier))
deviceManager.setMidiInputDeviceEnabled(newInput.identifier, true);
//[5]リスナーを選択したMIDIデバイスに設定します。
deviceManager.addMidiInputDeviceCallback(newInput.identifier, this);
midiInputList.setSelectedId(index + 1, juce::dontSendNotification);
lastInputIndex = index;//[6]現在のindexを保存しておきます。
}
[1]ではまずMIDIデバイスを取得します。[2]で、「removeMidiInputDeviceCallback」関数を利用して、「addMidiInputDeviceCallback」で登録されたMIDIデバイスのリスナーを取り除きます。初回、コンストラクタでまず実行されます。その時は、「lastInputIndex」が0なので、ここで「0」番のMIDIデバイスのリスナーが取り除かれます。その後、有効なMIDIデバイスがある場合、最初に見つかった有効なMIDIデバイスの番号が引数のindexに入ってきていますので、この番号のMIDIデバイスに[3]以降の処理で変更されます。
[4]では、AudioDeviceManagerクラスの「deviceManager」の関数「isMidiInputDeviceEnabled」を使って、MIDIデバイスが有効であるかのフラグを取得しています。もしFlaseの場合、これをTrueにしないとMIDIデバイスからのメッセージを受信できませんので、「setMidiInputDeviceEnable」関数でTrueにします。これは一度Trueにすれば、2回目以降、再度MIDIデバイスが選択されてもTrueのままになっていますので、条件に該当せず、処理をしないというようになります。とにかく有効にしておく、という点が重要なようです。
[5]で、リスナーを変更後のMIDIデバイスに設定します。そして、コンボボックスの表記も変更します。
[6]で、現在のMIDIデバイスの番号を保存しておき、次回のsetMidiInput関数実行時に、変更前のMIDIデバイスの番号として利用することになります。