LCOV - code coverage report
Current view: top level - src/engraving/compat/midi - velocitymap.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 73.4 % 297 218
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 VelocityMap.
      26              : */
      27              : 
      28              : #include "velocitymap.h"
      29              : 
      30              : #include <cmath>
      31              : #include <cassert>
      32              : 
      33              : #include "containers.h"
      34              : 
      35              : using namespace mu;
      36              : 
      37              : namespace mu::engraving {
      38              : //---------------------------------------------------------
      39              : //   interpolateVelocity
      40              : ///   the maths looks complex, but is just a series of graph transformations.
      41              : ///   You can see these graphically at: https://www.desmos.com/calculator/kk89ficmjk
      42              : //---------------------------------------------------------
      43              : 
      44         1946 : int VelocityMap::interpolate(Fraction& eventTick, VelocityEvent& event, Fraction& tick)
      45              : {
      46         1946 :     assert(event.m_type == VelocityEventType::HAIRPIN);
      47              : 
      48              :     // Prevent zero-division error
      49         1946 :     if (event.m_cachedStartVal == event.m_cachedEndVal || event.m_length.isZero()) {
      50            0 :         return event.m_cachedStartVal;
      51              :     }
      52              : 
      53              :     // Ticks to change expression over
      54         1946 :     int exprTicks = event.m_length.ticks();
      55         1946 :     int exprDiff = event.m_cachedEndVal - event.m_cachedStartVal;
      56              : 
      57         1946 :     std::function<int(int)> valueFunction;
      58         1946 :     switch (event.m_method) {
      59            0 :     case ChangeMethod::EXPONENTIAL:
      60              :         // Due to the nth-root, exponential functions do not flip with negative values, and cause errors,
      61              :         // so treat it as a piecewise function.
      62            0 :         if (exprDiff > 0) {
      63            0 :             valueFunction = [&](int ct) {
      64              :                 return int(
      65            0 :                     pow(
      66            0 :                         pow((exprDiff + 1), 1.0 / double(exprTicks)),                 // the exprTicks root of d+1
      67              :                         double(ct)                    // to the power of the current tick (exponential)
      68            0 :                         ) - 1
      69            0 :                     );
      70            0 :             };
      71              :         } else {
      72            0 :             valueFunction = [&](int ct) {
      73            0 :                 return -int(
      74            0 :                     pow(
      75            0 :                         pow((-exprDiff + 1), 1.0 / double(exprTicks)),                 // the exprTicks root of 1-d
      76              :                         double(ct)                    // again to the power of ct
      77            0 :                         ) + 1
      78            0 :                     );
      79            0 :             };
      80              :         }
      81            0 :         break;
      82              :     // Uses sin x transformed, which _does_ flip with negative numbers
      83            0 :     case ChangeMethod::EASE_IN_OUT:
      84            0 :         valueFunction = [&](int ct) {
      85              :             return int(
      86            0 :                 (double(exprDiff) / 2.0) * (
      87            0 :                     sin(
      88            0 :                         double(ct) * (
      89            0 :                             double(M_PI / double(exprTicks))
      90              :                             ) - double(M_PI / 2.0)
      91            0 :                         ) + 1
      92              :                     )
      93            0 :                 );
      94            0 :         };
      95            0 :         break;
      96            0 :     case ChangeMethod::EASE_IN:
      97            0 :         valueFunction = [&](int ct) {
      98              :             return int(
      99            0 :                 double(exprDiff) * (
     100            0 :                     sin(
     101            0 :                         double(ct - double(exprTicks)) * (
     102            0 :                             double(M_PI / double(2 * exprTicks))
     103              :                             )
     104            0 :                         ) + 1
     105              :                     )
     106            0 :                 );
     107            0 :         };
     108            0 :         break;
     109            0 :     case ChangeMethod::EASE_OUT:
     110            0 :         valueFunction = [&](int ct) {
     111              :             return int(
     112            0 :                 double(exprDiff) * sin(
     113            0 :                     double(ct) * (
     114            0 :                         double(M_PI / double(2 * exprTicks))
     115              :                         )
     116              :                     )
     117            0 :                 );
     118            0 :         };
     119            0 :         break;
     120         1946 :     case ChangeMethod::NORMAL:
     121              :     default:
     122         5838 :         valueFunction = [&](int ct) {
     123              :             return int(
     124         1946 :                 double(exprDiff) * (double(ct) / double(exprTicks))
     125         1946 :                 );
     126         1946 :         };
     127         1946 :         break;
     128              :     }
     129              : 
     130         1946 :     return event.m_cachedStartVal + valueFunction(tick.ticks() - eventTick.ticks());
     131         1946 : }
     132              : 
     133              : //---------------------------------------------------------
     134              : //   val
     135              : ///   return value at tick position.
     136              : //---------------------------------------------------------
     137              : 
     138         2540 : int VelocityMap::val(Fraction tick) const
     139              : {
     140         2540 :     auto eventIter = upper_bound(tick);
     141         2540 :     if (eventIter == begin()) {
     142          264 :         return DEFAULT_VALUE;
     143              :     }
     144              : 
     145         2276 :     eventIter--;
     146         2276 :     bool foundHairpin = false;
     147         2276 :     VelocityEvent hairpinFound = eventIter->second;         // only used to init
     148         2276 :     Fraction hairpinFoundStartTick = eventIter->first;
     149         2276 :     auto values = equal_range(hairpinFoundStartTick);
     150         6498 :     for (auto it = values.first; it != values.second; ++it) {
     151         4222 :         auto& event = it->second;
     152         4222 :         if (event.m_type == VelocityEventType::HAIRPIN) {
     153         1946 :             foundHairpin = true;
     154         1946 :             hairpinFound = event;
     155              :         }
     156              :     }
     157              : 
     158         2276 :     if (!foundHairpin) {
     159              :         // Last event must be a dynamic, since there are max two events at one tick
     160          330 :         return eventIter->second.m_value;
     161              :     }
     162              : 
     163         1946 :     if (tick >= (hairpinFoundStartTick + hairpinFound.m_length)) {
     164            0 :         return hairpinFound.m_cachedEndVal;
     165              :     } else {
     166              :         // Do some maths!
     167         1946 :         return interpolate(hairpinFoundStartTick, hairpinFound, tick);        // NOTE:JT check should hairpinFound be eventIter.value()
     168              :     }
     169              : }
     170              : 
     171              : //---------------------------------------------------------
     172              : //   addDynamic
     173              : //---------------------------------------------------------
     174              : 
     175          332 : void VelocityMap::addDynamic(Fraction tick, int value)
     176              : {
     177          332 :     insert({ tick, VelocityEvent(value) });
     178          332 : }
     179              : 
     180              : //---------------------------------------------------------
     181              : //   addHairpin
     182              : ///   A `change` of 0 means that the change in velocity should be calculated from the next dynamic
     183              : ///   velocity event.
     184              : //---------------------------------------------------------
     185              : 
     186           32 : void VelocityMap::addHairpin(Fraction stick, Fraction etick, int change, ChangeMethod method, ChangeDirection direction)
     187              : {
     188           32 :     change = std::abs(change);
     189           32 :     change *= (direction == ChangeDirection::INCREASING) ? 1 : -1;
     190           32 :     insert({ stick, VelocityEvent(stick, etick, change, method, direction) });
     191           32 : }
     192              : 
     193              : //---------------------------------------------------------
     194              : //   sortHairpins
     195              : ///   put the hairpins in size order if they start at the same point
     196              : //---------------------------------------------------------
     197              : 
     198         1864 : void VelocityMap::sortHairpins()
     199              : {
     200         2176 :     for (auto& tick : muse::uniqueKeys(*this)) {
     201              :         // hairpinEvents will contain all the hairpins at this tick
     202          312 :         std::vector<VelocityEvent> hairpinEvents;
     203          312 :         auto values = this->equal_range(tick);
     204          660 :         for (auto it = values.first; it != values.second; ++it) {
     205          348 :             auto& event = it->second;
     206          348 :             if (event.m_type == VelocityEventType::DYNAMIC) {
     207          316 :                 continue;
     208              :             }
     209           32 :             hairpinEvents.push_back(event);
     210              :         }
     211              : 
     212          312 :         if (int(hairpinEvents.size()) > 1) {
     213              :             // Sort hairpinEvents so that the longest hairpins come first -
     214              :             // this is important for when we remove hairpins/dynamics enclosed within other
     215              :             // hairpins during stage 1.
     216            0 :             std::sort(hairpinEvents.begin(), hairpinEvents.end(), VelocityMap::compareHairpinEvents);
     217            0 :             for (auto& event : hairpinEvents) {
     218            0 :                 insert({ tick, event });
     219              :             }
     220              :         }
     221         2176 :     }
     222         1864 : }
     223              : 
     224              : //---------------------------------------------------------
     225              : //   resolveHairpinCollisions
     226              : ///   remove any hairpins that are completely enclosed within other hairpins
     227              : //---------------------------------------------------------
     228              : 
     229         1864 : void VelocityMap::resolveHairpinCollisions()
     230              : {
     231         1864 :     Fraction currentHairpinEnd = Fraction(-1, 1);        // end point of hairpin we're in
     232         1864 :     bool inHairpin = false;                              // whether we're in a hairpin or not
     233              : 
     234              :     // Keep a record of the endpoints
     235         1864 :     EndPointsVector endPoints;
     236         1864 :     std::vector<bool> startsInHairpin;
     237              : 
     238         1864 :     auto i = begin();
     239         2212 :     while (i != end()) {
     240          348 :         Fraction tick = i->first;
     241          348 :         VelocityEvent& event = i->second;
     242          348 :         Fraction etick = tick + event.m_length;
     243              : 
     244              :         // Reset if we've left the hairpin we were in
     245          348 :         if (currentHairpinEnd < tick) {
     246          320 :             inHairpin = false;
     247              :         }
     248              : 
     249          348 :         if (event.m_type == VelocityEventType::HAIRPIN) {
     250           32 :             if (inHairpin) {
     251            0 :                 if (etick <= currentHairpinEnd) {
     252              :                     // delete, this event is enveloped
     253            0 :                     i = erase(i);
     254              :                     // don't add to the end points
     255            0 :                     continue;
     256              :                 } else {
     257            0 :                     currentHairpinEnd = etick;
     258            0 :                     startsInHairpin.push_back(true);
     259              :                 }
     260              :             } else {
     261           32 :                 currentHairpinEnd = etick;
     262           32 :                 inHairpin = true;
     263           32 :                 startsInHairpin.push_back(false);
     264              :             }
     265              : 
     266           32 :             endPoints.push_back(std::make_pair(tick, etick));
     267              :         }
     268              : 
     269          348 :         i++;
     270              :     }
     271              : 
     272         1864 :     adjustCollidingHairpinsLength(startsInHairpin, endPoints);
     273         1864 : }
     274              : 
     275              : //---------------------------------------------------------
     276              : //   adjustCollidingHairpinsLength
     277              : ///   readjust lengths of any colliding hairpins
     278              : //---------------------------------------------------------
     279              : 
     280         1864 : void VelocityMap::adjustCollidingHairpinsLength(std::vector<bool>& startsInHairpin, EndPointsVector& endPoints)
     281              : {
     282              :     // moveTo stores the events that need to be moved to a Fraction position
     283         1864 :     std::map<Fraction, VelocityEvent> moveTo;
     284         1864 :     auto i = begin();
     285         1864 :     int j = -1;
     286         2212 :     while (i != end()) {
     287          348 :         Fraction tick = i->first;
     288          348 :         VelocityEvent& event = i->second;
     289          348 :         if (event.m_type != VelocityEventType::HAIRPIN) {
     290          316 :             i++;
     291          348 :             continue;
     292              :         }
     293              : 
     294           32 :         j++;
     295           32 :         if (!startsInHairpin[j]) {
     296           32 :             i++;
     297           32 :             continue;
     298              :         }
     299              : 
     300              :         // Take a copy of the event and remove it
     301            0 :         Fraction newTick = endPoints[j - 1].second;
     302            0 :         event.m_length -= (newTick - tick);
     303            0 :         moveTo[newTick] = event;
     304            0 :         i = erase(i);
     305              :     }
     306              : 
     307              :     // Re-insert the events that we need to move in their new positions
     308         1864 :     for (auto k = moveTo.begin(); k != moveTo.end(); k++) {
     309            0 :         insert({ k->first, k->second });
     310              :     }
     311         1864 : }
     312              : 
     313              : //---------------------------------------------------------
     314              : //   resolveDynamicInsideHairpinCollisions
     315              : ///   if dynamic is inside the hairpin, shorten hairpin length up to this dynamic
     316              : //---------------------------------------------------------
     317              : 
     318         1864 : void VelocityMap::resolveDynamicInsideHairpinCollisions()
     319              : {
     320         1864 :     auto lastEventIt = end();
     321         1864 :     auto i = begin();
     322         2212 :     while (i != end()) {
     323          348 :         VelocityEvent& event = i->second;
     324          316 :         if (event.m_type == VelocityEventType::DYNAMIC && lastEventIt != end()
     325          664 :             && lastEventIt->second.m_type == VelocityEventType::HAIRPIN) {
     326           28 :             Fraction dynamicTick = i->first;
     327           28 :             Fraction lastHairpinStart = lastEventIt->first;
     328           28 :             Fraction lastHairpinEnd = lastHairpinStart + lastEventIt->second.m_length;
     329           28 :             if (lastHairpinEnd > dynamicTick) {
     330            4 :                 lastEventIt->second.m_length = dynamicTick - lastHairpinStart;
     331              :             }
     332              :         }
     333              : 
     334          348 :         lastEventIt = i;
     335          348 :         i++;
     336              :     }
     337         1864 : }
     338              : 
     339           32 : bool VelocityMap::dynamicExistsOnTick(Fraction tick) const
     340              : {
     341           32 :     auto values = equal_range(tick);
     342           32 :     for (auto it = values.first; it != values.second; ++it) {
     343           24 :         auto& event = it->second;
     344           24 :         if (event.m_type == VelocityEventType::DYNAMIC) {
     345           24 :             return true;
     346              :         }
     347              :     }
     348              : 
     349            8 :     return false;
     350              : }
     351              : 
     352           56 : VelocityEvent VelocityMap::dynamicEventForTick(Fraction tick) const
     353              : {
     354           56 :     auto equalValues = equal_range(tick);
     355           60 :     for (auto it = equalValues.first; it != equalValues.second; ++it) {
     356           56 :         auto& event = it->second;
     357           56 :         if (event.m_type == VelocityEventType::DYNAMIC) {
     358           52 :             return event;
     359              :         }
     360              :     }
     361              : 
     362            4 :     if (equalValues.first == begin()) {
     363            0 :         return VelocityEvent();
     364              :     }
     365              : 
     366            4 :     auto previousIt = std::prev(equalValues.first);
     367            4 :     auto smallerValues = equal_range(previousIt->first);
     368            4 :     for (auto it = smallerValues.first; it != smallerValues.second; ++it) {
     369            4 :         auto& event = it->second;
     370            4 :         if (event.m_type == VelocityEventType::DYNAMIC) {
     371            4 :             return event;
     372              :         }
     373              :     }
     374              : 
     375            0 :     return VelocityEvent();
     376              : }
     377              : 
     378         1864 : void VelocityMap::addMissingDynamicsAfterHairpins()
     379              : {
     380           20 :     auto nextDynamicVal = [](int previousDynamicValue, ChangeDirection hairpinDirection) {
     381           20 :         int newVal
     382              :             = (hairpinDirection
     383           20 :                == ChangeDirection::INCREASING ? previousDynamicValue + VelocityMap::STEP : previousDynamicValue - VelocityMap::STEP);
     384           20 :         return std::clamp(newVal, VelocityMap::MIN_VALUE, VelocityMap::MAX_VALUE);
     385              :     };
     386              : 
     387         2220 :     for (auto it = begin(); it != end(); it++) {
     388          356 :         auto& event = it->second;
     389          356 :         if (event.m_type == VelocityEventType::HAIRPIN && event.m_value == 0) {
     390           32 :             Fraction startHairpinTick = it->first;
     391           32 :             Fraction endHairpinTick = it->first + it->second.m_length;
     392           32 :             VelocityEvent dynamicOnCurrentTick = dynamicEventForTick(startHairpinTick);
     393              : 
     394           32 :             if (!dynamicExistsOnTick(endHairpinTick)) {
     395            8 :                 if (dynamicOnCurrentTick.m_type != VelocityEventType::INVALID) {
     396            8 :                     int newVal = nextDynamicVal(dynamicOnCurrentTick.m_value, event.m_direction);
     397            8 :                     insert({ endHairpinTick, VelocityEvent(newVal) });
     398              :                 }
     399              :             } else {
     400           24 :                 VelocityEvent dynamicOnEndTick = dynamicEventForTick(endHairpinTick);
     401           24 :                 int velocityJump = dynamicOnEndTick.m_value - dynamicOnCurrentTick.m_value;
     402              : 
     403           24 :                 if ((event.m_direction
     404           16 :                      == ChangeDirection::INCREASING && velocityJump < 0)
     405           16 :                     || (event.m_direction == ChangeDirection::DECREASING && velocityJump > 0)) {
     406           12 :                     int newVal = nextDynamicVal(dynamicOnCurrentTick.m_value, event.m_direction);
     407           12 :                     event.m_value = newVal - dynamicOnCurrentTick.m_value;
     408              :                 }
     409              :             }
     410              :         }
     411              :     }
     412         1864 : }
     413              : 
     414              : //---------------------------------------------------------
     415              : //   fillHairpinsCache
     416              : ///   cache start and end values for each hairpin
     417              : //---------------------------------------------------------
     418              : 
     419         1864 : void VelocityMap::fillHairpinsCache()
     420              : {
     421         2220 :     for (auto i = begin(); i != end(); i++) {
     422          356 :         Fraction tick = i->first;
     423          356 :         auto& event = i->second;
     424          356 :         if (event.m_type != VelocityEventType::HAIRPIN) {
     425          324 :             continue;
     426              :         }
     427              : 
     428              :         // Phase 1: cache a start value for the hairpin
     429              :         // Try and get a dynamic at the tick of this hairpin
     430           32 :         bool foundDynamic = false;
     431           32 :         auto values = this->equal_range(tick);
     432           36 :         for (auto it = values.first; it != values.second; ++it) {
     433           32 :             auto& currentChangeEvent = it->second;
     434           32 :             if (currentChangeEvent.m_type == VelocityEventType::DYNAMIC) {
     435           28 :                 event.m_cachedStartVal = currentChangeEvent.m_value;
     436           28 :                 foundDynamic = true;
     437           28 :                 break;
     438              :             }
     439              :         }
     440              : 
     441              :         // If there isn't a dynamic, use from the last event:
     442              :         //  - the cached end value if it's a hairpin
     443              :         //  - the value if it's a dynamic
     444           32 :         if (!foundDynamic) {
     445            4 :             if (i != begin()) {
     446            4 :                 auto prevChangeEventIter = i;
     447            4 :                 prevChangeEventIter--;
     448              : 
     449              :                 // Look for a hairpin first
     450            4 :                 bool foundHairpin = false;
     451            4 :                 auto prevValues = this->equal_range(prevChangeEventIter->first);
     452            8 :                 for (auto it = prevValues.first; it != prevValues.second; ++it) {
     453            4 :                     auto& prevChangeEvent = it->second;
     454            4 :                     if (prevChangeEvent.m_type == VelocityEventType::HAIRPIN) {
     455            0 :                         event.m_cachedStartVal = prevChangeEvent.m_cachedEndVal;
     456            0 :                         foundHairpin = true;
     457            0 :                         break;
     458              :                     }
     459              :                 }
     460              : 
     461            4 :                 if (!foundHairpin) {
     462              :                     // prevChangeEventIter must point to a dynamic in this case
     463            4 :                     event.m_cachedStartVal = prevChangeEventIter->second.m_value;
     464              :                 }
     465              :             } else {
     466            0 :                 event.m_cachedStartVal = DEFAULT_VALUE;
     467              :             }
     468              :         }
     469              : 
     470              :         // Phase 2: cache an end value for the hairpin
     471              :         // If there's no set velocity change:
     472           32 :         if (event.m_value == 0) {
     473           20 :             auto nextChangeEventIter = i;
     474           20 :             nextChangeEventIter++;
     475              :             // There's a chance that the next event is a dynamic at the same tick as the
     476              :             // start of the current hairpin. If so, get the next event, which is assured
     477              :             // to be a different (larger) tick
     478           20 :             if (nextChangeEventIter != end() && nextChangeEventIter->first == tick) {
     479            0 :                 nextChangeEventIter++;
     480              :             }
     481              : 
     482              :             // If this is the last event, there is no change
     483           20 :             if (nextChangeEventIter == end()) {
     484            0 :                 event.m_cachedEndVal = event.m_cachedStartVal;
     485              :             } else {
     486              :                 // Search for a dynamic event at the next event point
     487           20 :                 bool foundDynamic2 = false;
     488           20 :                 auto nextValues = this->equal_range(nextChangeEventIter->first);
     489           20 :                 for (auto it = nextValues.first; it != nextValues.second; ++it) {
     490           20 :                     auto& nextChangeEvent = it->second;
     491           20 :                     if (nextChangeEvent.m_type == VelocityEventType::DYNAMIC) {
     492           20 :                         event.m_cachedEndVal = nextChangeEvent.m_value;
     493           20 :                         foundDynamic2 = true;
     494           20 :                         break;
     495              :                     }
     496              :                 }
     497              : 
     498              :                 // We haven't found a dynamic, so there must be a hairpin. What does the user want?
     499              :                 // A good guess would to be to interpolate, but that might get complex, so just ignore
     500              :                 // this hairpin and set the ending to be the same as the start.
     501              :                 // TODO: implementing some form of smart interpolation would be nice.
     502           20 :                 if (!foundDynamic2) {
     503            0 :                     event.m_cachedEndVal = event.m_cachedStartVal;
     504              :                 }
     505              :             }
     506              :         } else {
     507           12 :             event.m_cachedEndVal = event.m_cachedStartVal + event.m_value;
     508              :         }
     509              : 
     510              :         // And finally... if something's wrong, make it not wrong
     511           32 :         if ((event.m_cachedStartVal > event.m_cachedEndVal && event.m_direction == ChangeDirection::INCREASING)
     512           32 :             || (event.m_cachedStartVal < event.m_cachedEndVal && event.m_direction == ChangeDirection::DECREASING)) {
     513            0 :             event.m_cachedEndVal = event.m_cachedStartVal;
     514              :         }
     515              :     }
     516         1864 : }
     517              : 
     518              : //---------------------------------------------------------
     519              : //   setup
     520              : //---------------------------------------------------------
     521              : 
     522         1864 : void VelocityMap::setup()
     523              : {
     524         1864 :     sortHairpins();
     525         1864 :     resolveHairpinCollisions();
     526         1864 :     resolveDynamicInsideHairpinCollisions();
     527         1864 :     addMissingDynamicsAfterHairpins();
     528         1864 :     fillHairpinsCache();
     529         1864 : }
     530              : 
     531              : //---------------------------------------------------------
     532              : //   changesInRange
     533              : ///   returns a list of changes in a range, and their start and end points
     534              : //---------------------------------------------------------
     535              : 
     536          168 : std::vector<std::pair<Fraction, Fraction> > VelocityMap::changesInRange(Fraction stick, Fraction etick) const
     537              : {
     538          168 :     std::vector<std::pair<Fraction, Fraction> > tempChanges;
     539              : 
     540              :     // Force a new event on every noteon, in case the velocity has changed
     541          168 :     tempChanges.push_back(std::make_pair(stick, stick));
     542          185 :     for (auto iter = lower_bound(stick); iter != end(); iter++) {
     543           75 :         Fraction tick = iter->first;
     544           75 :         if (tick > etick) {
     545           58 :             break;
     546              :         }
     547              : 
     548           17 :         auto& event = iter->second;
     549           17 :         if (event.m_type == VelocityEventType::DYNAMIC) {
     550           16 :             tempChanges.push_back(std::make_pair(tick, tick));
     551            1 :         } else if (event.m_type == VelocityEventType::HAIRPIN) {
     552            1 :             Fraction eventEtick = tick + event.m_length;
     553            1 :             Fraction useEtick = eventEtick > etick ? etick : eventEtick;
     554            1 :             tempChanges.push_back(std::make_pair(tick, useEtick));
     555              :         }
     556              :     }
     557              : 
     558              :     // And also go back one and try to find hairpin coming into this range
     559          168 :     auto iter = lower_bound(stick);
     560          168 :     if (iter != begin()) {
     561           36 :         iter--;
     562           36 :         auto& event = iter->second;
     563           36 :         if (event.m_type == VelocityEventType::HAIRPIN) {
     564            0 :             Fraction eventEtick = iter->first + event.m_length;
     565            0 :             if (eventEtick > stick) {
     566            0 :                 tempChanges.push_back(std::make_pair(stick, eventEtick));
     567              :             }
     568              :         }
     569              :     }
     570          168 :     return tempChanges;
     571            0 : }
     572              : 
     573              : //---------------------------------------------------------
     574              : //   operator==
     575              : //---------------------------------------------------------
     576              : 
     577            0 : bool VelocityEvent::operator==(const VelocityEvent& event) const
     578              : {
     579              :     return
     580            0 :         m_value == event.m_value
     581            0 :         && m_type == event.m_type
     582            0 :         && m_length == event.m_length
     583            0 :         && m_method == event.m_method
     584            0 :         && m_direction == event.m_direction
     585              :     ;
     586              : }
     587              : }
        

Generated by: LCOV version 2.0-1