Best practice for overriding a component/module (MidiKeyboardComponent) (Solved)

Hi, I am fairly new to Juce and c++.

I have added some code to the MidiKeyboardComponent module, but have read that adding or modifying code directly in the modules is bad practice. And overriding is the way to go.

But I have no clue how overriding works or how to do it in Juce. I tried different tutorials I found online and have also looked into custom modules. I am just not sure what to do.

The added code works fine, but I want to do it the proper way.

Appreciate all help.

It depends on the underlying class. You could simply inherit and add your own methods to your new class. Alternatively you can simply clone the entire class, rename it and go from there. I have done this on occasion when I needed to modify some of the non virtual functions. I actually had to do this with the MidiKeyboard class recently. I just cloned it and hacked it to pieces.

Thanks for the quick answer Rory

If I went with cloning the entire class, where would I place the files?

I tried it but if I added the new files (.h and .cpp) to the source folder it did not work.
It could not find the other Juce components that were used in the MidiKeyboardComponent.

Should i add it to the modules folder or maybe custom modules?

First have a look at the functions you modified. If they are virtual, you are better off inheriting the class and overriding the virtual methods. That is the intended use.

For sure you can add functions, but most likely you want to change how the class reacts to existing structures, which you can only accomplish by overriding virtual functions.

If you share details of your modifications it would be easier to advise

Hey Daniel thanks for the reply.

Here is the modifications I did :smiley:

juce_MidiKeyboardComponent.h - Added two new functions

//a function that can set the notes/keys that should be highlighted.
void setHighlightedNotes(int highlightedKeys[3]);

//a function that removes all the highlighted notes/keys
void removeHighlightedNotes();

Changes to the paint function.
juce_MidiKeyboardComponent.cpp - public void paint function

