| 
							- /*
 -   ==============================================================================
 - 
 -    This file is part of the JUCE library.
 -    Copyright (c) 2022 - Raw Material Software Limited
 - 
 -    JUCE is an open source library subject to commercial or open-source
 -    licensing.
 - 
 -    By using JUCE, you agree to the terms of both the JUCE 7 End-User License
 -    Agreement and JUCE Privacy Policy.
 - 
 -    End User License Agreement: www.juce.com/juce-7-licence
 -    Privacy Policy: www.juce.com/juce-privacy-policy
 - 
 -    Or: You may also use this code under the terms of the GPL v3 (see
 -    www.gnu.org/licenses).
 - 
 -    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
 -    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
 -    DISCLAIMED.
 - 
 -   ==============================================================================
 - */
 - 
 - namespace juce
 - {
 - 
 - //==============================================================================
 - MidiKeyboardComponent::MidiKeyboardComponent (MidiKeyboardState& stateToUse, Orientation orientationToUse)
 -     : KeyboardComponentBase (orientationToUse), state (stateToUse)
 - {
 -     state.addListener (this);
 - 
 -     // initialise with a default set of qwerty key-mappings..
 -     int note = 0;
 - 
 -     for (char c : "awsedftgyhujkolp;")
 -         setKeyPressForNote ({ c, 0, 0 }, note++);
 - 
 -     mouseOverNotes.insertMultiple (0, -1, 32);
 -     mouseDownNotes.insertMultiple (0, -1, 32);
 - 
 -     colourChanged();
 -     setWantsKeyboardFocus (true);
 - 
 -     startTimerHz (20);
 - }
 - 
 - MidiKeyboardComponent::~MidiKeyboardComponent()
 - {
 -     state.removeListener (this);
 - }
 - 
 - //==============================================================================
 - void MidiKeyboardComponent::setVelocity (float v, bool useMousePosition)
 - {
 -     velocity = v;
 -     useMousePositionForVelocity = useMousePosition;
 - }
 - 
 - //==============================================================================
 - void MidiKeyboardComponent::setMidiChannel (int midiChannelNumber)
 - {
 -     jassert (midiChannelNumber > 0 && midiChannelNumber <= 16);
 - 
 -     if (midiChannel != midiChannelNumber)
 -     {
 -         resetAnyKeysInUse();
 -         midiChannel = jlimit (1, 16, midiChannelNumber);
 -     }
 - }
 - 
 - void MidiKeyboardComponent::setMidiChannelsToDisplay (int midiChannelMask)
 - {
 -     midiInChannelMask = midiChannelMask;
 -     noPendingUpdates.store (false);
 - }
 - 
 - //==============================================================================
 - void MidiKeyboardComponent::clearKeyMappings()
 - {
 -     resetAnyKeysInUse();
 -     keyPressNotes.clear();
 -     keyPresses.clear();
 - }
 - 
 - void MidiKeyboardComponent::setKeyPressForNote (const KeyPress& key, int midiNoteOffsetFromC)
 - {
 -     removeKeyPressForNote (midiNoteOffsetFromC);
 - 
 -     keyPressNotes.add (midiNoteOffsetFromC);
 -     keyPresses.add (key);
 - }
 - 
 - void MidiKeyboardComponent::removeKeyPressForNote (int midiNoteOffsetFromC)
 - {
 -     for (int i = keyPressNotes.size(); --i >= 0;)
 -     {
 -         if (keyPressNotes.getUnchecked (i) == midiNoteOffsetFromC)
 -         {
 -             keyPressNotes.remove (i);
 -             keyPresses.remove (i);
 -         }
 -     }
 - }
 - 
 - void MidiKeyboardComponent::setKeyPressBaseOctave (int newOctaveNumber)
 - {
 -     jassert (newOctaveNumber >= 0 && newOctaveNumber <= 10);
 - 
 -     keyMappingOctave = newOctaveNumber;
 - }
 - 
 - //==============================================================================
 - void MidiKeyboardComponent::resetAnyKeysInUse()
 - {
 -     if (! keysPressed.isZero())
 -     {
 -         for (int i = 128; --i >= 0;)
 -             if (keysPressed[i])
 -                 state.noteOff (midiChannel, i, 0.0f);
 - 
 -         keysPressed.clear();
 -     }
 - 
 -     for (int i = mouseDownNotes.size(); --i >= 0;)
 -     {
 -         auto noteDown = mouseDownNotes.getUnchecked (i);
 - 
 -         if (noteDown >= 0)
 -         {
 -             state.noteOff (midiChannel, noteDown, 0.0f);
 -             mouseDownNotes.set (i, -1);
 -         }
 - 
 -         mouseOverNotes.set (i, -1);
 -     }
 - }
 - 
 - void MidiKeyboardComponent::updateNoteUnderMouse (const MouseEvent& e, bool isDown)
 - {
 -     updateNoteUnderMouse (e.getEventRelativeTo (this).position, isDown, e.source.getIndex());
 - }
 - 
 - void MidiKeyboardComponent::updateNoteUnderMouse (Point<float> pos, bool isDown, int fingerNum)
 - {
 -     const auto noteInfo = getNoteAndVelocityAtPosition (pos);
 -     const auto newNote = noteInfo.note;
 -     const auto oldNote = mouseOverNotes.getUnchecked (fingerNum);
 -     const auto oldNoteDown = mouseDownNotes.getUnchecked (fingerNum);
 -     const auto eventVelocity = useMousePositionForVelocity ? noteInfo.velocity * velocity : velocity;
 - 
 -     if (oldNote != newNote)
 -     {
 -         repaintNote (oldNote);
 -         repaintNote (newNote);
 -         mouseOverNotes.set (fingerNum, newNote);
 -     }
 - 
 -     if (isDown)
 -     {
 -         if (newNote != oldNoteDown)
 -         {
 -             if (oldNoteDown >= 0)
 -             {
 -                 mouseDownNotes.set (fingerNum, -1);
 - 
 -                 if (! mouseDownNotes.contains (oldNoteDown))
 -                     state.noteOff (midiChannel, oldNoteDown, eventVelocity);
 -             }
 - 
 -             if (newNote >= 0 && ! mouseDownNotes.contains (newNote))
 -             {
 -                 state.noteOn (midiChannel, newNote, eventVelocity);
 -                 mouseDownNotes.set (fingerNum, newNote);
 -             }
 -         }
 -     }
 -     else if (oldNoteDown >= 0)
 -     {
 -         mouseDownNotes.set (fingerNum, -1);
 - 
 -         if (! mouseDownNotes.contains (oldNoteDown))
 -             state.noteOff (midiChannel, oldNoteDown, eventVelocity);
 -     }
 - }
 - 
 - void MidiKeyboardComponent::repaintNote (int noteNum)
 - {
 -     if (getRangeStart() <= noteNum && noteNum <= getRangeEnd())
 -         repaint (getRectangleForKey (noteNum).getSmallestIntegerContainer());
 - }
 - 
 - 
 - void MidiKeyboardComponent::mouseMove (const MouseEvent& e)
 - {
 -     updateNoteUnderMouse (e, false);
 - }
 - 
 - void MidiKeyboardComponent::mouseDrag (const MouseEvent& e)
 - {
 -     auto newNote = getNoteAndVelocityAtPosition (e.position).note;
 - 
 -     if (newNote >= 0 && mouseDraggedToKey (newNote, e))
 -         updateNoteUnderMouse (e, true);
 - }
 - 
 - void MidiKeyboardComponent::mouseDown (const MouseEvent& e)
 - {
 -     auto newNote = getNoteAndVelocityAtPosition (e.position).note;
 - 
 -     if (newNote >= 0 && mouseDownOnKey (newNote, e))
 -         updateNoteUnderMouse (e, true);
 - }
 - 
 - void MidiKeyboardComponent::mouseUp (const MouseEvent& e)
 - {
 -     updateNoteUnderMouse (e, false);
 - 
 -     auto note = getNoteAndVelocityAtPosition (e.position).note;
 - 
 -     if (note >= 0)
 -         mouseUpOnKey (note, e);
 - }
 - 
 - void MidiKeyboardComponent::mouseEnter (const MouseEvent& e)
 - {
 -     updateNoteUnderMouse (e, false);
 - }
 - 
 - void MidiKeyboardComponent::mouseExit (const MouseEvent& e)
 - {
 -     updateNoteUnderMouse (e, false);
 - }
 - 
 - void MidiKeyboardComponent::timerCallback()
 - {
 -     if (noPendingUpdates.exchange (true))
 -         return;
 - 
 -     for (auto i = getRangeStart(); i <= getRangeEnd(); ++i)
 -     {
 -         const auto isOn = state.isNoteOnForChannels (midiInChannelMask, i);
 - 
 -         if (keysCurrentlyDrawnDown[i] != isOn)
 -         {
 -             keysCurrentlyDrawnDown.setBit (i, isOn);
 -             repaintNote (i);
 -         }
 -     }
 - }
 - 
 - bool MidiKeyboardComponent::keyStateChanged (bool /*isKeyDown*/)
 - {
 -     bool keyPressUsed = false;
 - 
 -     for (int i = keyPresses.size(); --i >= 0;)
 -     {
 -         auto note = 12 * keyMappingOctave + keyPressNotes.getUnchecked (i);
 - 
 -         if (keyPresses.getReference(i).isCurrentlyDown())
 -         {
 -             if (! keysPressed[note])
 -             {
 -                 keysPressed.setBit (note);
 -                 state.noteOn (midiChannel, note, velocity);
 -                 keyPressUsed = true;
 -             }
 -         }
 -         else
 -         {
 -             if (keysPressed[note])
 -             {
 -                 keysPressed.clearBit (note);
 -                 state.noteOff (midiChannel, note, 0.0f);
 -                 keyPressUsed = true;
 -             }
 -         }
 -     }
 - 
 -     return keyPressUsed;
 - }
 - 
 - bool MidiKeyboardComponent::keyPressed (const KeyPress& key)
 - {
 -     return keyPresses.contains (key);
 - }
 - 
 - void MidiKeyboardComponent::focusLost (FocusChangeType)
 - {
 -     resetAnyKeysInUse();
 - }
 - 
 - //==============================================================================
 - void MidiKeyboardComponent::drawKeyboardBackground (Graphics& g, Rectangle<float> area)
 - {
 -     g.fillAll (findColour (whiteNoteColourId));
 - 
 -     auto width = area.getWidth();
 -     auto height = area.getHeight();
 -     auto currentOrientation = getOrientation();
 -     Point<float> shadowGradientStart, shadowGradientEnd;
 - 
 -     if (currentOrientation == verticalKeyboardFacingLeft)
 -     {
 -         shadowGradientStart.x = width - 1.0f;
 -         shadowGradientEnd.x   = width - 5.0f;
 -     }
 -     else if (currentOrientation == verticalKeyboardFacingRight)
 -     {
 -         shadowGradientEnd.x = 5.0f;
 -     }
 -     else
 -     {
 -         shadowGradientEnd.y = 5.0f;
 -     }
 - 
 -     auto keyboardWidth = getRectangleForKey (getRangeEnd()).getRight();
 -     auto shadowColour = findColour (shadowColourId);
 - 
 -     if (! shadowColour.isTransparent())
 -     {
 -         g.setGradientFill ({ shadowColour, shadowGradientStart,
 -                              shadowColour.withAlpha (0.0f), shadowGradientEnd,
 -                              false });
 - 
 -         switch (currentOrientation)
 -         {
 -             case horizontalKeyboard:            g.fillRect (0.0f, 0.0f, keyboardWidth, 5.0f); break;
 -             case verticalKeyboardFacingLeft:    g.fillRect (width - 5.0f, 0.0f, 5.0f, keyboardWidth); break;
 -             case verticalKeyboardFacingRight:   g.fillRect (0.0f, 0.0f, 5.0f, keyboardWidth); break;
 -             default: break;
 -         }
 -     }
 - 
 -     auto lineColour = findColour (keySeparatorLineColourId);
 - 
 -     if (! lineColour.isTransparent())
 -     {
 -         g.setColour (lineColour);
 - 
 -         switch (currentOrientation)
 -         {
 -             case horizontalKeyboard:            g.fillRect (0.0f, height - 1.0f, keyboardWidth, 1.0f); break;
 -             case verticalKeyboardFacingLeft:    g.fillRect (0.0f, 0.0f, 1.0f, keyboardWidth); break;
 -             case verticalKeyboardFacingRight:   g.fillRect (width - 1.0f, 0.0f, 1.0f, keyboardWidth); break;
 -             default: break;
 -         }
 -     }
 - }
 - 
 - void MidiKeyboardComponent::drawWhiteNote (int midiNoteNumber, Graphics& g, Rectangle<float> area,
 -                                            bool isDown, bool isOver, Colour lineColour, Colour textColour)
 - {
 -     auto c = Colours::transparentWhite;
 - 
 -     if (isDown)  c = findColour (keyDownOverlayColourId);
 -     if (isOver)  c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId));
 - 
 -     g.setColour (c);
 -     g.fillRect (area);
 - 
 -     const auto currentOrientation = getOrientation();
 - 
 -     auto text = getWhiteNoteText (midiNoteNumber);
 - 
 -     if (text.isNotEmpty())
 -     {
 -         auto fontHeight = jmin (12.0f, getKeyWidth() * 0.9f);
 - 
 -         g.setColour (textColour);
 -         g.setFont (Font (fontHeight).withHorizontalScale (0.8f));
 - 
 -         switch (currentOrientation)
 -         {
 -             case horizontalKeyboard:            g.drawText (text, area.withTrimmedLeft (1.0f).withTrimmedBottom (2.0f), Justification::centredBottom, false); break;
 -             case verticalKeyboardFacingLeft:    g.drawText (text, area.reduced (2.0f), Justification::centredLeft,   false); break;
 -             case verticalKeyboardFacingRight:   g.drawText (text, area.reduced (2.0f), Justification::centredRight,  false); break;
 -             default: break;
 -         }
 -     }
 - 
 -     if (! lineColour.isTransparent())
 -     {
 -         g.setColour (lineColour);
 - 
 -         switch (currentOrientation)
 -         {
 -             case horizontalKeyboard:            g.fillRect (area.withWidth (1.0f)); break;
 -             case verticalKeyboardFacingLeft:    g.fillRect (area.withHeight (1.0f)); break;
 -             case verticalKeyboardFacingRight:   g.fillRect (area.removeFromBottom (1.0f)); break;
 -             default: break;
 -         }
 - 
 -         if (midiNoteNumber == getRangeEnd())
 -         {
 -             switch (currentOrientation)
 -             {
 -                 case horizontalKeyboard:            g.fillRect (area.expanded (1.0f, 0).removeFromRight (1.0f)); break;
 -                 case verticalKeyboardFacingLeft:    g.fillRect (area.expanded (0, 1.0f).removeFromBottom (1.0f)); break;
 -                 case verticalKeyboardFacingRight:   g.fillRect (area.expanded (0, 1.0f).removeFromTop (1.0f)); break;
 -                 default: break;
 -             }
 -         }
 -     }
 - }
 - 
 - void MidiKeyboardComponent::drawBlackNote (int /*midiNoteNumber*/, Graphics& g, Rectangle<float> area,
 -                                            bool isDown, bool isOver, Colour noteFillColour)
 - {
 -     auto c = noteFillColour;
 - 
 -     if (isDown)  c = c.overlaidWith (findColour (keyDownOverlayColourId));
 -     if (isOver)  c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId));
 - 
 -     g.setColour (c);
 -     g.fillRect (area);
 - 
 -     if (isDown)
 -     {
 -         g.setColour (noteFillColour);
 -         g.drawRect (area);
 -     }
 -     else
 -     {
 -         g.setColour (c.brighter());
 -         auto sideIndent = 1.0f / 8.0f;
 -         auto topIndent = 7.0f / 8.0f;
 -         auto w = area.getWidth();
 -         auto h = area.getHeight();
 - 
 -         switch (getOrientation())
 -         {
 -             case horizontalKeyboard:            g.fillRect (area.reduced (w * sideIndent, 0).removeFromTop   (h * topIndent)); break;
 -             case verticalKeyboardFacingLeft:    g.fillRect (area.reduced (0, h * sideIndent).removeFromRight (w * topIndent)); break;
 -             case verticalKeyboardFacingRight:   g.fillRect (area.reduced (0, h * sideIndent).removeFromLeft  (w * topIndent)); break;
 -             default: break;
 -         }
 -     }
 - }
 - 
 - String MidiKeyboardComponent::getWhiteNoteText (int midiNoteNumber)
 - {
 -     if (midiNoteNumber % 12 == 0)
 -         return MidiMessage::getMidiNoteName (midiNoteNumber, true, true, getOctaveForMiddleC());
 - 
 -     return {};
 - }
 - 
 - void MidiKeyboardComponent::colourChanged()
 - {
 -     setOpaque (findColour (whiteNoteColourId).isOpaque());
 -     repaint();
 - }
 - 
 - //==============================================================================
 - void MidiKeyboardComponent::drawWhiteKey (int midiNoteNumber, Graphics& g, Rectangle<float> area)
 - {
 -     drawWhiteNote (midiNoteNumber, g, area, state.isNoteOnForChannels (midiInChannelMask, midiNoteNumber),
 -                    mouseOverNotes.contains (midiNoteNumber), findColour (keySeparatorLineColourId), findColour (textLabelColourId));
 - }
 - 
 - void MidiKeyboardComponent::drawBlackKey (int midiNoteNumber, Graphics& g, Rectangle<float> area)
 - {
 -     drawBlackNote (midiNoteNumber, g, area, state.isNoteOnForChannels (midiInChannelMask, midiNoteNumber),
 -                    mouseOverNotes.contains (midiNoteNumber), findColour (blackNoteColourId));
 - }
 - 
 - //==============================================================================
 - void MidiKeyboardComponent::handleNoteOn (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/, float /*velocity*/)
 - {
 -     noPendingUpdates.store (false);
 - }
 - 
 - void MidiKeyboardComponent::handleNoteOff (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/, float /*velocity*/)
 - {
 -     noPendingUpdates.store (false);
 - }
 - 
 - } // namespace juce
 
 
  |