LCOV - code coverage report
Current view: top level - src/engraving/dom - tuplet.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 68.5 % 447 306
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 "tuplet.h"
      24              : 
      25              : #include "../editing/elementeditdata.h"
      26              : #include "style/textstyle.h"
      27              : 
      28              : #include "beam.h"
      29              : #include "chord.h"
      30              : #include "engravingitem.h"
      31              : #include "factory.h"
      32              : #include "measure.h"
      33              : #include "note.h"
      34              : #include "rest.h"
      35              : #include "score.h"
      36              : #include "text.h"
      37              : 
      38              : #include "log.h"
      39              : 
      40              : using namespace mu;
      41              : using namespace mu::engraving;
      42              : 
      43              : namespace mu::engraving {
      44              : //---------------------------------------------------------
      45              : //   tupletStyle
      46              : //---------------------------------------------------------
      47              : 
      48              : static const ElementStyle tupletStyle {
      49              :     { Sid::tupletDirection,                    Pid::DIRECTION },
      50              :     { Sid::tupletNumberType,                   Pid::NUMBER_TYPE },
      51              :     { Sid::tupletBracketType,                  Pid::BRACKET_TYPE },
      52              :     { Sid::tupletBracketWidth,                 Pid::LINE_WIDTH },
      53              :     { Sid::tupletFontFace,                     Pid::FONT_FACE },
      54              :     { Sid::tupletFontSize,                     Pid::FONT_SIZE },
      55              :     { Sid::tupletFontStyle,                    Pid::FONT_STYLE },
      56              :     { Sid::tupletAlign,                        Pid::ALIGN },
      57              :     { Sid::tupletPosition,                     Pid::POSITION },
      58              :     { Sid::tupletMinDistance,                  Pid::MIN_DISTANCE },
      59              :     { Sid::tupletFontSpatiumDependent,         Pid::SIZE_SPATIUM_DEPENDENT },
      60              : };
      61              : 
      62              : //---------------------------------------------------------
      63              : //   Tuplet
      64              : //---------------------------------------------------------
      65              : 
      66         1041 : Tuplet::Tuplet(DummyParentOr<Measure> parent)
      67         1041 :     : DurationElement(ElementType::TUPLET, parent)
      68              : {
      69         1041 :     m_direction    = DirectionV::AUTO;
      70         1041 :     m_numberType   = TupletNumberType::SHOW_NUMBER;
      71         1041 :     m_bracketType  = TupletBracketType::AUTO_BRACKET;
      72         1041 :     m_ratio        = Fraction(1, 1);
      73         1041 :     m_number       = 0;
      74         1041 :     m_hasBracket   = false;
      75         1041 :     m_isUp         = true;
      76         1041 :     initElementStyle(&tupletStyle);
      77         1041 : }
      78              : 
      79           52 : Tuplet::Tuplet(const Tuplet& t)
      80           52 :     : DurationElement(t)
      81              : {
      82           52 :     m_tick         = t.m_tick;
      83           52 :     m_hasBracket   = t.m_hasBracket;
      84           52 :     m_ratio        = t.m_ratio;
      85           52 :     m_baseLen      = t.m_baseLen;
      86           52 :     m_direction    = t.m_direction;
      87           52 :     m_numberType   = t.m_numberType;
      88           52 :     m_bracketType  = t.m_bracketType;
      89           52 :     m_bracketWidth = t.m_bracketWidth;
      90              : 
      91           52 :     m_isUp          = t.m_isUp;
      92              : 
      93           52 :     m_p1             = t.m_p1;
      94           52 :     m_p2             = t.m_p2;
      95           52 :     m_userP1            = t.m_userP1;
      96           52 :     m_userP2            = t.m_userP2;
      97              : 
      98              :     // recreated on layout
      99           52 :     m_number = 0;
     100           52 : }
     101              : 
     102              : //---------------------------------------------------------
     103              : //   ~Tuplet
     104              : //---------------------------------------------------------
     105              : 
     106         1230 : Tuplet::~Tuplet()
     107              : {
     108          615 :     m_beingDestructed = true;
     109              : 
     110          713 :     for (DurationElement* de : m_allElements) {
     111           98 :         assert(de->tuplet() == this);
     112           98 :         de->setTuplet(nullptr);
     113              :     }
     114          615 :     delete m_number;
     115         1230 : }
     116              : 
     117           96 : void Tuplet::setOwnershipParent(DummyParentOr<Measure> parent)
     118              : {
     119           96 :     EngravingItem::setOwnershipParent(parent);
     120           96 : }
     121              : 
     122              : //---------------------------------------------------------
     123              : //   setSelected
     124              : //---------------------------------------------------------
     125              : 
     126          522 : void Tuplet::setSelected(bool f)
     127              : {
     128          522 :     EngravingItem::setSelected(f);
     129          522 :     if (m_number) {
     130          479 :         m_number->setSelected(f);
     131              :     }
     132          522 : }
     133              : 
     134              : //---------------------------------------------------------
     135              : //   setVisible
     136              : //---------------------------------------------------------
     137              : 
     138            0 : void Tuplet::setVisible(bool f)
     139              : {
     140            0 :     EngravingItem::setVisible(f);
     141            0 :     if (m_number) {
     142            0 :         m_number->setVisible(f);
     143              :     }
     144            0 : }
     145              : 
     146              : //---------------------------------------------------------
     147              : //   setColor
     148              : //---------------------------------------------------------
     149              : 
     150            3 : void Tuplet::setColor(const Color& col)
     151              : {
     152            3 :     EngravingItem::setColor(col);
     153            3 :     if (m_number) {
     154            0 :         m_number->setColor(col);
     155              :     }
     156            3 : }
     157              : 
     158        48678 : EngravingObject* Tuplet::propertyDelegate(Pid id) const
     159              : {
     160        48678 :     switch (id) {
     161        20593 :     case Pid::FONT_FACE:
     162              :     case Pid::FONT_STYLE:
     163              :     case Pid::FONT_SIZE:
     164              :     case Pid::ALIGN:
     165              :     case Pid::POSITION:
     166              :     case Pid::SIZE_SPATIUM_DEPENDENT:
     167        20593 :         return m_number;
     168        28085 :     default:
     169        28085 :         return nullptr;
     170              :     }
     171              : }
     172              : 
     173              : //---------------------------------------------------------
     174              : //   rtick
     175              : //---------------------------------------------------------
     176              : 
     177          521 : Fraction Tuplet::rtick() const
     178              : {
     179          521 :     return tick() - measure()->tick();
     180              : }
     181              : 
     182              : //---------------------------------------------------------
     183              : //   resetNumberProperty
     184              : //   reset number properties to default values
     185              : //   Set FONT_ITALIC to true, because for tuplets number should be italic
     186              : //---------------------------------------------------------
     187              : 
     188          392 : void Tuplet::resetNumberProperty()
     189              : {
     190          392 :     resetNumberProperty(m_number);
     191          392 : }
     192              : 
     193          778 : void Tuplet::resetNumberProperty(Text* number)
     194              : {
     195         5446 :     for (auto p : { Pid::FONT_FACE, Pid::FONT_STYLE, Pid::FONT_SIZE, Pid::ALIGN, Pid::POSITION, Pid::SIZE_SPATIUM_DEPENDENT }) {
     196         4668 :         number->resetProperty(p);
     197              :     }
     198          778 : }
     199              : 
     200              : //---------------------------------------------------------
     201              : //   calcHasBracket
     202              : //---------------------------------------------------------
     203              : 
     204         2029 : bool Tuplet::calcHasBracket(const DurationElement* cr1, const DurationElement* cr2) const
     205              : {
     206         2029 :     if (m_bracketType != TupletBracketType::AUTO_BRACKET) {
     207          180 :         return m_bracketType != TupletBracketType::SHOW_NO_BRACKET;
     208              :     }
     209         1849 :     if (cr1 == cr2) { // Degenerate tuplet
     210           27 :         return false;
     211              :     }
     212         1822 :     if (!cr1->isChord() || !cr2->isChord()) {
     213          631 :         return true;
     214              :     }
     215         1191 :     const Chord* c1 = toChord(cr1);
     216         1191 :     const Chord* c2 = toChord(cr2);
     217              : 
     218         1191 :     Beam const* beamStart = c1->beam();
     219         1191 :     Beam const* beamEnd = c2->beam();
     220         1191 :     if (!beamStart || !beamEnd || beamStart != beamEnd) {
     221          278 :         return true;
     222              :     }
     223          913 :     bool tupletStartsBeam = beamStart->elements().front() == c1;
     224          913 :     bool tupletEndsBeam = beamEnd->elements().back() == c2;
     225          913 :     bool headSide = isUp() != (c1->up() || c2->up());
     226          913 :     bool isCross = c1->vStaffIdx() != c2->vStaffIdx();
     227          913 :     if (tupletStartsBeam && tupletEndsBeam && (!headSide || isCross)) {
     228          621 :         return false;
     229              :     }
     230              : 
     231          292 :     int beamCount = -1;
     232         1162 :     for (DurationElement* e : m_currentElements) {
     233          899 :         if (e->isTuplet() || e->isRest()) {
     234           29 :             return true;
     235          873 :         } else if (e->isChordRest()) {
     236          873 :             ChordRest* cr = toChordRest(e);
     237          873 :             if (cr->beam() == 0) {
     238            0 :                 return true;
     239              :             }
     240          873 :             if (beamCount == -1) {
     241          282 :                 beamCount = cr->beams();
     242          591 :             } else if (beamCount != cr->beams()) {
     243            3 :                 return true;
     244              :             }
     245              :         }
     246              :     }
     247          263 :     if (beamCount < 1) {
     248            0 :         return true;
     249              :     }
     250              : 
     251          263 :     bool startChordBreaks32 = false;
     252          263 :     bool startChordBreaks64 = false;
     253          263 :     Chord* prevStartChord = c1->prev();
     254          263 :     if (prevStartChord) {
     255          257 :         beamStart->calcBeamBreaks(c1, prevStartChord, beamCount - 1, startChordBreaks32, startChordBreaks64);
     256              :     }
     257          263 :     bool startChordDefinesTuplet = startChordBreaks32 || startChordBreaks64 || tupletStartsBeam;
     258          263 :     if (prevStartChord) {
     259          257 :         startChordDefinesTuplet = startChordDefinesTuplet || prevStartChord->beams() < beamCount;
     260              :     }
     261              : 
     262          263 :     bool endChordBreaks32 = false;
     263          263 :     bool endChordBreaks64 = false;
     264          263 :     Chord* nextEndChord = c2->next();
     265          263 :     if (nextEndChord) {
     266          144 :         beamEnd->calcBeamBreaks(nextEndChord, c2, beamCount - 1, endChordBreaks32, endChordBreaks64);
     267              :     }
     268          263 :     bool endChordDefinesTuplet = endChordBreaks32 || endChordBreaks64 || tupletEndsBeam;
     269          263 :     if (nextEndChord) {
     270          144 :         endChordDefinesTuplet = endChordDefinesTuplet || nextEndChord->beams() < beamCount;
     271              :     }
     272              : 
     273          263 :     if (startChordDefinesTuplet && endChordDefinesTuplet && (!headSide || isCross)) {
     274          251 :         return false;
     275              :     }
     276              : 
     277           12 :     return true;
     278              : }
     279              : 
     280              : //---------------------------------------------------------
     281              : //   scanElements
     282              : //---------------------------------------------------------
     283              : 
     284         1451 : void Tuplet::scanElements(std::function<void(EngravingItem*)> func)
     285              : {
     286         1451 :     if (m_number) {
     287          917 :         m_number->scanElements(func);
     288              :     }
     289              : 
     290         1451 :     func(this);
     291         1451 : }
     292              : 
     293              : //---------------------------------------------------------
     294              : //   add
     295              : //---------------------------------------------------------
     296              : 
     297         4082 : void Tuplet::add(EngravingItem* e)
     298              : {
     299         4082 :     switch (e->type()) {
     300         4082 :     case ElementType::CHORD:
     301              :     case ElementType::REST:
     302              :     case ElementType::TUPLET: {
     303         4082 :         DurationElement* de = toDurationElement(e);
     304         4082 :         IF_ASSERT_FAILED(!contains(de)) {
     305            0 :             return;
     306              :         }
     307         4082 :         de->setTuplet(this);
     308              : 
     309         4082 :         bool found = false;
     310         4082 :         Fraction tick = de->rtick();
     311         4082 :         if (tick != Fraction(-1, 1)) {
     312        10823 :             for (unsigned int i = 0; i < m_currentElements.size(); ++i) {
     313         6901 :                 if (m_currentElements[i]->rtick() > tick) {
     314          160 :                     m_currentElements.insert(m_currentElements.begin() + i, de);
     315          160 :                     found = true;
     316          160 :                     break;
     317              :                 }
     318              :             }
     319              :         }
     320         4082 :         if (!found) {
     321         3922 :             m_currentElements.push_back(de);
     322              :         }
     323              :     }
     324         4082 :     break;
     325              : 
     326            0 :     default:
     327            0 :         LOGD("Tuplet::add() unknown element");
     328            0 :         return;
     329              :     }
     330              : 
     331         4082 :     e->added();
     332              : }
     333              : 
     334              : //---------------------------------------------------------
     335              : //   remove
     336              : //---------------------------------------------------------
     337              : 
     338          517 : void Tuplet::remove(EngravingItem* e)
     339              : {
     340          517 :     switch (e->type()) {
     341              : //            case ElementType::TEXT:
     342              : //                  if (e == _number)
     343              : //                        _number = 0;
     344              : //                  break;
     345          517 :     case ElementType::CHORD:
     346              :     case ElementType::REST:
     347              :     case ElementType::TUPLET: {
     348          517 :         auto i = std::find(m_currentElements.begin(), m_currentElements.end(), toDurationElement(e));
     349          517 :         if (i == m_currentElements.end()) {
     350            0 :             LOGD("Tuplet::remove: cannot find element <%s>", e->typeName());
     351            0 :             LOGD("  elements %zu", m_currentElements.size());
     352              :         } else {
     353          517 :             m_currentElements.erase(i);
     354          517 :             e->removed();
     355              :         }
     356              :     }
     357          517 :     break;
     358            0 :     default:
     359            0 :         LOGD("Tuplet::remove: unknown element");
     360            0 :         break;
     361              :     }
     362          517 : }
     363              : 
     364         5657 : void Tuplet::addDurationElement(DurationElement* de)
     365              : {
     366         5657 :     assert(!m_beingDestructed);
     367         5657 :     m_allElements.insert(de);
     368         5657 : }
     369              : 
     370         4115 : void Tuplet::removeDurationElement(DurationElement* de)
     371              : {
     372         4115 :     if (m_beingDestructed) {
     373           98 :         return;
     374              :     }
     375              : 
     376         4017 :     m_allElements.erase(de);
     377              : }
     378              : 
     379              : //---------------------------------------------------------
     380              : //   isEditable
     381              : //---------------------------------------------------------
     382              : 
     383            0 : bool Tuplet::isEditable() const
     384              : {
     385            0 :     return m_hasBracket;
     386              : }
     387              : 
     388              : //---------------------------------------------------------
     389              : //   startDragGrip
     390              : //---------------------------------------------------------
     391              : 
     392            0 : void Tuplet::startDragGrip(EditData& ed)
     393              : {
     394            0 :     DurationElement::startDragGrip(ed);
     395            0 :     ElementEditDataPtr eed = ed.getData(this);
     396              : 
     397            0 :     eed->pushProperty(Pid::P1);
     398            0 :     eed->pushProperty(Pid::P2);
     399            0 : }
     400              : 
     401              : //---------------------------------------------------------
     402              : //   dragGrip
     403              : //---------------------------------------------------------
     404              : 
     405            0 : void Tuplet::dragGrip(EditData& ed)
     406              : {
     407            0 :     if (ed.curGrip == Grip::START) {
     408            0 :         m_userP1 += ed.delta;
     409            0 :     } else if (ed.curGrip == Grip::END) {
     410            0 :         m_userP2 += ed.delta;
     411            0 :     } else if (ed.curGrip == Grip::MIDDLE) {
     412            0 :         m_userP1 += ed.delta;
     413            0 :         m_userP2 += ed.delta;
     414              :     } else {
     415            0 :         UNREACHABLE;
     416              :         return;
     417              :     }
     418              : 
     419            0 :     setGenerated(false);
     420            0 :     triggerLayout();
     421              : }
     422              : 
     423              : //---------------------------------------------------------
     424              : //   gripsPositions
     425              : //---------------------------------------------------------
     426              : 
     427            0 : int Tuplet::gripsCount() const
     428              : {
     429            0 :     return m_hasBracket ? 3 : 0;
     430              : }
     431              : 
     432            0 : std::vector<PointF> Tuplet::gripsPositions(const EditData&) const
     433              : {
     434            0 :     IF_ASSERT_FAILED(gripsCount() != 0) {
     435            0 :         return std::vector<PointF>();
     436              :     }
     437              : 
     438            0 :     const PointF pp(pagePos());
     439            0 :     PointF left = pp + m_p1;
     440            0 :     PointF right = pp + m_p2;
     441            0 :     PointF middle = 0.5 * (left + right);
     442              : 
     443            0 :     return { left, right, middle };
     444              : }
     445              : 
     446              : //---------------------------------------------------------
     447              : //   reset
     448              : //---------------------------------------------------------
     449              : 
     450            0 : void Tuplet::reset()
     451              : {
     452            0 :     undoChangeProperty(Pid::P1, PointF());
     453            0 :     undoChangeProperty(Pid::P2, PointF());
     454            0 :     EngravingItem::reset();
     455            0 : }
     456              : 
     457              : //---------------------------------------------------------
     458              : //   dump
     459              : //---------------------------------------------------------
     460              : 
     461            0 : void Tuplet::dump() const
     462              : {
     463            0 :     EngravingItem::dump();
     464            0 :     LOGD() << "ratio: " << m_ratio.toString();
     465            0 : }
     466              : 
     467              : //---------------------------------------------------------
     468              : //   setTrack
     469              : //---------------------------------------------------------
     470              : 
     471         2326 : void Tuplet::setTrack(track_idx_t val)
     472              : {
     473         2326 :     if (tuplet()) {
     474          266 :         tuplet()->setTrack(val);
     475              :     }
     476         2326 :     if (m_number) {
     477          694 :         m_number->setTrack(val);
     478              :     }
     479         2326 :     EngravingItem::setTrack(val);
     480         2326 : }
     481              : 
     482              : //---------------------------------------------------------
     483              : //   tickGreater
     484              : //---------------------------------------------------------
     485              : 
     486          611 : static bool tickGreater(const DurationElement* a, const DurationElement* b)
     487              : {
     488          611 :     return a->tick() < b->tick();
     489              : }
     490              : 
     491              : //---------------------------------------------------------
     492              : //   sortElements
     493              : //---------------------------------------------------------
     494              : 
     495           83 : void Tuplet::sortElements()
     496              : {
     497           83 :     std::sort(m_currentElements.begin(), m_currentElements.end(), tickGreater);
     498           83 : }
     499              : 
     500              : //---------------------------------------------------------
     501              : //   cross
     502              : //---------------------------------------------------------
     503              : 
     504         5930 : bool Tuplet::cross() const
     505              : {
     506        25716 :     for (DurationElement* de : m_currentElements) {
     507        19909 :         if (!de) {
     508            0 :             continue;
     509        19909 :         } else if (de->isChordRest()) {
     510        17951 :             if (toChordRest(de)->staffMove()) {
     511          123 :                 return true;
     512              :             }
     513         1958 :         } else if (de->isTuplet()) {
     514         1958 :             if (toTuplet(de)->cross()) {
     515            0 :                 return true;
     516              :             }
     517              :         }
     518              :     }
     519         5807 :     return false;
     520              : }
     521              : 
     522              : //---------------------------------------------------------
     523              : //   vStaffIdx
     524              : ///  Staff index for layout based on the first ChordRest
     525              : //---------------------------------------------------------
     526              : 
     527         4270 : staff_idx_t Tuplet::vStaffIdx() const
     528              : {
     529         4270 :     if (elements().empty()) {
     530          148 :         return muse::nidx;
     531              :     }
     532              : 
     533         4122 :     const DurationElement* cr = elements().front();
     534         4122 :     if (!cr) {
     535            0 :         return muse::nidx;
     536              :     }
     537              : 
     538         4324 :     while (cr->isTuplet()) {
     539          202 :         const Tuplet* t = toTuplet(cr);
     540          202 :         if (t->elements().empty()) {
     541            0 :             return muse::nidx;
     542              :         }
     543          202 :         cr = t->elements().front();
     544              :     }
     545              : 
     546         4122 :     return cr->vStaffIdx();
     547              : }
     548              : 
     549              : //---------------------------------------------------------
     550              : //   elementsDuration
     551              : ///  Get the sum of the element fraction in the tuplet,
     552              : ///  even if the tuplet is not complete yet
     553              : //---------------------------------------------------------
     554              : 
     555           57 : Fraction Tuplet::elementsDuration()
     556              : {
     557           57 :     Fraction f;
     558          174 :     for (DurationElement* el : m_currentElements) {
     559          117 :         f += el->ticks();
     560              :     }
     561           57 :     return f;
     562              : }
     563              : 
     564              : //---------------------------------------------------------
     565              : //   getProperty
     566              : //---------------------------------------------------------
     567              : 
     568         8031 : PropertyValue Tuplet::getProperty(Pid propertyId) const
     569              : {
     570         8031 :     if (EngravingObject* delegate = propertyDelegate(propertyId)) {
     571            0 :         return delegate->getProperty(propertyId);
     572              :     }
     573         8031 :     switch (propertyId) {
     574          159 :     case Pid::DIRECTION:
     575          159 :         return PropertyValue::fromValue<DirectionV>(m_direction);
     576           13 :     case Pid::NUMBER_TYPE:
     577           13 :         return int(m_numberType);
     578           11 :     case Pid::BRACKET_TYPE:
     579           11 :         return int(m_bracketType);
     580            1 :     case Pid::LINE_WIDTH:
     581            1 :         return m_bracketWidth;
     582          599 :     case Pid::NORMAL_NOTES:
     583          599 :         return m_ratio.denominator();
     584          599 :     case Pid::ACTUAL_NOTES:
     585          599 :         return m_ratio.numerator();
     586          599 :     case Pid::P1:
     587          599 :         return m_userP1;
     588          599 :     case Pid::P2:
     589          599 :         return m_userP2;
     590         5451 :     default:
     591         5451 :         break;
     592              :     }
     593         5451 :     return DurationElement::getProperty(propertyId);
     594              : }
     595              : 
     596              : //---------------------------------------------------------
     597              : //   setProperty
     598              : //---------------------------------------------------------
     599              : 
     600        12099 : bool Tuplet::setProperty(Pid propertyId, const PropertyValue& v)
     601              : {
     602        12099 :     if (EngravingObject* delegate = propertyDelegate(propertyId)) {
     603           36 :         return delegate->setProperty(propertyId, v);
     604              :     }
     605        12063 :     switch (propertyId) {
     606         1187 :     case Pid::DIRECTION:
     607         1187 :         setDirection(v.value<DirectionV>());
     608         1187 :         break;
     609         1324 :     case Pid::NUMBER_TYPE:
     610         1324 :         setNumberType(TupletNumberType(v.toInt()));
     611         1324 :         break;
     612         1067 :     case Pid::BRACKET_TYPE:
     613         1067 :         setBracketType(TupletBracketType(v.toInt()));
     614         1067 :         break;
     615         1054 :     case Pid::LINE_WIDTH:
     616         1054 :         setBracketWidth(v.value<Spatium>());
     617         1054 :         break;
     618           27 :     case Pid::NORMAL_NOTES:
     619           27 :         m_ratio.setDenominator(v.toInt());
     620           27 :         break;
     621           27 :     case Pid::ACTUAL_NOTES:
     622           27 :         m_ratio.setNumerator(v.toInt());
     623           27 :         break;
     624            0 :     case Pid::P1:
     625            0 :         m_userP1 = v.value<PointF>();
     626            0 :         break;
     627            0 :     case Pid::P2:
     628            0 :         m_userP2 = v.value<PointF>();
     629            0 :         break;
     630           23 :     case Pid::TICK:
     631           23 :         setTick(v.value<Fraction>());
     632           23 :         break;
     633         7354 :     default:
     634         7354 :         return DurationElement::setProperty(propertyId, v);
     635              :     }
     636         4709 :     if (!m_currentElements.empty()) {
     637           47 :         m_currentElements.front()->triggerLayout();
     638           47 :         m_currentElements.back()->triggerLayout();
     639              :     }
     640         4709 :     return true;
     641              : }
     642              : 
     643              : //---------------------------------------------------------
     644              : //   propertyDefault
     645              : //---------------------------------------------------------
     646              : 
     647        13508 : PropertyValue Tuplet::propertyDefault(Pid id) const
     648              : {
     649        13508 :     switch (id) {
     650            0 :     case Pid::TEXT_STYLE:
     651            0 :         return TextStyleType::TUPLET;
     652            0 :     case Pid::SYSTEM_FLAG:
     653            0 :         return false;
     654          589 :     case Pid::TEXT:
     655         1178 :         return String();
     656         1198 :     case Pid::NORMAL_NOTES:
     657              :     case Pid::ACTUAL_NOTES:
     658         1198 :         return 0;
     659         1198 :     case Pid::P1:
     660              :     case Pid::P2:
     661         2396 :         return PointF();
     662          778 :     case Pid::POSITION:
     663          778 :         return style().styleV(Sid::tupletPosition);
     664          794 :     case Pid::ALIGN:
     665          794 :         return style().styleV(Sid::tupletAlign);
     666          866 :     case Pid::FONT_FACE:
     667          866 :         return style().styleV(Sid::tupletFontFace);
     668          866 :     case Pid::FONT_SIZE:
     669          866 :         return style().styleV(Sid::tupletFontSize);
     670          866 :     case Pid::FONT_STYLE:
     671          866 :         return style().styleV(Sid::tupletFontStyle);
     672          778 :     case Pid::SIZE_SPATIUM_DEPENDENT:
     673          778 :         return style().styleV(Sid::tupletFontSpatiumDependent);
     674         5575 :     default:
     675         5575 :         break;
     676              :     }
     677              : 
     678              :     // TODO: what does this do? Why TextStyleType::DEFAULT?
     679        88186 :     for (const auto& spp : *textStyle(TextStyleType::DEFAULT)) {
     680        83210 :         if (spp.pid == id) {
     681          599 :             return styleValue(id, spp.sid);
     682              :         }
     683              :     }
     684              : 
     685         4976 :     return DurationElement::propertyDefault(id);
     686              : }
     687              : 
     688              : //---------------------------------------------------------
     689              : //   sanitizeTuplet
     690              : ///    Check validity of tuplets and coherence between duration
     691              : ///    and baselength. Needed for importing old files due to a bug
     692              : ///    in the released version for corner-case tuplets.
     693              : ///    See issue #136406 and Pull request #2881
     694              : //---------------------------------------------------------
     695              : 
     696           28 : void Tuplet::sanitizeTuplet()
     697              : {
     698           28 :     if (ratio().numerator() == ratio().reduced().numerator()) { // return if the ratio is an irreducible fraction
     699           16 :         return;
     700              :     }
     701           12 :     Fraction baseLenDuration = (Fraction(ratio().denominator(), 1) * baseLen().fraction()).reduced();
     702              : 
     703              :     // Due to a bug present in 2.1 (and before), a tuplet with non-reduced ratio could be
     704              :     // in a corrupted state (mismatch between duration and base length).
     705              :     // A tentative will now be made to retrieve the correct duration by summing up all the
     706              :     // durations of the elements constituting the tuplet. This does not work for
     707              :     // not-completely filled tuplets, such as tuplets in voices > 0 with
     708              :     // gaps (for example, a tuplet in second voice with a deleted chordrest element)
     709              : 
     710           12 :     Fraction testDuration(0, 1);
     711           89 :     for (DurationElement* de : elements()) {
     712           77 :         if (!de) {
     713            0 :             continue;
     714              :         }
     715           77 :         Fraction elementDuration(0, 1);
     716           77 :         if (de->isTuplet()) {
     717            1 :             Tuplet* t = toTuplet(de);
     718            1 :             t->sanitizeTuplet();
     719            1 :             elementDuration = t->ticks();
     720              :         } else {
     721           76 :             elementDuration = de->ticks();
     722              :         }
     723           77 :         testDuration += elementDuration;
     724              :     }
     725           12 :     testDuration = testDuration / ratio();
     726           12 :     testDuration.reduce();
     727           12 :     if (elements().back()->endTick() - elements().front()->tick() > testDuration) {
     728            0 :         return;         // this tuplet has missing elements; do not sanitize
     729              :     }
     730           12 :     if (!(testDuration == baseLenDuration && baseLenDuration == ticks())) {
     731           10 :         Fraction f = testDuration * Fraction(1, ratio().denominator());
     732           10 :         f.reduce();
     733           10 :         Fraction fbl(1, f.denominator());
     734           10 :         if (TDuration::isValid(fbl)) {
     735           10 :             setTicks(testDuration);
     736           10 :             setBaseLen(fbl);
     737           10 :             LOGD("Tuplet %p sanitized duration %d/%d   baseLen %d/%d", this,
     738              :                  testDuration.numerator(), testDuration.denominator(),
     739              :                  1, fbl.denominator());
     740              :         } else {
     741            0 :             LOGD("Impossible to sanitize the tuplet");
     742              :         }
     743              :     }
     744              : }
     745              : 
     746              : //---------------------------------------------------------
     747              : //   addMissingElement
     748              : //     Add a rest with the given start and end ticks.
     749              : //     Should only be called from Tuplet::addMissingElements().
     750              : //     Needed for importing files that saved incomplete tuplets.
     751              : //---------------------------------------------------------
     752              : 
     753            0 : Fraction Tuplet::addMissingElement(const Fraction& startTick, const Fraction& endTick)
     754              : {
     755            0 :     Fraction f = (endTick - startTick) * ratio();
     756            0 :     TDuration d = TDuration(f, true);
     757            0 :     if (!d.isValid()) {
     758            0 :         LOGD("Tuplet::addMissingElement(): invalid duration: %d/%d", f.numerator(), f.denominator());
     759            0 :         return Fraction::fromTicks(0);
     760              :     }
     761            0 :     f = d.fraction();
     762            0 :     Segment* segment = measure()->getSegment(SegmentType::ChordRest, startTick);
     763            0 :     Rest* rest = Factory::createRest(segment);
     764            0 :     rest->setDurationType(d);
     765            0 :     rest->setTicks(f);
     766            0 :     rest->setTrack(track());
     767            0 :     rest->setVisible(false);
     768              : 
     769            0 :     segment->add(rest);
     770            0 :     add(rest);
     771            0 :     return f;
     772              : }
     773              : 
     774              : //---------------------------------------------------------
     775              : //   addMissingElements
     776              : //     Make this tuplet complete by filling in holes where
     777              : //     there ought to be rests. Needed for importing files
     778              : //     that saved incomplete tuplets.
     779              : //---------------------------------------------------------
     780              : 
     781           27 : void Tuplet::addMissingElements()
     782              : {
     783           27 :     if (tuplet()) {
     784           27 :         return;         // do not correct nested tuplets
     785              :     }
     786           20 :     if (voice() == 0) {
     787           20 :         return;         // nothing to do for tuplets in voice 1
     788              :     }
     789              : 
     790            0 :     Fraction missingElementsDuration = ticks() * ratio() - elementsDuration();
     791            0 :     if (missingElementsDuration.isZero()) {
     792            0 :         return;
     793              :     }
     794              : 
     795              :     // first, fill in any holes in the middle of the tuplet
     796            0 :     Fraction expectedTick = elements().front()->tick();
     797              : 
     798            0 :     const std::vector<DurationElement*> elementsCopy = elements(); // mofified during loop
     799            0 :     for (const DurationElement* de : elementsCopy) {
     800            0 :         if (!de) {
     801            0 :             continue;
     802              :         }
     803            0 :         if (de->tick() != expectedTick) {
     804            0 :             missingElementsDuration -= addMissingElement(expectedTick, de->tick());
     805            0 :             if (missingElementsDuration.isZero()) {
     806            0 :                 return;
     807              :             }
     808              :         }
     809            0 :         expectedTick += de->actualTicks();
     810              :     }
     811              : 
     812              :     // calculate the tick where we would expect a tuplet of this duration to start
     813              :     // TODO: check:
     814            0 :     expectedTick = elements().front()->tick() - Fraction::fromTicks(elements().front()->tick().ticks() % ticks().ticks());
     815            0 :     if (expectedTick != elements().front()->tick()) {
     816              :         // try to fill a hole at the beginning of the tuplet
     817            0 :         Fraction firstAvailableTick = measure()->tick();
     818            0 :         Segment* segment = measure()->findSegment(SegmentType::ChordRest, elements().front()->tick());
     819            0 :         ChordRest* prevChordRest = segment && segment->prev() ? segment->prev()->nextChordRest(track(), true) : nullptr;
     820            0 :         if (prevChordRest && prevChordRest->measure() == measure()) {
     821            0 :             firstAvailableTick = prevChordRest->endTick();
     822              :         }
     823            0 :         if (firstAvailableTick != elements().front()->tick()) {
     824            0 :             Fraction f = missingElementsDuration / ratio();
     825            0 :             Fraction ticksRequired = f;
     826            0 :             Fraction endTick = elements().front()->tick();
     827            0 :             Fraction startTick = std::max(firstAvailableTick, endTick - ticksRequired);
     828            0 :             if (expectedTick > startTick) {
     829            0 :                 startTick = expectedTick;
     830              :             }
     831            0 :             missingElementsDuration -= addMissingElement(startTick, endTick);
     832            0 :             if (missingElementsDuration.isZero()) {
     833            0 :                 return;
     834              :             }
     835              :         }
     836              :     }
     837              :     // now fill a hole at the end of the tuplet
     838            0 :     Fraction startTick = elements().back()->endTick();
     839            0 :     Fraction endTick = elements().front()->tick() + ticks();
     840              :     // just to be safe, find the next ChordRest in the track, and adjust endTick if necessary
     841            0 :     Segment* segment = measure()->findSegment(SegmentType::ChordRest, elements().back()->tick());
     842            0 :     ChordRest* nextChordRest = segment && segment->next() ? segment->next()->nextChordRest(track(), false) : nullptr;
     843            0 :     if (nextChordRest && nextChordRest->tick() < endTick) {
     844            0 :         endTick = nextChordRest->tick();
     845              :     }
     846            0 :     missingElementsDuration -= addMissingElement(startTick, endTick);
     847            0 :     if (!missingElementsDuration.isZero()) {
     848            0 :         LOGD("Tuplet::addMissingElements(): still missing duration of %d/%d",
     849              :              missingElementsDuration.numerator(), missingElementsDuration.denominator());
     850              :     }
     851            0 : }
     852              : 
     853           11 : int Tuplet::computeTupletDenominator(int numerator, Fraction totalDuration)
     854              : {
     855           11 :     if (numerator <= 0) {
     856            0 :         return 0;
     857              :     }
     858           11 :     Fraction noteDuration = totalDuration / numerator;
     859           11 :     Fraction baseLen(1, 1);
     860           11 :     Fraction ratio = (totalDuration / baseLen).reduced();
     861           46 :     while (ratio.denominator() != 1 || baseLen / 2 >= noteDuration) {
     862           35 :         baseLen /= 2;
     863           35 :         ratio = (totalDuration / baseLen).reduced();
     864              :     }
     865           11 :     return ratio.numerator();
     866              : }
     867              : 
     868            0 : EngravingItem* Tuplet::nextElement()
     869              : {
     870            0 :     ChordRest* firstElement = toChordRest(elements().front());
     871            0 :     if (firstElement->isChord()) {
     872            0 :         Chord* chord = toChord(firstElement);
     873            0 :         return chord->firstGraceOrNote();
     874              :     }
     875            0 :     return firstElement;
     876              : }
     877              : 
     878            0 : EngravingItem* Tuplet::prevElement()
     879              : {
     880            0 :     ChordRest* firstElement = toChordRest(elements().front());
     881            0 :     staff_idx_t staffId = firstElement->staffIdx();
     882            0 :     EngravingItem* prevItem = firstElement->segment()->prevElement(staffId);
     883            0 :     return prevItem;
     884              : }
     885              : } // namespace mu::engraving
        

Generated by: LCOV version 2.0-1