1 /* 2 ============================================================================== 3 4 This file is part of the JUCE library. 5 Copyright (c) 2020 - Raw Material Software Limited 6 7 JUCE is an open source library subject to commercial or open-source 8 licensing. 9 10 By using JUCE, you agree to the terms of both the JUCE 6 End-User License 11 Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). 12 13 End User License Agreement: www.juce.com/juce-6-licence 14 Privacy Policy: www.juce.com/juce-privacy-policy 15 16 Or: You may also use this code under the terms of the GPL v3 (see 17 www.gnu.org/licenses). 18 19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER 20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE 21 DISCLAIMED. 22 23 ============================================================================== 24 */ 25 26 namespace juce 27 { 28 29 //============================================================================== 30 /** 31 A component that displays a piano keyboard, whose notes can be clicked on. 32 33 This component will mimic a physical midi keyboard, showing the current state of 34 a MidiKeyboardState object. When the on-screen keys are clicked on, it will play these 35 notes by calling the noteOn() and noteOff() methods of its MidiKeyboardState object. 36 37 Another feature is that the computer keyboard can also be used to play notes. By 38 default it maps the top two rows of a standard qwerty keyboard to the notes, but 39 these can be remapped if needed. It will only respond to keypresses when it has 40 the keyboard focus, so to disable this feature you can call setWantsKeyboardFocus (false). 41 42 The component is also a ChangeBroadcaster, so if you want to be informed when the 43 keyboard is scrolled, you can register a ChangeListener for callbacks. 44 45 @see MidiKeyboardState 46 47 @tags{Audio} 48 */ 49 class JUCE_API MidiKeyboardComponent : public Component, 50 public MidiKeyboardState::Listener, 51 public ChangeBroadcaster, 52 private Timer 53 { 54 public: 55 //============================================================================== 56 /** The direction of the keyboard. 57 @see setOrientation 58 */ 59 enum Orientation 60 { 61 horizontalKeyboard, 62 verticalKeyboardFacingLeft, 63 verticalKeyboardFacingRight, 64 }; 65 66 /** Creates a MidiKeyboardComponent. 67 68 @param state the midi keyboard model that this component will represent 69 @param orientation whether the keyboard is horizontal or vertical 70 */ 71 MidiKeyboardComponent (MidiKeyboardState& state, 72 Orientation orientation); 73 74 /** Destructor. */ 75 ~MidiKeyboardComponent() override; 76 77 //============================================================================== 78 /** Changes the velocity used in midi note-on messages that are triggered by clicking 79 on the component. 80 81 Values are 0 to 1.0, where 1.0 is the heaviest. 82 83 @see setMidiChannel 84 */ 85 void setVelocity (float velocity, bool useMousePositionForVelocity); 86 87 /** Changes the midi channel number that will be used for events triggered by clicking 88 on the component. 89 90 The channel must be between 1 and 16 (inclusive). This is the channel that will be 91 passed on to the MidiKeyboardState::noteOn() method when the user clicks the component. 92 93 Although this is the channel used for outgoing events, the component can display 94 incoming events from more than one channel - see setMidiChannelsToDisplay() 95 96 @see setVelocity 97 */ 98 void setMidiChannel (int midiChannelNumber); 99 100 /** Returns the midi channel that the keyboard is using for midi messages. 101 @see setMidiChannel 102 */ getMidiChannel()103 int getMidiChannel() const noexcept { return midiChannel; } 104 105 /** Sets a mask to indicate which incoming midi channels should be represented by 106 key movements. 107 108 The mask is a set of bits, where bit 0 = midi channel 1, bit 1 = midi channel 2, etc. 109 110 If the MidiKeyboardState has a key down for any of the channels whose bits are set 111 in this mask, the on-screen keys will also go down. 112 113 By default, this mask is set to 0xffff (all channels displayed). 114 115 @see setMidiChannel 116 */ 117 void setMidiChannelsToDisplay (int midiChannelMask); 118 119 /** Returns the current set of midi channels represented by the component. 120 This is the value that was set with setMidiChannelsToDisplay(). 121 */ getMidiChannelsToDisplay()122 int getMidiChannelsToDisplay() const noexcept { return midiInChannelMask; } 123 124 //============================================================================== 125 /** Changes the width used to draw the white keys. */ 126 void setKeyWidth (float widthInPixels); 127 128 /** Returns the width that was set by setKeyWidth(). */ getKeyWidth()129 float getKeyWidth() const noexcept { return keyWidth; } 130 131 /** Changes the width used to draw the buttons that scroll the keyboard up/down in octaves. */ 132 void setScrollButtonWidth (int widthInPixels); 133 134 /** Returns the width that was set by setScrollButtonWidth(). */ getScrollButtonWidth()135 int getScrollButtonWidth() const noexcept { return scrollButtonWidth; } 136 137 /** Changes the keyboard's current direction. */ 138 void setOrientation (Orientation newOrientation); 139 140 /** Returns the keyboard's current direction. */ getOrientation()141 Orientation getOrientation() const noexcept { return orientation; } 142 143 /** Sets the range of midi notes that the keyboard will be limited to. 144 145 By default the range is 0 to 127 (inclusive), but you can limit this if you 146 only want a restricted set of the keys to be shown. 147 148 Note that the values here are inclusive and must be between 0 and 127. 149 */ 150 void setAvailableRange (int lowestNote, 151 int highestNote); 152 153 /** Returns the first note in the available range. 154 @see setAvailableRange 155 */ getRangeStart()156 int getRangeStart() const noexcept { return rangeStart; } 157 158 /** Returns the last note in the available range. 159 @see setAvailableRange 160 */ getRangeEnd()161 int getRangeEnd() const noexcept { return rangeEnd; } 162 163 /** If the keyboard extends beyond the size of the component, this will scroll 164 it to show the given key at the start. 165 166 Whenever the keyboard's position is changed, this will use the ChangeBroadcaster 167 base class to send a callback to any ChangeListeners that have been registered. 168 */ 169 void setLowestVisibleKey (int noteNumber); 170 171 /** Returns the number of the first key shown in the component. 172 @see setLowestVisibleKey 173 */ getLowestVisibleKey()174 int getLowestVisibleKey() const noexcept { return (int) firstKey; } 175 176 /** Sets the length of the black notes as a proportion of the white note length. */ 177 void setBlackNoteLengthProportion (float ratio) noexcept; 178 179 /** Returns the length of the black notes as a proportion of the white note length. */ getBlackNoteLengthProportion()180 float getBlackNoteLengthProportion() const noexcept { return blackNoteLengthRatio; } 181 182 /** Returns the absolute length of the black notes. 183 This will be their vertical or horizontal length, depending on the keyboard's orientation. 184 */ 185 float getBlackNoteLength() const noexcept; 186 187 /** Sets the width of the black notes as a proportion of the white note width. */ 188 void setBlackNoteWidthProportion (float ratio) noexcept; 189 190 /** Returns the width of the black notes as a proportion of the white note width. */ getBlackNoteWidthProportion()191 float getBlackNoteWidthProportion() const noexcept { return blackNoteWidthRatio; } 192 193 /** Returns the absolute width of the black notes. 194 This will be their vertical or horizontal width, depending on the keyboard's orientation. 195 */ getBlackNoteWidth()196 float getBlackNoteWidth() const noexcept { return keyWidth * blackNoteWidthRatio; } 197 198 /** If set to true, then scroll buttons will appear at either end of the keyboard 199 if there are too many notes to fit them all in the component at once. 200 */ 201 void setScrollButtonsVisible (bool canScroll); 202 203 //============================================================================== 204 /** A set of colour IDs to use to change the colour of various aspects of the keyboard. 205 206 These constants can be used either via the Component::setColour(), or LookAndFeel::setColour() 207 methods. 208 209 @see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour 210 */ 211 enum ColourIds 212 { 213 whiteNoteColourId = 0x1005000, 214 blackNoteColourId = 0x1005001, 215 keySeparatorLineColourId = 0x1005002, 216 mouseOverKeyOverlayColourId = 0x1005003, /**< This colour will be overlaid on the normal note colour. */ 217 keyDownOverlayColourId = 0x1005004, /**< This colour will be overlaid on the normal note colour. */ 218 textLabelColourId = 0x1005005, 219 upDownButtonBackgroundColourId = 0x1005006, 220 upDownButtonArrowColourId = 0x1005007, 221 shadowColourId = 0x1005008 222 }; 223 224 /** Returns the position within the component of the left-hand edge of a key. 225 226 Depending on the keyboard's orientation, this may be a horizontal or vertical 227 distance, in either direction. 228 */ 229 float getKeyStartPosition (int midiNoteNumber) const; 230 231 /** Returns the total width needed to fit all the keys in the available range. */ 232 float getTotalKeyboardWidth() const noexcept; 233 234 /** Returns the key at a given coordinate. */ 235 int getNoteAtPosition (Point<float> position); 236 237 //============================================================================== 238 /** Deletes all key-mappings. 239 @see setKeyPressForNote 240 */ 241 void clearKeyMappings(); 242 243 /** Maps a key-press to a given note. 244 245 @param key the key that should trigger the note 246 @param midiNoteOffsetFromC how many semitones above C the triggered note should 247 be. The actual midi note that gets played will be 248 this value + (12 * the current base octave). To change 249 the base octave, see setKeyPressBaseOctave() 250 */ 251 void setKeyPressForNote (const KeyPress& key, 252 int midiNoteOffsetFromC); 253 254 /** Removes any key-mappings for a given note. 255 For a description of what the note number means, see setKeyPressForNote(). 256 */ 257 void removeKeyPressForNote (int midiNoteOffsetFromC); 258 259 /** Changes the base note above which key-press-triggered notes are played. 260 261 The set of key-mappings that trigger notes can be moved up and down to cover 262 the entire scale using this method. 263 264 The value passed in is an octave number between 0 and 10 (inclusive), and 265 indicates which C is the base note to which the key-mapped notes are 266 relative. 267 */ 268 void setKeyPressBaseOctave (int newOctaveNumber); 269 270 /** This sets the octave number which is shown as the octave number for middle C. 271 272 This affects only the default implementation of getWhiteNoteText(), which 273 passes this octave number to MidiMessage::getMidiNoteName() in order to 274 get the note text. See MidiMessage::getMidiNoteName() for more info about 275 the parameter. 276 277 By default this value is set to 3. 278 279 @see getOctaveForMiddleC 280 */ 281 void setOctaveForMiddleC (int octaveNumForMiddleC); 282 283 /** This returns the value set by setOctaveForMiddleC(). 284 @see setOctaveForMiddleC 285 */ getOctaveForMiddleC()286 int getOctaveForMiddleC() const noexcept { return octaveNumForMiddleC; } 287 288 //============================================================================== 289 /** @internal */ 290 void paint (Graphics&) override; 291 /** @internal */ 292 void resized() override; 293 /** @internal */ 294 void mouseMove (const MouseEvent&) override; 295 /** @internal */ 296 void mouseDrag (const MouseEvent&) override; 297 /** @internal */ 298 void mouseDown (const MouseEvent&) override; 299 /** @internal */ 300 void mouseUp (const MouseEvent&) override; 301 /** @internal */ 302 void mouseEnter (const MouseEvent&) override; 303 /** @internal */ 304 void mouseExit (const MouseEvent&) override; 305 /** @internal */ 306 void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) override; 307 /** @internal */ 308 void timerCallback() override; 309 /** @internal */ 310 bool keyStateChanged (bool isKeyDown) override; 311 /** @internal */ 312 bool keyPressed (const KeyPress&) override; 313 /** @internal */ 314 void focusLost (FocusChangeType) override; 315 /** @internal */ 316 void handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override; 317 /** @internal */ 318 void handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override; 319 /** @internal */ 320 void colourChanged() override; 321 322 protected: 323 //============================================================================== 324 /** Draws a white note in the given rectangle. 325 326 isOver indicates whether the mouse is over the key, isDown indicates whether the key is 327 currently pressed down. 328 329 When doing this, be sure to note the keyboard's orientation. 330 */ 331 virtual void drawWhiteNote (int midiNoteNumber, 332 Graphics& g, Rectangle<float> area, 333 bool isDown, bool isOver, 334 Colour lineColour, Colour textColour); 335 336 /** Draws a black note in the given rectangle. 337 338 isOver indicates whether the mouse is over the key, isDown indicates whether the key is 339 currently pressed down. 340 341 When doing this, be sure to note the keyboard's orientation. 342 */ 343 virtual void drawBlackNote (int midiNoteNumber, 344 Graphics& g, Rectangle<float> area, 345 bool isDown, bool isOver, 346 Colour noteFillColour); 347 348 /** Allows text to be drawn on the white notes. 349 By default this is used to label the C in each octave, but could be used for other things. 350 @see setOctaveForMiddleC 351 */ 352 virtual String getWhiteNoteText (int midiNoteNumber); 353 354 /** Draws the up and down buttons that scroll the keyboard up/down in octaves. */ 355 virtual void drawUpDownButton (Graphics& g, int w, int h, 356 bool isMouseOver, 357 bool isButtonPressed, 358 bool movesOctavesUp); 359 360 /** Callback when the mouse is clicked on a key. 361 362 You could use this to do things like handle right-clicks on keys, etc. 363 364 Return true if you want the click to trigger the note, or false if you 365 want to handle it yourself and not have the note played. 366 367 @see mouseDraggedToKey 368 */ 369 virtual bool mouseDownOnKey (int midiNoteNumber, const MouseEvent& e); 370 371 /** Callback when the mouse is dragged from one key onto another. 372 373 Return true if you want the drag to trigger the new note, or false if you 374 want to handle it yourself and not have the note played. 375 376 @see mouseDownOnKey 377 */ 378 virtual bool mouseDraggedToKey (int midiNoteNumber, const MouseEvent& e); 379 380 /** Callback when the mouse is released from a key. 381 @see mouseDownOnKey 382 */ 383 virtual void mouseUpOnKey (int midiNoteNumber, const MouseEvent& e); 384 385 /** Calculates the position of a given midi-note. 386 387 This can be overridden to create layouts with custom key-widths. 388 389 @param midiNoteNumber the note to find 390 @param keyWidth the desired width in pixels of one key - see setKeyWidth() 391 @returns the start and length of the key along the axis of the keyboard 392 */ 393 virtual Range<float> getKeyPosition (int midiNoteNumber, float keyWidth) const; 394 395 /** Returns the rectangle for a given key if within the displayable range */ 396 Rectangle<float> getRectangleForKey (int midiNoteNumber) const; 397 398 399 private: 400 //============================================================================== 401 struct UpDownButton; 402 403 MidiKeyboardState& state; 404 float blackNoteLengthRatio = 0.7f; 405 float blackNoteWidthRatio = 0.7f; 406 float xOffset = 0; 407 float keyWidth = 16.0f; 408 int scrollButtonWidth = 12; 409 Orientation orientation; 410 411 int midiChannel = 1, midiInChannelMask = 0xffff; 412 float velocity = 1.0f; 413 414 Array<int> mouseOverNotes, mouseDownNotes; 415 BigInteger keysPressed, keysCurrentlyDrawnDown; 416 bool shouldCheckState = false; 417 418 int rangeStart = 0, rangeEnd = 127; 419 float firstKey = 12 * 4.0f; 420 bool canScroll = true, useMousePositionForVelocity = true; 421 std::unique_ptr<Button> scrollDown, scrollUp; 422 423 Array<KeyPress> keyPresses; 424 Array<int> keyPressNotes; 425 int keyMappingOctave = 6, octaveNumForMiddleC = 3; 426 427 Range<float> getKeyPos (int midiNoteNumber) const; 428 int xyToNote (Point<float>, float& mousePositionVelocity); 429 int remappedXYToNote (Point<float>, float& mousePositionVelocity) const; 430 void resetAnyKeysInUse(); 431 void updateNoteUnderMouse (Point<float>, bool isDown, int fingerNum); 432 void updateNoteUnderMouse (const MouseEvent&, bool isDown); 433 void repaintNote (int midiNoteNumber); 434 void setLowestVisibleKeyFloat (float noteNumber); 435 436 #if JUCE_CATCH_DEPRECATED_CODE_MISUSE 437 // Note that the parameters for these method have changed getKeyPosition(int,float,int &,int &)438 virtual int getKeyPosition (int, float, int&, int&) const { return 0; } drawWhiteNote(int,Graphics &,int,int,int,int,bool,bool,const Colour &,const Colour &)439 virtual int drawWhiteNote (int, Graphics&, int, int, int, int, bool, bool, const Colour&, const Colour&) { return 0; } drawBlackNote(int,Graphics &,int,int,int,int,bool,bool,const Colour &)440 virtual int drawBlackNote (int, Graphics&, int, int, int, int, bool, bool, const Colour&) { return 0; } 441 #endif 442 443 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiKeyboardComponent) 444 }; 445 446 } // namespace juce 447