Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-Studio-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2021 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 :
23 : /**
24 : \file
25 : Implementation of class EaseInOut for implementing transfer curve with
26 : parametrerable ease-In and ease-Out.
27 : */
28 :
29 : #include "easeinout.h"
30 :
31 : #include <cmath>
32 :
33 : #include "realfn.h"
34 :
35 : using namespace mu;
36 : using namespace muse;
37 :
38 : namespace mu::engraving {
39 : //-------------------------------------------------------------------------------------------------
40 : // The following function is inspired by "A Primer on Bézier Curve" sections 17 and 23 by Pomax:
41 : // https://pomax.github.io/bezierinfo/
42 : // However, the function is greatly specialized, simplified and optimized for use as an ease-in and
43 : // ease-out transfer curve for bends, glissandi and portamenti in MuseScore. The function computes
44 : // Y from X by performing a cubic root finding to compute t from X on the X component of the
45 : // transfer curve and then a cubic Bezier evaluation to compute Y from t on the Y component of the
46 : // transfer curve. The code could be simplified because the Bezier curve is constrained as a well
47 : // defined transfer function. It is not suitable for general or arbitrary Bezier curve work. -YP
48 : //-------------------------------------------------------------------------------------------------
49 0 : double EaseInOut::tFromX(const double x) const
50 : {
51 0 : const double pi = 3.14159265358979323846;
52 0 : double w1 = m_easeIn - x;
53 0 : double w2 = 1.0 - m_easeOut - x;
54 0 : double d = x + 3.0 * w1 - 3.0 * w2 + (1.0 - x);
55 0 : double a = (-3.0 * x - 6.0 * w1 + 3 * w2) / d;
56 0 : double b = (3.0 * x + 3.0 * w1) / d;
57 0 : double c = -x / d;
58 0 : double a2 = a * a;
59 0 : double p = (3.0 * b - a2) / 3.0;
60 0 : double q = (2.0 * a2 * a - 9.0 * a * b + 27.0 * c) / 27.0;
61 0 : double discr = (q * q) / 4.0 + (p * p * p) / 27.0;
62 0 : double t = 0.0;
63 : // Crazy idea to first test the least probable case with such an expensive test but...
64 0 : if (muse::RealIsNull(discr)) {
65 : // Case that happens extremely rarely --> 2 roots.
66 0 : double q2 = q / 2.0;
67 0 : double u = q2 < 0.0 ? std::pow(-q2, 1.0 / 3.0) : -std::pow(q2, 1.0 / 3.0);
68 : // Find the only root that is within the 0 to 1 interval.
69 0 : t = 2.0 * u - a / 3.0;
70 0 : if (0.0 > t || t > 1.0) {
71 0 : t = -u - a / 3.0;
72 : }
73 0 : } else if (discr < 0.0) {
74 : // Case that happens about 75% of the time --> 3 roots.
75 0 : double mp3 = -p / 3.0;
76 0 : double r = std::sqrt(mp3 * mp3 * mp3);
77 0 : double phi = std::acos(std::min(std::max(-1.0, -q / (2.0 * r)), 1.0));
78 0 : double t1 = 2.0 * std::pow(r, 1.0 / 3.0);
79 : // Find the only root that is within the 0 to 1 interval.
80 0 : t = t1 * std::cos(phi / 3.0) - a / 3.0;
81 0 : if (0.0 > t || t > 1.0) {
82 0 : t = t1 * std::cos((phi + 2.0 * pi) / 3.0) - a / 3.0;
83 0 : if (0.0 > t || t > 1.0) {
84 0 : t = t1 * std::cos((phi + 4.0 * pi) / 3.0) - a / 3.0;
85 : }
86 : }
87 0 : } else if (discr > 0.0) {
88 : // Case that happens about 25% of the time --> 1 root.
89 0 : double q2 = q / 2.0;
90 0 : double sd = std::sqrt(discr);
91 0 : double u = std::pow(-q2 + sd, 1.0 / 3.0);
92 0 : double v = std::pow(q2 + sd, 1.0 / 3.0);
93 0 : t = u - v - a / 3.0;
94 : }
95 0 : return t;
96 : }
97 :
98 : //-------------------------------------------------------------------------------------------------
99 : // This function is even more simplified and optimized because all the Bezier control points are
100 : // constant. Thus, when computing the Y root there is only one case and the math simplifies to the
101 : // following simple expression.
102 : //-------------------------------------------------------------------------------------------------
103 0 : double EaseInOut::tFromY(const double y) const
104 : {
105 0 : return 0.5 + std::cos((4.0 * M_PI + std::acos(1.0 - 2.0 * y)) / 3.0);
106 : }
107 :
108 : //-------------------------------------------------------------------------------------------------
109 : // Given a number of note to place within the given duration, return the list of on-times for each
110 : // note given the current ease-in and ease-out parameters. The first note is at time 0 while the
111 : //-------------------------------------------------------------------------------------------------
112 4 : void EaseInOut::timeList(const int nbNotes, const int duration, std::vector<int>* times) const
113 : {
114 4 : double nNotes = double(nbNotes);
115 4 : double space = double(duration);
116 4 : if (RealIsNull(m_easeIn) && RealIsNull(m_easeOut)) {
117 20 : for (int n = 0; n <= nbNotes; n++) {
118 16 : times->push_back(static_cast<int>(std::lround((static_cast<double>(n) / nNotes) * space)));
119 : }
120 : } else {
121 0 : for (int n = 0; n <= nbNotes; n++) {
122 0 : times->push_back(static_cast<int>(std::lround(XfromY(static_cast<double>(n) / nNotes) * space)));
123 : }
124 : }
125 4 : }
126 : }
|