Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ease in out gliss graph #6945

Merged
merged 9 commits into from
Nov 30, 2020
4 changes: 3 additions & 1 deletion libmscore/easeInOut.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,6 @@ void EaseInOut::timeList(const int nbNotes, const int duration, QList<int>* time
}
}

}
}


4 changes: 1 addition & 3 deletions libmscore/easeInOut.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,13 @@ class EaseInOut final {
qreal _easeOut;

public:
typedef std::tuple<qreal, qreal> Ease2D;

EaseInOut() : _easeIn(0.0), _easeOut(1.0) {}
EaseInOut(qreal easeIn, qreal easeOut) : _easeIn(easeIn), _easeOut(easeOut) {}

void SetEases(qreal easeIn, qreal easeOut) { _easeIn = easeIn; _easeOut = easeOut; }
qreal EvalX(const qreal t) const { qreal tCompl = 1.0 - t; return (3.0 * _easeIn * tCompl * tCompl + (3.0 - 3.0 * _easeOut * tCompl - 2.0 * t) * t) * t; }
qreal EvalY(const qreal t) const { return -(t * t) * (2.0 * t - 3.0); }
Ease2D Eval(const qreal t) const { return {EvalX(t), EvalY(t)}; }
QPointF Eval(const qreal t) const { return {EvalX(t), EvalY(t)}; }
qreal tFromX(const qreal x) const;
qreal tFromY(const qreal y) const;
qreal YfromX(const qreal x) const { return EvalY(tFromX(x)); }
Expand Down
114 changes: 56 additions & 58 deletions libmscore/rendermidi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1840,72 +1840,70 @@ bool noteHasGlissando(Note *note)
return false;
}

//---------------------------------------------------------
// glissandoPitchOffsets
//---------------------------------------------------------

bool glissandoPitchOffsets(const Spanner* spanner, std::vector<int>& pitchOffsets)
{
if (!spanner->endElement()->isNote())
return false;
const Glissando* glissando = toGlissando(spanner);
if (!glissando->playGlissando())
return false;
GlissandoStyle glissandoStyle = glissando->glissandoStyle();
if (glissandoStyle == GlissandoStyle::PORTAMENTO)
return false;
// only consider glissando connected to NOTE.
Note* noteStart = toNote(spanner->startElement());
Note* noteEnd = toNote(spanner->endElement());
int pitchStart = noteStart->ppitch();
int pitchEnd = noteEnd->ppitch();
if (pitchEnd == pitchStart)
return false;
int direction = pitchEnd > pitchStart ? 1 : -1;
pitchOffsets.clear();
if (glissandoStyle == GlissandoStyle::DIATONIC) {
int lineStart = noteStart->line();
int lineEnd = noteEnd->line() - direction;
// scale obeying accidentals
for (int line = lineStart, pitch = pitchStart; (direction == 1) ? (pitch < pitchEnd) : (pitch > pitchEnd); line -= direction) {
int halfSteps = articulationExcursion(noteStart, noteEnd, lineStart - line);
pitch = pitchStart + halfSteps;
if ((direction == 1) ? (pitch < pitchEnd) : (pitch > pitchEnd))
pitchOffsets.push_back(halfSteps);
}
return pitchOffsets.size() > 0;
}
if (glissandoStyle == GlissandoStyle::CHROMATIC) {
for (int pitch = pitchStart; pitch != pitchEnd; pitch += direction)
pitchOffsets.push_back(pitch - pitchStart);
return true;
}
static std::vector<bool> whiteNotes = { true, false, true, false, true, true, false, true, false, true, false, true };
int Cnote = 60; // pitch of middle C
bool notePick = glissandoStyle == GlissandoStyle::WHITE_KEYS;
for (int pitch = pitchStart; pitch != pitchEnd; pitch += direction) {
int idx = ((pitch - Cnote) + 1200) % 12;
if (whiteNotes[idx] == notePick)
pitchOffsets.push_back(pitch - pitchStart);
}
return true;
}

//---------------------------------------------------------
// renderGlissando
//---------------------------------------------------------

