1 //=============================================================================
2 //  MusE Score
3 //  Linux Music Score Editor
4 //
5 //  Copyright (C) 2009 Werner Schweer and others
6 //
7 //  This program is free software; you can redistribute it and/or modify
8 //  it under the terms of the GNU General Public License version 2.
9 //
10 //  This program is distributed in the hope that it will be useful,
11 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //  GNU General Public License for more details.
14 //
15 //  You should have received a copy of the GNU General Public License
16 //  along with this program; if not, write to the Free Software
17 //  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 //=============================================================================
19 
20 #include "pianolevels.h"
21 
22 #include "pianoruler.h"
23 #include "pianokeyboard.h"
24 #include "pianoview.h"
25 #include "pianolevelsfilter.h"
26 #include "preferences.h"
27 #include "libmscore/segment.h"
28 #include "libmscore/score.h"
29 #include "libmscore/staff.h"
30 #include "libmscore/chord.h"
31 #include "libmscore/rest.h"
32 #include "libmscore/note.h"
33 #include "libmscore/slur.h"
34 #include "libmscore/tie.h"
35 #include "libmscore/tuplet.h"
36 #include "libmscore/noteevent.h"
37 
38 namespace Ms {
39 
40 //---------------------------------------------------------
41 //   PianoLevels
42 //---------------------------------------------------------
43 
PianoLevels(QWidget * parent)44 PianoLevels::PianoLevels(QWidget *parent)
45     : QWidget(parent)
46        {
47        setMouseTracking(true);
48        _score     = nullptr;
49        _xpos      = 0;
50        _xZoom     = X_ZOOM_INITIAL;
51        _locator   = nullptr;
52        _staff     = nullptr;
53        _tuplet    = 1;
54        _subdiv    = 0;
55        _levelsIndex = 0;
56        minBeatGap = 20;
57        vMargin    = 10;
58        levelLen   = 20;
59        mouseDown  = false;
60        dragging   = false;
61        }
62 
63 //---------------------------------------------------------
64 //   ~PianoLevels
65 //---------------------------------------------------------
66 
~PianoLevels()67 PianoLevels::~PianoLevels()
68       {
69       clearNoteData();
70       }
71 
72 //---------------------------------------------------------
73 //   setScore
74 //---------------------------------------------------------
75 
setScore(Score * s,Pos * lc)76 void PianoLevels::setScore(Score* s, Pos* lc)
77       {
78       _score = s;
79       _locator = lc;
80       if (_score)
81             _cursor.setContext(_score->tempomap(), _score->sigmap());
82       setEnabled(_score != 0);
83       }
84 
85 //---------------------------------------------------------
86 //   setXpos
87 //---------------------------------------------------------
88 
setXpos(int val)89 void PianoLevels::setXpos(int val)
90       {
91       _xpos = val;
92       update();
93       }
94 
95 
96 //---------------------------------------------------------
97 //   pixelXToTick
98 //---------------------------------------------------------
99 
pixelXToTick(int pixX)100 int PianoLevels::pixelXToTick(int pixX) {
101       return static_cast<int>((pixX + _xpos) / _xZoom) - MAP_OFFSET;
102       }
103 
104 
105 //---------------------------------------------------------
106 //   tickToPixelX
107 //---------------------------------------------------------
108 
tickToPixelX(int tick)109 int PianoLevels::tickToPixelX(int tick) {
110       return static_cast<int>(tick + MAP_OFFSET) * _xZoom - _xpos;
111       }
112 
113 
114 //---------------------------------------------------------
115 //   paintEvent
116 //---------------------------------------------------------
117 
paintEvent(QPaintEvent * e)118 void PianoLevels::paintEvent(QPaintEvent* e)
119       {
120       QPainter p(this);
121 
122       QColor colPianoBg;
123       QColor noteDeselected;
124       QColor noteSelected;
125 
126       QColor colGridLine;
127       QColor colText;
128 
129       switch (preferences.effectiveGlobalStyle()) {
130             case MuseScoreEffectiveStyleType::DARK_FUSION:
131                   colPianoBg = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_BG_BASE_COLOR));
132                   noteDeselected = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_NOTE_UNSEL_COLOR));
133                   noteSelected = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_NOTE_SEL_COLOR));
134 
135                   colGridLine = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_BG_GRIDLINE_COLOR));
136                   colText = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_BG_TEXT_COLOR));
137                   break;
138             default:
139                   colPianoBg = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_BG_BASE_COLOR));
140                   noteDeselected = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_NOTE_UNSEL_COLOR));
141                   noteSelected = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_NOTE_SEL_COLOR));
142 
143                   colGridLine = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_BG_GRIDLINE_COLOR));
144                   colText = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_BG_TEXT_COLOR));
145                   break;
146             }
147 
148 
149       const QPen penLineMajor = QPen(colGridLine, 2.0, Qt::SolidLine);
150       const QPen penLineMinor = QPen(colGridLine, 1.0, Qt::SolidLine);
151       const QPen penLineSub = QPen(colGridLine, 1.0, Qt::DotLine);
152 
153       const QRect& r = e->rect();
154 
155       p.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing);
156 
157       p.setBrush(colPianoBg);
158       p.drawRect(0, 0, width(), height());
159 
160       if (!_score)
161             return;
162 
163       Pos pos1(_score->tempomap(), _score->sigmap(), qMax(pixelXToTick(r.x()), 0), TType::TICKS);
164       Pos pos2(_score->tempomap(), _score->sigmap(), qMax(pixelXToTick(r.x() + r.width()), 0), TType::TICKS);
165 
166       //draw vert lines
167       int bar1, bar2, beat, tick;
168 
169       pos1.mbt(&bar1, &beat, &tick);
170       pos2.mbt(&bar2, &beat, &tick);
171 
172       //Estimate bar width since changing time signatures can make this inconsistent.
173       // Assuming 480 ticks per beat, 4 beats per bar
174       qreal pixPerBar = MScore::division * 4 * _xZoom;
175       qreal pixPerBeat = MScore::division * _xZoom;
176 
177       int barSkip = ceil(minBeatGap / pixPerBar);
178       barSkip = (int)pow(2, ceil(log(barSkip)/log(2)));
179 
180       int beatSkip = ceil(minBeatGap / pixPerBeat);
181       beatSkip = (int)pow(2, ceil(log(beatSkip)/log(2)));
182 
183       //Round down to first bar to be a multiple of barSkip
184       bar1 = (bar1 / barSkip) * barSkip;
185 
186       for (int bar = bar1; bar <= bar2; bar += barSkip) {
187             Pos stick(_score->tempomap(), _score->sigmap(), bar, 0, 0);
188 
189             SigEvent sig = stick.timesig();
190             int z = sig.timesig().numerator();
191             for (int beat1 = 0; beat1 < z; beat1 += beatSkip) {
192                   Pos beatPos(_score->tempomap(), _score->sigmap(), bar, beat1, 0);
193                   double xp = tickToPixelX(beatPos.time(TType::TICKS));
194                   if (xp < 0)
195                         continue;
196 
197                   if (beat1 == 0) {
198                         p.setPen(penLineMajor);
199                         }
200                   else {
201                         p.setPen(penLineMinor);
202                         }
203 
204                   p.drawLine(xp, 0, xp, height());
205 
206                   int subbeats = _tuplet * (1 << _subdiv);
207 
208                   for (int sub = 1; sub < subbeats; ++sub) {
209                         Pos subBeatPos(_score->tempomap(), _score->sigmap(), bar, beat1, sub * MScore::division / subbeats);
210                         xp = tickToPixelX(subBeatPos.time(TType::TICKS));
211 
212                         p.setPen(penLineSub);
213                         p.drawLine(xp, 0, xp, height());
214                         }
215                   }
216             }
217 
218 
219       //draw horiz lines
220       PianoLevelsFilter* filter = PianoLevelsFilter::FILTER_LIST[_levelsIndex];
221 
222       int div = filter->divisionGap();
223       int minGuide = (int)floor(filter->minRange() / (qreal)div);
224       int maxGuide = (int)ceil(filter->maxRange() / (qreal)div);
225 
226       QFont f("FreeSans", 7);
227       p.setFont(f);
228 
229       for (int i = minGuide; i <= maxGuide; ++i) {
230             p.setPen(i == 0 || i == minGuide || i == maxGuide ? penLineMajor : penLineMinor);
231 
232             int y = valToPixelY(i * div);
233             p.drawLine(0, y, width(), y);
234 
235             //labels
236             QRectF textRect(2, y - 12, width() - 2, 12);
237             p.setPen(QPen(colText));
238             p.drawText(textRect,
239                   Qt::AlignLeft | Qt::AlignBottom, QString::number(i * div));
240             }
241 
242 
243       //Note lines
244       p.setBrush(Qt::NoBrush);
245       int pix0 = valToPixelY(0);
246 
247       for (int i = 0; i < noteList.size(); ++i) {
248             Note* note = noteList[i];
249             if (filter->isPerEvent()) {
250                   for (NoteEvent& ne : note->playEvents()) {
251                         int x = tickToPixelX(noteStartTick(note, &ne));
252 
253                         int val = filter->value(_staff, note, &ne);
254                         p.setPen(QPen(note->selected() ? noteSelected : noteDeselected, 2));
255                         int pixY = valToPixelY(val);
256                         p.drawLine(x, pix0, x, pixY);
257 
258                         //hbar
259                         p.setPen(QPen(note->selected() ? noteSelected : noteDeselected, 2));
260                         p.drawLine(x, pixY, x + levelLen, pixY);
261                         p.drawEllipse(x - 1, pixY - 1, 3, 3);
262                         }
263 
264                   }
265             else {
266                   int x = tickToPixelX(noteStartTick(note, 0));
267 
268                   int val = filter->value(_staff, note, 0);
269 
270                   p.setPen(QPen(note->selected() ? noteSelected : noteDeselected, 2));
271                   int pixY = valToPixelY(val);
272                   p.drawLine(x, pix0, x, pixY);
273 
274                   //hbar
275                   p.setPen(QPen(note->selected() ? noteSelected : noteDeselected, 2));
276                   p.drawLine(x, pixY, x + levelLen, pixY);
277                   p.drawEllipse(x - 1, pixY - 1, 3, 3);
278                   }
279             }
280       }
281 
282 
283 //---------------------------------------------------------
284 //   noteStartTick
285 //---------------------------------------------------------
286 
noteStartTick(Note * note,NoteEvent * evt)287 int PianoLevels::noteStartTick(Note* note, NoteEvent* evt)
288       {
289       Chord* chord = note->chord();
290       int ticks = chord->ticks().ticks();
291 
292       return note->chord()->tick().ticks() + (evt ? evt->ontime() * ticks / 1000 : 0);
293       }
294 
295 
296 //---------------------------------------------------------
297 //   valToPixelY
298 //---------------------------------------------------------
299 
valToPixelY(int value)300 int PianoLevels::valToPixelY(int value) {
301       PianoLevelsFilter* filter = PianoLevelsFilter::FILTER_LIST[_levelsIndex];
302 
303       int range = filter->maxRange() - filter->minRange();
304       qreal frac = (value - filter->minRange()) / (qreal)range;
305 
306       return static_cast<int>(height() - vMargin * 2) * (1 - frac) + vMargin;
307       }
308 
309 
310 //---------------------------------------------------------
311 //   pixelYToVal
312 //---------------------------------------------------------
313 
pixelYToVal(int pix)314 int PianoLevels::pixelYToVal(int pix) {
315       qreal frac = 1 - (pix - vMargin) / (qreal)(height() - vMargin * 2);
316 
317       PianoLevelsFilter* filter = PianoLevelsFilter::FILTER_LIST[_levelsIndex];
318       int range = filter->maxRange() - filter->minRange();
319       return static_cast<int>(frac * range + filter->minRange());
320       }
321 
322 //---------------------------------------------------------
323 //   pickNoteEvent
324 //---------------------------------------------------------
325 
pickNoteEvent(int x,int y,bool selectedOnly,Note * & pickedNote,NoteEvent * & pickedNoteEvent)326 bool PianoLevels::pickNoteEvent(int x, int y, bool selectedOnly, Note*& pickedNote, NoteEvent*& pickedNoteEvent)
327       {
328       PianoLevelsFilter* filter = PianoLevelsFilter::FILTER_LIST[_levelsIndex];
329 
330       for (int i = 0; i < noteList.size(); ++i) {
331             Note* note = noteList[i];
332             if (selectedOnly && !note->selected())
333                   continue;
334 
335             if (filter->isPerEvent()) {
336                   for (NoteEvent& e : note->playEvents()) {
337                         int noteX = tickToPixelX(noteStartTick(note, &e));
338                         int noteY = valToPixelY(filter->value(_staff, note, &e));
339                         int dx = noteX - x;
340                         int dy = noteY - y;
341 
342                         if (dx * dx + dy * dy < pickRadius * pickRadius) {
343                               pickedNote = note;
344                               pickedNoteEvent = &e;
345                               return true;
346                               }
347                         }
348                   }
349             else {
350                   int noteX = tickToPixelX(noteStartTick(note, nullptr));
351                   int noteY = valToPixelY(filter->value(_staff, note, nullptr));
352                   int dx = noteX - x;
353                   int dy = noteY - y;
354 
355                   if (dx * dx + dy * dy < pickRadius * pickRadius) {
356                         pickedNote = note;
357                         pickedNoteEvent = nullptr;
358                         return true;
359                         }
360                   }
361             }
362 
363       pickedNote = nullptr;
364       pickedNoteEvent = nullptr;
365       return false;
366       }
367 
368 //---------------------------------------------------------
369 //   adjustLevelLerp
370 //---------------------------------------------------------
371 
adjustLevel(Note * note,NoteEvent * noteEvt,int value)372 void PianoLevels::adjustLevel(Note* note, NoteEvent* noteEvt, int value)
373       {
374       PianoLevelsFilter* filter = PianoLevelsFilter::FILTER_LIST[_levelsIndex];
375 
376       filter->setValue(_staff, note, noteEvt, value);
377 
378       update();
379       emit noteLevelsChanged();
380       }
381 
382 
383 //---------------------------------------------------------
384 //   adjustLevelLerp
385 //       For all points between tick0 and tick1, linearly interploate between value0 and value1 and
386 //       use it to set the value of the level.
387 //---------------------------------------------------------
388 
adjustLevelLerp(int tick0,int value0,int tick1,int value1,bool selectedOnly)389 void PianoLevels::adjustLevelLerp(int tick0, int value0, int tick1, int value1, bool selectedOnly)
390       {
391       if (tick1 < tick0) {
392             std::swap(tick0, tick1);
393             std::swap(value0, value1);
394             }
395 
396       PianoLevelsFilter* filter = PianoLevelsFilter::FILTER_LIST[_levelsIndex];
397       bool hitNote = false;
398 
399       for (int i = 0; i < noteList.size(); ++i) {
400             Note* note = noteList[i];
401             if (selectedOnly && !note->selected())
402                   continue;
403 
404             if (filter->isPerEvent()) {
405                   for (NoteEvent& e : note->playEvents()) {
406                         int tick = noteStartTick(note, &e);
407                         if (tick0 <= tick && tick <= tick1) {
408                               int value = tick0 == tick1 ? value0
409                                     : (value1 - value0) * (tick - tick0) / (tick1 - tick0) + value0;
410 
411                               filter->setValue(_staff, note, &e, value);
412                               hitNote = true;
413                               }
414                         }
415                   }
416             else {
417                   int tick = noteStartTick(note, 0);
418                   if (tick0 <= tick && tick <= tick1) {
419                         int value = tick0 == tick1 ? value0
420                               : (value1 - value0) * (tick - tick0) / (tick1 - tick0) + value0;
421                         filter->setValue(_staff, note, 0, value);
422                         hitNote = true;
423                         }
424                   }
425             }
426 
427       if (hitNote) {
428             update();
429             emit noteLevelsChanged();
430             }
431 
432       }
433 
434 //---------------------------------------------------------
435 //   mousePressEvent
436 //---------------------------------------------------------
437 
mousePressEvent(QMouseEvent * e)438 void PianoLevels::mousePressEvent(QMouseEvent* e)
439       {
440       if (e->button() == Qt::LeftButton) {
441             mouseDown = true;
442             mouseDownPos = e->pos();
443             lastMousePos = mouseDownPos;
444 
445             if (pickNoteEvent(mouseDownPos.x(), mouseDownPos.y(), true, singleNoteDrag, singleNoteEventDrag)) {
446                   dragStyle = DragStyle::OFFSET;
447                   }
448             else {
449                   dragStyle = DragStyle::LERP;
450                   }
451             }
452       }
453 
454 
455 //---------------------------------------------------------
456 //   mouseReleaseEvent
457 //---------------------------------------------------------
458 
mouseReleaseEvent(QMouseEvent * e)459 void PianoLevels::mouseReleaseEvent(QMouseEvent* e)
460       {
461       if (e->button() == Qt::LeftButton) {
462 
463             if (!dragging) {
464                   //Handle click
465                   lastMousePos = e->pos();
466 
467                   int tick0 = pixelXToTick(lastMousePos.x() - 4);
468                   int tick1 = pixelXToTick(lastMousePos.x() + 4);
469                   int val = pixelYToVal(lastMousePos.y());
470                   adjustLevelLerp(tick0, val, tick1, val);
471             }
472 
473             mouseDown = false;
474             dragging = false;
475             }
476       }
477 
478 //---------------------------------------------------------
479 //   mouseMoveEvent
480 //---------------------------------------------------------
481 
mouseMoveEvent(QMouseEvent * e)482 void PianoLevels::mouseMoveEvent(QMouseEvent* e)
483       {
484       int modifiers = QGuiApplication::keyboardModifiers();
485       bool bnShift = modifiers & Qt::ShiftModifier;
486 
487       if (mouseDown) {
488             if (!dragging) {
489                   int dx = e->x() - mouseDownPos.x();
490                   int dy = e->y() - mouseDownPos.y();
491                   if (dx * dx + dy * dy > pickRadius * pickRadius) {
492                         //Start dragging
493                         dragging = true;
494                         }
495                   }
496 
497             if (dragging) {
498                   if (dragStyle == DragStyle::OFFSET) {
499                         int val = pixelYToVal(lastMousePos.y());
500                         adjustLevel(singleNoteDrag, singleNoteEventDrag, val);
501                         }
502                   else {
503                         int tick0 = pixelXToTick(lastMousePos.x());
504                         int tick1 = pixelXToTick(e->x());
505 
506                         int val0;
507                         int val1;
508 
509                         if (bnShift) {
510                               //If shift is held, set to value at mousedown
511                               val0 = pixelYToVal(mouseDownPos.y());
512                               val1 = pixelYToVal(mouseDownPos.y());
513                               }
514                         else {
515                               val0 = pixelYToVal(lastMousePos.y());
516                               val1 = pixelYToVal(e->y());
517                               }
518 
519                         adjustLevelLerp(tick0, val0, tick1, val1);
520                         }
521 
522                   lastMousePos = e->pos();
523                   }
524 
525             }
526       }
527 
528 //---------------------------------------------------------
529 //   moveLocator
530 //---------------------------------------------------------
531 
moveLocator(QMouseEvent * e)532 void PianoLevels::moveLocator(QMouseEvent* e)
533       {
534       Pos pos(_score->tempomap(), _score->sigmap(), qMax(pixelXToTick(e->pos().x()), 0), TType::TICKS);
535       if (e->buttons() & Qt::LeftButton)
536             emit locatorMoved(0, pos);
537       else if (e->buttons() & Qt::MidButton)
538             emit locatorMoved(1, pos);
539       else if (e->buttons() & Qt::RightButton)
540             emit locatorMoved(2, pos);
541       }
542 
543 //---------------------------------------------------------
544 //   leaveEvent
545 //---------------------------------------------------------
546 
leaveEvent(QEvent *)547 void PianoLevels::leaveEvent(QEvent*)
548       {
549       _cursor.setInvalid();
550       emit posChanged(_cursor);
551       update();
552       }
553 
554 //---------------------------------------------------------
555 //   setPos
556 //---------------------------------------------------------
557 
setPos(const Pos & pos)558 void PianoLevels::setPos(const Pos& pos)
559       {
560       if (_cursor != pos) {
561             _cursor = pos;
562             update();
563             }
564       }
565 
566 //---------------------------------------------------------
567 //   setXZoom
568 //---------------------------------------------------------
569 
setXZoom(qreal xZoom)570 void PianoLevels::setXZoom(qreal xZoom)
571       {
572       _xZoom = xZoom;
573       update();
574       }
575 
576 //---------------------------------------------------------
577 //   setStaff
578 //---------------------------------------------------------
579 
setStaff(Staff * s,Pos * l)580 void PianoLevels::setStaff(Staff* s, Pos* l)
581       {
582       _locator = l;
583 
584       if (_staff == s)
585             return;
586 
587       _staff    = s;
588       updateNotes();
589       }
590 
591 
592 //---------------------------------------------------------
593 //   addChord
594 //---------------------------------------------------------
595 
addChord(Chord * chord,int voice)596 void PianoLevels::addChord(Chord* chord, int voice)
597       {
598       for (Chord* c : chord->graceNotes())
599             addChord(c, voice);
600       for (Note* note : chord->notes()) {
601             if (note->tieBack())
602                   continue;
603             noteList.append(note);
604             }
605       }
606 
607 //---------------------------------------------------------
608 //   updateNotes
609 //---------------------------------------------------------
610 
updateNotes()611 void PianoLevels::updateNotes()
612       {
613       clearNoteData();
614 
615       if (!_staff) {
616             return;
617             }
618 
619       int staffIdx = _staff->idx();
620       if (staffIdx == -1)
621             return;
622 
623       SegmentType st = SegmentType::ChordRest;
624       for (Segment* s = _staff->score()->firstSegment(st); s; s = s->next1(st)) {
625             for (int voice = 0; voice < VOICES; ++voice) {
626                   int track = voice + staffIdx * VOICES;
627                   Element* e = s->element(track);
628                   if (e && e->isChord())
629                         addChord(toChord(e), voice);
630                   }
631             }
632 
633       update();
634       }
635 
636 //---------------------------------------------------------
637 //   clearNoteData
638 //---------------------------------------------------------
639 
clearNoteData()640 void PianoLevels::clearNoteData()
641       {
642       noteList.clear();
643       }
644 
645 //---------------------------------------------------------
646 //   setTuplet
647 //---------------------------------------------------------
648 
setTuplet(int value)649 void PianoLevels::setTuplet(int value)
650       {
651       if (_tuplet != value) {
652             _tuplet = value;
653             update();
654             emit tupletChanged(_tuplet);
655             }
656       }
657 
658 //---------------------------------------------------------
659 //   setSubdiv
660 //---------------------------------------------------------
661 
setSubdiv(int value)662 void PianoLevels::setSubdiv(int value)
663       {
664       if (_subdiv != value) {
665             _subdiv = value;
666             update();
667             emit subdivChanged(_subdiv);
668             }
669       }
670 
671 //---------------------------------------------------------
672 //   setLevelsIndex
673 //---------------------------------------------------------
674 
setLevelsIndex(int index)675 void PianoLevels::setLevelsIndex(int index)
676       {
677       if (_levelsIndex != index) {
678             _levelsIndex = index;
679             update();
680             emit levelsIndexChanged(_levelsIndex);
681             }
682       }
683 
684 }
685