JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

« « « Anklang Documentation
Loading...
Searching...
No Matches
juce_MidiKeyboardComponent.cpp
Go to the documentation of this file.
1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - 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 7 End-User License
11 Agreement and JUCE Privacy Policy.
12
13 End User License Agreement: www.juce.com/juce-7-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
26namespace juce
27{
28
29//==============================================================================
32{
33 state.addListener (this);
34
35 // initialise with a default set of qwerty key-mappings.
36 const std::string_view keys { "awsedftgyhujkolp;" };
37
38 for (const char& c : keys)
39 setKeyPressForNote ({c, 0, 0}, (int) std::distance (keys.data(), &c));
40
41 mouseOverNotes.insertMultiple (0, -1, 32);
42 mouseDownNotes.insertMultiple (0, -1, 32);
43
46
47 startTimerHz (20);
48}
49
54
55//==============================================================================
57{
58 velocity = v;
59 useMousePositionForVelocity = useMousePosition;
60}
61
62//==============================================================================
64{
66
67 if (midiChannel != midiChannelNumber)
68 {
69 resetAnyKeysInUse();
70 midiChannel = jlimit (1, 16, midiChannelNumber);
71 }
72}
73
75{
76 midiInChannelMask = midiChannelMask;
77 noPendingUpdates.store (false);
78}
79
80//==============================================================================
82{
83 resetAnyKeysInUse();
84 keyPressNotes.clear();
85 keyPresses.clear();
86}
87
89{
91
92 keyPressNotes.add (midiNoteOffsetFromC);
93 keyPresses.add (key);
94}
95
97{
98 for (int i = keyPressNotes.size(); --i >= 0;)
99 {
100 if (keyPressNotes.getUnchecked (i) == midiNoteOffsetFromC)
101 {
102 keyPressNotes.remove (i);
103 keyPresses.remove (i);
104 }
105 }
106}
107
114
115//==============================================================================
116void MidiKeyboardComponent::resetAnyKeysInUse()
117{
118 if (! keysPressed.isZero())
119 {
120 for (int i = 128; --i >= 0;)
121 if (keysPressed[i])
122 state.noteOff (midiChannel, i, 0.0f);
123
124 keysPressed.clear();
125 }
126
127 for (int i = mouseDownNotes.size(); --i >= 0;)
128 {
129 auto noteDown = mouseDownNotes.getUnchecked (i);
130
131 if (noteDown >= 0)
132 {
133 state.noteOff (midiChannel, noteDown, 0.0f);
134 mouseDownNotes.set (i, -1);
135 }
136
137 mouseOverNotes.set (i, -1);
138 }
139}
140
141void MidiKeyboardComponent::updateNoteUnderMouse (const MouseEvent& e, bool isDown)
142{
143 updateNoteUnderMouse (e.getEventRelativeTo (this).position, isDown, e.source.getIndex());
144}
145
146void MidiKeyboardComponent::updateNoteUnderMouse (Point<float> pos, bool isDown, int fingerNum)
147{
148 const auto noteInfo = getNoteAndVelocityAtPosition (pos);
149 const auto newNote = noteInfo.note;
150 const auto oldNote = mouseOverNotes.getUnchecked (fingerNum);
151 const auto oldNoteDown = mouseDownNotes.getUnchecked (fingerNum);
152 const auto eventVelocity = useMousePositionForVelocity ? noteInfo.velocity * velocity : velocity;
153
154 if (oldNote != newNote)
155 {
156 repaintNote (oldNote);
157 repaintNote (newNote);
158 mouseOverNotes.set (fingerNum, newNote);
159 }
160
161 if (isDown)
162 {
163 if (newNote != oldNoteDown)
164 {
165 if (oldNoteDown >= 0)
166 {
167 mouseDownNotes.set (fingerNum, -1);
168
169 if (! mouseDownNotes.contains (oldNoteDown))
170 state.noteOff (midiChannel, oldNoteDown, eventVelocity);
171 }
172
173 if (newNote >= 0 && ! mouseDownNotes.contains (newNote))
174 {
175 state.noteOn (midiChannel, newNote, eventVelocity);
176 mouseDownNotes.set (fingerNum, newNote);
177 }
178 }
179 }
180 else if (oldNoteDown >= 0)
181 {
182 mouseDownNotes.set (fingerNum, -1);
183
184 if (! mouseDownNotes.contains (oldNoteDown))
185 state.noteOff (midiChannel, oldNoteDown, eventVelocity);
186 }
187}
188
189void MidiKeyboardComponent::repaintNote (int noteNum)
190{
191 if (getRangeStart() <= noteNum && noteNum <= getRangeEnd())
192 repaint (getRectangleForKey (noteNum).getSmallestIntegerContainer());
193}
194
195
197{
198 updateNoteUnderMouse (e, false);
199}
200
202{
204
205 if (newNote >= 0 && mouseDraggedToKey (newNote, e))
206 updateNoteUnderMouse (e, true);
207}
208
210{
212
213 if (newNote >= 0 && mouseDownOnKey (newNote, e))
214 updateNoteUnderMouse (e, true);
215}
216
218{
219 updateNoteUnderMouse (e, false);
220
221 auto note = getNoteAndVelocityAtPosition (e.position).note;
222
223 if (note >= 0)
224 mouseUpOnKey (note, e);
225}
226
228{
229 updateNoteUnderMouse (e, false);
230}
231
233{
234 updateNoteUnderMouse (e, false);
235}
236
238{
239 if (noPendingUpdates.exchange (true))
240 return;
241
242 for (auto i = getRangeStart(); i <= getRangeEnd(); ++i)
243 {
244 const auto isOn = state.isNoteOnForChannels (midiInChannelMask, i);
245
246 if (keysCurrentlyDrawnDown[i] != isOn)
247 {
248 keysCurrentlyDrawnDown.setBit (i, isOn);
249 repaintNote (i);
250 }
251 }
252}
253
255{
256 bool keyPressUsed = false;
257
258 for (int i = keyPresses.size(); --i >= 0;)
259 {
260 auto note = 12 * keyMappingOctave + keyPressNotes.getUnchecked (i);
261
262 if (keyPresses.getReference (i).isCurrentlyDown())
263 {
264 if (! keysPressed[note])
265 {
266 keysPressed.setBit (note);
267 state.noteOn (midiChannel, note, velocity);
268 keyPressUsed = true;
269 }
270 }
271 else
272 {
273 if (keysPressed[note])
274 {
275 keysPressed.clearBit (note);
276 state.noteOff (midiChannel, note, 0.0f);
277 keyPressUsed = true;
278 }
279 }
280 }
281
282 return keyPressUsed;
283}
284
286{
287 return keyPresses.contains (key);
288}
289
291{
292 resetAnyKeysInUse();
293}
294
295//==============================================================================
296void MidiKeyboardComponent::drawKeyboardBackground (Graphics& g, Rectangle<float> area)
297{
298 g.fillAll (findColour (whiteNoteColourId));
299
300 auto width = area.getWidth();
301 auto height = area.getHeight();
304
305 if (currentOrientation == verticalKeyboardFacingLeft)
306 {
307 shadowGradientStart.x = width - 1.0f;
308 shadowGradientEnd.x = width - 5.0f;
309 }
310 else if (currentOrientation == verticalKeyboardFacingRight)
311 {
312 shadowGradientEnd.x = 5.0f;
313 }
314 else
315 {
316 shadowGradientEnd.y = 5.0f;
317 }
318
320 auto shadowColour = findColour (shadowColourId);
321
322 if (! shadowColour.isTransparent())
323 {
325 shadowColour.withAlpha (0.0f), shadowGradientEnd,
326 false });
327
328 switch (currentOrientation)
329 {
330 case horizontalKeyboard: g.fillRect (0.0f, 0.0f, keyboardWidth, 5.0f); break;
331 case verticalKeyboardFacingLeft: g.fillRect (width - 5.0f, 0.0f, 5.0f, keyboardWidth); break;
332 case verticalKeyboardFacingRight: g.fillRect (0.0f, 0.0f, 5.0f, keyboardWidth); break;
333 default: break;
334 }
335 }
336
337 auto lineColour = findColour (keySeparatorLineColourId);
338
339 if (! lineColour.isTransparent())
340 {
342
343 switch (currentOrientation)
344 {
345 case horizontalKeyboard: g.fillRect (0.0f, height - 1.0f, keyboardWidth, 1.0f); break;
346 case verticalKeyboardFacingLeft: g.fillRect (0.0f, 0.0f, 1.0f, keyboardWidth); break;
347 case verticalKeyboardFacingRight: g.fillRect (width - 1.0f, 0.0f, 1.0f, keyboardWidth); break;
348 default: break;
349 }
350 }
351}
352
354 bool isDown, bool isOver, Colour lineColour, Colour textColour)
355{
356 auto c = Colours::transparentWhite;
357
358 if (isDown) c = findColour (keyDownOverlayColourId);
359 if (isOver) c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId));
360
361 g.setColour (c);
362 g.fillRect (area);
363
364 const auto currentOrientation = getOrientation();
365
366 auto text = getWhiteNoteText (midiNoteNumber);
367
368 if (text.isNotEmpty())
369 {
370 auto fontHeight = jmin (12.0f, getKeyWidth() * 0.9f);
371
373 g.setFont (Font (fontHeight).withHorizontalScale (0.8f));
374
375 switch (currentOrientation)
376 {
377 case horizontalKeyboard: g.drawText (text, area.withTrimmedLeft (1.0f).withTrimmedBottom (2.0f), Justification::centredBottom, false); break;
378 case verticalKeyboardFacingLeft: g.drawText (text, area.reduced (2.0f), Justification::centredLeft, false); break;
379 case verticalKeyboardFacingRight: g.drawText (text, area.reduced (2.0f), Justification::centredRight, false); break;
380 default: break;
381 }
382 }
383
384 if (! lineColour.isTransparent())
385 {
387
388 switch (currentOrientation)
389 {
390 case horizontalKeyboard: g.fillRect (area.withWidth (1.0f)); break;
391 case verticalKeyboardFacingLeft: g.fillRect (area.withHeight (1.0f)); break;
392 case verticalKeyboardFacingRight: g.fillRect (area.removeFromBottom (1.0f)); break;
393 default: break;
394 }
395
397 {
398 switch (currentOrientation)
399 {
400 case horizontalKeyboard: g.fillRect (area.expanded (1.0f, 0).removeFromRight (1.0f)); break;
401 case verticalKeyboardFacingLeft: g.fillRect (area.expanded (0, 1.0f).removeFromBottom (1.0f)); break;
402 case verticalKeyboardFacingRight: g.fillRect (area.expanded (0, 1.0f).removeFromTop (1.0f)); break;
403 default: break;
404 }
405 }
406 }
407}
408
410 bool isDown, bool isOver, Colour noteFillColour)
411{
412 auto c = noteFillColour;
413
414 if (isDown) c = c.overlaidWith (findColour (keyDownOverlayColourId));
415 if (isOver) c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId));
416
417 g.setColour (c);
418 g.fillRect (area);
419
420 if (isDown)
421 {
423 g.drawRect (area);
424 }
425 else
426 {
427 g.setColour (c.brighter());
428 auto sideIndent = 1.0f / 8.0f;
429 auto topIndent = 7.0f / 8.0f;
430 auto w = area.getWidth();
431 auto h = area.getHeight();
432
433 switch (getOrientation())
434 {
435 case horizontalKeyboard: g.fillRect (area.reduced (w * sideIndent, 0).removeFromTop (h * topIndent)); break;
436 case verticalKeyboardFacingLeft: g.fillRect (area.reduced (0, h * sideIndent).removeFromRight (w * topIndent)); break;
437 case verticalKeyboardFacingRight: g.fillRect (area.reduced (0, h * sideIndent).removeFromLeft (w * topIndent)); break;
438 default: break;
439 }
440 }
441}
442
450
452{
453 setOpaque (findColour (whiteNoteColourId).isOpaque());
454 repaint();
455}
456
457//==============================================================================
458void MidiKeyboardComponent::drawWhiteKey (int midiNoteNumber, Graphics& g, Rectangle<float> area)
459{
460 drawWhiteNote (midiNoteNumber, g, area, state.isNoteOnForChannels (midiInChannelMask, midiNoteNumber),
461 mouseOverNotes.contains (midiNoteNumber), findColour (keySeparatorLineColourId), findColour (textLabelColourId));
462}
463
464void MidiKeyboardComponent::drawBlackKey (int midiNoteNumber, Graphics& g, Rectangle<float> area)
465{
466 drawBlackNote (midiNoteNumber, g, area, state.isNoteOnForChannels (midiInChannelMask, midiNoteNumber),
467 mouseOverNotes.contains (midiNoteNumber), findColour (blackNoteColourId));
468}
469
470//==============================================================================
471void MidiKeyboardComponent::handleNoteOn (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/, float /*velocity*/)
472{
473 noPendingUpdates.store (false);
474}
475
476void MidiKeyboardComponent::handleNoteOff (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/, float /*velocity*/)
477{
478 noPendingUpdates.store (false);
479}
480
481//==============================================================================
485
486} // namespace juce
ElementType getUnchecked(int index) const
Returns one of the elements in the array, without checking the index passed in.
Definition juce_Array.h:252
int size() const noexcept
Returns the current number of elements in the array.
Definition juce_Array.h:215
void remove(int indexToRemove)
Removes an element from the array.
Definition juce_Array.h:742
void add(const ElementType &newElement)
Appends a new element at the end of the array.
Definition juce_Array.h:418
void set(int indexToChange, ParameterType newValue)
Replaces an element with a new value.
Definition juce_Array.h:542
bool contains(ParameterType elementToLookFor) const
Returns true if the array contains at least one occurrence of an object.
Definition juce_Array.h:400
void insertMultiple(int indexToInsertAt, ParameterType newElement, int numberOfTimesToInsertIt)
Inserts multiple copies of an element into the array at a given position.
Definition juce_Array.h:480
void clear()
Removes all elements from the array.
Definition juce_Array.h:188
BigInteger & clear() noexcept
Resets the value to 0.
BigInteger & clearBit(int bitNumber) noexcept
Clears a particular bit in the number.
bool isZero() const noexcept
Returns true if no bits are set.
BigInteger & setBit(int bitNumber)
Sets a specified bit to 1.
Represents a colour, also including a transparency value.
Definition juce_Colour.h:38
bool isOpaque() const noexcept
Returns true if no parts of this component are transparent.
FocusChangeType
Enumeration used by the focusGained() and focusLost() methods.
void setOpaque(bool shouldBeOpaque)
Indicates whether any parts of the component might be transparent.
void repaint()
Marks the whole component as needing to be redrawn.
void setWantsKeyboardFocus(bool wantsFocus) noexcept
Sets a flag to indicate whether this component wants keyboard focus or not.
Colour findColour(int colourID, bool inheritFromParent=false) const
Looks for a colour that has been registered with the given colour ID number.
Represents a particular font, including its size, style, etc.
Definition juce_Font.h:42
A graphics context, used for drawing a component or image.
void drawText(const String &text, int x, int y, int width, int height, Justification justificationType, bool useEllipsesIfTooBig=true) const
Draws a line of text within a specified rectangle.
void setFont(const Font &newFont)
Changes the font to use for subsequent text-drawing functions.
void setGradientFill(const ColourGradient &gradient)
Sets the context to use a gradient for its fill pattern.
void drawRect(int x, int y, int width, int height, int lineThickness=1) const
Draws a rectangular outline, using the current colour or brush.
void fillRect(Rectangle< int > rectangle) const
Fills a rectangle with the current colour or brush.
void setColour(Colour newColour)
Changes the current drawing colour.
void fillAll() const
Fills the context's entire clip region with the current colour or brush.
@ centredRight
Indicates that the item should be centred vertically but placed on the right hand side.
@ centredBottom
Indicates that the item should be centred horizontally and placed at the bottom.
@ centredLeft
Indicates that the item should be centred vertically but placed on the left hand side.
Represents a key press, including any modifier keys that are needed.
A base class for drawing a custom MIDI keyboard component.
float getKeyWidth() const noexcept
Returns the width that was set by setKeyWidth().
int getRangeStart() const noexcept
Returns the first note in the available range.
int getRangeEnd() const noexcept
Returns the last note in the available range.
Rectangle< float > getRectangleForKey(int midiNoteNumber) const
Returns the rectangle for a given key.
int getOctaveForMiddleC() const noexcept
This returns the value set by setOctaveForMiddleC().
NoteAndVelocity getNoteAndVelocityAtPosition(Point< float > position, bool includeChildComponents=false)
Returns the note number and velocity for a given position within the component.
Orientation getOrientation() const noexcept
Returns the keyboard's current direction.
Orientation
The direction of the keyboard.
virtual void drawWhiteNote(int midiNoteNumber, Graphics &g, Rectangle< float > area, bool isDown, bool isOver, Colour lineColour, Colour textColour)
Use this method to draw a white note of the keyboard in a given rectangle.
void colourChanged() override
This method is called when a colour is changed by the setColour() method, or when the look-and-feel i...
void setMidiChannelsToDisplay(int midiChannelMask)
Sets a mask to indicate which incoming midi channels should be represented by key movements.
virtual bool mouseDownOnKey(int midiNoteNumber, const MouseEvent &e)
Callback when the mouse is clicked on a key.
virtual void mouseUpOnKey(int midiNoteNumber, const MouseEvent &e)
Callback when the mouse is released from a key.
@ keyDownOverlayColourId
This colour will be overlaid on the normal note colour.
@ mouseOverKeyOverlayColourId
This colour will be overlaid on the normal note colour.
void timerCallback() override
The user-defined callback routine that actually gets called periodically.
void setVelocity(float velocity, bool useMousePositionForVelocity)
Changes the velocity used in midi note-on messages that are triggered by clicking on the component.
void setKeyPressForNote(const KeyPress &key, int midiNoteOffsetFromC)
Maps a key-press to a given note.
void mouseEnter(const MouseEvent &) override
Called when the mouse first enters a component.
bool keyPressed(const KeyPress &) override
Called when a key is pressed.
void setKeyPressBaseOctave(int newOctaveNumber)
Changes the base note above which key-press-triggered notes are played.
void mouseDown(const MouseEvent &) override
Called when a mouse button is pressed.
MidiKeyboardComponent(MidiKeyboardState &state, Orientation orientation)
Creates a MidiKeyboardComponent.
virtual void drawBlackNote(int midiNoteNumber, Graphics &g, Rectangle< float > area, bool isDown, bool isOver, Colour noteFillColour)
Use this method to draw a black note of the keyboard in a given rectangle.
void mouseExit(const MouseEvent &) override
Called when the mouse moves out of a component.
void mouseMove(const MouseEvent &) override
Called when the mouse moves inside a component.
virtual String getWhiteNoteText(int midiNoteNumber)
Allows text to be drawn on the white notes.
virtual bool mouseDraggedToKey(int midiNoteNumber, const MouseEvent &e)
Callback when the mouse is dragged from one key onto another.
bool keyStateChanged(bool isKeyDown) override
Called when a key is pressed or released.
void mouseUp(const MouseEvent &) override
Called when a mouse button is released.
void mouseDrag(const MouseEvent &) override
Called when the mouse is moved while a button is held down.
void focusLost(FocusChangeType) override
Called to indicate that this component has just lost the keyboard focus.
void setMidiChannel(int midiChannelNumber)
Changes the midi channel number that will be used for events triggered by clicking on the component.
void removeKeyPressForNote(int midiNoteOffsetFromC)
Removes any key-mappings for a given note.
void clearKeyMappings()
Deletes all key-mappings.
Represents a piano keyboard, keeping track of which keys are currently pressed.
void addListener(Listener *listener)
Registers a listener for callbacks when keys go up or down.
void noteOff(int midiChannel, int midiNoteNumber, float velocity)
Turns a specified note off.
void removeListener(Listener *listener)
Deregisters a listener.
void noteOn(int midiChannel, int midiNoteNumber, float velocity)
Turns a specified note on.
bool isNoteOnForChannels(int midiChannelMask, int midiNoteNumber) const noexcept
Returns true if the given midi key is currently held down on any of a set of midi channels.
static String getMidiNoteName(int noteNumber, bool useSharps, bool includeOctaveNumber, int octaveNumForMiddleC)
Returns the name of a midi note number.
Contains position and status information about a mouse event.
const Point< float > position
The position of the mouse when the event occurred.
A pair of (x, y) coordinates.
Definition juce_Point.h:42
ValueType x
The point's X coordinate.
Definition juce_Point.h:251
Manages a rectangle and allows geometric operations to be performed on it.
ValueType getRight() const noexcept
Returns the x coordinate of the rectangle's right-hand-side.
Rectangle withHeight(ValueType newHeight) const noexcept
Returns a rectangle which has the same position and width as this one, but with a different height.
Rectangle removeFromRight(ValueType amountToRemove) noexcept
Removes a strip from the right-hand edge of this rectangle, reducing this rectangle by the specified ...
Rectangle removeFromBottom(ValueType amountToRemove) noexcept
Removes a strip from the bottom of this rectangle, reducing this rectangle by the specified amount an...
ValueType getWidth() const noexcept
Returns the width of the rectangle.
Rectangle removeFromTop(ValueType amountToRemove) noexcept
Removes a strip from the top of this rectangle, reducing this rectangle by the specified amount and r...
Rectangle reduced(ValueType deltaX, ValueType deltaY) const noexcept
Returns a rectangle that is smaller than this one by a given amount.
Rectangle removeFromLeft(ValueType amountToRemove) noexcept
Removes a strip from the left-hand edge of this rectangle, reducing this rectangle by the specified a...
Rectangle withTrimmedLeft(ValueType amountToRemove) const noexcept
Returns a version of this rectangle with the given amount removed from its left-hand edge.
Rectangle withWidth(ValueType newWidth) const noexcept
Returns a rectangle which has the same position and height as this one, but with a different width.
ValueType getHeight() const noexcept
Returns the height of the rectangle.
Rectangle withTrimmedBottom(ValueType amountToRemove) const noexcept
Returns a version of this rectangle with the given amount removed from its bottom edge.
Rectangle expanded(ValueType deltaX, ValueType deltaY) const noexcept
Returns a rectangle that is larger than this one by a given amount.
The JUCE String class!
Definition juce_String.h:53
void startTimerHz(int timerFrequencyHz) noexcept
Starts the timer with an interval specified in Hertz.
T distance(T... args)
T exchange(T... args)
#define jassert(expression)
Platform-independent assertion macro.
typedef int
JUCE Namespace.
constexpr Type jmin(Type a, Type b)
Returns the smaller of two values.
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
Constrains a value to keep it within a given range.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
Definition juce_Memory.h:88
T store(T... args)