LCOV - code coverage report
Current view: top level - src/engraving/dom - gradualtempochange.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 85.5 % 214 183
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              : #include "gradualtempochange.h"
      24              : 
      25              : #include "measure.h"
      26              : #include "rehearsalmark.h"
      27              : #include "score.h"
      28              : #include "segment.h"
      29              : #include "staff.h"
      30              : #include "system.h"
      31              : #include "tempotext.h"
      32              : #include "text.h"
      33              : 
      34              : #include "types/typesconv.h"
      35              : 
      36              : #include "log.h"
      37              : 
      38              : using namespace mu;
      39              : using namespace mu::engraving;
      40              : 
      41              : static const ElementStyle tempoStyle {
      42              :     { Sid::tempoChangeSystemFlag, Pid::SYSTEM_FLAG },
      43              :     { Sid::tempoChangePlacement, Pid::PLACEMENT },
      44              :     { Sid::tempoChangeMinDistance, Pid::MIN_DISTANCE },
      45              : 
      46              :     { Sid::tempoChangeColor, Pid::COLOR },
      47              : 
      48              :     { Sid::tempoChangeFontFace, Pid::BEGIN_FONT_FACE },
      49              :     { Sid::tempoChangeFontFace, Pid::CONTINUE_FONT_FACE },
      50              :     { Sid::tempoChangeFontFace, Pid::END_FONT_FACE },
      51              : 
      52              :     { Sid::tempoChangeFontSize, Pid::BEGIN_FONT_SIZE },
      53              :     { Sid::tempoChangeFontSize, Pid::CONTINUE_FONT_SIZE },
      54              :     { Sid::tempoChangeFontSize, Pid::END_FONT_SIZE },
      55              : 
      56              :     { Sid::tempoChangeFontStyle, Pid::BEGIN_FONT_STYLE },
      57              :     { Sid::tempoChangeFontStyle, Pid::CONTINUE_FONT_STYLE },
      58              :     { Sid::tempoChangeFontStyle, Pid::END_FONT_STYLE },
      59              : 
      60              :     { Sid::tempoChangeAlign, Pid::BEGIN_TEXT_ALIGN },
      61              :     { Sid::tempoChangeAlign, Pid::CONTINUE_TEXT_ALIGN },
      62              :     { Sid::tempoChangeAlign, Pid::END_TEXT_ALIGN },
      63              : 
      64              :     { Sid::tempoChangePosition, Pid::BEGIN_TEXT_POSITION },
      65              :     { Sid::tempoChangePosition, Pid::CONTINUE_TEXT_POSITION },
      66              :     { Sid::tempoChangePosition, Pid::END_TEXT_POSITION },
      67              : 
      68              :     { Sid::tempoChangeFontSpatiumDependent, Pid::SIZE_SPATIUM_DEPENDENT },
      69              :     { Sid::tempoChangeLineWidth, Pid::LINE_WIDTH },
      70              :     { Sid::tempoChangeLineStyle, Pid::LINE_STYLE },
      71              :     { Sid::tempoChangeDashLineLen, Pid::DASH_LINE_LEN },
      72              :     { Sid::tempoChangeDashGapLen, Pid::DASH_GAP_LEN },
      73              :     { Sid::tempoChangeFontSpatiumDependent, Pid::TEXT_SIZE_SPATIUM_DEPENDENT },
      74              : 
      75              :     { Sid::gradualTempoChangeEndLineArrowHeight,         Pid::END_LINE_ARROW_HEIGHT },
      76              :     { Sid::gradualTempoChangeEndLineArrowWidth,          Pid::END_LINE_ARROW_WIDTH },
      77              :     { Sid::gradualTempoChangeBeginLineArrowHeight,       Pid::BEGIN_LINE_ARROW_HEIGHT },
      78              :     { Sid::gradualTempoChangeBeginLineArrowWidth,        Pid::BEGIN_LINE_ARROW_WIDTH },
      79              :     { Sid::gradualTempoChangeEndFilledArrowHeight,       Pid::END_FILLED_ARROW_HEIGHT },
      80              :     { Sid::gradualTempoChangeEndFilledArrowWidth,        Pid::END_FILLED_ARROW_WIDTH },
      81              :     { Sid::gradualTempoChangeBeginFilledArrowHeight,     Pid::BEGIN_FILLED_ARROW_HEIGHT },
      82              :     { Sid::gradualTempoChangeBeginFilledArrowWidth,      Pid::BEGIN_FILLED_ARROW_WIDTH },
      83              : 
      84              :     { Sid::tempoChangeMusicalSymbolSize,          Pid::BEGIN_TEXT_MUSIC_SYMBOLS_SIZE },
      85              :     { Sid::tempoChangeMusicalSymbolSize,          Pid::CONTINUE_TEXT_MUSIC_SYMBOLS_SIZE },
      86              :     { Sid::tempoChangeMusicalSymbolSize,          Pid::END_TEXT_MUSIC_SYMBOLS_SIZE },
      87              :     { Sid::dummyMusicalSymbolsScale,           Pid::BEGIN_TEXT_MUSICAL_SYMBOLS_SCALE },
      88              :     { Sid::dummyMusicalSymbolsScale,           Pid::CONTINUE_TEXT_MUSICAL_SYMBOLS_SCALE },
      89              :     { Sid::dummyMusicalSymbolsScale,           Pid::END_TEXT_MUSICAL_SYMBOLS_SCALE },
      90              : };
      91              : 
      92              : static const ElementStyle tempoSegmentStyle {
      93              :     { Sid::tempoChangeMinDistance, Pid::MIN_DISTANCE }
      94              : };
      95              : 
      96              : static const std::unordered_map<GradualTempoChangeType, double> DEFAULT_FACTORS_MAP {
      97              :     { GradualTempoChangeType::Accelerando, 1.33 },
      98              :     { GradualTempoChangeType::Allargando, 0.75 },
      99              :     { GradualTempoChangeType::Calando, 0.5 },
     100              :     { GradualTempoChangeType::Lentando, 0.75 },
     101              :     { GradualTempoChangeType::Morendo, 0.5 },
     102              :     { GradualTempoChangeType::Precipitando, 1.15 },
     103              :     { GradualTempoChangeType::Rallentando, 0.75 },
     104              :     { GradualTempoChangeType::Ritardando, 0.75 },
     105              :     { GradualTempoChangeType::Smorzando, 0.5 },
     106              :     { GradualTempoChangeType::Sostenuto, 0.95 },
     107              :     { GradualTempoChangeType::Stringendo, 1.5 }
     108              : };
     109              : 
     110           91 : GradualTempoChange::GradualTempoChange(DummyParentOr<EngravingItem> parent)
     111           91 :     : TextLineBase(ElementType::GRADUAL_TEMPO_CHANGE, parent, ElementFlag::SYSTEM)
     112              : {
     113           91 :     initElementStyle(&tempoStyle);
     114              : 
     115           91 :     resetProperty(Pid::LINE_VISIBLE);
     116           91 :     resetProperty(Pid::BEGIN_TEXT_PLACE);
     117           91 :     resetProperty(Pid::BEGIN_TEXT);
     118           91 :     resetProperty(Pid::CONTINUE_TEXT_PLACE);
     119           91 :     resetProperty(Pid::CONTINUE_TEXT);
     120           91 :     resetProperty(Pid::END_TEXT_PLACE);
     121           91 :     resetProperty(Pid::END_TEXT);
     122           91 : }
     123              : 
     124            3 : GradualTempoChange* GradualTempoChange::clone() const
     125              : {
     126            3 :     return new GradualTempoChange(*this);
     127              : }
     128              : 
     129           12 : LineSegment* GradualTempoChange::createLineSegment()
     130              : {
     131           12 :     GradualTempoChangeSegment* lineSegment = new GradualTempoChangeSegment(this);
     132           12 :     lineSegment->setTrack(track());
     133           12 :     return lineSegment;
     134              : }
     135              : 
     136            0 : GradualTempoChangeType GradualTempoChange::tempoChangeType() const
     137              : {
     138            0 :     return m_tempoChangeType;
     139              : }
     140              : 
     141           15 : ChangeMethod GradualTempoChange::easingMethod() const
     142              : {
     143           15 :     return m_tempoEasingMethod;
     144              : }
     145              : 
     146            5 : void GradualTempoChange::setTempoChangeType(const GradualTempoChangeType type)
     147              : {
     148            5 :     m_tempoChangeType = type;
     149            5 : }
     150              : 
     151           65 : double GradualTempoChange::tempoChangeFactor() const
     152              : {
     153           65 :     if (m_tempoChangeFactor.has_value()) {
     154           15 :         return m_tempoChangeFactor.value();
     155              :     }
     156              : 
     157           50 :     return muse::value(DEFAULT_FACTORS_MAP, m_tempoChangeType, 1.0);
     158              : }
     159              : 
     160         1613 : PropertyValue GradualTempoChange::getProperty(Pid id) const
     161              : {
     162         1613 :     switch (id) {
     163           50 :     case Pid::TEMPO_CHANGE_TYPE:
     164           50 :         return m_tempoChangeType;
     165           50 :     case Pid::TEMPO_EASING_METHOD:
     166           50 :         return m_tempoEasingMethod;
     167           50 :     case Pid::TEMPO_CHANGE_FACTOR:
     168           50 :         return tempoChangeFactor();
     169           50 :     case Pid::SNAP_AFTER:
     170           50 :         return snapToItemAfter();
     171            0 :     case Pid::TEMPO_ALIGN_RIGHT_OF_REHEARSAL_MARK:
     172            0 :         return m_alignRightOfRehearsalMark;
     173         1413 :     default:
     174         1413 :         return TextLineBase::getProperty(id);
     175              :     }
     176              : }
     177              : 
     178         4663 : bool GradualTempoChange::setProperty(Pid id, const PropertyValue& val)
     179              : {
     180         4663 :     switch (id) {
     181            6 :     case Pid::TEMPO_CHANGE_TYPE:
     182            6 :         m_tempoChangeType = val.value<GradualTempoChangeType>();
     183            6 :         break;
     184            0 :     case Pid::TEMPO_EASING_METHOD:
     185            0 :         m_tempoEasingMethod = val.value<ChangeMethod>();
     186            0 :         break;
     187            4 :     case Pid::TEMPO_CHANGE_FACTOR:
     188            4 :         m_tempoChangeFactor = val.toReal();
     189            4 :         break;
     190            0 :     case Pid::SNAP_AFTER:
     191            0 :         setSnapToItemAfter(val.toBool());
     192            0 :         break;
     193            0 :     case Pid::TEMPO_ALIGN_RIGHT_OF_REHEARSAL_MARK:
     194            0 :         m_alignRightOfRehearsalMark = val.toBool();
     195            0 :         break;
     196         4653 :     default:
     197         4653 :         if (!TextLineBase::setProperty(id, val)) {
     198            0 :             return false;
     199              :         }
     200         4653 :         break;
     201              :     }
     202              : 
     203         4663 :     triggerLayout();
     204         4663 :     return true;
     205              : }
     206              : 
     207         2112 : PropertyValue GradualTempoChange::propertyDefault(Pid propertyId) const
     208              : {
     209         2112 :     switch (propertyId) {
     210            0 :     case Pid::ALIGN:
     211            0 :         return style().styleV(Sid::tempoChangeAlign);
     212              : 
     213            2 :     case Pid::LINE_WIDTH:
     214            2 :         return style().styleV(Sid::tempoChangeLineWidth);
     215            2 :     case Pid::LINE_STYLE:
     216            2 :         return style().styleV(Sid::tempoChangeLineStyle);
     217          141 :     case Pid::LINE_VISIBLE:
     218          141 :         return true;
     219              : 
     220          100 :     case Pid::CONTINUE_TEXT_OFFSET:
     221              :     case Pid::END_TEXT_OFFSET:
     222          100 :         return PropertyValue::fromValue(PointF(0, 0));
     223              : 
     224            2 :     case Pid::BEGIN_FONT_STYLE:
     225            2 :         return style().styleV(Sid::tempoChangeFontStyle);
     226              : 
     227          423 :     case Pid::BEGIN_TEXT:
     228              :     case Pid::CONTINUE_TEXT:
     229              :     case Pid::END_TEXT:
     230          423 :         return "";
     231              : 
     232           50 :     case Pid::BEGIN_HOOK_TYPE:
     233           50 :         return HookType::NONE;
     234              : 
     235          423 :     case Pid::BEGIN_TEXT_PLACE:
     236              :     case Pid::CONTINUE_TEXT_PLACE:
     237              :     case Pid::END_TEXT_PLACE:
     238          423 :         return TextPlace::AUTO;
     239              : 
     240           50 :     case Pid::TEMPO_CHANGE_TYPE:
     241           50 :         return GradualTempoChangeType::Undefined;
     242           50 :     case Pid::TEMPO_EASING_METHOD:
     243           50 :         return ChangeMethod::NORMAL;
     244           50 :     case Pid::TEMPO_CHANGE_FACTOR:
     245          100 :         return muse::value(DEFAULT_FACTORS_MAP, m_tempoChangeType, 1.0);
     246              : 
     247           50 :     case Pid::SNAP_AFTER:
     248           50 :         return true;
     249              : 
     250            0 :     case Pid::TEMPO_ALIGN_RIGHT_OF_REHEARSAL_MARK:
     251            0 :         return true;
     252              : 
     253           24 :     case Pid::TEXT_STYLE:
     254           24 :         return TextStyleType::TEMPO_CHANGE;
     255              : 
     256          745 :     default:
     257          745 :         return TextLineBase::propertyDefault(propertyId);
     258              :     }
     259              : }
     260              : 
     261         3993 : Sid GradualTempoChange::getPropertyStyle(Pid id) const
     262              : {
     263         3993 :     switch (id) {
     264          103 :     case Pid::PLACEMENT:
     265          103 :         return Sid::tempoChangePlacement;
     266          291 :     case Pid::BEGIN_FONT_FACE:
     267              :     case Pid::CONTINUE_FONT_FACE:
     268              :     case Pid::END_FONT_FACE:
     269          291 :         return Sid::tempoChangeFontFace;
     270          291 :     case Pid::BEGIN_FONT_SIZE:
     271              :     case Pid::CONTINUE_FONT_SIZE:
     272              :     case Pid::END_FONT_SIZE:
     273          291 :         return Sid::tempoChangeFontSize;
     274          289 :     case Pid::BEGIN_FONT_STYLE:
     275              :     case Pid::CONTINUE_FONT_STYLE:
     276              :     case Pid::END_FONT_STYLE:
     277          289 :         return Sid::tempoChangeFontStyle;
     278          291 :     case Pid::BEGIN_TEXT_ALIGN:
     279              :     case Pid::CONTINUE_TEXT_ALIGN:
     280              :     case Pid::END_TEXT_ALIGN:
     281          291 :         return Sid::tempoChangeAlign;
     282            0 :     case Pid::BEGIN_TEXT:
     283            0 :         return Sid::letRingText;
     284         2728 :     default:
     285         2728 :         break;
     286              :     }
     287         2728 :     return TextLineBase::getPropertyStyle(id);
     288              : }
     289              : 
     290           48 : bool GradualTempoChange::adjustForRehearsalMark(bool start) const
     291              : {
     292           48 :     const Segment* segment = start ? startSegment() : endSegment();
     293           48 :     if (!m_alignRightOfRehearsalMark || !segment) {
     294            0 :         return false;
     295              :     }
     296              : 
     297           48 :     const RehearsalMark* rehearsalMark = toRehearsalMark(segment->findAnnotation(ElementType::REHEARSAL_MARK, track(), track()));
     298           48 :     if (!rehearsalMark) {
     299           44 :         return false;
     300              :     }
     301              : 
     302            4 :     double staffHeight = staff() && placeBelow() ? staff()->staffHeight(tick()) : 0.0;
     303            4 :     double tempoChangePos = staffHeight + defaultPos().y() + (autoplace() ? absoluteFromSpatium(minDistance()) : 0.0);
     304            4 :     RectF rehearsalMarkBbox = rehearsalMark ? rehearsalMark->ldata()->bbox().translated(rehearsalMark->pos()) : RectF();
     305              : 
     306            4 :     const bool sameSide = placeAbove() == rehearsalMark->placeAbove();
     307            4 :     const bool collision = muse::RealIsEqualOrMore(rehearsalMarkBbox.bottom(), tempoChangePos) && muse::RealIsEqualOrLess(
     308            4 :         rehearsalMarkBbox.top(), tempoChangePos);
     309              : 
     310            4 :     return sameSide && collision;
     311              : }
     312              : 
     313           44 : PointF GradualTempoChange::linePos(Grip grip, System** system) const
     314              : {
     315           44 :     bool start = grip == Grip::START;
     316           44 :     PointF defaultPos = TextLineBase::linePos(grip, system);
     317           44 :     if (!adjustForRehearsalMark(start)) {
     318           40 :         return defaultPos;
     319              :     }
     320              : 
     321            4 :     const Segment* segment = start ? startSegment() : endSegment();
     322            4 :     const RehearsalMark* rehearsalMark = toRehearsalMark(segment->findAnnotation(ElementType::REHEARSAL_MARK, track(), track()));
     323            4 :     RectF rehearsalMarkBbox = rehearsalMark ? rehearsalMark->ldata()->bbox().translated(rehearsalMark->pos()) : RectF();
     324              : 
     325            4 :     PointF rehearsalMarkPos = segment->pos() + segment->measure()->pos();
     326            4 :     rehearsalMarkBbox.translate(rehearsalMarkPos);
     327              : 
     328            4 :     Text* text = start ? toGradualTempoChangeSegment(frontSegment())->text() : toGradualTempoChangeSegment(backSegment())->endText();
     329              : 
     330            4 :     const double sp = spatium();
     331              : 
     332            4 :     double padding = sp;
     333            4 :     if (text) {
     334            4 :         const double fontSizeScaleFactor = text->size() / 10.0;
     335            4 :         padding = 0.5 * sp * fontSizeScaleFactor;
     336              :     }
     337              : 
     338            4 :     padding *= start ? 1.0 : -1.0;
     339            4 :     double x = (start ? rehearsalMarkBbox.right() : rehearsalMarkBbox.left()) + padding;
     340              : 
     341            4 :     *system = segment->measure()->system();
     342              : 
     343            4 :     x = start ? std::max(x, defaultPos.x()) : x;
     344              : 
     345            4 :     return PointF(x, 0.0);
     346              : }
     347              : 
     348            1 : TranslatableString GradualTempoChange::subtypeUserName() const
     349              : {
     350            1 :     return TConv::userName(m_tempoChangeType);
     351              : }
     352              : 
     353           29 : Sid GradualTempoChange::defaultPosSid() const
     354              : {
     355           29 :     return placeAbove() ? Sid::tempoChangePosAbove : Sid::tempoChangePosBelow;
     356              : }
     357              : 
     358           12 : GradualTempoChangeSegment::GradualTempoChangeSegment(GradualTempoChange* annotation)
     359            0 :     : TextLineBaseSegment(ElementType::GRADUAL_TEMPO_CHANGE_SEGMENT, annotation,
     360           12 :                           ElementFlag::MOVABLE | ElementFlag::ON_STAFF | ElementFlag::SYSTEM)
     361              : {
     362           12 :     initElementStyle(&tempoSegmentStyle);
     363           12 :     m_text->setTextStyleType(propertyDefault(Pid::TEXT_STYLE).value<TextStyleType>());
     364           12 :     m_endText->setTextStyleType(propertyDefault(Pid::TEXT_STYLE).value<TextStyleType>());
     365           12 : }
     366              : 
     367            2 : GradualTempoChangeSegment* GradualTempoChangeSegment::clone() const
     368              : {
     369            2 :     return new GradualTempoChangeSegment(*this);
     370              : }
     371              : 
     372           70 : GradualTempoChange* GradualTempoChangeSegment::tempoChange() const
     373              : {
     374           70 :     return static_cast<GradualTempoChange*>(spanner());
     375              : }
     376              : 
     377           22 : GradualTempoChangeSegment* GradualTempoChangeSegment::findElementToSnapBefore() const
     378              : {
     379           22 :     const System* sys = system();
     380           22 :     IF_ASSERT_FAILED(sys) {
     381            0 :         return nullptr;
     382              :     }
     383              : 
     384           22 :     GradualTempoChange* thisTempoChange = tempoChange();
     385           22 :     Fraction startTick = thisTempoChange->tick();
     386           22 :     if (!sys->measures().empty() && startTick == sys->measures().front()->tick()) {
     387            6 :         return nullptr;
     388              :     }
     389              : 
     390           16 :     auto intervals = score()->spannerMap().findOverlapping(startTick.ticks(), startTick.ticks());
     391           44 :     for (auto interval : intervals) {
     392           28 :         Spanner* spanner = interval.value;
     393           48 :         bool isValidTempoChange = spanner->isGradualTempoChange() && !spanner->segmentsEmpty() && spanner->visible()
     394           48 :                                   && spanner != thisTempoChange;
     395           28 :         if (!isValidTempoChange) {
     396           25 :             continue;
     397              :         }
     398              : 
     399            3 :         GradualTempoChange* precedingTempoChange = toGradualTempoChange(spanner);
     400            3 :         bool endsMatch = precedingTempoChange->track() == thisTempoChange->track()
     401            0 :                          && precedingTempoChange->tick2() == startTick
     402            3 :                          && precedingTempoChange->placeAbove() == thisTempoChange->placeAbove();
     403              : 
     404            3 :         if (endsMatch && precedingTempoChange->snapToItemAfter()) {
     405            0 :             return toGradualTempoChangeSegment(precedingTempoChange->backSegment());
     406              :         }
     407              :     }
     408              : 
     409           16 :     return nullptr;
     410           16 : }
     411              : 
     412           22 : TempoText* GradualTempoChangeSegment::findElementToSnapAfter() const
     413              : {
     414           22 :     if (!tempoChange()->snapToItemAfter()) {
     415            0 :         return nullptr;
     416              :     }
     417              : 
     418           22 :     System* sys = system();
     419           22 :     IF_ASSERT_FAILED(sys) {
     420            0 :         return nullptr;
     421              :     }
     422              : 
     423              :     // Note: we don't need to look for a tempoChange after.
     424              :     // It is the next tempoChange which looks for a tempoChange before.
     425           22 :     Fraction refTick = tempoChange()->tick2();
     426           22 :     Measure* measure = score()->tick2measureMM(refTick);
     427           22 :     if (!measure) {
     428            0 :         return nullptr;
     429              :     }
     430              : 
     431          155 :     for (Segment* segment = measure->last(); segment; segment = segment->prev1()) {
     432          155 :         if (segment->system() != sys) {
     433          105 :             continue;
     434              :         }
     435          151 :         Fraction segmentTick = segment->tick();
     436          151 :         if (segmentTick > refTick) {
     437          101 :             continue;
     438              :         }
     439           50 :         if (segmentTick < refTick) {
     440           18 :             break;
     441              :         }
     442           32 :         EngravingItem* tempoText = segment->findAnnotation(ElementType::TEMPO_TEXT, track(), track());
     443           32 :         if (tempoText && tempoText->placeAbove() == placeAbove() && tempoText->visible()) {
     444            4 :             return toTempoText(tempoText);
     445              :         }
     446              :     }
     447              : 
     448           18 :     return nullptr;
     449              : }
     450              : 
     451            0 : void GradualTempoChangeSegment::endEdit(EditData& editData)
     452              : {
     453            0 :     TextLineBaseSegment::endEdit(editData);
     454            0 : }
        

Generated by: LCOV version 2.0-1