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