LCOV - code coverage report
Current view: top level - src/engraving/compat/midi - easeinout.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 14.9 % 47 7
Test Date: 2026-11-05 04:07:02

            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              : }
        

Generated by: LCOV version 2.0-1