void renderGlissando(NoteEventList* events, Note *notestart)
{
std::vector<int> empty = {};
int Cnote = 60; // pitch of middle C
int pitchstart = notestart->ppitch();
int linestart = notestart->line();

std::set<int> blacknotes = { 1, 3, 6, 8, 10};
std::set<int> whitenotes = {0, 2, 4, 5, 7, 9, 11};

std::vector<int> body;
for (Spanner* spanner : notestart->spannerFor()) {
if (spanner->type() == ElementType::GLISSANDO) {
Glissando *glissando = toGlissando(spanner);
GlissandoStyle glissandoStyle = glissando->glissandoStyle();
Element* ee = spanner->endElement();
// handle this elsewhere in a different way
if (glissandoStyle == GlissandoStyle::PORTAMENTO)
continue;
// only consider glissando connected to NOTE.
if (glissando->playGlissando() && ElementType::NOTE == ee->type()) {
std::vector<int> body;
Note *noteend = toNote(ee);
int pitchend = noteend->ppitch();
bool direction = pitchend > pitchstart;
if (pitchend == pitchstart)
continue; // next spanner
if (glissandoStyle == GlissandoStyle::DIATONIC) { // scale obeying accidentals
int line;
int p = pitchstart;
// iterate as long as we haven't past the pitchend.
for (line = linestart; (direction) ? (p<pitchend) : (p>pitchend);
(direction) ? line-- : line++) {
int halfsteps = articulationExcursion(notestart, noteend, linestart - line);
p = pitchstart + halfsteps;
if (direction ? p < pitchend : p > pitchend)
body.push_back(halfsteps);
}
}
else {
for (int p = pitchstart; direction ? p < pitchend : p > pitchend; p += (direction ? 1 : -1)) {
bool choose = false;
int mod = ((p - Cnote) + 1200) % 12;
switch (glissandoStyle) {
case GlissandoStyle::CHROMATIC:
choose = true;
break;
case GlissandoStyle::WHITE_KEYS: // white note
choose = (whitenotes.find(mod) != whitenotes.end());
break;
case GlissandoStyle::BLACK_KEYS: // black note
choose = (blacknotes.find(mod) != blacknotes.end());
break;
default:
choose = false;
}
if (choose)
body.push_back(p - pitchstart);
}
}
renderNoteArticulation(events, notestart, true, MScore::division, empty, body, false, true, empty, 16, 0);
}
}
if (spanner->type() == ElementType::GLISSANDO
&& toGlissando(spanner)->playGlissando()
&& glissandoPitchOffsets(spanner, body))
renderNoteArticulation(events, notestart, true, MScore::division, empty, body, false, true, empty, 16, 0);
}
}

Expand Down
3 changes: 3 additions & 0 deletions libmscore/rendermidi.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ class MidiRenderer {
static const int ARTICULATION_CONV_FACTOR { 100000 };
};

class Spanner;
extern bool glissandoPitchOffsets(const Spanner* spanner, std::vector<int>& pitchOffsets);

} // namespace Ms

