LCOV - code coverage report
Current view: top level - src/engraving/dom - tremolotwochord.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 37.3 % 255 95
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 "tremolotwochord.h"
      24              : 
      25              : #include "draw/types/transform.h"
      26              : 
      27              : #include "types/typesconv.h"
      28              : 
      29              : #include "style/style.h"
      30              : 
      31              : #include "rendering/score/beamtremololayout.h"
      32              : 
      33              : #include "chord.h"
      34              : #include "stem.h"
      35              : #include "system.h"
      36              : #include "stafftype.h"
      37              : 
      38              : #include "log.h"
      39              : 
      40              : using namespace mu;
      41              : using namespace muse::draw;
      42              : using namespace mu::engraving;
      43              : 
      44              : namespace mu::engraving {
      45              : //---------------------------------------------------------
      46              : //   tremoloStyle
      47              : //---------------------------------------------------------
      48              : 
      49              : static const ElementStyle TREMOLO_STYLE {
      50              :     { Sid::tremoloStyle, Pid::TREMOLO_STYLE }
      51              : };
      52              : 
      53              : //---------------------------------------------------------
      54              : //   Tremolo
      55              : //---------------------------------------------------------
      56              : 
      57           73 : TremoloTwoChord::TremoloTwoChord(DummyParentOr<Chord> parent)
      58           73 :     : BeamBase(ElementType::TREMOLO_TWOCHORD, parent, ElementFlag::MOVABLE)
      59              : {
      60           73 :     initElementStyle(&TREMOLO_STYLE);
      61           73 : }
      62              : 
      63            8 : TremoloTwoChord::TremoloTwoChord(const TremoloTwoChord& t)
      64            8 :     : BeamBase(t)
      65              : {
      66            8 :     setTremoloType(t.tremoloType());
      67            8 :     m_chord1       = t.chord1();
      68            8 :     m_chord2       = t.chord2();
      69            8 :     m_durationType = t.m_durationType;
      70            8 : }
      71              : 
      72          152 : TremoloTwoChord::~TremoloTwoChord()
      73              : {
      74              :     //
      75              :     // delete all references from chords
      76              :     //
      77           76 :     if (m_chord1 && m_chord1->tremoloTwoChord() == this) {
      78            0 :         m_chord1->setTremoloTwoChord(nullptr);
      79              :     }
      80           76 :     if (m_chord2 && m_chord2->tremoloTwoChord() == this) {
      81            0 :         m_chord2->setTremoloTwoChord(nullptr);
      82              :     }
      83              : 
      84           76 :     clearBeamSegments();
      85          152 : }
      86              : 
      87              : //---------------------------------------------------------
      88              : //   chordMag
      89              : //---------------------------------------------------------
      90              : 
      91            0 : double TremoloTwoChord::chordMag() const
      92              : {
      93            0 :     return chord() ? chord()->intrinsicMag() : 1.0;
      94              : }
      95              : 
      96              : //---------------------------------------------------------
      97              : //   minHeight
      98              : //---------------------------------------------------------
      99              : 
     100            0 : double TremoloTwoChord::minHeight() const
     101              : {
     102            0 :     const double sw = style().styleS(Sid::tremoloLineWidth).val() * chordMag();
     103            0 :     const double td = style().styleS(Sid::tremoloDistance).val() * chordMag();
     104            0 :     return (lines() - 1) * td + sw;
     105              : }
     106              : 
     107              : //---------------------------------------------------------
     108              : //   chordBeamAnchor
     109              : //---------------------------------------------------------
     110              : 
     111            0 : PointF TremoloTwoChord::chordBeamAnchor(const ChordRest* chord, ChordBeamAnchorType anchorType) const
     112              : {
     113            0 :     return rendering::score::BeamTremoloLayout::chordBeamAnchor(this->ldata(), chord, anchorType);
     114              : }
     115              : 
     116              : //---------------------------------------------------------
     117              : //   drag
     118              : //---------------------------------------------------------
     119              : 
     120            0 : RectF TremoloTwoChord::drag(EditData& ed)
     121              : {
     122            0 :     int idx = directionIdx();
     123            0 :     double dy = ed.pos.y() - ed.lastPos.y();
     124              : 
     125            0 :     double y1 = m_beamFragment.py1[idx];
     126            0 :     double y2 = m_beamFragment.py2[idx];
     127              : 
     128            0 :     y1 += dy;
     129            0 :     y2 += dy;
     130              : 
     131            0 :     double _spatium = spatium();
     132              :     // Because of the logic in TremoloTwoChord::setProperty(),
     133              :     // changing Pid::BEAM_POS only has an effect if Pid::USER_MODIFIED is true.
     134            0 :     undoChangeProperty(Pid::USER_MODIFIED, true);
     135            0 :     undoChangeProperty(Pid::BEAM_POS, PairF(y1 / _spatium, y2 / _spatium));
     136            0 :     undoChangeProperty(Pid::GENERATED, false);
     137              : 
     138            0 :     triggerLayout();
     139              : 
     140            0 :     return canvasBoundingRect();
     141              : }
     142              : 
     143              : //---------------------------------------------------------
     144              : //   setTremoloType
     145              : //---------------------------------------------------------
     146              : 
     147           81 : void TremoloTwoChord::setTremoloType(TremoloType t)
     148              : {
     149           81 :     IF_ASSERT_FAILED(isTremoloTwoChord(t)) {
     150            0 :         return;
     151              :     }
     152           81 :     m_tremoloType = t;
     153           81 :     switch (tremoloType()) {
     154           26 :     case TremoloType::R16:
     155              :     case TremoloType::C16:
     156           26 :         m_lines = 2;
     157           26 :         break;
     158           33 :     case TremoloType::R32:
     159              :     case TremoloType::C32:
     160           33 :         m_lines = 3;
     161           33 :         break;
     162            8 :     case TremoloType::R64:
     163              :     case TremoloType::C64:
     164            8 :         m_lines = 4;
     165            8 :         break;
     166            0 :     case TremoloType::R128:
     167              :     case TremoloType::C128:
     168            0 :         m_lines = 5;
     169            0 :         break;
     170            0 :     case TremoloType::R256:
     171              :     case TremoloType::C256:
     172            0 :         m_lines = 6;
     173            0 :         break;
     174           14 :     default:
     175           14 :         m_lines = 1;
     176           14 :         break;
     177              :     }
     178              : 
     179           81 :     styleChanged();
     180              : }
     181              : 
     182              : //---------------------------------------------------------
     183              : //   reset
     184              : //---------------------------------------------------------
     185              : 
     186            0 : void TremoloTwoChord::reset()
     187              : {
     188            0 :     EngravingItem::reset();
     189            0 :     if (userModified()) {
     190              :         //undoChangeProperty(Pid::BEAM_POS, PropertyValue::fromValue(beamPos()));
     191            0 :         undoChangeProperty(Pid::USER_MODIFIED, false);
     192              :     }
     193            0 :     undoChangeProperty(Pid::STEM_DIRECTION, DirectionV::AUTO);
     194            0 :     resetProperty(Pid::BEAM_NO_SLOPE);
     195            0 : }
     196              : 
     197              : //---------------------------------------------------------
     198              : //   setBeamDirection
     199              : //---------------------------------------------------------
     200              : 
     201            1 : void TremoloTwoChord::setDirection(DirectionV d)
     202              : {
     203            1 :     if (direction() == d) {
     204            0 :         return;
     205              :     }
     206              : 
     207            1 :     doSetDirection(d);
     208              : 
     209            1 :     if (d != DirectionV::AUTO) {
     210            1 :         setUp(d == DirectionV::UP);
     211              :     }
     212            1 :     if (m_chord1) {
     213            1 :         m_chord1->setStemDirection(d);
     214              :     }
     215            1 :     if (m_chord2) {
     216            1 :         m_chord2->setStemDirection(d);
     217              :     }
     218              : }
     219              : 
     220              : //---------------------------------------------------------
     221              : //   crossStaffBeamBetween
     222              : //    Return true if tremolo is two-note cross-staff and beams between staves
     223              : //---------------------------------------------------------
     224              : 
     225            0 : bool TremoloTwoChord::crossStaffBeamBetween() const
     226              : {
     227            0 :     return ((m_chord1->staffMove() > m_chord2->staffMove()) && m_chord1->up() && !m_chord2->up())
     228            0 :            || ((m_chord1->staffMove() < m_chord2->staffMove()) && !m_chord1->up() && m_chord2->up());
     229              : }
     230              : 
     231         1050 : TDuration TremoloTwoChord::durationType() const
     232              : {
     233         1050 :     return m_durationType;
     234              : }
     235              : 
     236           73 : void TremoloTwoChord::setDurationType(TDuration d)
     237              : {
     238           73 :     if (m_durationType == d) {
     239            0 :         return;
     240              :     }
     241              : 
     242           73 :     m_durationType = d;
     243           73 :     styleChanged();
     244              : }
     245              : 
     246              : //---------------------------------------------------------
     247              : //   tremoloLen
     248              : //---------------------------------------------------------
     249              : 
     250            0 : Fraction TremoloTwoChord::tremoloLen() const
     251              : {
     252            0 :     Fraction f;
     253            0 :     switch (lines()) {
     254            0 :     case 1: f.set(1, 8);
     255            0 :         break;
     256            0 :     case 2: f.set(1, 16);
     257            0 :         break;
     258            0 :     case 3: f.set(1, 32);
     259            0 :         break;
     260            0 :     case 4: f.set(1, 64);
     261            0 :         break;
     262            0 :     case 5: f.set(1, 128);
     263            0 :         break;
     264            0 :     case 6: f.set(1, 256);
     265            0 :         break;
     266              :     }
     267            0 :     return f;
     268              : }
     269              : 
     270              : //---------------------------------------------------------
     271              : //   setBeamPos
     272              : //---------------------------------------------------------
     273              : 
     274            0 : void TremoloTwoChord::setBeamPos(const PairF& bp)
     275              : {
     276            0 :     int idx = directionIdx();
     277            0 :     setUserModified(true);
     278            0 :     setGenerated(false);
     279              : 
     280            0 :     double _spatium = spatium();
     281            0 :     m_beamFragment.py1[idx] = bp.first * _spatium;
     282            0 :     m_beamFragment.py2[idx] = bp.second * _spatium;
     283            0 : }
     284              : 
     285              : //---------------------------------------------------------
     286              : //   beamPos
     287              : //---------------------------------------------------------
     288              : 
     289            0 : PairF TremoloTwoChord::beamPos() const
     290              : {
     291            0 :     int idx = directionIdx();
     292            0 :     double _spatium = spatium();
     293            0 :     return PairF(m_beamFragment.py1[idx] / _spatium, m_beamFragment.py2[idx] / _spatium);
     294              : }
     295              : 
     296              : //---------------------------------------------------------
     297              : //   triggerLayout
     298              : //---------------------------------------------------------
     299              : 
     300          235 : void TremoloTwoChord::triggerLayout() const
     301              : {
     302          235 :     if (m_chord1 && m_chord2) {
     303           34 :         toChordRest(m_chord1)->triggerLayout();
     304           34 :         toChordRest(m_chord2)->triggerLayout();
     305              :     } else {
     306          201 :         EngravingItem::triggerLayout();
     307              :     }
     308          235 : }
     309              : 
     310            0 : bool TremoloTwoChord::needStartEditingAfterSelecting() const
     311              : {
     312            0 :     return true;
     313              : }
     314              : 
     315            0 : int TremoloTwoChord::gripsCount() const
     316              : {
     317            0 :     return 3;
     318              : }
     319              : 
     320            0 : Grip TremoloTwoChord::initialEditModeGrip() const
     321              : {
     322            0 :     return Grip::END;
     323              : }
     324              : 
     325            0 : Grip TremoloTwoChord::defaultGrip() const
     326              : {
     327            0 :     return Grip::MIDDLE;
     328              : }
     329              : 
     330              : //---------------------------------------------------------
     331              : //   gripsPositions
     332              : //---------------------------------------------------------
     333              : 
     334            0 : std::vector<PointF> TremoloTwoChord::gripsPositions(const EditData&) const
     335              : {
     336            0 :     int idx = directionIdx();
     337              : 
     338            0 :     int y = pagePos().y();
     339            0 :     double beamStartX = m_startAnchor.x() + m_chord1->pageX();
     340            0 :     double beamEndX = m_endAnchor.x() + m_chord1->pageX(); // intentional--chord1 is start x
     341            0 :     double middleX = (beamStartX + beamEndX) / 2;
     342            0 :     double middleY = (m_beamFragment.py1[idx] + y + m_beamFragment.py2[idx] + y) / 2;
     343              : 
     344              :     return {
     345            0 :         PointF(beamStartX, m_beamFragment.py1[idx] + y),
     346            0 :         PointF(beamEndX, m_beamFragment.py2[idx] + y),
     347              :         PointF(middleX, middleY)
     348            0 :     };
     349              : }
     350              : 
     351              : //---------------------------------------------------------
     352              : //   dragGrip
     353              : //---------------------------------------------------------
     354              : 
     355            0 : void TremoloTwoChord::dragGrip(EditData& ed)
     356              : {
     357            0 :     int idx = directionIdx();
     358            0 :     double dy = ed.delta.y();
     359            0 :     double y1 = m_beamFragment.py1[idx];
     360            0 :     double y2 = m_beamFragment.py2[idx];
     361              : 
     362            0 :     if (ed.curGrip == Grip::MIDDLE) {
     363            0 :         y1 += dy;
     364            0 :         y2 += dy;
     365            0 :     } else if (ed.curGrip == Grip::START) {
     366            0 :         y1 += dy;
     367            0 :     } else if (ed.curGrip == Grip::END) {
     368            0 :         y2 += dy;
     369              :     } else {
     370            0 :         UNREACHABLE;
     371              :         return;
     372              :     }
     373              : 
     374            0 :     double _spatium = spatium();
     375              :     // Because of the logic in Beam::setProperty(),
     376              :     // changing Pid::BEAM_POS only has an effect if Pid::USER_MODIFIED is true.
     377            0 :     undoChangeProperty(Pid::USER_MODIFIED, true);
     378            0 :     undoChangeProperty(Pid::BEAM_POS, PairF(y1 / _spatium, y2 / _spatium));
     379            0 :     undoChangeProperty(Pid::GENERATED, false);
     380              : 
     381            0 :     triggerLayout();
     382              : }
     383              : 
     384              : //---------------------------------------------------------
     385              : //   subtypeName
     386              : //---------------------------------------------------------
     387              : 
     388            0 : TranslatableString TremoloTwoChord::subtypeUserName() const
     389              : {
     390            0 :     return TConv::userName(tremoloType());
     391              : }
     392              : 
     393              : //---------------------------------------------------------
     394              : //   accessibleInfo
     395              : //---------------------------------------------------------
     396              : 
     397            0 : String TremoloTwoChord::accessibleInfo() const
     398              : {
     399            0 :     return String(u"%1: %2").arg(EngravingItem::accessibleInfo(), translatedSubtypeUserName());
     400              : }
     401              : 
     402              : //---------------------------------------------------------
     403              : //   customStyleApplicable
     404              : //---------------------------------------------------------
     405              : 
     406          624 : bool TremoloTwoChord::customStyleApplicable() const
     407              : {
     408         1248 :     return (durationType().type() == DurationType::V_HALF)
     409         1248 :            && (staffType()->group() != StaffGroup::TAB);
     410              : }
     411              : 
     412              : //---------------------------------------------------------
     413              : //   getProperty
     414              : //---------------------------------------------------------
     415              : 
     416          565 : PropertyValue TremoloTwoChord::getProperty(Pid propertyId) const
     417              : {
     418          565 :     switch (propertyId) {
     419           51 :     case Pid::TREMOLO_TYPE:
     420           51 :         return int(m_tremoloType);
     421            0 :     case Pid::TREMOLO_STYLE:
     422            0 :         return int(m_style);
     423           51 :     case Pid::PLAY:
     424           51 :         return m_playTremolo;
     425          463 :     default:
     426          463 :         break;
     427              :     }
     428          463 :     return BeamBase::getProperty(propertyId);
     429              : }
     430              : 
     431              : //---------------------------------------------------------
     432              : //   setProperty
     433              : //---------------------------------------------------------
     434              : 
     435          231 : bool TremoloTwoChord::setProperty(Pid propertyId, const PropertyValue& val)
     436              : {
     437          231 :     switch (propertyId) {
     438            0 :     case Pid::TREMOLO_TYPE:
     439            0 :         setTremoloType(TremoloType(val.toInt()));
     440            0 :         break;
     441          229 :     case Pid::TREMOLO_STYLE:
     442          229 :         if (customStyleApplicable()) {
     443           31 :             setTremoloStyle(TremoloStyle(val.toInt()));
     444              :         }
     445          229 :         break;
     446            0 :     case Pid::BEAM_POS:
     447            0 :         if (userModified()) {
     448            0 :             setBeamPos(val.value<PairF>());
     449              :         }
     450            0 :         break;
     451            0 :     case Pid::PLAY:
     452            0 :         setPlayTremolo(val.toBool());
     453            0 :         break;
     454            2 :     default:
     455            2 :         return BeamBase::setProperty(propertyId, val);
     456              :     }
     457          229 :     triggerLayout();
     458          229 :     return true;
     459              : }
     460              : 
     461              : //---------------------------------------------------------
     462              : //   propertyDefault
     463              : //---------------------------------------------------------
     464              : 
     465          561 : PropertyValue TremoloTwoChord::propertyDefault(Pid propertyId) const
     466              : {
     467          561 :     switch (propertyId) {
     468            0 :     case Pid::TREMOLO_STYLE:
     469            0 :         return style().styleI(Sid::tremoloStyle);
     470           51 :     case Pid::PLAY:
     471           51 :         return true;
     472          510 :     default:
     473          510 :         return BeamBase::propertyDefault(propertyId);
     474              :     }
     475              : }
     476              : 
     477              : //---------------------------------------------------------
     478              : //   scanElements
     479              : //---------------------------------------------------------
     480              : 
     481            1 : void TremoloTwoChord::scanElements(std::function<void(EngravingItem*)> func)
     482              : {
     483            1 :     if (chord() && chord()->tremoloChordType() == TremoloChordType::TremoloSecondChord) {
     484            0 :         return;
     485              :     }
     486            1 :     EngravingItem::scanElements(func);
     487              : }
     488              : 
     489          471 : void TremoloTwoChord::clearBeamSegments()
     490              : {
     491          471 :     BeamSegment* chord1Segment = m_chord1 ? m_chord1->beamlet() : nullptr;
     492          471 :     BeamSegment* chord2Segment = m_chord2 ? m_chord2->beamlet() : nullptr;
     493              : 
     494          471 :     if (chord1Segment || chord2Segment) {
     495            0 :         for (BeamSegment* segment : m_beamSegments) {
     496            0 :             if (chord1Segment && chord1Segment == segment) {
     497            0 :                 m_chord1->setBeamlet(nullptr);
     498            0 :             } else if (chord2Segment && chord2Segment == segment) {
     499            0 :                 m_chord2->setBeamlet(nullptr);
     500              :             }
     501              :         }
     502              :     }
     503              : 
     504          471 :     BeamBase::clearBeamSegments();
     505          471 : }
     506              : 
     507         1100 : int TremoloTwoChord::maxCRMove() const
     508              : {
     509         1100 :     return std::max(m_chord1->staffMove(), m_chord2->staffMove());
     510              : }
     511              : 
     512          825 : int TremoloTwoChord::minCRMove() const
     513              : {
     514          825 :     return std::min(m_chord1->staffMove(), m_chord2->staffMove());
     515              : }
     516              : 
     517              : // used for palettes
     518            0 : PainterPath TremoloTwoChord::basePath(double stretch) const
     519              : {
     520            0 :     bool tradAlternate = m_style == TremoloStyle::TRADITIONAL_ALTERNATE;
     521            0 :     if (tradAlternate && muse::RealIsEqual(stretch, 0.)) {
     522              :         // this shape will have to be constructed after the stretch
     523              :         // is known
     524            0 :         return PainterPath();
     525              :     }
     526              : 
     527              :     // TODO: This should be a style setting, to replace tremoloStrokeLengthMultiplier
     528              :     static constexpr Spatium stemGapSp = 0.65_sp;
     529              : 
     530            0 :     const double sp = spatium() * chordMag();
     531              : 
     532              :     // overall width of two-note tremolos should not be changed if chordMag() isn't 1.0
     533            0 :     double w2  = sp * style().styleS(Sid::tremoloWidth).val() * .5 / chordMag();
     534            0 :     double lw  = sp * style().styleS(Sid::tremoloLineWidth).val();
     535            0 :     double td  = sp * style().styleS(Sid::tremoloDistance).val();
     536              : 
     537            0 :     PainterPath ppath;
     538              : 
     539              :     // first line
     540            0 :     ppath.addRect(-w2, 0.0, 2.0 * w2, lw);
     541            0 :     double ty = td;
     542              : 
     543              :     // other lines
     544            0 :     for (int i = 1; i < m_lines; i++) {
     545            0 :         if (tradAlternate) {
     546            0 :             double stemWidth1 = m_chord1->stem()->lineWidthMag() / stretch;
     547            0 :             double stemWidth2 = m_chord2->stem()->lineWidthMag() / stretch;
     548            0 :             double inset = stemGapSp.toAbsolute(spatium()) / stretch;
     549              : 
     550            0 :             ppath.addRect(-w2 + inset + stemWidth1, ty,
     551            0 :                           2.0 * w2 - (inset * 2.) - (stemWidth2 + stemWidth1), lw);
     552              :         } else {
     553            0 :             ppath.addRect(-w2, ty, 2.0 * w2, lw);
     554              :         }
     555            0 :         ty += td;
     556              :     }
     557              : 
     558            0 :     if (!chord()) {
     559              :         // for the palette or for one-note tremolos
     560            0 :         Transform shearTransform;
     561            0 :         shearTransform.shear(0.0, -(lw / 2.0) / w2);
     562            0 :         ppath = shearTransform.map(ppath);
     563              :     }
     564              : 
     565            0 :     return ppath;
     566            0 : }
     567              : 
     568            0 : void TremoloTwoChord::computeShape()
     569              : {
     570            0 :     m_path = basePath();
     571            0 :     setbbox(m_path.boundingRect());
     572            0 : }
     573              : }
        

Generated by: LCOV version 2.0-1