From abd7bf59a321fe60c466eb5417035d30f68401b0 Mon Sep 17 00:00:00 2001 From: Ian Caio Date: Tue, 2 Mar 2021 14:34:51 -0300 Subject: [PATCH 1/3] Improves the PianoRoll detuning drawing This PR rewrites the detuning drawing method of the PianoRoll to behave similar to the AutomationEditor. It now draws Cubic Hermite progressions as well, and the detuning is displayed as a shape instead of a line. The color of the detuning shape is the same as m_noteColor, except it's lighter and has some alpha. --- include/PianoRoll.h | 2 +- src/gui/editors/PianoRoll.cpp | 128 ++++++++++++++++++---------------- 2 files changed, 68 insertions(+), 62 deletions(-) diff --git a/include/PianoRoll.h b/include/PianoRoll.h index 77d372bcccc..ea62c49a75a 100644 --- a/include/PianoRoll.h +++ b/include/PianoRoll.h @@ -405,7 +405,7 @@ protected slots: void copyToClipboard(const NoteVector & notes ) const; - void drawDetuningInfo( QPainter & _p, const Note * _n, int _x, int _y ) const; + void drawDetuningInfo(QPainter & p, const Note * n, int x, int y) const; bool mouseOverNote(); Note * noteUnderMouse(); diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index 76732cd8c0d..92591e54d4a 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -976,82 +976,88 @@ void PianoRoll::drawNoteRect( QPainter & p, int x, int y, -void PianoRoll::drawDetuningInfo( QPainter & _p, const Note * _n, int _x, - int _y ) const +void PianoRoll::drawDetuningInfo(QPainter & p, const Note * n, int x, int y) const { - int middle_y = _y + m_keyLineHeight / 2; - _p.setPen(m_noteColor); - _p.setClipRect( + // Get a timeMap of the detuning pattern + AutomationPattern* detuningPattern = n->detuning()->automationPattern(); + timeMap & detuningMap = detuningPattern->getTimeMap(); + + // Return if it's empty + if (detuningMap.isEmpty()) { return; } + + // Set the detuning color to the same color as the notes, + // but lighter and with some alpha + QColor detuningColor = QColor(m_noteColor.lighter(150)); + detuningColor.setAlpha(70); + + // Get the first node + timeMap::iterator it = detuningMap.begin(); + + // Set the clip area to the PianoRoll area + p.setClipRect( m_whiteKeyWidth, PR_TOP_MARGIN, width() - m_whiteKeyWidth, - keyAreaBottom() - PR_TOP_MARGIN); + keyAreaBottom() - PR_TOP_MARGIN + ); - // Draw lines for the detuning automation, treating cubic hermit curves - // as straight lines for now. Also draw discrete jumps. - int old_x = 0; - int old_y = 0; + // Reference pixel Y (that will be equivalent to + // zero in the automation pattern) + int baseY = y + m_keyLineHeight / 2; - timeMap & map = _n->detuning()->automationPattern()->getTimeMap(); - for (timeMap::const_iterator it = map.begin(); it != map.end(); ++it) + // Two lambda functions to calculate the X and Y position + // of nodes/values + auto getX = [this, x](int tick) { - // Current node values - int cur_ticks = POS(it); - int cur_x = _x + cur_ticks * m_ppb / TimePos::ticksPerBar(); - const float cur_level = INVAL(it); - int cur_y = middle_y - cur_level * m_keyLineHeight; + return x + tick * m_ppb / TimePos::ticksPerBar(); + }; + auto getY = [this, baseY](float value) + { + return baseY - value * m_keyLineHeight; + }; + + while (it + 1 != detuningMap.end()) + { + // Get the current node's x position in ticks + auto nodePos = POS(it); + // Convert it to a X position in the piano roll + int nodeX = getX(nodePos); - // First line to represent the inValue of the first node - if (it == map.begin()) + // Get values after this node + float* values = detuningPattern->valuesAfter(POS(it)); + + // We are creating a path to draw a polygon representing the values between two + // nodes. When we have two nodes with discrete progression, we will basically have + // a rectangle with the outValue of the first node (that's why nextValue will match + // the outValue of the current node). When we have nodes with linear or cubic progression + // the value of the end of the shape between the two nodes will be the inValue of + // the next node. + float nextValue; + if (detuningPattern->progressionType() == AutomationPattern::DiscreteProgression) { - _p.drawLine(cur_x - 1, cur_y, cur_x + 1, cur_y); - _p.drawLine(cur_x, cur_y - 1, cur_x, cur_y + 1); + nextValue = OUTVAL(it); } - // All subsequent lines will take the outValue of the previous node - // and the inValue of the current node. It will also draw a vertical - // line if there was a discrete jump (from old_x,old_y to pre_x,pre_y) else { - // Previous node values (based on outValue). We just calculate - // the y level because the x will be the same as old_x. - const float pre_level = OUTVAL(it - 1); - int pre_y = middle_y - pre_level * m_keyLineHeight; - - // Draws the line representing the discrete jump if there's one - if (old_y != pre_y) - { - _p.drawLine(old_x, old_y, old_x, pre_y); - } + nextValue = INVAL(it + 1); + } - // Now draw the lines representing the actual progression from one - // node to the other - switch (_n->detuning()->automationPattern()->progressionType()) - { - case AutomationPattern::DiscreteProgression: - _p.drawLine(old_x, pre_y, cur_x, pre_y); - _p.drawLine(cur_x, pre_y, cur_x, cur_y); - break; - case AutomationPattern::CubicHermiteProgression: /* TODO */ - case AutomationPattern::LinearProgression: - _p.drawLine(old_x, pre_y, cur_x, cur_y); - break; - } + p.setRenderHints(QPainter::Antialiasing, true); + QPainterPath path; - // If we are in the last node and there's a discrete jump, we draw a - // vertical line representing it - if ((it + 1) == map.end()) - { - const float last_level = OUTVAL(it); - if (cur_level != last_level) - { - int last_y = middle_y - last_level * m_keyLineHeight; - _p.drawLine(cur_x, cur_y, cur_x, last_y); - } - } + path.moveTo(QPointF(nodeX, getY(0))); + for (int i = 0; i < POS(it + 1) - POS(it); i++) + { + path.lineTo(QPointF(getX(nodePos + i), getY(values[i]))); } - - old_x = cur_x; - old_y = cur_y; + path.lineTo(QPointF(getX(POS(it + 1)), getY(nextValue))); + path.lineTo(QPointF(getX(POS(it + 1)), getY(0))); + path.lineTo(QPointF(nodeX, getY(0))); + p.fillPath(path, detuningColor); + p.setRenderHints(QPainter::Antialiasing, false); + delete [] values; + + ++it; } } From d6f9f9dc41e765704c726449637f094f30b2eaae Mon Sep 17 00:00:00 2001 From: Ian Caio Date: Tue, 2 Mar 2021 20:21:53 -0300 Subject: [PATCH 2/3] Addresses Veratil's and Cyber-bridge reviews Moves lambda functions up, uses keyAreaTop() instead of PR_TOP_MARGIN, and include missing header (). --- src/gui/editors/PianoRoll.cpp | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index 92591e54d4a..3b652dc7c7f 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -985,22 +986,6 @@ void PianoRoll::drawDetuningInfo(QPainter & p, const Note * n, int x, int y) con // Return if it's empty if (detuningMap.isEmpty()) { return; } - // Set the detuning color to the same color as the notes, - // but lighter and with some alpha - QColor detuningColor = QColor(m_noteColor.lighter(150)); - detuningColor.setAlpha(70); - - // Get the first node - timeMap::iterator it = detuningMap.begin(); - - // Set the clip area to the PianoRoll area - p.setClipRect( - m_whiteKeyWidth, - PR_TOP_MARGIN, - width() - m_whiteKeyWidth, - keyAreaBottom() - PR_TOP_MARGIN - ); - // Reference pixel Y (that will be equivalent to // zero in the automation pattern) int baseY = y + m_keyLineHeight / 2; @@ -1016,6 +1001,22 @@ void PianoRoll::drawDetuningInfo(QPainter & p, const Note * n, int x, int y) con return baseY - value * m_keyLineHeight; }; + // Set the detuning color to the same color as the notes, + // but lighter and with some alpha + QColor detuningColor = QColor(m_noteColor.lighter(150)); + detuningColor.setAlpha(70); + + // Get the first node + timeMap::iterator it = detuningMap.begin(); + + // Set the clip area to the PianoRoll area + p.setClipRect( + m_whiteKeyWidth, + keyAreaTop(), + width() - m_whiteKeyWidth, + keyAreaBottom() - keyAreaTop() + ); + while (it + 1 != detuningMap.end()) { // Get the current node's x position in ticks From c3bd55083e09d2f474e0504002342b296bd4af53 Mon Sep 17 00:00:00 2001 From: Hyunjin Song Date: Tue, 4 Jul 2023 14:10:44 +0900 Subject: [PATCH 3/3] Replace pattern with clip --- src/gui/editors/PianoRoll.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index f92dcbd9a37..cc08f818ef0 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -1099,7 +1099,7 @@ void PianoRoll::drawNoteRect( QPainter & p, int x, int y, void PianoRoll::drawDetuningInfo(QPainter & p, const Note * n, int x, int y) const { - // Get a timeMap of the detuning pattern + // Get a timeMap of the detuning clip AutomationClip* detuningClip = n->detuning()->automationClip(); timeMap & detuningMap = detuningClip->getTimeMap(); @@ -1107,7 +1107,7 @@ void PianoRoll::drawDetuningInfo(QPainter & p, const Note * n, int x, int y) con if (detuningMap.isEmpty()) { return; } // Reference pixel Y (that will be equivalent to - // zero in the automation pattern) + // zero in the automation clip) int baseY = y + m_keyLineHeight / 2; // Two lambda functions to calculate the X and Y position