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_KeyboardComponentBase.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
29constexpr uint8 whiteNotes[] = { 0, 2, 4, 5, 7, 9, 11 };
30constexpr uint8 blackNotes[] = { 1, 3, 6, 8, 10 };
31
32//==============================================================================
34{
36 : Button ({}), owner (c), delta (d)
37 {
38 }
39
40 void clicked() override
41 {
42 auto note = owner.getLowestVisibleKey();
43
44 note = delta < 0 ? (note - 1) / 12 : note / 12 + 1;
45
46 owner.setLowestVisibleKey (note * 12);
47 }
48
49 using Button::clicked;
50
57
58private:
60 int delta;
61
63};
64
65//==============================================================================
67{
68 scrollDown = std::make_unique<UpDownButton> (*this, -1);
69 scrollUp = std::make_unique<UpDownButton> (*this, 1);
70
71 addChildComponent (*scrollDown);
72 addChildComponent (*scrollUp);
73
75}
76
77//==============================================================================
79{
81
82 if (! approximatelyEqual (keyWidth, widthInPixels)) // Prevent infinite recursion if the width is being computed in a 'resized()' callback
83 {
84 keyWidth = widthInPixels;
85 resized();
86 }
87}
88
90{
92
93 if (scrollButtonWidth != widthInPixels)
94 {
95 scrollButtonWidth = widthInPixels;
96 resized();
97 }
98}
99
101{
102 if (orientation != newOrientation)
103 {
104 orientation = newOrientation;
105 resized();
106 }
107}
108
110{
111 jassert (lowestNote >= 0 && lowestNote <= 127);
112 jassert (highestNote >= 0 && highestNote <= 127);
114
115 if (rangeStart != lowestNote || rangeEnd != highestNote)
116 {
117 rangeStart = jlimit (0, 127, lowestNote);
118 rangeEnd = jlimit (0, 127, highestNote);
119 firstKey = jlimit ((float) rangeStart, (float) rangeEnd, firstKey);
120 resized();
121 }
122}
123
125{
126 setLowestVisibleKeyFloat ((float) noteNumber);
127}
128
129void KeyboardComponentBase::setLowestVisibleKeyFloat (float noteNumber)
130{
131 noteNumber = jlimit ((float) rangeStart, (float) rangeEnd, noteNumber);
132
133 if (! approximatelyEqual (noteNumber, firstKey))
134 {
135 bool hasMoved = (((int) firstKey) != (int) noteNumber);
136 firstKey = noteNumber;
137
138 if (hasMoved)
140
141 resized();
142 }
143}
144
146{
147 return (orientation == horizontalKeyboard) ? (float) getHeight() : (float) getWidth();
148}
149
151{
152 jassert (ratio >= 0.0f && ratio <= 1.0f);
153
154 if (! approximatelyEqual (blackNoteLengthRatio, ratio))
155 {
156 blackNoteLengthRatio = ratio;
157 resized();
158 }
159}
160
162{
163 auto whiteNoteLength = orientation == horizontalKeyboard ? getHeight() : getWidth();
164 return (float) whiteNoteLength * blackNoteLengthRatio;
165}
166
168{
169 jassert (ratio >= 0.0f && ratio <= 1.0f);
170
171 if (! approximatelyEqual (blackNoteWidthRatio, ratio))
172 {
173 blackNoteWidthRatio = ratio;
174 resized();
175 }
176}
177
179{
180 if (canScroll != newCanScroll)
181 {
182 canScroll = newCanScroll;
183 resized();
184 }
185}
186
187//==============================================================================
188Range<float> KeyboardComponentBase::getKeyPos (int midiNoteNumber) const
189{
190 return getKeyPosition (midiNoteNumber, keyWidth)
191 - xOffset
192 - getKeyPosition (rangeStart, keyWidth).getStart();
193}
194
196{
197 return getKeyPos (midiNoteNumber).getStart();
198}
199
201{
202 return getKeyPos (rangeEnd).getEnd();
203}
204
206{
207 if (! reallyContains (pos, children))
208 return { -1, 0.0f };
209
210 auto p = pos;
211
212 if (orientation != horizontalKeyboard)
213 {
214 p = { p.y, p.x };
215
216 if (orientation == verticalKeyboardFacingLeft)
217 p = { p.x, (float) getWidth() - p.y };
218 else
219 p = { (float) getHeight() - p.x, p.y };
220 }
221
222 return remappedXYToNote (p + Point<float> (xOffset, 0));
223}
224
225KeyboardComponentBase::NoteAndVelocity KeyboardComponentBase::remappedXYToNote (Point<float> pos) const
226{
228
229 if (pos.getY() < blackNoteLength)
230 {
231 for (int octaveStart = 12 * (rangeStart / 12); octaveStart <= rangeEnd; octaveStart += 12)
232 {
233 for (int i = 0; i < 5; ++i)
234 {
235 auto note = octaveStart + blackNotes[i];
236
237 if (rangeStart <= note && note <= rangeEnd)
238 {
239 if (getKeyPos (note).contains (pos.x - xOffset))
240 {
241 return { note, jmax (0.0f, pos.y / blackNoteLength) };
242 }
243 }
244 }
245 }
246 }
247
248 for (int octaveStart = 12 * (rangeStart / 12); octaveStart <= rangeEnd; octaveStart += 12)
249 {
250 for (int i = 0; i < 7; ++i)
251 {
252 auto note = octaveStart + whiteNotes[i];
253
254 if (note >= rangeStart && note <= rangeEnd)
255 {
256 if (getKeyPos (note).contains (pos.x - xOffset))
257 {
258 auto whiteNoteLength = (orientation == horizontalKeyboard) ? getHeight() : getWidth();
259 return { note, jmax (0.0f, pos.y / (float) whiteNoteLength) };
260 }
261 }
262 }
263 }
264
265 return { -1, 0 };
266}
267
269{
270 jassert (note >= rangeStart && note <= rangeEnd);
271
272 auto pos = getKeyPos (note);
273 auto x = pos.getStart();
274 auto w = pos.getLength();
275
277 {
279
280 switch (orientation)
281 {
282 case horizontalKeyboard: return { x, 0, w, blackNoteLength };
283 case verticalKeyboardFacingLeft: return { (float) getWidth() - blackNoteLength, x, blackNoteLength, w };
284 case verticalKeyboardFacingRight: return { 0, (float) getHeight() - x - w, blackNoteLength, w };
285 default: jassertfalse; break;
286 }
287 }
288 else
289 {
290 switch (orientation)
291 {
292 case horizontalKeyboard: return { x, 0, w, (float) getHeight() };
293 case verticalKeyboardFacingLeft: return { 0, x, (float) getWidth(), w };
294 case verticalKeyboardFacingRight: return { 0, (float) getHeight() - x - w, (float) getWidth(), w };
295 default: jassertfalse; break;
296 }
297 }
298
299 return {};
300}
301
302//==============================================================================
304{
305 octaveNumForMiddleC = octaveNum;
306 repaint();
307}
308
309//==============================================================================
310void KeyboardComponentBase::drawUpDownButton (Graphics& g, int w, int h, bool mouseOver, bool buttonDown, bool movesOctavesUp)
311{
312 g.fillAll (findColour (upDownButtonBackgroundColourId));
313
314 float angle = 0;
315
316 switch (getOrientation())
317 {
318 case horizontalKeyboard: angle = movesOctavesUp ? 0.0f : 0.5f; break;
319 case verticalKeyboardFacingLeft: angle = movesOctavesUp ? 0.25f : 0.75f; break;
320 case verticalKeyboardFacingRight: angle = movesOctavesUp ? 0.75f : 0.25f; break;
321 default: jassertfalse; break;
322 }
323
324 Path path;
325 path.addTriangle (0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f);
327
328 g.setColour (findColour (upDownButtonArrowColourId)
329 .withAlpha (buttonDown ? 1.0f : (mouseOver ? 0.6f : 0.4f)));
330
331 g.fillPath (path, path.getTransformToScaleToFit (1.0f, 1.0f, (float) w - 2.0f, (float) h - 2.0f, true));
332}
333
335{
336 auto ratio = getBlackNoteWidthProportion();
337
338 static const float notePos[] = { 0.0f, 1 - ratio * 0.6f,
339 1.0f, 2 - ratio * 0.4f,
340 2.0f,
341 3.0f, 4 - ratio * 0.7f,
342 4.0f, 5 - ratio * 0.5f,
343 5.0f, 6 - ratio * 0.3f,
344 6.0f };
345
346 auto octave = midiNoteNumber / 12;
347 auto note = midiNoteNumber % 12;
348
349 auto start = (float) octave * 7.0f * targetKeyWidth + notePos[note] * targetKeyWidth;
350 auto width = MidiMessage::isMidiNoteBlack (note) ? blackNoteWidthRatio * targetKeyWidth : targetKeyWidth;
351
352 return { start, start + width };
353}
354
355//==============================================================================
357{
358 drawKeyboardBackground (g, getLocalBounds().toFloat());
359
360 for (int octaveBase = 0; octaveBase < 128; octaveBase += 12)
361 {
362 for (auto noteNum : whiteNotes)
363 {
364 const auto key = octaveBase + noteNum;
365
366 if (rangeStart <= key && key <= rangeEnd)
367 drawWhiteKey (key, g, getRectangleForKey (key));
368 }
369
370 for (auto noteNum : blackNotes)
371 {
372 const auto key = octaveBase + noteNum;
373
374 if (rangeStart <= key && key <= rangeEnd)
375 drawBlackKey (key, g, getRectangleForKey (key));
376 }
377 }
378}
379
381{
382 auto w = getWidth();
383 auto h = getHeight();
384
385 if (w > 0 && h > 0)
386 {
387 if (orientation != horizontalKeyboard)
388 std::swap (w, h);
389
390 auto kx2 = getKeyPos (rangeEnd).getEnd();
391
392 if ((int) firstKey != rangeStart)
393 {
394 auto kx1 = getKeyPos (rangeStart).getStart();
395
396 if (kx2 - kx1 <= (float) w)
397 {
398 firstKey = (float) rangeStart;
400 repaint();
401 }
402 }
403
404 scrollDown->setVisible (canScroll && firstKey > (float) rangeStart);
405
406 xOffset = 0;
407
408 if (canScroll)
409 {
410 auto scrollButtonW = jmin (scrollButtonWidth, w / 2);
411 auto r = getLocalBounds();
412
413 if (orientation == horizontalKeyboard)
414 {
415 scrollDown->setBounds (r.removeFromLeft (scrollButtonW));
416 scrollUp ->setBounds (r.removeFromRight (scrollButtonW));
417 }
418 else if (orientation == verticalKeyboardFacingLeft)
419 {
420 scrollDown->setBounds (r.removeFromTop (scrollButtonW));
421 scrollUp ->setBounds (r.removeFromBottom (scrollButtonW));
422 }
423 else
424 {
425 scrollDown->setBounds (r.removeFromBottom (scrollButtonW));
426 scrollUp ->setBounds (r.removeFromTop (scrollButtonW));
427 }
428
429 auto endOfLastKey = getKeyPos (rangeEnd).getEnd();
430
431 auto spaceAvailable = w;
432 auto lastStartKey = remappedXYToNote ({ endOfLastKey - (float) spaceAvailable, 0 }).note + 1;
433
434 if (lastStartKey >= 0 && ((int) firstKey) > lastStartKey)
435 {
436 firstKey = (float) jlimit (rangeStart, rangeEnd, lastStartKey);
438 }
439
440 xOffset = getKeyPos ((int) firstKey).getStart();
441 }
442 else
443 {
444 firstKey = (float) rangeStart;
445 }
446
447 scrollUp->setVisible (canScroll && getKeyPos (rangeEnd).getStart() > (float) w);
448 repaint();
449 }
450}
451
452//==============================================================================
454{
455 auto amount = (orientation == horizontalKeyboard && ! approximatelyEqual (wheel.deltaX, 0.0f))
456 ? wheel.deltaX : (orientation == verticalKeyboardFacingLeft ? wheel.deltaY
457 : -wheel.deltaY);
458
459 setLowestVisibleKeyFloat (firstKey - amount * keyWidth);
460}
461
462} // namespace juce
static AffineTransform rotation(float angleInRadians) noexcept
Returns a new transform which is a rotation about (0, 0).
A base class for buttons.
Definition juce_Button.h:43
virtual void clicked()
This method is called when the button has been clicked.
void sendChangeMessage()
Causes an asynchronous change message to be sent to all the registered listeners.
bool contains(Point< int > localPoint)
Returns true if a given point lies within this component or one of its children.
bool reallyContains(Point< int > localPoint, bool returnTrueIfWithinAChild)
Returns true if a given point lies in this component, taking any overlapping siblings into account.
int getHeight() const noexcept
Returns the component's height in pixels.
void repaint()
Marks the whole component as needing to be redrawn.
Colour findColour(int colourID, bool inheritFromParent=false) const
Looks for a colour that has been registered with the given colour ID number.
int getWidth() const noexcept
Returns the component's width in pixels.
Rectangle< int > getLocalBounds() const noexcept
Returns the component's bounds, relative to its own origin.
virtual void colourChanged()
This method is called when a colour is changed by the setColour() method, or when the look-and-feel i...
void addChildComponent(Component *child, int zOrder=-1)
Adds a child component to this one.
A graphics context, used for drawing a component or image.
void fillPath(const Path &path) const
Fills a path using the currently selected 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.
A base class for drawing a custom MIDI keyboard component.
void setBlackNoteLengthProportion(float ratio) noexcept
Sets the length of the black notes as a proportion of the white note length.
void setLowestVisibleKey(int noteNumber)
If the keyboard extends beyond the size of the component, this will scroll it to show the given key a...
virtual void drawKeyboardBackground(Graphics &g, Rectangle< float > area)=0
Use this method to draw the background of the keyboard that will be drawn under the white and black n...
virtual void drawUpDownButton(Graphics &g, int w, int h, bool isMouseOver, bool isButtonPressed, bool movesOctavesUp)
This can be overridden to draw the up and down buttons that scroll the keyboard up/down in octaves.
virtual void drawWhiteKey(int midiNoteNumber, Graphics &g, Rectangle< float > area)=0
Use this method to draw a white key of the keyboard in a given rectangle.
void setOctaveForMiddleC(int octaveNumForMiddleC)
This sets the octave number which is shown as the octave number for middle C.
void resized() override
Called when this component's size has been changed.
void setScrollButtonWidth(int widthInPixels)
Changes the width used to draw the buttons that scroll the keyboard up/down in octaves.
void mouseWheelMove(const MouseEvent &, const MouseWheelDetails &) override
Called when the mouse-wheel is moved.
void setBlackNoteWidthProportion(float ratio) noexcept
Sets the width of the black notes as a proportion of the white note width.
virtual void drawBlackKey(int midiNoteNumber, Graphics &g, Rectangle< float > area)=0
Use this method to draw a black key of the keyboard in a given rectangle.
float getKeyStartPosition(int midiNoteNumber) const
Returns the position within the component of the left-hand edge of a key.
void setAvailableRange(int lowestNote, int highestNote)
Sets the range of midi notes that the keyboard will be limited to.
float getBlackNoteLength() const noexcept
Returns the absolute length of the black notes.
void setKeyWidth(float widthInPixels)
Changes the width used to draw the white keys.
void paint(Graphics &) override
Components can override this method to draw their content.
int getLowestVisibleKey() const noexcept
Returns the number of the first key shown in the component.
Rectangle< float > getRectangleForKey(int midiNoteNumber) const
Returns the rectangle for a given key.
virtual Range< float > getKeyPosition(int midiNoteNumber, float keyWidth) const
Calculates the position of a given midi-note.
KeyboardComponentBase(Orientation orientation)
Constructor.
void setScrollButtonsVisible(bool canScroll)
If set to true, then scroll buttons will appear at either end of the keyboard if there are too many n...
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.
float getTotalKeyboardWidth() const noexcept
Returns the total width needed to fit all the keys in the available range.
void setOrientation(Orientation newOrientation)
Changes the keyboard's current direction.
float getWhiteNoteLength() const noexcept
Returns the absolute length of the white notes.
float getBlackNoteWidthProportion() const noexcept
Returns the width of the black notes as a proportion of the white note width.
This structure is returned by the getNoteAndVelocityAtPosition() method.
static bool isMidiNoteBlack(int noteNumber) noexcept
Returns true if the given midi note number is a black key.
Contains position and status information about a mouse event.
A path is a sequence of lines and curves that may either form a closed shape or be open-ended.
Definition juce_Path.h:65
void addTriangle(float x1, float y1, float x2, float y2, float x3, float y3)
Adds a triangle to the path.
AffineTransform getTransformToScaleToFit(float x, float y, float width, float height, bool preserveProportions, Justification justificationType=Justification::centred) const
Returns a transform that can be used to rescale the path to fit into a given space.
void applyTransform(const AffineTransform &transform) noexcept
Applies a 2D transform to all the vertices in the path.
A pair of (x, y) coordinates.
Definition juce_Point.h:42
constexpr ValueType getY() const noexcept
Returns the point's y coordinate.
Definition juce_Point.h:75
ValueType y
The point's Y coordinate.
Definition juce_Point.h:252
ValueType x
The point's X coordinate.
Definition juce_Point.h:251
A general-purpose range object, that simply represents any linear range with a start and end point.
Definition juce_Range.h:40
constexpr ValueType getStart() const noexcept
Returns the start of the range.
Definition juce_Range.h:80
constexpr ValueType getEnd() const noexcept
Returns the end of the range.
Definition juce_Range.h:86
Manages a rectangle and allows geometric operations to be performed on it.
#define jassert(expression)
Platform-independent assertion macro.
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
This is a shorthand way of writing both a JUCE_DECLARE_NON_COPYABLE and JUCE_LEAK_DETECTOR macro for ...
#define jassertfalse
This will always cause an assertion failure.
typedef int
typedef float
JUCE Namespace.
constexpr bool approximatelyEqual(Type a, Type b, Tolerance< Type > tolerance=Tolerance< Type >{} .withAbsolute(std::numeric_limits< Type >::min()) .withRelative(std::numeric_limits< Type >::epsilon()))
Returns true if the two floating-point numbers are approximately equal.
constexpr Type jmin(Type a, Type b)
Returns the smaller of two values.
constexpr Type jmax(Type a, Type b)
Returns the larger 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
unsigned char uint8
A platform-independent 8-bit unsigned integer type.
Contains status information about a mouse wheel event.
void paintButton(Graphics &g, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) override
Subclasses should override this to actually paint the button's contents.
void clicked() override
This method is called when the button has been clicked.
Commonly used mathematical constants.
T swap(T... args)