iPlug2 - C++ Audio Plug-in Framework
Loading...
Searching...
No Matches
IVKeyboardControl.h
Go to the documentation of this file.
1/*
2 ==============================================================================
3
4 This file is part of the iPlug 2 library. Copyright (C) the iPlug 2 developers.
5
6 See LICENSE.txt for more info.
7
8 ==============================================================================
9*/
10
11#pragma once
12
19#include "IControl.h"
20#include "IPlugMidi.h"
21
22BEGIN_IPLUG_NAMESPACE
23BEGIN_IGRAPHICS_NAMESPACE
24
25/*
26
27 IVKeyboardControl by Eugene Yakshin, 2018
28
29 based on
30
31 IKeyboardControl
32 (c) Theo Niessink 2009, 2010
33 <http://www.taletn.com/>
34
35 This software is provided 'as-is', without any express or implied
36 warranty. In no event will the authors be held liable for any damages
37 arising from the use of this software.
38
39 Permission is granted to anyone to use this software for any purpose,
40 including commercial applications, and to alter it and redistribute it
41 freely, subject to the following restrictions:
42
43 1. The origin of this software must not be misrepresented; you must not
44 claim that you wrote the original software. If you use this software in a
45 product, an acknowledgment in the product documentation would be
46 appreciated but is not required.
47 2. Altered source versions must be plainly marked as such, and must not be
48 misrepresented as being the original software.
49 3. This notice may not be removed or altered from any source distribution.
50
51
52 This keyboard is runtime customizable. Any key range is supported.
53 Key proportions, colors and some other design elements can be changed at any time too.
54 See the interface for details.
55 */
56
60{
61public:
62 static const IColor DEFAULT_BK_COLOR;
63 static const IColor DEFAULT_WK_COLOR;
64 static const IColor DEFAULT_PK_COLOR;
65 static const IColor DEFAULT_FR_COLOR;
66 static const IColor DEFAULT_HK_COLOR;
67
68 IVKeyboardControl(const IRECT& bounds, int minNote = 48, int maxNote = 72, bool roundedKeys = false,
69 const IColor& WK_COLOR = DEFAULT_WK_COLOR,
70 const IColor& BK_COLOR = DEFAULT_BK_COLOR,
71 const IColor& PK_COLOR = DEFAULT_PK_COLOR,
72 const IColor& FR_COLOR = DEFAULT_FR_COLOR,
73 const IColor& HK_COLOR = DEFAULT_HK_COLOR)
74 : IControl(bounds, kNoParameter)
75 , mWK_COLOR(WK_COLOR)
76 , mBK_COLOR(BK_COLOR)
77 , mPK_COLOR(PK_COLOR)
78 , mFR_COLOR(FR_COLOR)
79 , mHK_COLOR(HK_COLOR)
80 , mRoundedKeys(roundedKeys)
81 {
82 mText.mFGColor = FR_COLOR;
83 mDblAsSingleClick = true;
84 bool keepWidth = !(bounds.W() <= 0.0);
85 if (bounds.W() <= 0.0)
86 {
87 mRECT.R = mRECT.L + mRECT.H();
88 mTargetRECT = mRECT;
89 }
90
91 SetNoteRange(minNote, maxNote, keepWidth);
92 SetWantsMidi(true);
93 }
94
95 void OnMouseDown(float x, float y, const IMouseMod& mod) override
96 {
97 int prevKey = mLastTouchedKey;
98 mLastTouchedKey = GetKeyAtPoint(x, y);
99
100 SetKeyIsPressed(mLastTouchedKey, true);
101
102 mMouseOverKey = mLastTouchedKey;
103
104 if(mLastTouchedKey != prevKey)
105 {
106 mLastVelocity = GetVelocity(y);
107
108 TriggerMidiMsgFromKeyPress(mLastTouchedKey, (int) (mLastVelocity * 127.f));
109 }
110
111 SetDirty(true);
112 }
113
114 void OnMouseUp(float x, float y, const IMouseMod& mod) override
115 {
116 if (mLastTouchedKey > -1)
117 {
118 SetKeyIsPressed(mLastTouchedKey, false);
119 TriggerMidiMsgFromKeyPress(mLastTouchedKey, 0);
120
121 mLastTouchedKey = -1;
122 mMouseOverKey = -1;
123 mLastVelocity = 0.;
124
125 SetDirty(false);
126 }
127 }
128
129 void OnMouseOut() override
130 {
131 if (mLastTouchedKey > -1 || mShowNoteAndVel)
132 {
133 mLastTouchedKey = -1;
134 mMouseOverKey = -1;
135 mLastVelocity = 0.;
136 SetDirty(false);
137 }
138 }
139
140 void OnMouseDrag(float x, float y, float dX, float dY, const IMouseMod& mod) override
141 {
142 int prevKey = mLastTouchedKey;
143 mLastTouchedKey = GetKeyAtPoint(x, y);
144
145 SetKeyIsPressed(mLastTouchedKey, true);
146
147 mMouseOverKey = mLastTouchedKey;
148
149 if(mLastTouchedKey != prevKey)
150 {
151 mLastVelocity = GetVelocity(y);
152
153 TriggerMidiMsgFromKeyPress(mLastTouchedKey, (int) (mLastVelocity * 127.f));
154
155 TriggerMidiMsgFromKeyPress(prevKey, 0);
156 SetKeyIsPressed(prevKey, false);
157 }
158
159 SetDirty(true);
160
161 }
162
163 void OnMouseOver(float x, float y, const IMouseMod& mod) override
164 {
165 if (mShowNoteAndVel)
166 {
167 mMouseOverKey = GetKeyAtPoint(x, y);
168 SetDirty(false);
169 }
170 }
171
172 void OnTouchCancelled(float x, float y, const IMouseMod& mod) override
173 {
174 if (mLastTouchedKey > -1)
175 {
176 SetKeyIsPressed(mLastTouchedKey, false);
177 TriggerMidiMsgFromKeyPress(mLastTouchedKey, 0);
178
179 mLastTouchedKey = -1;
180 mMouseOverKey = -1;
181 mLastVelocity = 0.;
182
183 SetDirty(false);
184 }
185 }
186
187 void OnResize() override
188 {
189 float r = mRECT.W() / mTargetRECT.W();
190 float dx = mRECT.L - mTargetRECT.L;
191 mWKWidth *= r;
192 for (int i = 0; i < NKeys(); ++i)
193 {
194 float* pKeyL = GetKeyXPos(i);
195 float d = *pKeyL - mRECT.L;
196 *pKeyL = mRECT.L + d * r + dx;
197 }
198
199 mTargetRECT = mRECT;
200 RecreateKeyBounds(true);
201 SetDirty(false);
202 }
203
204 void OnMidi(const IMidiMsg& msg) override
205 {
206 switch (msg.StatusMsg())
207 {
208 case IMidiMsg::kNoteOn:
209 SetNoteFromMidi(msg.NoteNumber(), (msg.Velocity() != 0));
210 break;
211 case IMidiMsg::kNoteOff:
212 SetNoteFromMidi(msg.NoteNumber(), false);
213 break;
214 case IMidiMsg::kControlChange:
215 if(msg.ControlChangeIdx() == IMidiMsg::kAllNotesOff)
216 ClearNotesFromMidi();
217 break;
218 default: break;
219 }
220
221 SetDirty(false);
222 }
223
224 void DrawKey(IGraphics& g, const IRECT& bounds, const IColor& color)
225 {
226 if(mRoundedKeys)
227 {
228 g.FillRoundRect(color, bounds, 0., 0., mRoundness, mRoundness/*, &blend*/);
229 }
230 else
231 g.FillRect(color, bounds/*, &blend*/);
232 }
233
234 void Draw(IGraphics& g) override
235 {
236 IColor shadowColor = IColor(60, 0, 0, 0);
237
238 float BKBottom = mRECT.T + mRECT.H() * mBKHeightRatio;
239 float BKWidth = GetBKWidth();
240
241 // first draw white keys
242 for (int i = 0; i < NKeys(); ++i)
243 {
244 if (!IsBlackKey(i))
245 {
246 float kL = *GetKeyXPos(i);
247 IRECT keyBounds = IRECT(kL, mRECT.T, kL + mWKWidth, mRECT.B);
248
249 DrawKey(g, keyBounds, i == mHighlight ? mHK_COLOR : mWK_COLOR);
250
251 if (GetKeyIsPressed(i))
252 {
253 // draw played white key
254 DrawKey(g, keyBounds, mPK_COLOR);
255
256 if (mDrawShadows)
257 {
258 IRECT shadowBounds = keyBounds;
259 shadowBounds.R = shadowBounds.L + 0.35f * shadowBounds.W();
260
261 if(!mRoundedKeys)
262 g.FillRect(shadowColor, shadowBounds, &mBlend);
263 else {
264 g.FillRoundRect(shadowColor, shadowBounds, 0., 0., mRoundness, mRoundness, &mBlend); // this one looks strange with rounded corners
265 }
266 }
267 }
268 if (mDrawFrame && i != 0)
269 { // only draw the left border if it doesn't overlay mRECT left border
270 g.DrawLine(mFR_COLOR, kL, mRECT.T, kL, mRECT.B, &mBlend, mFrameThickness);
271 if (i == NKeys() - 2 && IsBlackKey(NKeys() - 1))
272 g.DrawLine(mFR_COLOR, kL + mWKWidth, mRECT.T, kL + mWKWidth, mRECT.B, &mBlend, mFrameThickness);
273 }
274 }
275 }
276
277 // then blacks
278 for (int i = 0; i < NKeys(); ++i)
279 {
280 if (IsBlackKey(i))
281 {
282 float kL = *GetKeyXPos(i);
283 IRECT keyBounds = IRECT(kL, mRECT.T, kL + BKWidth, BKBottom);
284 // first draw underlying shadows
285 if (mDrawShadows && !GetKeyIsPressed(i) && i < NKeys() - 1)
286 {
287 IRECT shadowBounds = keyBounds;
288 float w = shadowBounds.W();
289 shadowBounds.L += 0.6f * w;
290 if (GetKeyIsPressed(i + 1))
291 {
292 // if white to the right is pressed, shadow is longer
293 w *= 1.3f;
294 shadowBounds.B = shadowBounds.T + 1.05f * shadowBounds.H();
295 }
296 shadowBounds.R = shadowBounds.L + w;
297 DrawKey(g, shadowBounds, shadowColor);
298 }
299 DrawKey(g, keyBounds, (i == mHighlight ? mHK_COLOR : mBK_COLOR.WithContrast(IsDisabled() ? GRAYED_ALPHA : 0.f)));
300
301 if (GetKeyIsPressed(i))
302 {
303 // draw pressed black key
304 IColor cBP = mPK_COLOR;
305 cBP.A = (int) mBKAlpha;
306 g.FillRect(cBP, keyBounds, &mBlend);
307 }
308
309 if(!mRoundedKeys)
310 {
311 // draw l, r and bottom if they don't overlay the mRECT borders
312 if (mBKHeightRatio != 1.0)
313 g.DrawLine(mFR_COLOR, kL, BKBottom, kL + BKWidth, BKBottom, &mBlend);
314 if (i > 0)
315 g.DrawLine(mFR_COLOR, kL, mRECT.T, kL, BKBottom, &mBlend);
316 if (i != NKeys() - 1)
317 g.DrawLine(mFR_COLOR, kL + BKWidth, mRECT.T, kL + BKWidth, BKBottom, &mBlend);
318 }
319 }
320 }
321
322 if (mDrawFrame)
323 g.DrawRect(mFR_COLOR, mRECT, &mBlend, mFrameThickness);
324
325 if (mShowNoteAndVel)
326 {
327 if (mMouseOverKey > -1)
328 {
329 IRECT r = IRECT(*GetKeyXPos(mMouseOverKey), mRECT.T, 0, 0);
330 r.B = r.T + 1.2f * mText.mSize;
331 r.R = r.L + 35.0f;
332 WDL_String t;
333 GetNoteNameStr(mMinNote + mMouseOverKey, false, t);
334 if (mLastTouchedKey > -1)
335 {
336 t.AppendFormatted(16, ", vel: %3.2f", mLastVelocity * 127.f);
337 r.R += 60.0;
338 }
339 float e = r.R - mRECT.R;
340 if (e > 0.0)
341 {
342 r.L -= e;
343 r.R -= e;
344 }
345 g.FillRect(mWK_COLOR, r, &mBlend);
346 g.DrawRect(mFR_COLOR, r, &mBlend);
347 g.DrawText(mText, t.Get(), r, &mBlend);
348 }
349 }
350
351#ifdef _DEBUG
352 //g.DrawRect(COLOR_GREEN, mTargetRECT);
353 //g.DrawRect(COLOR_BLUE, mRECT);
354 WDL_String ti;
355 ti.SetFormatted(32, "key: %d, vel: %3.2f", mLastTouchedKey, mLastVelocity * 127.f);
356 //ti.SetFormatted(16, "mBAlpha: %d", mBAlpha);
357 IText txt(20, COLOR_RED);
358 IRECT tr(mRECT.L + 20, mRECT.B - 20, mRECT.L + 160, mRECT.B);
359 g.DrawText(txt, ti.Get(), tr, &mBlend);
360#endif
361 }
362
363#pragma mark -
364
365 void SetNoteRange(int min, int max, bool keepWidth = true)
366 {
367 if (min < 0 || max < 0) return;
368 if (min < max)
369 {
370 mMinNote = min;
371 mMaxNote = max;
372 }
373 else
374 {
375 mMinNote = max;
376 mMaxNote = min;
377 }
378
379 mPressedKeys.Resize(NKeys());
380 memset(mPressedKeys.Get(), 0, mPressedKeys.GetSize() * sizeof(bool));
381
382 RecreateKeyBounds(keepWidth);
383 }
384
385 void SetNoteFromMidi(int noteNum, bool played)
386 {
387 if (noteNum < mMinNote || noteNum > mMaxNote) return;
388 SetKeyIsPressed(noteNum - mMinNote, played);
389 }
390
391 void SetKeyIsPressed(int key, bool pressed)
392 {
393 mPressedKeys.Get()[key] = pressed;
394 SetDirty(false);
395 }
396
397 void SetKeyHighlight(int key)
398 {
399 mHighlight = key;
400 SetDirty(false);
401 }
402
403 void ClearNotesFromMidi()
404 {
405 memset(mPressedKeys.Get(), 0, mPressedKeys.GetSize() * sizeof(bool));
406 SetDirty(false);
407 }
408
409 void SetBlackToWhiteRatios(float widthRatio, float heightRatio = 0.6)
410 {
411 widthRatio = Clip(widthRatio, 0.1f, 1.f);
412 heightRatio = Clip(heightRatio, 0.1f, 1.f);
413
414 float halfW = 0.5f * mWKWidth * mBKWidthRatio;
415 float r = widthRatio / mBKWidthRatio;
416 mBKWidthRatio = widthRatio;
417 mBKHeightRatio = heightRatio;
418
419 for (int i = 0; i < NKeys(); ++i)
420 {
421 if (IsBlackKey(i))
422 {
423 float* pKeyL = GetKeyXPos(i);
424 float mid = *pKeyL + halfW;
425 *pKeyL = mid - halfW * r;
426 if (*pKeyL < mRECT.L)
427 *pKeyL = mRECT.L;
428 }
429 }
430
431 SetDirty(false);
432 }
433
434 void SetHeight(float h, bool keepAspectRatio = false)
435 {
436 if (h <= 0.0) return;
437 float r = h / mRECT.H();
438 mRECT.B = mRECT.T + mRECT.H() * r;
439
440 mTargetRECT = mRECT;
441
442 if (keepAspectRatio)
443 SetWidth(mRECT.W() * r);
444 SetDirty(false);
445 }
446
447 void SetWidth(float w, bool keepAspectRatio = false)
448 {
449 if (w <= 0.0) return;
450 float r = w / mRECT.W();
451 mRECT.R = mRECT.L + mRECT.W() * r;
452 mWKWidth *= r;
453 for (int i = 0; i < NKeys(); ++i)
454 {
455 float* pKeyL = GetKeyXPos(i);
456 float d = *pKeyL - mRECT.L;
457 *pKeyL = mRECT.L + d * r;
458 }
459
460 mTargetRECT = mRECT;
461
462 if (keepAspectRatio)
463 SetHeight(mRECT.H() * r);
464
465 SetDirty(false);
466 }
467
468 void SetShowNotesAndVelocity(bool show)
469 {
470 mShowNoteAndVel = show;
471 }
472
473 void SetColors(const IColor BKColor, const IColor& WKColor, const IColor& PKColor = DEFAULT_PK_COLOR, const IColor& FRColor = DEFAULT_FR_COLOR)
474 {
475 mBK_COLOR = BKColor;
476 mWK_COLOR = WKColor;
477 mPK_COLOR = PKColor;
478 mFR_COLOR = FRColor;
479
480 mBKAlpha = (float) PKColor.A;
481
482 if (mBKAlpha < 240.f)
483 {
484 const float lumWK = WKColor.GetLuminosity() * WKColor.A / 255.f;
485 const float adjustment = PKColor.A / 255.f;
486 const float lumPK = PKColor.GetLuminosity() * adjustment;
487 const float lumRes = (1.f - adjustment) * lumWK + lumPK;
488 const float lumDW = lumRes - lumWK;
489 const float lumBK = BKColor.GetLuminosity() * BKColor.A / 255.f;
490
491 if ((lumDW < 0 && lumBK < lumWK) || (lumDW > 0 && lumBK > lumWK))
492 {
493 float dbWB = lumWK - lumBK; // not used in the conditions ^^ for readability
494 mBKAlpha += (255.f - mBKAlpha) * (1.f - dbWB * dbWB / 255.f / 255.f) + 0.5f;
495 }
496 else
497 mBKAlpha += lumDW + 0.5f;
498
499 mBKAlpha = Clip(mBKAlpha, 15.f, 255.f);
500 }
501
502 SetDirty(false);
503 }
504
505 // returns pressed Midi note number
506 int GetMidiNoteNumberForKey(int key) const
507 {
508 if (key > -1) return mMinNote + key;
509 else return -1;
510 }
511
512// double GetVelocity() const { return mVelocity * 127.f; }
513
514private:
515 void RecreateKeyBounds(bool keepWidth)
516 {
517 if (keepWidth)
518 mWKWidth = 0.f;
519
520 // create size-independent data.
521 mIsBlackKeyList.Resize(NKeys());
522 mKeyXPos.Resize(NKeys());
523
524 float numWhites = 0.f;
525 for (int n = mMinNote, i = 0; n <= mMaxNote; ++n, i++)
526 {
527 if (n % 12 == 1 || n % 12 == 3 || n % 12 == 6 || n % 12 == 8 || n % 12 == 10)
528 {
529 mIsBlackKeyList.Get()[i] = true;
530 }
531 else
532 {
533 mIsBlackKeyList.Get()[i] = false;
534 numWhites += 1.f;
535 }
536 }
537
538 // black key middle isn't aligned exactly between whites
539 float WKPadStart = 0.f; // 1st note may be black
540 float WKPadEnd = 0.f; // last note may be black
541
542 auto GetShiftForPitchClass = [this](int pitch) {
543 // usually black key width + distance to the closest black key = white key width,
544 // and often b width is ~0.6 * w width
545 if (pitch == 0) return 0.f;
546 else if (pitch % 12 == 1) return 7.f / 12.f;
547 else if (pitch % 12 == 3) return 5.f / 12.f;
548 else if (pitch % 12 == 6) return 2.f / 3.f;
549 else if (pitch % 12 == 8) return 0.5f;
550 else if (pitch % 12 == 10) return 1.f / 3.f;
551 else return 0.f;
552 };
553
554 WKPadStart = GetShiftForPitchClass(mMinNote);
555
556 if (mMinNote != mMaxNote && IsBlackKey(mIsBlackKeyList.GetSize() - 1))
557 WKPadEnd = 1.f - GetShiftForPitchClass(mMaxNote);
558
559 // build rects
560 if (mWKWidth == 0.f)
561 mWKWidth = 0.2f * mRECT.H(); // first call from the constructor
562
563 if (keepWidth)
564 {
565 mWKWidth = mRECT.W();
566 if (numWhites)
567 mWKWidth /= (numWhites + mBKWidthRatio * (WKPadStart + WKPadEnd));
568 }
569
570 float BKWidth = mWKWidth;
571
572 if (numWhites)
573 BKWidth *= mBKWidthRatio;
574
575 float prevWKLeft = mRECT.L;
576
577 for (int k = 0; k < mIsBlackKeyList.GetSize(); ++k)
578 {
579 if (IsBlackKey(k))
580 {
581 float l = prevWKLeft;
582 if (k != 0)
583 {
584 l -= GetShiftForPitchClass(mMinNote + k) * BKWidth;
585 }
586 else prevWKLeft += WKPadStart * BKWidth;
587 mKeyXPos.Get()[k] = l;
588 }
589 else
590 {
591 mKeyXPos.Get()[k] = prevWKLeft;
592 prevWKLeft += mWKWidth;
593 }
594 }
595
596 mTargetRECT = mRECT;
597 SetDirty(false);
598 }
599
600 int GetKeyAtPoint(float x, float y)
601 {
602 IRECT clipRect = mRECT.GetPadded(-2);
603 clipRect.Constrain(x, y);
604
605 float BKBottom = mRECT.T + mRECT.H() * mBKHeightRatio;
606 float BKWidth = GetBKWidth();
607
608 // black keys are on top
609 int k = -1;
610 for (int i = 0; i < NKeys(); ++i)
611 {
612 if (IsBlackKey(i))
613 {
614 float kL = *GetKeyXPos(i);
615 IRECT keyBounds = IRECT(kL, mRECT.T, kL + BKWidth, BKBottom);
616 if (keyBounds.Contains(x, y))
617 {
618 k = i;
619 break;
620 }
621 }
622 }
623
624 if (k == -1)
625 {
626 for (int i = 0; i < NKeys(); ++i)
627 {
628 if (!IsBlackKey(i))
629 {
630 float kL = *GetKeyXPos(i);
631 IRECT keyBounds = IRECT(kL, mRECT.T, kL + mWKWidth, mRECT.B);
632 if (keyBounds.Contains(x, y))
633 {
634 k = i;
635 break;
636 }
637 }
638 }
639 }
640
641 return k;
642 }
643
644 float GetVelocity(float yPos)
645 {
646 float velocity = 0.;
647
648 if (mLastTouchedKey > -1)
649 {
650 float h = mRECT.H();
651
652 if (IsBlackKey(mLastTouchedKey))
653 h *= mBKHeightRatio;
654
655 float fracPos = (yPos - mRECT.T) / (0.95f * h); // 0.95 is to get max velocity around the bottom
656
657 velocity = Clip(fracPos, 1.f / 127.f, 1.f);
658 }
659
660 return velocity;
661 }
662
663 void GetNoteNameStr(int midiNoteNum, bool addOctave, WDL_String& str)
664 {
665 int oct = midiNoteNum / 12;
666 midiNoteNum -= 12 * oct;
667 const char* notes[12] = { "C","C#","D","D#","E","F","F#","G","G#","A","A#","B" };
668 const char* n = notes[midiNoteNum];
669 str.Set(n);
670 if (addOctave)
671 str.AppendFormatted(2, "%d", --oct);
672 }
673
674 bool IsBlackKey(int i) const { return *(mIsBlackKeyList.Get() + i); }
675
676 float* GetKeyXPos(int i) { return mKeyXPos.Get() + i; }
677
678 bool GetKeyIsPressed(int i) const { return *(mPressedKeys.Get() + i); }
679
680 int NKeys() const { return mMaxNote - mMinNote + 1; }
681
682 float GetBKWidth() const
683 {
684 float w = mWKWidth;
685 if (NKeys() > 1)
686 w *= mBKWidthRatio;
687 return w;
688 }
689
690 void TriggerMidiMsgFromKeyPress(int key, int velocity)
691 {
692 IMidiMsg msg;
693
694 const int nn = GetMidiNoteNumberForKey(key);
695
696 if(velocity > 0)
697 msg.MakeNoteOnMsg(nn, velocity, 0);
698 else
699 msg.MakeNoteOffMsg(nn, 0);
700
702 }
703
704protected:
705 IColor mWK_COLOR;
706 IColor mBK_COLOR;
707 IColor mPK_COLOR;
708 IColor mFR_COLOR;
709 IColor mHK_COLOR;
710
711 bool mRoundedKeys = false;
712 float mRoundness = 5.f;
713 bool mDrawShadows = false;
714 bool mDrawFrame = true;
715 float mFrameThickness = 1.f;
716 bool mShowNoteAndVel = false;
717 float mWKWidth = 0.f;
718 float mBKWidthRatio = 0.6f;
719 float mBKHeightRatio = 0.6f;
720 float mBKAlpha = 100.f;
721 int mLastTouchedKey = -1;
722 float mLastVelocity = 0.f;
723 int mMouseOverKey = -1;
724 int mMinNote, mMaxNote;
725 WDL_TypedBuf<bool> mIsBlackKeyList;
726 WDL_TypedBuf<bool> mPressedKeys;
727 WDL_TypedBuf<float> mKeyXPos;
728 int mHighlight = -1;
729};
730
734{
735 static constexpr int kSpringAnimationTime = 50;
736 static constexpr int kNumRungs = 10;
737public:
738 static constexpr int kMessageTagSetPitchBendRange = 0;
739
743 IWheelControl(const IRECT& bounds, IMidiMsg::EControlChangeMsg cc = IMidiMsg::EControlChangeMsg::kNoCC, int initBendRange = 12)
744 : ISliderControlBase(bounds, kNoParameter, EDirection::Vertical, DEFAULT_GEARING, 40.f)
745 , mPitchBendRange(initBendRange)
746 , mCC(cc)
747 {
748 mMenu.AddItem("1 semitone");
749 mMenu.AddItem("2 semitones");
750 mMenu.AddItem("Fifth");
751 mMenu.AddItem("Octave");
752
753 SetValue(cc == IMidiMsg::EControlChangeMsg::kNoCC ? 0.5 : 0.);
754 SetWantsMidi(true);
755 SetActionFunction([cc](IControl* pControl){
756 IMidiMsg msg;
757 if(cc == IMidiMsg::EControlChangeMsg::kNoCC) // pitchbend
758 msg.MakePitchWheelMsg((pControl->GetValue() * 2.) - 1.);
759 else
760 msg.MakeControlChangeMsg(cc, pControl->GetValue());
761
762 pControl->GetDelegate()->SendMidiMsgFromUI(msg);
763 });
764 }
765
766 void Draw(IGraphics& g) override
767 {
768 IRECT handleBounds = mRECT.GetPadded(-10.f);
769 const float stepSize = handleBounds.H() / (float) kNumRungs;
770 g.FillRoundRect(DEFAULT_SHCOLOR, mRECT.GetPadded(-5.f));
771
772 if(!g.CheckLayer(mLayer))
773 {
774 const IRECT layerRect = handleBounds.GetMidVPadded(handleBounds.H() + stepSize);
775
776 if(layerRect.W() > 0 && layerRect.H() > 0)
777 {
778 g.StartLayer(this, layerRect);
779 g.DrawGrid(COLOR_BLACK.WithOpacity(0.5f), layerRect, 0.f, stepSize, nullptr, 2.f);
780 mLayer = g.EndLayer();
781 }
782 }
783
784 // NanoVG only has 2 stop gradients
785 IRECT r = handleBounds.FracRectVertical(0.5, true);
786 g.PathRect(r);
787 g.PathFill(IPattern::CreateLinearGradient(r, EDirection::Vertical, {{COLOR_BLACK, 0.f},{COLOR_MID_GRAY, 1.f}}));
788 r = handleBounds.FracRectVertical(0.51f, false); // slight overlap
789 g.PathRect(r);
790 g.PathFill(IPattern::CreateLinearGradient(r, EDirection::Vertical, {{COLOR_MID_GRAY, 0.f},{COLOR_BLACK, 1.f}}));
791
792 const float value = static_cast<float>(GetValue());
793 const float y = (handleBounds.H() - (stepSize)) * value;
794 const float triangleRamp = std::fabs(value-0.5f) * 2.f;
795
796 g.DrawBitmap(mLayer->GetBitmap(), handleBounds, 0, (int) y);
797
798 const IRECT cutoutBounds = handleBounds.GetFromBottom(stepSize).GetTranslated(0, -y);
799 g.PathRect(cutoutBounds);
800 g.PathFill(IPattern::CreateLinearGradient(cutoutBounds, EDirection::Vertical,
801 {
802 //TODO: this can be improved
803 {COLOR_BLACK.WithContrast(iplug::Lerp(0.f, 0.5f, triangleRamp)), 0.f},
804 {COLOR_BLACK.WithContrast(iplug::Lerp(0.5f, 0.f, triangleRamp)), 1.f}
805 }));
806
807 g.DrawVerticalLine(COLOR_BLACK, cutoutBounds, 0.f);
808 g.DrawVerticalLine(COLOR_BLACK, cutoutBounds, 1.f);
809 g.DrawRect(COLOR_BLACK, handleBounds);
810 }
811
812 void OnMidi(const IMidiMsg& msg) override
813 {
814 if(mCC == IMidiMsg::EControlChangeMsg::kNoCC)
815 {
816 if(msg.StatusMsg() == IMidiMsg::kPitchWheel)
817 {
818 SetValue((msg.PitchWheel() + 1.) * 0.5);
819 SetDirty(false);
820 }
821 }
822 else
823 {
824 if(msg.ControlChangeIdx() == mCC)
825 {
826 SetValue(msg.ControlChange(mCC));
827 SetDirty(false);
828 }
829 }
830 }
831
832 void OnMouseWheel(float x, float y, const IMouseMod &mod, float d) override
833 {
834 /* NO-OP */
835 }
836
837 void OnPopupMenuSelection(IPopupMenu* pSelectedMenu, int) override
838 {
839 if(pSelectedMenu)
840 {
841 switch (pSelectedMenu->GetChosenItemIdx())
842 {
843 case 0: mPitchBendRange = 1; break;
844 case 1: mPitchBendRange = 2; break;
845 case 2: mPitchBendRange = 7; break;
846 case 3:
847 default:
848 mPitchBendRange = 12; break;
849 }
850
851 GetDelegate()->SendArbitraryMsgFromUI(kMessageTagSetPitchBendRange, GetTag(), sizeof(int), &mPitchBendRange);
852 }
853 }
854
855 void OnMouseDown(float x, float y, const IMouseMod &mod) override
856 {
857 if(mod.R && mCC == IMidiMsg::EControlChangeMsg::kNoCC)
858 {
859 switch (mPitchBendRange)
860 {
861 case 1: mMenu.CheckItemAlone(0); break;
862 case 2: mMenu.CheckItemAlone(1); break;
863 case 7: mMenu.CheckItemAlone(2); break;
864 case 12: mMenu.CheckItemAlone(3); break;
865 default:
866 break;
867 }
868
869 GetUI()->CreatePopupMenu(*this, mMenu, x, y);
870 }
871 else
873 }
874
875 void OnMouseUp(float x, float y, const IMouseMod &mod) override
876 {
877 if(mCC == IMidiMsg::EControlChangeMsg::kNoCC) // pitchbend
878 {
879 double startValue = GetValue();
880 SetAnimation([startValue](IControl* pCaller) {
881 pCaller->SetValue(iplug::Lerp(startValue, 0.5, Clip(pCaller->GetAnimationProgress(), 0., 1.)));
882 if(pCaller->GetAnimationProgress() > 1.) {
883 pCaller->SetDirty(true);
884 pCaller->OnEndAnimation();
885 return;
886 }
887 }, kSpringAnimationTime);
888 }
889
891 }
892
893private:
894 IPopupMenu mMenu;
895 int mPitchBendRange;
897 ILayerPtr mLayer;
898};
899
900END_IGRAPHICS_NAMESPACE
901END_IPLUG_NAMESPACE
This file contains the base IControl implementation, along with some base classes for specific types ...
MIDI and sysex structs/utilites.
The lowest level base class of an IGraphics control.
Definition: IControl.h:49
IGraphics * GetUI()
Definition: IControl.h:467
void SetWantsMidi(bool enable=true)
Specify whether this control wants to know about MIDI messages sent to the UI.
Definition: IControl.h:424
bool IsDisabled() const
Definition: IControl.h:362
IGEditorDelegate * GetDelegate()
Gets a pointer to the class implementing the IEditorDelegate interface that handles parameter changes...
Definition: IControl.h:449
double GetAnimationProgress() const
Get the progress in a control's animation, in the range 0-1.
Definition: IControl.cpp:431
int GetTag() const
Get the control's tag.
Definition: IControl.h:421
virtual void SetValue(double value, int valIdx=0)
Set one of the control's values.
Definition: IControl.cpp:147
double GetValue(int valIdx=0) const
Get the control's value.
Definition: IControl.cpp:153
virtual void SetDirty(bool triggerAction=true, int valIdx=kNoValIdx)
Mark the control as dirty, i.e.
Definition: IControl.cpp:198
IControl * SetActionFunction(IActionFunction actionFunc)
Set an Action Function for this control.
Definition: IControl.h:206
void SetAnimation(IAnimationFunction func)
Set the animation function.
Definition: IControl.h:492
virtual void SendMidiMsgFromUI(const IMidiMsg &msg)
SendMidiMsgFromUI (Abbreviation: SMMFUI) This method should be used when sending a MIDI message from ...
virtual void SendArbitraryMsgFromUI(int msgTag, int ctrlTag=kNoTag, int dataSize=0, const void *pData=nullptr)
SendArbitraryMsgFromUI (Abbreviation: SAMFUI)
The lowest level base class of an IGraphics context.
Definition: IGraphics.h:86
virtual void DrawRect(const IColor &color, const IRECT &bounds, const IBlend *pBlend=0, float thickness=1.f)
Draw a rectangle to the graphics context.
Definition: IGraphics.cpp:2497
virtual void PathFill(const IPattern &pattern, const IFillOptions &options=IFillOptions(), const IBlend *pBlend=0)=0
Fill the current current path.
void DrawText(const IText &text, const char *str, const IRECT &bounds, const IBlend *pBlend=0)
Draw some text to the graphics context in a specific rectangle.
Definition: IGraphics.cpp:670
void CreatePopupMenu(IControl &control, IPopupMenu &menu, const IRECT &bounds, int valIdx=0)
Shows a pop up/contextual menu in relation to a rectangular region of the graphics context.
Definition: IGraphics.cpp:1960
void PathRect(const IRECT &bounds)
Add a rectangle to the current path.
Definition: IGraphics.cpp:2635
bool CheckLayer(const ILayerPtr &layer)
Test to see if a layer needs drawing, for instance if the control's bounds were changed.
Definition: IGraphics.cpp:2032
virtual void FillRect(const IColor &color, const IRECT &bounds, const IBlend *pBlend=0)
Fill a rectangular region of the graphics context with a color.
Definition: IGraphics.cpp:2569
virtual void DrawLine(const IColor &color, float x1, float y1, float x2, float y2, const IBlend *pBlend=0, float thickness=1.f)
Draw a line to the graphics context.
Definition: IGraphics.cpp:2416
virtual void FillRoundRect(const IColor &color, const IRECT &bounds, float cornerRadius=5.f, const IBlend *pBlend=0)
Fill a rounded rectangle with a color.
Definition: IGraphics.cpp:2576
virtual void DrawGrid(const IColor &color, const IRECT &bounds, float gridSizeH, float gridSizeV, const IBlend *pBlend=0, float thickness=1.f)
Draw a grid to the graphics context.
Definition: IGraphics.cpp:2424
void DrawVerticalLine(const IColor &color, const IRECT &bounds, float x, const IBlend *pBlend=0, float thickness=1.f)
Draw a vertical line, within a rectangular region of the graphics context.
Definition: IGraphics.cpp:787
void StartLayer(IControl *pOwner, const IRECT &r, bool cacheable=false)
Create an IGraphics layer.
Definition: IGraphics.cpp:1977
ILayerPtr EndLayer()
End an IGraphics layer.
Definition: IGraphics.cpp:2000
virtual void DrawBitmap(const IBitmap &bitmap, const IRECT &bounds, int srcX, int srcY, const IBlend *pBlend=0)=0
Draw a bitmap (raster) image to the graphics context.
A class for setting the contents of a pop up menu.
A base class for slider/fader controls, to handle mouse action and Sender.
Definition: IControl.h:1398
void OnMouseDown(float x, float y, const IMouseMod &mod) override
Implement this method to respond to a mouse down event on this control.
Definition: IControl.cpp:895
void OnMouseUp(float x, float y, const IMouseMod &mod) override
Implement this method to respond to a mouse up event on this control.
Definition: IControl.cpp:915
Vectorial keyboard control.
void OnMouseOut() override
Implement this method to respond to a mouseout event on this control.
void OnMouseUp(float x, float y, const IMouseMod &mod) override
Implement this method to respond to a mouse up event on this control.
void OnMouseOver(float x, float y, const IMouseMod &mod) override
Implement this method to respond to a mouseover event on this control.
void OnResize() override
Called when IControl is constructed or resized using SetRect().
void OnMidi(const IMidiMsg &msg) override
Implement to receive MIDI messages sent to the control if mWantsMidi == true, see IEditorDelegate:Sen...
void OnTouchCancelled(float x, float y, const IMouseMod &mod) override
Implement this method to respond to a touch cancel event on this control.
void OnMouseDown(float x, float y, const IMouseMod &mod) override
Implement this method to respond to a mouse down event on this control.
void Draw(IGraphics &g) override
Draw the control to the graphics context.
void OnMouseDrag(float x, float y, float dX, float dY, const IMouseMod &mod) override
Implement this method to respond to a mouse drag event on this control.
Vectorial "wheel" control for pitchbender/modwheel.
void OnMidi(const IMidiMsg &msg) override
Implement to receive MIDI messages sent to the control if mWantsMidi == true, see IEditorDelegate:Sen...
void OnPopupMenuSelection(IPopupMenu *pSelectedMenu, int) override
Implement this method to handle popup menu selection after IGraphics::CreatePopupMenu/IControlPromptU...
void OnMouseDown(float x, float y, const IMouseMod &mod) override
Implement this method to respond to a mouse down event on this control.
void Draw(IGraphics &g) override
Draw the control to the graphics context.
void OnMouseUp(float x, float y, const IMouseMod &mod) override
Implement this method to respond to a mouse up event on this control.
void OnMouseWheel(float x, float y, const IMouseMod &mod, float d) override
Implement this method to respond to a mouse wheel event on this control.
IWheelControl(const IRECT &bounds, IMidiMsg::EControlChangeMsg cc=IMidiMsg::EControlChangeMsg::kNoCC, int initBendRange=12)
Create a WheelControl.
std::unique_ptr< ILayer > ILayerPtr
ILayerPtr is a managed pointer for transferring the ownership of layers.
BEGIN_IPLUG_NAMESPACE T Clip(T x, T lo, T hi)
Clips the value x between lo and hi.
Used to manage color data, independent of draw class/platform.
IColor WithContrast(float c) const
Returns a new contrasted IColor based on this one.
IColor WithOpacity(float alpha) const
Returns a new IColor with a different opacity.
int GetLuminosity() const
Encapsulates a MIDI message and provides helper functions.
Definition: IPlugMidi.h:31
void MakeNoteOnMsg(int noteNumber, int velocity, int offset, int channel=0)
Make a Note On message.
Definition: IPlugMidi.h:145
double ControlChange(EControlChangeMsg idx) const
Get the value of a CC message.
Definition: IPlugMidi.h:340
void MakeNoteOffMsg(int noteNumber, int offset, int channel=0)
Make a Note Off message.
Definition: IPlugMidi.h:158
void MakePitchWheelMsg(double value, int channel=0, int offset=0)
Create a pitch wheel/bend message.
Definition: IPlugMidi.h:170
int Velocity() const
Get the velocity value of a NoteOn/NoteOff message.
Definition: IPlugMidi.h:270
void MakeControlChangeMsg(EControlChangeMsg idx, double value, int channel=0, int offset=0)
Create a CC message.
Definition: IPlugMidi.h:186
EControlChangeMsg
Constants for MIDI CC messages.
Definition: IPlugMidi.h:50
EControlChangeMsg ControlChangeIdx() const
Gets the controller index of a CC message.
Definition: IPlugMidi.h:333
double PitchWheel() const
Get the value from a Pitchwheel message.
Definition: IPlugMidi.h:321
int NoteNumber() const
Gets the MIDI note number.
Definition: IPlugMidi.h:255
EStatusMsg StatusMsg() const
Gets the MIDI Status message.
Definition: IPlugMidi.h:243
Used to manage mouse modifiers i.e.
static IPattern CreateLinearGradient(float x1, float y1, float x2, float y2, const std::initializer_list< IColorStop > &stops={})
Create a linear gradient IPattern.
Used to manage a rectangular area, independent of draw class/platform.
IRECT GetFromBottom(float amount) const
Get a subrect of this IRECT bounded in Y by 'amount' and the bottom edge.
IRECT GetTranslated(float x, float y) const
Get a translated copy of this rectangle.
IRECT FracRectVertical(float frac, bool fromTop=false) const
Returns a new IRECT with a height that is multiplied by frac.
float W() const
void Constrain(float &x, float &y) const
Ensure the point (x,y) is inside this IRECT.
float H() const
bool Contains(const IRECT &rhs) const
Returns true if this IRECT completely contains rhs.
IRECT GetPadded(float padding) const
Get a copy of this IRECT with each value padded by padding N.B.
IRECT GetMidVPadded(float padding) const
Get a copy of this IRECT where its height = 2 * padding but the center point on the Y-axis has not ch...
IText is used to manage font and text/text entry style for a piece of text on the UI,...