#endif
4 changes: 2 additions & 2 deletions mscore/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ add_library(mscoreapp STATIC
analyse.h articulationprop.h
bendcanvas.h breaksdialog.h
chordview.h click.h clickableLabel.h continuouspanel.h downloadUtils.h
drumroll.h drumtools.h drumview.h editdrumset.h
drumroll.h drumtools.h drumview.h easeinoutcanvas.h editdrumset.h
editinstrument.h editpitch.h editraster.h editstaff.h
editstafftype.h editstringdata.h editstyle.h enableplayforwidget.h
exampleview.h excerptsdialog.h exportdialog.h extension.h
Expand Down Expand Up @@ -193,7 +193,7 @@ add_library(mscoreapp STATIC
recordbutton.h greendotbutton prefsdialog.h prefsdialog.cpp
stringutils.h stringutils.cpp
scoreview.cpp editharmony.cpp editfiguredbass.cpp events.cpp
editinstrument.cpp editstyle.cpp
editinstrument.cpp editstyle.cpp easeinoutcanvas.cpp
clickableLabel.cpp
icons.cpp
instrdialog.cpp instrwidget.cpp
Expand Down
211 changes: 211 additions & 0 deletions mscore/easeinoutcanvas.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2010-2019 Werner Schweer and others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================

#include "easeinoutcanvas.h"
#include "libmscore/easeInOut.h"
#include "preferences.h"

namespace Ms {

//---------------------------------------------------------
// EaseInOutCanvas
//---------------------------------------------------------

EaseInOutCanvas::EaseInOutCanvas(QWidget* parent)
: QFrame(parent),
m_easeIn(0.0),
m_easeOut(0.0),
m_pitchDelta(0)
{
setFrameStyle(QFrame::NoFrame);
}

//---------------------------------------------------------
// paintEvent
//---------------------------------------------------------

void EaseInOutCanvas::paintEvent(QPaintEvent* ev)
{
// not qreal here, even though elsewhere yes,
// because width and height return a number of pixels,
// hence integers.
const int w = width();
const int h = height();
const int border = 1;
const int nPitches = std::abs(m_pitchDelta) + 1;

const qreal graphWidth = static_cast<qreal>(w - 2 * border);
const qreal graphHeight = static_cast<qreal>(h - 2 * border);
const qreal nbEvents = static_cast<qreal>(m_events.size());
const qreal pitchDelta = static_cast<qreal>(nPitches);

// let half a column of margin around
const qreal leftPos = static_cast<qreal>(border); // also left margin
const qreal topPos = leftPos; // also top margin
const qreal bottomPos = static_cast<qreal>(h - border); // bottom end position of graph
const qreal rightPos = static_cast<qreal>(w - border); // right end position of graph

EaseInOut eio(static_cast<qreal>(m_easeIn) / 100.0, static_cast<qreal>(m_easeOut) / 100.0);

char noteNames[] = ("C D EF G A B");

QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, preferences.getBool(PREF_UI_CANVAS_MISC_ANTIALIASEDDRAWING));

painter.fillRect(rect(), QApplication::palette().color(QPalette::Window).lighter());
QPen pen = painter.pen();
pen.setWidth(1);

QColor eventLinesColor(Qt::gray);
eventLinesColor.setAlphaF(0.5);
QColor pitchLinesColor(Qt::gray);
pitchLinesColor.setAlphaF(0.25);

// Color scheme based on the MuseScore blue.
QColor borderLinesColor, warpLineColor, pitchFillColor, pitchGraphColor, pitchNameColor;
if (preferences.isThemeDark()) {
pitchFillColor.setRgbF(0.078, 0.284, 0.463);
pitchGraphColor.setRgbF(0.125, 0.455, 0.741);
warpLineColor.setRgbF(0.621, 0.315, 0.132);
borderLinesColor.setRgbF(0.891, 0.932, 0.968);
pitchNameColor.setRgbF(0.935, 0.782, 0.691);
}
else {
pitchFillColor.setRgbF(0.891, 0.932, 0.968);
pitchGraphColor.setRgbF(0.125, 0.455, 0.741);
warpLineColor.setRgbF(0.914, 0.710, 0.588);
borderLinesColor.setRgbF(0.016, 0.057, 0.093);
pitchNameColor.setRgbF(0.310, 0.157, 0.066);
}

// this lambda takes as input a pitch value, and determines where what are its x and y coordinates
auto getPosition = [this, graphWidth, graphHeight, leftPos, bottomPos](const QPointF& p) -> QPointF {
return { leftPos + p.x() * graphWidth, bottomPos - p.y() * graphHeight };
};

std::vector<QPointF> pitchPoints;
qreal offset = m_pitchDelta < 0 ? 1.0 : 0.0;
QPointF prevPoint, currPoint;
for (int i = 0; i < m_events.size(); i++) {
currPoint = getPosition({ eio.XfromY(static_cast<qreal>(i) / nbEvents), offset + static_cast<qreal>(m_events[i]) / static_cast<qreal>(pitchDelta) });
pitchPoints.push_back(currPoint);
}

// Draw the pitches barchart graph in the background first
if (pitchPoints.size() > 1) {
prevPoint = pitchPoints[0];
for (int i = 1; i < pitchPoints.size(); i++) {
currPoint = pitchPoints[i];
painter.fillRect(prevPoint.x(), bottomPos, (currPoint.x() - prevPoint.x()) + 1, prevPoint.y() - bottomPos, pitchFillColor);
prevPoint = currPoint;
}
painter.fillRect(prevPoint.x(), bottomPos, (rightPos - prevPoint.x()) + 1, prevPoint.y() - bottomPos, pitchFillColor);

// draw time-warped vertical lines in lighter gray.
// These lines will move as ease-in and ease-out are adjusted.
pen.setWidth(0);
pen.setColor(eventLinesColor);
painter.setPen(pen);
for (int i = 1; i < pitchPoints.size(); ++i) {
qreal xPos = pitchPoints[i].x();
painter.drawLine(xPos, topPos, xPos, bottomPos);
}
}

// draw half step horigontal lines in even lighter gray
pen.setWidth(0);
pen.setColor(pitchLinesColor);
painter.setPen(pen);
for (int i = 1; i < nPitches; ++i) {
qreal yPos = topPos + (static_cast<qreal>(i) / pitchDelta) * graphHeight;
painter.drawLine(leftPos, yPos, rightPos, yPos);
}

// draw note names
pen.setColor(pitchNameColor);
painter.setPen(pen);
QFont font;
qreal fontHeight = std::min(12.0, (graphHeight * 0.875) / pitchDelta);
font.setPixelSize(fontHeight);
painter.setFont(font);
int curPitch = m_bottomPitch;
for (int i = 0; i <= nPitches; ++i) {
QString pitchName(noteNames[(curPitch - 60) % 12]);
QPointF pos = { 4, topPos + fontHeight * 0.3 + (1.0 - (static_cast<qreal>(i) / pitchDelta)) * graphHeight };
painter.drawText(pos, pitchName);
curPitch++;
}

if (m_events.size()) {
// Not a portamento style glissando.
// Draw the Bezier transfer curve only in ease-in or ease-out are not zero. This warps
// the event times so this curve always go from lower left corner to upper-right corner.
if (m_easeIn != 0 || m_easeOut != 0) {
pen.setWidth(2);
pen.setColor(warpLineColor);
painter.setPen(pen);
prevPoint = { leftPos, bottomPos };
for (int i = 1; i <= 33; i++) {
currPoint = getPosition(eio.Eval(static_cast<qreal>(i) / 33.0));
painter.drawLine(prevPoint, currPoint);
prevPoint = currPoint;
}
}
}
else {
// Draw the portamento style glissando curve instead of the transfer curve
pen.setWidth(3);
pen.setColor(pitchGraphColor);
painter.setPen(pen);
prevPoint = { leftPos, m_pitchDelta > 0 ? bottomPos : topPos };
for (int i = 1; i <= 33; i++) {
currPoint = eio.Eval(static_cast<qreal>(i) / 33.0);
if (m_pitchDelta < 0)
currPoint.setY(1.0 - currPoint.y());
currPoint = getPosition(currPoint);
painter.drawLine(prevPoint, currPoint);
prevPoint = currPoint;
}
}

// Draw the pitches level lines next so they cover the Bezier transfer curve.
if (pitchPoints.size() > 1) {
pen.setWidth(3);
pen.setCapStyle(Qt::FlatCap);
pen.setColor(pitchGraphColor);
painter.setPen(pen);
prevPoint = pitchPoints[0];
for (int i = 1; i < pitchPoints.size(); i++) {
currPoint = pitchPoints[i];
painter.drawLine(prevPoint, { currPoint.x(), prevPoint.y() });
prevPoint = currPoint;
}
painter.drawLine(prevPoint, { rightPos, prevPoint.y() });
}

// draw the graph frame after all the other graphics elements to cover them
pen.setColor(borderLinesColor);
pen.setWidth(1);
painter.setPen(pen);
painter.drawRect(border, border, w - 2 * border, h - 2 * border);

QFrame::paintEvent(ev);
}

} // namespace Ms
Loading