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