void MidiKeyboardComponent::paint (Graphics& g)
    {
        g.fillAll (findColour (whiteNoteColourId));

    auto lineColour = findColour (keySeparatorLineColourId);
    auto textColour = findColour (textLabelColourId);

    for (int octave = 0; octave < 128; octave += 12)
    {
        for (int white = 0; white < 7; ++white)
        {
            auto noteNum = octave + whiteNotes[white];

            //NOT ORIGINAL EDIT# added bool that controls if the note/key is highlighted
            bool highlight = false;

            if (noteNum >= rangeStart && noteNum <= rangeEnd)
            {
                //NOT ORIGINAL EDIT# checks if note/key is a highlighted note/key
                for each (int highlightedNote in highlightNotesForPress)
                {
                    if (noteNum == highlightedNote) {
                        highlight = true;
                    }
                }
                
                drawWhiteNote(noteNum, g, getRectangleForKey(noteNum),
                    state.isNoteOnForChannels(midiInChannelMask, noteNum),
                    mouseOverNotes.contains(noteNum), lineColour, textColour, highlight);
            }
        }
    }...

I have also changed something i the drawWhiteNote and drawBlackNote functions.
These functions are “protected virtual void”

Ok, so the drawWhiteNote and drawBlackNote will work with override.

The highlight: how is that different from the pressed keys? is that an additional highlight?

I think you would get away with a subclass:

class HighlightableKeyboard : public juce::MidiKeyboardComponent
{
public:
    void paint (Graphics& g) override;

    //a function that can set the notes/keys that should be highlighted.
    void setHighlightedNotes(int highlightedKeys[3]);

    //a function that removes all the highlighted notes/keys
    void removeHighlightedNotes();
private:
    // ...
};

If you have a referene to the actual type, you can easily call the additional functions.

1 Like

The highlight is for highlighting a key that should be pressed. So C3 is red and after pressing it the highlight is removed, so the key goes back to being white. It’s used to learn the different keys on a piano.

And where would I add the new class HighlightableKeyboard? This is where I’m struggling a bit. Should it be added to the Editor file for a plugin application.

Yes exactly, just where you placed the standard MidiKeyboarComponent, replace it with the derived class.

I think for a pure GUI feature it is fine to have the information in the component.
If it was something processing related, you would need to change it in the MidiKeyboardState as well…

A drawback of this method is, if the editor is closed and reopened, the highlight is gone and will start with an initial state.

Oh dear, I never realised drawBlackNote() and drawWhiteNote() were virtual :laughing: How did I miss that, it makes total sense that they would be. Too much coffee that morning I guess.

1 Like

I tried doing what you wrote Daniel and it kind of works.
I have made the class HighlightableKeyboard and derived it from MidiKeyboardComponent
class HighlightableKeyboard : public juce::MidiKeyboardComponent { }

Then I added and changed some of the derived functions inside the new class.

But it makes one error, i think its what you wrote about the MidiKeyboardState.
Im my PluginEditor.cpp file i have class

NewProjectAudioProcessorEditor::NewProjectAudioProcessorEditor (NewProjectAudioProcessor& p)
    : AudioProcessorEditor (&p), audioProcessor (p), highlightableKeyboard (keyboardState, juce::MidiKeyboardComponent::horizontalKeyboard)
{

but it says:
no instance of constructor “NewProjectAudioProcessorEditor::HighlightableKeyboard::HighlightableKeyboard” matches the argument list

I dont know if its something with this? juce_MidiKeyboardComponent.h

class JUCE_API  MidiKeyboardComponent  : public Component,
                                         public MidiKeyboardState::Listener,
                                         public ChangeBroadcaster,
                                         private Timer

Do I somehow need to add these arguments to the class?

It is literally saying what you need to do:

Write a constructor, that takes the state as argument and supplies it to the base class

HighlightableKeyboard (MidiKeyboardState &state, Orientation orientation) : MidiKeyboardComponent (state, orientation) {}

Does that mean I can change the function like this?

    class HighlightableKeyboard : public juce::MidiKeyboardComponent {
    public: 

void drawWhiteNote(int midiNoteNumber,
                    Graphics& g, Rectangle<float> area,
                    bool isDown, bool isOver,
                    Colour lineColour, Colour textColour, bool isHighlighted) override {
                    //THIS CODE WOULD BE HOW THE FUNCTION WORKS? 
                };

                void drawBlackNote(int midiNoteNumber,
                    Graphics& g, Rectangle<float> area,
                    bool isDown, bool isOver,
                    Colour noteFillColour, bool isHighlighted) override{
                    //THIS CODE WOULD BE HOW THE FUNCTION WORKS? 
                };

                //a function that can set the notes/keys that should be highlighted.
                void setHighlightedNotes(int highlightedKeys[3]) {
                    for (int i = 0; i < sizeof(highlightNotesForPress); i++) {
                        highlightNotesForPress[i] = highlightedNotes[i];
                    }
                }

                //a function that removes all the highlighted notes/keys
                void removeHighlightedNotes() {
                    for (int i = 0; i < sizeof(highlightNotesForPress); i++) {
                        highlightNotesForPress[i] = -1;
                    }
                }

                enum ColourIds
                {
                    whiteNoteColourId = 0x1005000,
                    blackNoteColourId = 0x1005001,
                    keySeparatorLineColourId = 0x1005002,
                    mouseOverKeyOverlayColourId = 0x1005003,  /**< This colour will be overlaid on the normal note colour. */
                    keyDownOverlayColourId = 0x1005004,  /**< This colour will be overlaid on the normal note colour. */
                    textLabelColourId = 0x1005005,
                    upDownButtonBackgroundColourId = 0x1005006,
                    upDownButtonArrowColourId = 0x1005007,
                    shadowColourId = 0x1005008,

                    //NOT ORIGINAL EDIT# set colour id for single notes
                    highlightColourId               = 0x1005008,
                };

            private:
                int highlightNotesForPress[3] = { -1, -1, -1 };

                
        };

Thanks Daniel I don’t know enough c++ to know how it works but it works. I should probably learn some more c++ before doing more in Juce.

This was a big help, thank you again :smiley:

No worries, we are happy to help if it falls on fruitful soil :slight_smile:

Glad it works!

1 Like

I tried this to be able to use key switches and it actually didn’t work… at I least I couldn’t get it to work by over riding the class. The reason being, the draw white and black notes are using constants for the color, black and white.

You have to modify the base class to make it work. So, I just copied the base class header and cpp to a new file, then I added a bool array initialized to false to represent the keys. In the draw key methods I just then test if that key set as a key switch and set the color to what I wanted.

EDIT: You should also rename the component in your new class to prevent conflicts…