FR: MidiKeyboardComponent to allow multiple key bindings to the same note

It doesn’t work as expected if I try the following:

    // bottom row
[...redacted...]
    keyboard.setKeyPressForNote(juce::KeyPress('j'), 10);
    keyboard.setKeyPressForNote(juce::KeyPress('m'), 11);
    keyboard.setKeyPressForNote(juce::KeyPress(','), 12);
    keyboard.setKeyPressForNote(juce::KeyPress('l'), 13);
    keyboard.setKeyPressForNote(juce::KeyPress('.'), 14);
    keyboard.setKeyPressForNote(juce::KeyPress(';'), 15);
    keyboard.setKeyPressForNote(juce::KeyPress('/'), 16);

    // top row
    keyboard.setKeyPressForNote(juce::KeyPress('q'), 12);
    keyboard.setKeyPressForNote(juce::KeyPress('2'), 13);
    keyboard.setKeyPressForNote(juce::KeyPress('w'), 14);
    keyboard.setKeyPressForNote(juce::KeyPress('3'), 15);
    keyboard.setKeyPressForNote(juce::KeyPress('e'), 16);
    keyboard.setKeyPressForNote(juce::KeyPress('r'), 17);
    keyboard.setKeyPressForNote(juce::KeyPress('5'), 18);
    keyboard.setKeyPressForNote(juce::KeyPress('t'), 19);
[...redacted...]

I expected that , and q would both trigger C+1, unfortunately it doesn’t work that way and only one key at a time can be assigned to a note. It would be great if that could be “fixed”.

I know it’s a year later but I figured out a workaround! I’m making an amiga-inspired sampler synth so having a two row ASCII note map was very important to me

In the component constructor I set up the basic keyboard component map as

    /* Clears previous ASCII-to-MIDI map for custom map */
    keyboardComponent.setAvailableRange(24, 108);
    keyboardComponent.clearKeyMappings();

    /* Creates first two octaves of ASCII-to-MIDI map, mapped to Protracker/Fasttracker ASCII MIDI map */
    /* First octave starts at key Z and ends at key M // Second octave starts at key Q and ends at key [ */
    int noteLower = 0;
    for (char c : "zsxdcvgbhnjmq2w3er5t6y7ui9o0p[")
        keyboardComponent.setKeyPressForNote({ c, 0, 0 }, noteLower++);

    /* JUCE ASCII map doesn't seem to capture keypresses for key = or \  so ] has to be 
       added separate in order for = and \ to be implemented in keypress function */
    keyboardComponent.setKeyPressForNote(juce::KeyPress(']'), 31);

    // Sets default C-note base octave to octave 5 (same as Fasttracker II default octave) //
    keyboardComponent.setKeyPressBaseOctave(numPadVal);

that sets up the majority of the notes except for the last five on the first row that will have the same notes as the first five on the second row. For those I did:

bool ControlComponent::keyPressed(const juce::KeyPress& k)
{   
    /* Changes ASCII map note base octave with function keys a la Fasttracker II */
    /* Any attempt at more "elegant" code worked fine in standalone but triggers DAW host key shortcuts when used as plugin :( */
    if (k.isKeyCode(juce::KeyPress::F1Key)) { numPadVal = 1; }
    if (k.isKeyCode(juce::KeyPress::F2Key)) { numPadVal = 2; }
    if (k.isKeyCode(juce::KeyPress::F3Key)) { numPadVal = 3; }
    if (k.isKeyCode(juce::KeyPress::F4Key)) { numPadVal = 4; }
    if (k.isKeyCode(juce::KeyPress::F5Key)) { numPadVal = 5; }
    if (k.isKeyCode(juce::KeyPress::F6Key)) { numPadVal = 6; }
    if (k.isKeyCode(juce::KeyPress::F7Key)) { numPadVal = 7; }

    keyboardComponent.setKeyPressBaseOctave(numPadVal);
    audioProcessor.setBaseOctave(numPadVal);

    /* JUCE only allows one ASCII key mapped to one note */
    /* This sends Note On MIDI events for note duplicates to maintain Protracker/Fastracker style map */
    /* m%Pressed bool is required to blocked repeated key presses after initial key press when key is held down */
    if (k.getTextCharacter() == ',' && !mCommaPressed) { handleExtraNoteOn(0); mCommaPressed = true; }
    if (k.getTextCharacter() == 'l' && !mLCharPressed) { handleExtraNoteOn(1); mLCharPressed = true; }
    if (k.getTextCharacter() == '.' && !mDotChPressed) { handleExtraNoteOn(2); mDotChPressed = true; }
    if (k.getTextCharacter() == ';' && !mColonPressed) { handleExtraNoteOn(3); mColonPressed = true; }
    if (k.getTextCharacter() == '/' && !mSlashPressed) { handleExtraNoteOn(4); mSlashPressed = true; }

    return true;
}

bool ControlComponent::keyStateChanged(const bool isKeyDown)
{
    /* sends a Note Off for extended ASCII map keys and resets m%Pressed bool to be used on next key press */
    /* ANDing key character and m%Pressed bool required to not send unwanted Note Off MIDI event to a key that may still be held down!!!! */ 
    if (!isKeyDown)
    {
        if (!juce::KeyPress::isKeyCurrentlyDown(',') && mCommaPressed) { handleExtraNoteOff(0); mCommaPressed = false; }
        if (!juce::KeyPress::isKeyCurrentlyDown('l') && mLCharPressed) { handleExtraNoteOff(1); mLCharPressed = false; }
        if (!juce::KeyPress::isKeyCurrentlyDown('.') && mDotChPressed) { handleExtraNoteOff(2); mDotChPressed = false; }
        if (!juce::KeyPress::isKeyCurrentlyDown(';') && mColonPressed) { handleExtraNoteOff(3); mColonPressed = false; }
        if (!juce::KeyPress::isKeyCurrentlyDown('/') && mSlashPressed) { handleExtraNoteOff(4); mSlashPressed = false; }
    }

    return true;
}

void ControlComponent::handleExtraNoteOn(const int note)
{
    /* Handles the Note On events for extended ASCII map */
    /* Blocks Note On event for extended map if second row keys of the same note are also held down */
    if(!audioProcessor.getKeyState().isNoteOn(1, (note + ((numPadVal + 1) * 12))))
        audioProcessor.getKeyState().noteOn(1, (note + ((numPadVal + 1) * 12)), 1.0f);
}

void ControlComponent::handleExtraNoteOff(const int note)
{
    /* Handles the Note Off events for extended ASCII map */
    audioProcessor.getKeyState().noteOff(1, (note + ((numPadVal + 1) * 12)), 0.0f);
}

This reads the keypress, send that keypress to the KeyState for a note on, blocks repeated “keypresses” from those keys being held down and then sends a note off one when that key is released. It also allows for octave switching with the first seven Function keys. Only thing I have buggy is the ‘=’ key and ‘' key. JUCE’s setKeyPressForNote() function doesn’t seem to read those for some reason and my implementation of extra notes only half works on those (they sometimes will release at other keys’ release)

1 Like

if you’re doing a standalone app, you can also write the function key octave switching as

if (k.getKeyCode() <= juce::KeyPress::F7Key && k.getKeyCode() >= juce::KeyPress::F1Key)
        numPadVal = k.getKeyCode() - juce::KeyPress::F1Key + 1;

Which is a H*LL of a lot cleaner and works great for standalone, but for some reason will pass your function key presses through to your plugin host when loaded as a vst T.T and that becomes a huge problem in ableton bc the function keys are shortcuts to much channels in ableton!

1 Like