Here is the code, if you want to have a look. I made changes from the tutorial Build a MIDI synthesizer in order to handle midi content and source.
I only changed the MainContentComponent class.
It inherit now from MidiInputCallback and MidiKeyboardStateListener, in order to handle external midi as well as internal midi keyboard.
#pragma once
//==============================================================================
struct SineWaveSound : public SynthesiserSound
{
SineWaveSound() {}
bool appliesToNote (int) override { return true; }
bool appliesToChannel (int) override { return true; }};
//==============================================================================
struct SineWaveVoice : public SynthesiserVoice
{
SineWaveVoice() {}
bool canPlaySound (SynthesiserSound* sound) override
{
return dynamic_cast<SineWaveSound*> (sound) != nullptr;
}
void startNote (int midiNoteNumber, float velocity,
SynthesiserSound*, int /*currentPitchWheelPosition*/) override
{
currentAngle = 0.0;
level = velocity * 0.15;
tailIn = tailOff;
tailOff = 0.0;
auto cyclesPerSecond = MidiMessage::getMidiNoteInHertz (midiNoteNumber);
auto cyclesPerSample = cyclesPerSecond / getSampleRate();
angleDelta = cyclesPerSample * 2.0 * MathConstants<double>::pi;
}
void stopNote (float /*velocity*/, bool allowTailOff) override
{
if (allowTailOff)
{
//if (tailOff == 0.0) //pat
// tailOff = 1.0;
tailOff = tailIn;
}
else
{
clearCurrentNote();
angleDelta = 0.0;
}
}
void pitchWheelMoved (int) override {}
void controllerMoved (int, int) override {}
void renderNextBlock (AudioSampleBuffer& outputBuffer, int startSample, int numSamples) override
{
if (angleDelta != 0.0)
{
if (tailOff > 0.0) // [7]
{
while (--numSamples >= 0)
{
auto currentSample = (float) (std::sin (currentAngle) * level * tailOff);
for (auto i = outputBuffer.getNumChannels(); --i >= 0;)
outputBuffer.addSample (i, startSample, currentSample);
currentAngle += angleDelta;
++startSample;
//tailOff *= 0.99; // [8]
tailOff *= 0.9999; // [8] //pat
//tailOff *= 0.5; // [8] //pat
//if (tailOff <= 0.005) // pat
if (tailOff <= 0.001)
{
clearCurrentNote(); // [9]
angleDelta = 0.0;
break;
}
}
}
else
{
while (--numSamples >= 0) // [6]
{
auto currentSample = (float) (std::sin (currentAngle) * level * tailIn); // pat
for (auto i = outputBuffer.getNumChannels(); --i >= 0;)
outputBuffer.addSample (i, startSample, currentSample);
currentAngle += angleDelta;
++startSample;
if (tailIn < 0.99)
{
tailIn += .0001; // pat
}
}
}
}
}
private:
double currentAngle = 0.0, angleDelta = 0.0, level = 0.0, tailOff = 0.0;
double tailIn = 0.0; //pat
};
//==========================================================================
class SynthAudioSource : public AudioSource
{
public:
SynthAudioSource (MidiKeyboardState& keyState)
: keyboardState (keyState)
{
for (auto i = 0; i < 10; ++i) // [1] pat
synth.addVoice (new SineWaveVoice());
synth.addSound (new SineWaveSound()); // [2]
}
void setUsingSineWaveSound()
{
synth.clearSounds();
}
void prepareToPlay (int /*samplesPerBlockExpected*/, double sampleRate) override
{
synth.setCurrentPlaybackSampleRate (sampleRate); // [3]
midiCollector.reset (sampleRate); // [10]
}
void releaseResources() override {}
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
{
bufferToFill.clearActiveBufferRegion();
MidiBuffer incomingMidi;
midiCollector.removeNextBlockOfMessages (incomingMidi, bufferToFill.numSamples); // [11]
keyboardState.processNextMidiBuffer (incomingMidi, bufferToFill.startSample,
bufferToFill.numSamples, true); // [4]
synth.renderNextBlock (*bufferToFill.buffer, incomingMidi,
bufferToFill.startSample, bufferToFill.numSamples); // [5]
}
MidiMessageCollector* getMidiCollector()
{
return &midiCollector;
}
private:
MidiKeyboardState& keyboardState;
Synthesiser synth;
MidiMessageCollector midiCollector;
};//==========================================================================
class MainContentComponent : public AudioAppComponent,
private MidiInputCallback, // added
private MidiKeyboardStateListener, // added
private Timer
{
public:
MainContentComponent()
: synthAudioSource (keyboardState),
keyboardComponent (keyboardState, MidiKeyboardComponent::horizontalKeyboard)
{
addAndMakeVisible (keyboardComponent);
setAudioChannels (0, 2);
std::cout << "-- Start --" << std::endl;
addAndMakeVisible (midiInputListLabel);
midiInputListLabel.setText ("MIDI Input:", dontSendNotification);
midiInputListLabel.attachToComponent (&midiInputList, true);
addAndMakeVisible (midiInputList);
midiInputList.setTextWhenNoChoicesAvailable ("No MIDI Inputs Enabled");
auto midiInputs = MidiInput::getAvailableDevices();
StringArray midiInputNames;
for (auto input : midiInputs)
midiInputNames.add (input.name);
midiInputList.addItemList (midiInputNames, 1);
midiInputList.onChange = [this] { setMidiInput (midiInputList.getSelectedItemIndex()); };
for (auto midiInput : midiInputs)
{
if (deviceManager.isMidiInputDeviceEnabled (midiInput.identifier))
{
setMidiInput (midiInputs.indexOf (midiInput));
break;
}
}
if (midiInputList.getSelectedId() == 0)
setMidiInput (0);
keyboardState.addListener (this); // added
setSize (600, 160);
startTimer (400);
}
~MainContentComponent() override
{
shutdownAudio();
}
void resized() override
{
midiInputList .setBounds (200, 10, getWidth() - 210, 20);
keyboardComponent.setBounds (10, 40, getWidth() - 20, getHeight() - 50);
}
void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override
{
synthAudioSource.prepareToPlay (samplesPerBlockExpected, sampleRate);
}
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
{
synthAudioSource.getNextAudioBlock (bufferToFill);
}
void releaseResources() override
{
synthAudioSource.releaseResources();
}
void setMidiInput (int index)
{
auto list = MidiInput::getAvailableDevices();
deviceManager.removeMidiInputDeviceCallback (list[lastInputIndex].identifier, synthAudioSource.getMidiCollector()); // [13]
auto newInput = list[index];
if (! deviceManager.isMidiInputDeviceEnabled (newInput.identifier))
deviceManager.setMidiInputDeviceEnabled (newInput.identifier, true);
deviceManager.addMidiInputDeviceCallback (newInput.identifier, synthAudioSource.getMidiCollector()); // [12]
deviceManager.addMidiInputDeviceCallback (newInput.identifier, this);
midiInputList.setSelectedId (index + 1, dontSendNotification);
lastInputIndex = index;
}
// These methods handle callbacks from the midi device + on-screen keyboard..
void handleIncomingMidiMessage (MidiInput* source, const MidiMessage& message) override
{
std::cout << "handleIncomingMidiMessage: " << source -> getName() << message.getRawData () <<std::endl;
//const ScopedValueSetter<bool> scopedInputFlag (isAddingFromMidiInput, true);
//keyboardState.processNextMidiEvent (message);
}
void handleNoteOn (MidiKeyboardState *source, int midiChannel, int midiNoteNumber, float velocity) override
{
//std::cout << "handleNoteOn" << std::endl;
//if (! isAddingFromMidiInput)
{
std::cout << "handleNoteOn: " << midiNoteNumber << std::endl;
}
}
void handleNoteOff (MidiKeyboardState *source, int midiChannel, int midiNoteNumber, float velocity) override
{
//std::cout << "handleNoteOff" << std::endl;
//if (! isAddingFromMidiInput)
{
std::cout << "handleNoteOff: " << midiNoteNumber << std::endl;
}
}
private:
void timerCallback() override
{
keyboardComponent.grabKeyboardFocus();
stopTimer();
}
//==========================================================================
SynthAudioSource synthAudioSource;
MidiKeyboardState keyboardState;
MidiKeyboardComponent keyboardComponent;
ComboBox midiInputList;
Label midiInputListLabel;
int lastInputIndex = 0;
bool isAddingFromMidiInput = false; // added
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};
As you can see if you try, when I use an external midi source, both the handleIncomingMidiMessage and handleNoteOn/Off are called.
Could you please help me to solve this?