MIDIコントローラで操作できるようにしたいね。
引き続きチュートリアルの実装でできるみたい
JUCEチュートリアルの、Synthの項目、「Build a MIDI synthesiser」について、進めていきます。今回は、チュートリアルの中の「Adding external MIDI input」のところからです。前回までは、GUIのMIDIキーボードと、PCのキーボードの入力のみを受け付けていましたが、USBなどで接続した外部MIDI入力装置(MIDIコン)に対応できるようなプログラムを追加していきます。ダウンロードできるサンプルでは、「SynthUsingMidiInputTutorial_02.h」の内容となります。
こんな人の役に立つかも
・JUCEプログラミングを勉強している人
・JUCEの「Build a MIDI synthesiser」チュートリアルを進めている人
・JUCEでシンセを作成したい人
外部MIDI入力への拡張
MainComponent.h
SynthAudioSourceクラスにMidiMessageCollectorクラスのオブジェクトを追加して、外部MIDI入力の拡張を行います。
MidiMessageCollectorクラスは、「MidiKeyboardState::Listener」と「MidiInputCallback」を継承していますので、以前MIDIのチュートリアルで行ったようにGUIキーボードの入力と外部MIDI入力の処理を連携させるプログラムをわざわざ記載しなくても、MIDI入力を検知できるようになるようです。MIDI入力について、どこからの入力かについて意識しないプログラムの場合、このようにクラス化されたものをポンっと利用するほうが楽になりますね。
以前学習した外部MIDI入力に関するプログラムはこちらをご参照ください。
class SynthAudioSource : public juce::AudioSource
{
//...略...
void prepareToPlay(int /*samplesPerBlockExpected*/, double sampleRate) override
{
synth.setCurrentPlaybackSampleRate(sampleRate);
//[2]midiCollectorにサンプルレートを渡します。
midiCollector.reset(sampleRate);
}
void releaseResources() override {}
void getNextAudioBlock(const juce::AudioSourceChannelInfo& bufferToFill) override
{
bufferToFill.clearActiveBufferRegion();
juce::MidiBuffer incomingMidi;
//[3]保留中のメッセージをキューから削除します。
midiCollector.removeNextBlockOfMessages(incomingMidi, bufferToFill.numSamples);
keyboardState.processNextMidiBuffer(incomingMidi, bufferToFill.startSample,
bufferToFill.numSamples, true);
synth.renderNextBlock(*bufferToFill.buffer, incomingMidi,
bufferToFill.startSample, bufferToFill.numSamples);
}
//[4]midiCollectorのアクセサを定義します。
juce::MidiMessageCollector* getMidiCollector()
{
return &midiCollector;
}
private:
juce::MidiKeyboardState& keyboardState;
juce::Synthesiser synth;
//[1]MidiMessageCollectorクラスのオブジェクトを追加します。
juce::MidiMessageCollector midiCollector;
};
プログラム一番下の、[1]で、ptivateなメンバとしてMidiMessageCollectorクラスのmidiCollectorオブジェクトを定義します。
[2]では、prepareToPlayで、サンプルレートをmidiCollectorに設定します。
[3]では、保留中のメッセージをキューから削除するそうです。おそらく、処理しきれなかったキューに存在しているメッセージなどをクリアすることで、次の音声処理(getNextAudioBlockは一定間隔で処理され続けるので)で邪魔にならないようにする、みたいなイメージでしょうか。現段階ではこのようにとらえています。
[4]では、privateなメンバのmidiCollectorにアクセスできるようなアクセサ(クラスの外から値などを取得できるようにする関数)を定義しています。MainComponentクラスのsetMidiInput関数で利用したいので、アクセサを定義して、midiCollectorのポインタを取得できるようにしています。
MIDI入力選択用のコンボボックス
この内容は、MIDIチュートリアルの「Handling MIDI Events」のプログラムを流用いています。
MainComponent.h
MainComponentクラスのprivateなメンバとして、外部MIDI入力(MIDIコントローラ)を選択するためのコンボボックスなどのオブジェクトを追加します。また、setMidiInput関数も定義しておきます。
class MainComponent : public juce::AudioAppComponent,
private juce::Timer
{
//...略...
//追加します。
void setMidiInput(int index);
juce::ComboBox midiInputList;
juce::Label midiInputListLabel;
int lastInputIndex = 0;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};
MainComponent.cpp
コンストラクタ
以下のプログラムを追加しました。
MainComponent::MainComponent()
: synthAudioSource(keyboardState),
keyboardComponent(keyboardState, juce::MidiKeyboardComponent::horizontalKeyboard)
{
//以下の部分を追加しました。
addAndMakeVisible(midiInputListLabel);
midiInputListLabel.setText("MIDI Input:", juce::dontSendNotification);
midiInputListLabel.attachToComponent(&midiInputList, true);
auto midiInputs = juce::MidiInput::getAvailableDevices();
addAndMakeVisible(midiInputList);
midiInputList.setTextWhenNoChoicesAvailable("No MIDI Inputs Enabled");
juce::StringArray midiInputNames;
for (auto input : midiInputs)
midiInputNames.add(input.name);
midiInputList.addItemList(midiInputNames, 1);
midiInputList.onChange = [this] { setMidiInput(midiInputList.getSelectedItemIndex()); };
for (auto input : midiInputs)
{
if (deviceManager.isMidiInputDeviceEnabled(input.identifier))
{
setMidiInput(midiInputs.indexOf(input));
break;
}
}
if (midiInputList.getSelectedId() == 0)
setMidiInput(0);
//===
//...略...
}
setMidiInput
setMidiInputも以前と同じ内容です。以下のプログラムを追加します。
void MainComponent::setMidiInput(int index)
{
auto list = juce::MidiInput::getAvailableDevices();
deviceManager.removeMidiInputDeviceCallback(list[lastInputIndex].identifier, synthAudioSource.getMidiCollector());
auto newInput = list[index];
if (!deviceManager.isMidiInputDeviceEnabled(newInput.identifier))
deviceManager.setMidiInputDeviceEnabled(newInput.identifier, true);
deviceManager.addMidiInputDeviceCallback(newInput.identifier, synthAudioSource.getMidiCollector());
midiInputList.setSelectedId(index + 1, juce::dontSendNotification);
lastInputIndex = index;
}
resized関数
resized関数で、コンボボックスの配置を行います。そのため、キーボードコンポーネントを少しY方向に下げています。
void MainComponent::resized()
{
//keyboardComponent.setBounds(10, 10, getWidth() - 20, getHeight() - 20);
//次のように変更しました。
midiInputList.setBounds(200, 10, getWidth() - 210, 20);
keyboardComponent.setBounds(10, 40, getWidth() - 20, getHeight() - 50);
}
MIDIキーボードを接続したら、演奏することができました。リストにも接続しているMIDIコントローラの名称が追加されて、いい感じの出来です。
実際動作するとすごくうれしいです。