text: /*
    ==============================================================================
  
     This file is part of the JUCE library - "Jules' Utility Class Extensions"
     Copyright 2004-11 by Raw Material Software Ltd.
  
    ------------------------------------------------------------------------------
  
     JUCE can be redistributed and/or modified under the terms of the GNU General
     Public License (Version 2), as published by the Free Software Foundation.
     A copy of the license is included in the JUCE distribution, or can be found
     online at www.gnu.org/licenses.
  
     JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
     WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
  
    ------------------------------------------------------------------------------
  
     To release a closed-source product which uses JUCE, commercial licenses are
     available: visit www.rawmaterialsoftware.com/juce for more information.
  
    ==============================================================================
  */
  
  class MidiKeyboardUpDownButton  : public Button
  {
  public:
      MidiKeyboardUpDownButton (MidiKeyboardComponent& owner_, const int delta_)
          : Button (String::empty),
            owner (owner_),
            delta (delta_)
      {
          setOpaque (true);
      }
  
      void clicked()
      {
          int note = owner.getLowestVisibleKey();
  
          if (delta < 0)
              note = (note - 1) / 12;
          else
              note = note / 12 + 1;
  
          owner.setLowestVisibleKey (note * 12);
      }
  
      void paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown)
      {
          owner.drawUpDownButton (g, getWidth(), getHeight(),
                                  isMouseOverButton, isButtonDown,
                                  delta > 0);
      }
  
  private:
      MidiKeyboardComponent& owner;
      const int delta;
  
      JUCE_DECLARE_NON_COPYABLE (MidiKeyboardUpDownButton);
  };
  
  //==============================================================================
  MidiKeyboardComponent::MidiKeyboardComponent (MidiKeyboardState& state_,
                                                const Orientation orientation_)
      : state (state_),
        xOffset (0),
        blackNoteLength (1),
        keyWidth (16.0f),
        orientation (orientation_),
        midiChannel (1),
        midiInChannelMask (0xffff),
        velocity (1.0f),
        shouldCheckState (false),
        rangeStart (0),
        rangeEnd (127),
        firstKey (12 * 4.0f),
        canScroll (true),
        useMousePositionForVelocity (true),
        shouldCheckMousePos (false),
        keyMappingOctave (6),
        octaveNumForMiddleC (3)
  {
      addChildComponent (scrollDown = new MidiKeyboardUpDownButton (*this, -1));
      addChildComponent (scrollUp   = new MidiKeyboardUpDownButton (*this, 1));
  
      // initialise with a default set of querty key-mappings..
      const char* const keymap = "awsedftgyhujkolp;";
  
      for (int i = 0; keymap[i] != 0; ++i)
          setKeyPressForNote (KeyPress (keymap[i], 0, 0), i);
  
      const int numSources = Desktop::getInstance().getNumMouseSources();
      mouseOverNotes.insertMultiple (0, -1, numSources);
      mouseDownNotes.insertMultiple (0, -1, numSources);
  
      setOpaque (true);
      setWantsKeyboardFocus (true);
  
      state.addListener (this);
  
      startTimer (1000 / 20);
  }
  
  MidiKeyboardComponent::~MidiKeyboardComponent()
  {
      state.removeListener (this);
  }
  
  //==============================================================================
  void MidiKeyboardComponent::setKeyWidth (const float widthInPixels)
  {
      keyWidth = widthInPixels;
      resized();
  }
  
  void MidiKeyboardComponent::setOrientation (const Orientation newOrientation)
  {
      if (orientation != newOrientation)
      {
          orientation = newOrientation;
          resized();
      }
  }
  
  void MidiKeyboardComponent::setAvailableRange (const int lowestNote,
                                                 const int highestNote)
  {
      jassert (lowestNote >= 0 && lowestNote <= 127);
      jassert (highestNote >= 0 && highestNote <= 127);
      jassert (lowestNote <= highestNote);
  
      if (rangeStart != lowestNote || rangeEnd != highestNote)
      {
          rangeStart = jlimit (0, 127, lowestNote);
          rangeEnd = jlimit (0, 127, highestNote);
          firstKey = jlimit ((float) rangeStart, (float) rangeEnd, firstKey);
          resized();
      }
  }
  
  void MidiKeyboardComponent::setLowestVisibleKey (int noteNumber)
  {
      setLowestVisibleKeyFloat ((float) noteNumber);
  }
  
  void MidiKeyboardComponent::setLowestVisibleKeyFloat (float noteNumber)
  {
      noteNumber = jlimit ((float) rangeStart, (float) rangeEnd, noteNumber);
  
      if (noteNumber != firstKey)
      {
          const bool hasMoved = (((int) firstKey) != (int) noteNumber);
          firstKey = noteNumber;
  
          if (hasMoved)
          {
              sendChangeMessage();
              resized();
          }
      }
  }
  
  void MidiKeyboardComponent::setScrollButtonsVisible (const bool canScroll_)
  {
      if (canScroll != canScroll_)
      {
          canScroll = canScroll_;
          resized();
      }
  }
  
  void MidiKeyboardComponent::colourChanged()
  {
      repaint();
  }
  
  //==============================================================================
  void MidiKeyboardComponent::setMidiChannel (const int midiChannelNumber)
  {
      jassert (midiChannelNumber > 0 && midiChannelNumber <= 16);
  
      if (midiChannel != midiChannelNumber)
      {
          resetAnyKeysInUse();
          midiChannel = jlimit (1, 16, midiChannelNumber);
      }
  }
  
  void MidiKeyboardComponent::setMidiChannelsToDisplay (const int midiChannelMask)
  {
      midiInChannelMask = midiChannelMask;
      shouldCheckState = true;
  }
  
  void MidiKeyboardComponent::setVelocity (const float velocity_, const bool useMousePositionForVelocity_)
  {
      velocity = jlimit (0.0f, 1.0f, velocity_);
      useMousePositionForVelocity = useMousePositionForVelocity_;
  }
  
  //==============================================================================
  void MidiKeyboardComponent::getKeyPosition (int midiNoteNumber, const float keyWidth_, int& x, int& w) const
  {
      jassert (midiNoteNumber >= 0 && midiNoteNumber < 128);
  
      static const float blackNoteWidth = 0.7f;
  
      static const float notePos[] = { 0.0f, 1 - blackNoteWidth * 0.6f,
                                       1.0f, 2 - blackNoteWidth * 0.4f,
                                       2.0f,
                                       3.0f, 4 - blackNoteWidth * 0.7f,
                                       4.0f, 5 - blackNoteWidth * 0.5f,
                                       5.0f, 6 - blackNoteWidth * 0.3f,
                                       6.0f };
  
      static const float widths[] = { 1.0f, blackNoteWidth,
                                      1.0f, blackNoteWidth,
                                      1.0f,
                                      1.0f, blackNoteWidth,
                                      1.0f, blackNoteWidth,
                                      1.0f, blackNoteWidth,
                                      1.0f };
  
      const int octave = midiNoteNumber / 12;
      const int note = midiNoteNumber % 12;
  
      x = roundToInt (octave * 7.0f * keyWidth_ + notePos [note] * keyWidth_);
      w = roundToInt (widths [note] * keyWidth_);
  }
  
  void MidiKeyboardComponent::getKeyPos (int midiNoteNumber, int& x, int& w) const
  {
      getKeyPosition (midiNoteNumber, keyWidth, x, w);
  
      int rx, rw;
      getKeyPosition (rangeStart, keyWidth, rx, rw);
  
      x -= xOffset + rx;
  }
  
  Rectangle<int> MidiKeyboardComponent::getWhiteNotePos (int noteNum) const
  {
      int x, w;
      getKeyPos (noteNum, x, w);
      Rectangle<int> pos;
  
      switch (orientation)
      {
          case horizontalKeyboard:            pos.setBounds (x, 0, w, getHeight()); break;
          case verticalKeyboardFacingLeft:    pos.setBounds (0, x, getWidth(), w); break;
          case verticalKeyboardFacingRight:   pos.setBounds (0, getHeight() - x - w, getWidth(), w); break;
          default: break;
      }
  
      return pos;
  }
  
  int MidiKeyboardComponent::getKeyStartPosition (const int midiNoteNumber) const
  {
      int x, y;
      getKeyPos (midiNoteNumber, x, y);
      return x;
  }
  
  const uint8 MidiKeyboardComponent::whiteNotes[] = { 0, 2, 4, 5, 7, 9, 11 };
  const uint8 MidiKeyboardComponent::blackNotes[] = { 1, 3, 6, 8, 10 };
  
  int MidiKeyboardComponent::xyToNote (const Point<int>& pos, float& mousePositionVelocity)
  {
      if (! reallyContains (pos, false))
          return -1;
  
      Point<int> p (pos);
  
      if (orientation != horizontalKeyboard)
      {
          p = Point<int> (p.y, p.x);
  
          if (orientation == verticalKeyboardFacingLeft)
              p = Point<int> (p.x, getWidth() - p.y);
          else
              p = Point<int> (getHeight() - p.x, p.y);
      }
  
      return remappedXYToNote (p + Point<int> (xOffset, 0), mousePositionVelocity);
  }
  
  int MidiKeyboardComponent::remappedXYToNote (const Point<int>& pos, float& mousePositionVelocity) const
  {
      if (pos.getY() < blackNoteLength)
      {
          for (int octaveStart = 12 * (rangeStart / 12); octaveStart <= rangeEnd; octaveStart += 12)
          {
              for (int i = 0; i < 5; ++i)
              {
                  const int note = octaveStart + blackNotes [i];
  
                  if (note >= rangeStart && note <= rangeEnd)
                  {
                      int kx, kw;
                      getKeyPos (note, kx, kw);
                      kx += xOffset;
  
                      if (pos.x >= kx && pos.x < kx + kw)
                      {
                          mousePositionVelocity = pos.y / (float) blackNoteLength;
                          return note;
                      }
                  }
              }
          }
      }
  
      for (int octaveStart = 12 * (rangeStart / 12); octaveStart <= rangeEnd; octaveStart += 12)
      {
          for (int i = 0; i < 7; ++i)
          {
              const int note = octaveStart + whiteNotes [i];
  
              if (note >= rangeStart && note <= rangeEnd)
              {
                  int kx, kw;
                  getKeyPos (note, kx, kw);
                  kx += xOffset;
  
                  if (pos.x >= kx && pos.x < kx + kw)
                  {
                      const int whiteNoteLength = (orientation == horizontalKeyboard) ? getHeight() : getWidth();
                      mousePositionVelocity = pos.y / (float) whiteNoteLength;
                      return note;
                  }
              }
          }
      }
  
      mousePositionVelocity = 0;
      return -1;
  }
  
  //==============================================================================
  void MidiKeyboardComponent::repaintNote (const int noteNum)
  {
      if (noteNum >= rangeStart && noteNum <= rangeEnd)
          repaint (getWhiteNotePos (noteNum));
  }
  
  void MidiKeyboardComponent::paint (Graphics& g)
  {
      g.fillAll (Colours::white.overlaidWith (findColour (whiteNoteColourId)));
  
      const Colour lineColour (findColour (keySeparatorLineColourId));
      const Colour textColour (findColour (textLabelColourId));
  
      int x, w, octave;
  
      for (octave = 0; octave < 128; octave += 12)
      {
          for (int white = 0; white < 7; ++white)
          {
              const int noteNum = octave + whiteNotes [white];
  
              if (noteNum >= rangeStart && noteNum <= rangeEnd)
              {
                  const Rectangle<int> pos (getWhiteNotePos (noteNum));
  
                  drawWhiteNote (noteNum, g, pos.getX(), pos.getY(), pos.getWidth(), pos.getHeight(),
                                 state.isNoteOnForChannels (midiInChannelMask, noteNum),
                                 mouseOverNotes.contains (noteNum), lineColour, textColour);
              }
          }
      }
  
      float x1 = 0.0f, y1 = 0.0f, x2 = 0.0f, y2 = 0.0f;
  
      if (orientation == verticalKeyboardFacingLeft)
      {
          x1 = getWidth() - 1.0f;
          x2 = getWidth() - 5.0f;
      }
      else if (orientation == verticalKeyboardFacingRight)
          x2 = 5.0f;
      else
          y2 = 5.0f;
  
      g.setGradientFill (ColourGradient (Colours::black.withAlpha (0.3f), x1, y1,
                                         Colours::transparentBlack, x2, y2, false));
  
      getKeyPos (rangeEnd, x, w);
      x += w;
  
      switch (orientation)
      {
          case horizontalKeyboard:            g.fillRect (0, 0, x, 5); break;
          case verticalKeyboardFacingLeft:    g.fillRect (getWidth() - 5, 0, 5, x); break;
          case verticalKeyboardFacingRight:   g.fillRect (0, 0, 5, x); break;
          default: break;
      }
  
      g.setColour (lineColour);
  
      switch (orientation)
      {
          case horizontalKeyboard:            g.fillRect (0, getHeight() - 1, x, 1); break;
          case verticalKeyboardFacingLeft:    g.fillRect (0, 0, 1, x); break;
          case verticalKeyboardFacingRight:   g.fillRect (getWidth() - 1, 0, 1, x); break;
          default: break;
      }
  
      const Colour blackNoteColour (findColour (blackNoteColourId));
  
      for (octave = 0; octave < 128; octave += 12)
      {
          for (int black = 0; black < 5; ++black)
          {
              const int noteNum = octave + blackNotes [black];
  
              if (noteNum >= rangeStart && noteNum <= rangeEnd)
              {
                  getKeyPos (noteNum, x, w);
                  Rectangle<int> pos;
  
                  switch (orientation)
                  {
                      case horizontalKeyboard:            pos.setBounds (x, 0, w, blackNoteLength); break;
                      case verticalKeyboardFacingLeft:    pos.setBounds (getWidth() - blackNoteLength, x, blackNoteLength, w); break;
                      case verticalKeyboardFacingRight:   pos.setBounds (0, getHeight() - x - w, blackNoteLength, w); break;
                      default: break;
                  }
  
                  drawBlackNote (noteNum, g, pos.getX(), pos.getY(), pos.getWidth(), pos.getHeight(),
                                 state.isNoteOnForChannels (midiInChannelMask, noteNum),
                                 mouseOverNotes.contains (noteNum), blackNoteColour);
              }
          }
      }
  }
  
  void MidiKeyboardComponent::drawWhiteNote (int midiNoteNumber,
                                             Graphics& g, int x, int y, int w, int h,
                                             bool isDown, bool isOver,
                                             const Colour& lineColour,
                                             const Colour& textColour)
  {
      Colour c (Colours::transparentWhite);
  
      if (isDown)
          c = findColour (keyDownOverlayColourId);
  
      if (isOver)
          c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId));
  
      g.setColour (c);
      g.fillRect (x, y, w, h);
  
      const String text (getWhiteNoteText (midiNoteNumber));
  
      if (text.isNotEmpty())
      {
          g.setColour (textColour);
          g.setFont (Font (jmin (12.0f, keyWidth * 0.9f)).withHorizontalScale (0.8f));
  
          Justification justification (Justification::centredBottom);
  
          if (orientation == verticalKeyboardFacingLeft)
              justification = Justification::centredLeft;
          else if (orientation == verticalKeyboardFacingRight)
              justification = Justification::centredRight;
  
          g.drawFittedText (text, x + 2, y + 2, w - 4, h - 4, justification, 1);
      }
  
      g.setColour (lineColour);
  
      switch (orientation)
      {
          case horizontalKeyboard:            g.fillRect (x, y, 1, h); break;
          case verticalKeyboardFacingLeft:    g.fillRect (x, y, w, 1); break;
          case verticalKeyboardFacingRight:   g.fillRect (x, y + h - 1, w, 1); break;
          default: break;
      }
  
      if (midiNoteNumber == rangeEnd)
      {
          switch (orientation)
          {
              case horizontalKeyboard:            g.fillRect (x + w, y, 1, h); break;
              case verticalKeyboardFacingLeft:    g.fillRect (x, y + h, w, 1); break;
              case verticalKeyboardFacingRight:   g.fillRect (x, y - 1, w, 1); break;
              default: break;
          }
      }
  }
  
  void MidiKeyboardComponent::drawBlackNote (int /*midiNoteNumber*/,
                                             Graphics& g, int x, int y, int w, int h,
                                             bool isDown, bool isOver,
                                             const Colour& noteFillColour)
  {
      Colour c (noteFillColour);
  
      if (isDown)
          c = c.overlaidWith (findColour (keyDownOverlayColourId));
  
      if (isOver)
          c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId));
  
      g.setColour (c);
      g.fillRect (x, y, w, h);
  
      if (isDown)
      {
          g.setColour (noteFillColour);
          g.drawRect (x, y, w, h);
      }
      else
      {
          g.setColour (c.brighter());
          const int xIndent = jmax (1, jmin (w, h) / 8);
  
          switch (orientation)
          {
              case horizontalKeyboard:            g.fillRect (x + xIndent, y, w - xIndent * 2, 7 * h / 8); break;
              case verticalKeyboardFacingLeft:    g.fillRect (x + w / 8, y + xIndent, w - w / 8, h - xIndent * 2); break;
              case verticalKeyboardFacingRight:   g.fillRect (x, y + xIndent, 7 * w / 8, h - xIndent * 2); break;
              default: break;
          }
      }
  }
  
  void MidiKeyboardComponent::setOctaveForMiddleC (const int octaveNumForMiddleC_)
  {
      octaveNumForMiddleC = octaveNumForMiddleC_;
      repaint();
  }
  
  String MidiKeyboardComponent::getWhiteNoteText (const int midiNoteNumber)
  {
      if (keyWidth > 14.0f && midiNoteNumber % 12 == 0)
          return MidiMessage::getMidiNoteName (midiNoteNumber, true, true, octaveNumForMiddleC);
  
      return String::empty;
  }
  
  void MidiKeyboardComponent::drawUpDownButton (Graphics& g, int w, int h,
                                                const bool isMouseOver_,
                                                const bool isButtonDown,
                                                const bool movesOctavesUp)
  {
      g.fillAll (findColour (upDownButtonBackgroundColourId));
  
      float angle;
  
      switch (orientation)
      {
          case horizontalKeyboard:            angle = movesOctavesUp ? 0.0f  : 0.5f;  break;
          case verticalKeyboardFacingLeft:    angle = movesOctavesUp ? 0.25f : 0.75f; break;
          case verticalKeyboardFacingRight:   angle = movesOctavesUp ? 0.75f : 0.25f; break;
          default:                            jassertfalse; angle = 0; break;
      }
  
      Path path;
      path.addTriangle (0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f);
      path.applyTransform (AffineTransform::rotation (float_Pi * 2.0f * angle, 0.5f, 0.5f));
  
      g.setColour (findColour (upDownButtonArrowColourId)
                    .withAlpha (isButtonDown ? 1.0f : (isMouseOver_ ? 0.6f : 0.4f)));
  
      g.fillPath (path, path.getTransformToScaleToFit (1.0f, 1.0f, w - 2.0f, h - 2.0f, true));
  }
  
  void MidiKeyboardComponent::resized()
  {
      int w = getWidth();
      int h = getHeight();
  
      if (w > 0 && h > 0)
      {
          if (orientation != horizontalKeyboard)
              std::swap (w, h);
  
          blackNoteLength = roundToInt (h * 0.7f);
  
          int kx2, kw2;
          getKeyPos (rangeEnd, kx2, kw2);
  
          kx2 += kw2;
  
          if ((int) firstKey != rangeStart)
          {
              int kx1, kw1;
              getKeyPos (rangeStart, kx1, kw1);
  
              if (kx2 - kx1 <= w)
              {
                  firstKey = (float) rangeStart;
                  sendChangeMessage();
                  repaint();
              }
          }
  
          const bool showScrollButtons = canScroll && (((int) firstKey) > rangeStart || kx2 > w + xOffset * 2);
  
          scrollDown->setVisible (showScrollButtons);
          scrollUp->setVisible (showScrollButtons);
  
          xOffset = 0;
  
          if (showScrollButtons)
          {
              const int scrollButtonW = jmin (12, w / 2);
  
              if (orientation == horizontalKeyboard)
              {
                  scrollDown->setBounds (0, 0, scrollButtonW, getHeight());
                  scrollUp->setBounds (getWidth() - scrollButtonW, 0, scrollButtonW, getHeight());
              }
              else if (orientation == verticalKeyboardFacingLeft)
              {
                  scrollDown->setBounds (0, 0, getWidth(), scrollButtonW);
                  scrollUp->setBounds (0, getHeight() - scrollButtonW, getWidth(), scrollButtonW);
              }
              else
              {
                  scrollDown->setBounds (0, getHeight() - scrollButtonW, getWidth(), scrollButtonW);
                  scrollUp->setBounds (0, 0, getWidth(), scrollButtonW);
              }
  
              int endOfLastKey, kw;
              getKeyPos (rangeEnd, endOfLastKey, kw);
              endOfLastKey += kw;
  
              float mousePositionVelocity;
              const int spaceAvailable = w - scrollButtonW * 2;
              const int lastStartKey = remappedXYToNote (Point<int> (endOfLastKey - spaceAvailable, 0), mousePositionVelocity) + 1;
  
              if (lastStartKey >= 0 && ((int) firstKey) > lastStartKey)
              {
                  firstKey = (float) jlimit (rangeStart, rangeEnd, lastStartKey);
                  sendChangeMessage();
              }
  
              int newOffset = 0;
              getKeyPos (((int) firstKey), newOffset, kw);
              xOffset = newOffset - scrollButtonW;
          }
          else
          {
              firstKey = (float) rangeStart;
          }
  
          repaint();
      }
  }
  
  //==============================================================================
  void MidiKeyboardComponent::handleNoteOn (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/, float /*velocity*/)
  {
      shouldCheckState = true; // (probably being called from the audio thread, so avoid blocking in here)
  }
  
  void MidiKeyboardComponent::handleNoteOff (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/)
  {
      shouldCheckState = true; // (probably being called from the audio thread, so avoid blocking in here)
  }
  
  //==============================================================================
  void MidiKeyboardComponent::resetAnyKeysInUse()
  {
      if (keysPressed.countNumberOfSetBits() > 0 || mouseDownNotes.size() > 0)
      {
          state.allNotesOff (midiChannel);
          keysPressed.clear();
  
          for (int i = mouseDownNotes.size(); --i >= 0;)
          {
              mouseDownNotes.set (i, -1);
              mouseOverNotes.set (i, -1);
          }
      }
  }
  
  void MidiKeyboardComponent::updateNoteUnderMouse (const MouseEvent& e, bool isDown)
  {
      updateNoteUnderMouse (e.getPosition(), isDown, e.source.getIndex());
  }
  
  void MidiKeyboardComponent::updateNoteUnderMouse (const Point<int>& pos, bool isDown, int fingerNum)
  {
      float mousePositionVelocity = 0.0f;
      const int newNote = xyToNote (pos, mousePositionVelocity);
      const int oldNote = mouseOverNotes.getUnchecked (fingerNum);
  
      if (oldNote != newNote)
      {
          repaintNote (oldNote);
          repaintNote (newNote);
          mouseOverNotes.set (fingerNum, newNote);
      }
  
      int oldNoteDown = mouseDownNotes.getUnchecked (fingerNum);
  
      if (isDown)
      {
          if (newNote != oldNoteDown)
          {
              if (oldNoteDown >= 0)
              {
                  mouseDownNotes.set (fingerNum, -1);
  
                  if (! mouseDownNotes.contains (oldNoteDown))
                      state.noteOff (midiChannel, oldNoteDown);
              }
  
              if (newNote >= 0)
              {
                  if (! useMousePositionForVelocity)
                      mousePositionVelocity = 1.0f;
  
                  state.noteOn (midiChannel, newNote, mousePositionVelocity * velocity);
                  mouseDownNotes.set (fingerNum, newNote);
              }
          }
      }
      else if (oldNoteDown >= 0)
      {
          mouseDownNotes.set (fingerNum, -1);
  
          if (! mouseDownNotes.contains (oldNoteDown))
              state.noteOff (midiChannel, oldNoteDown);
      }
  }
  
  void MidiKeyboardComponent::mouseMove (const MouseEvent& e)
  {
      updateNoteUnderMouse (e, false);
      shouldCheckMousePos = false;
  }
  
  void MidiKeyboardComponent::mouseDrag (const MouseEvent& e)
  {
      float mousePositionVelocity;
      const int newNote = xyToNote (e.getPosition(), mousePositionVelocity);
  
      if (newNote >= 0)
          mouseDraggedToKey (newNote, e);
  
      updateNoteUnderMouse (e, true);
  }
  
  bool MidiKeyboardComponent::mouseDownOnKey (int /*midiNoteNumber*/, const MouseEvent&)
  {
      return true;
  }
  
  void MidiKeyboardComponent::mouseDraggedToKey (int /*midiNoteNumber*/, const MouseEvent&)
  {
  }
  
  void MidiKeyboardComponent::mouseDown (const MouseEvent& e)
  {
      float mousePositionVelocity;
      const int newNote = xyToNote (e.getPosition(), mousePositionVelocity);
  
      if (newNote >= 0 && mouseDownOnKey (newNote, e))
      {
          updateNoteUnderMouse (e, true);
          shouldCheckMousePos = true;
      }
  }
  
  void MidiKeyboardComponent::mouseUp (const MouseEvent& e)
  {
      updateNoteUnderMouse (e, false);
      shouldCheckMousePos = false;
  }
  
  void MidiKeyboardComponent::mouseEnter (const MouseEvent& e)
  {
      updateNoteUnderMouse (e, false);
  }
  
  void MidiKeyboardComponent::mouseExit (const MouseEvent& e)
  {
      updateNoteUnderMouse (e, false);
  }
  
  void MidiKeyboardComponent::mouseWheelMove (const MouseEvent&, const MouseWheelDetails& wheel)
  {
      const float amount = (orientation == horizontalKeyboard && wheel.deltaX != 0)
                              ? wheel.deltaX : (orientation == verticalKeyboardFacingLeft ? wheel.deltaY
                                                                                           : -wheel.deltaY);
  
      setLowestVisibleKeyFloat (firstKey - amount * keyWidth);
  }
  
  void MidiKeyboardComponent::timerCallback()
  {
      if (shouldCheckState)
      {
          shouldCheckState = false;
  
          for (int i = rangeStart; i <= rangeEnd; ++i)
          {
              if (keysCurrentlyDrawnDown[i] != state.isNoteOnForChannels (midiInChannelMask, i))
              {
                  keysCurrentlyDrawnDown.setBit (i, state.isNoteOnForChannels (midiInChannelMask, i));
                  repaintNote (i);
              }
          }
      }
  
      if (shouldCheckMousePos)
      {
          Desktop& desktop = Desktop::getInstance();
  
          for (int i = desktop.getNumMouseSources(); --i >= 0;)
          {
              MouseInputSource* source = desktop.getMouseSource (i);
              jassert (source != nullptr);
              updateNoteUnderMouse (getLocalPoint (nullptr, source->getScreenPosition()),
                                    source->isDragging(), source->getIndex());
          }
      }
  }
  
  //==============================================================================
  void MidiKeyboardComponent::clearKeyMappings()
  {
      resetAnyKeysInUse();
      keyPressNotes.clear();
      keyPresses.clear();
  }
  
  void MidiKeyboardComponent::setKeyPressForNote (const KeyPress& key,
                                                  const int midiNoteOffsetFromC)
  {
      removeKeyPressForNote (midiNoteOffsetFromC);
  
      keyPressNotes.add (midiNoteOffsetFromC);
      keyPresses.add (key);
  }
  
  void MidiKeyboardComponent::removeKeyPressForNote (const int midiNoteOffsetFromC)
  {
      for (int i = keyPressNotes.size(); --i >= 0;)
      {
          if (keyPressNotes.getUnchecked (i) == midiNoteOffsetFromC)
          {
              keyPressNotes.remove (i);
              keyPresses.remove (i);
          }
      }
  }
  
  void MidiKeyboardComponent::setKeyPressBaseOctave (const int newOctaveNumber)
  {
      jassert (newOctaveNumber >= 0 && newOctaveNumber <= 10);
  
      keyMappingOctave = newOctaveNumber;
  }
  
  bool MidiKeyboardComponent::keyStateChanged (const bool /*isKeyDown*/)
  {
      bool keyPressUsed = false;
  
      for (int i = keyPresses.size(); --i >= 0;)
      {
          const int 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);
                  keyPressUsed = true;
              }
          }
      }
  
      return keyPressUsed;
  }
  
  void MidiKeyboardComponent::focusLost (FocusChangeType)
  {
      resetAnyKeysInUse();
  }
  