How to correctly use multiple "MidiKeyboardComponent" objects in a plugin?

Hi everyone! :grinning:
I’m creating an organ plugin and I’m having some trouble using three MidiKeyboardComponent objects at once. Right now I’m creating three keyboards (upper manual, lower manual, pedal board), and the result looks like I’d expect:

I started by declaring three public MidiKeyboardState objects in PluginProcessor.h:

juce::MidiKeyboardState keyboardStateUpperManual;
juce::MidiKeyboardState keyboardStateLowerManual;
juce::MidiKeyboardState keyboardStatePedalBoard;

Then in In PluginEditor.h, I’ve declared three MidiKeyboard objects and three MidiKeyboardState objects.

juce::MidiKeyboard midikeyboardUpperManual;
juce::MidiKeyboard midikeyboardLowerManual;
juce::MidiKeyboard midikeyboardPedalBoard;

juce::MidiKeyboardState keyboardStateUpperManual;
juce::MidiKeyboardState keyboardStateLowerManual;
juce::MidiKeyboardState keyboardStatePedalBoard;

In PluginEditor.cpp I’ve modified the constructor like this to initialize the six objects:

OpenB3AudioProcessorEditor::OpenB3AudioProcessorEditor (OpenB3AudioProcessor& p)
: AudioProcessorEditor (&p),
audioProcessor (p),
midiKeyboardUpperManual(audioProcessor.keyboardStateUpperManual, juce::MidiKeyboardComponent::horizontalKeyboard),
midiKeyboardLowerManual(audioProcessor.keyboardStateLowerManual, juce::MidiKeyboardComponent::horizontalKeyboard),
midiKeyboardPedalBoard(audioProcessor.keyboardStatePedalBoard, juce::MidiKeyboardComponent::horizontalKeyboard)
...

And I’ve configured each MidiKeyboard with a different MIDI channel:

midiKeyboardUpperManual.setMidiChannel(1);
midiKeyboardLowerManual.setMidiChannel(2);
midiKeyboardPedalBoard.setMidiChannel(3);

In PluginProcessor.cpp, inside processBlock, I call processNextMidiBuffer on each keyboard state to add its MIDI messages to the midi buffer. Then based on the channel number, I choose to what manual I should send the note on/note off messages:

keyboardStateUpperManual.processNextMidiBuffer (midiMessages, 0, buffer.getNumSamples(), true);
keyboardStateLowerManual.processNextMidiBuffer (midiMessages, 0, buffer.getNumSamples(), true);
keyboardStatePedalBoard.processNextMidiBuffer (midiMessages, 0, buffer.getNumSamples(), true);

for (const auto metadata : midiMessages)
{
    juce::MidiMessage message = metadata.getMessage();

    switch (message.getChannel())
    {
    case 1: // Upper Manual
        std::cout << "You're playing the upper manual" << std::endl;
        if (message.isNoteOn())
            beatrix->note_on(message.getNoteNumber()-36);
        else if(message.isNoteOff())
            beatrix->note_off(message.getNoteNumber()-36);
        break;
    case 2: // Lower Manual
        std::cout << "You're playing the lower manual" << std::endl;
        if (message.isNoteOn())
            beatrix->note_on(message.getNoteNumber()+28);
        else if(message.isNoteOff())
            beatrix->note_off(message.getNoteNumber()+28);
        break;
    case 3: // Pedal Board
        std::cout << "You're playing the pedal board" << std::endl;
        if (message.isNoteOn())
            beatrix->note_on(message.getNoteNumber()+92);
        else if(message.isNoteOff())
            beatrix->note_off(message.getNoteNumber()+92);
        break;
    default:
        break;
    }
}

From the console and from the organ sound, I can confirm that the right notes are played (each manual has a different register setting, so I can tell).

Now to the problem: when I press a note on the upper manual, I can clearly see that the same notes are being pressed on the keyboards of the lower manual and pedal board. When I press a note on the lower manual, the same notes are pressed on the pedal board. When I press a note on the pedal board, the behavior is correct (only pedal board notes are showed as pressed).
Since in all three cases, the organ plays the right notes, I conclude that this is a GUI problem. I must have made some mistake with the usage of MidiKeyboard and/or MidiKeyboardState.

Would anybody be so kind to share their thoughts? Thank you so much :smiling_face_with_three_hearts:

MidiKeyboardState::processNextMidiBuffer will update the keyboard UI with any notes that are in progress, as well as adding any user-initiated events to the buffer. I think this is why notes played on the upper manual appear on the other keyboards.

You could try using MidiKeyboardComponent::setMidiChannelsToDisplay(), so that each component will only display notes on the channel corresponding to that manual.

1 Like

Thank you very much, that did it! :hugs: I went through the documentation again and I found this line that I must have missed before:

Although this is the channel used for outgoing events, the component can display incoming events from more than one channel - see setMidiChannelsToDisplay()

By the way – is it necessary for every MidiKeyboard to have its own MidiKeyboardState? Or would it be OK to initialize all three MidiKeyboard objects with the same MidiKeyboardState? I tried it and it seems to work, but I wouldn’t want strange things to happen :sweat_smile: I’m also thinking in view of adding a Listener to MidiKeyboardState

Hey, I made an organ plugin as well!
You should be fine with a single instance of MidiKeyboardState.

Edit: in case you’re wondering how the plugin looks like, here it is :slight_smile:

3 Likes

Many thanks, I’ll stick with one MidiKeyboardState then :slightly_smiling_face:
What I started to do (yesterday) is a C++ wrapper around Beatrix, which is the sound engine of setBfree, to make the usage of Beatrix simple inside JUCE. I will publish the source code on Github soon.

Wait a sec… you’re saying you’re from IK Multimedia? :exploding_head:
The GUI you made is wonderful. And sorry for the OT, but why isn’t the B-3X available also for Linux?

Yep. About Linux, I don’t know, I think many companies don’t see that as a priority.

Nice :slightly_smiling_face:
Well, I always ask myself this question, because once you write a plugin in JUCE, it really takes nothing to compile it also for Linux. E.g. Modartt does it and they even provide ARM builds, so people can play Pianoteq on their Rasperry Pis. Now as funny as it may sound, one of the reasons I’m rewriting setBfree is to address the opposite problem: it currently relies on Jack and/or LV2 support, which is not what people outside Linux really use.
Sorry for the digression and thanks again. Buona giornata :wink: