LCOV - code coverage report
Current view: top level - src/engraving/dom - arpeggio.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 25.8 % 291 75
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 "arpeggio.h"
      24              : 
      25              : #include "../editing/elementeditdata.h"
      26              : #include "../editing/mscoreview.h"
      27              : #include "../types/typesconv.h"
      28              : 
      29              : #include "accidental.h"
      30              : #include "chord.h"
      31              : #include "note.h"
      32              : #include "page.h"
      33              : #include "part.h"
      34              : #include "property.h"
      35              : #include "score.h"
      36              : #include "segment.h"
      37              : #include "staff.h"
      38              : 
      39              : #include "log.h"
      40              : 
      41              : using namespace mu;
      42              : using namespace mu::engraving;
      43              : 
      44          144 : Arpeggio::Arpeggio(DummyParentOr<Chord> parent, ElementType type)
      45          144 :     : EngravingItem(type, parent, ElementFlag::MOVABLE)
      46              : {
      47          144 :     m_arpeggioType = ArpeggioType::NORMAL;
      48          144 :     m_span     = 1;
      49          144 :     m_userLen1 = 0.0;
      50          144 :     m_userLen2 = 0.0;
      51          144 :     m_playArpeggio = true;
      52          144 :     m_stretch = 1.0;
      53              : 
      54          144 :     EngravingObject* p = parent;
      55          144 :     if (type == ElementType::ARPEGGIO && p->isChord()) {
      56          132 :         toChord(p)->setSpanArpeggio(this);
      57              :     }
      58          144 : }
      59              : 
      60          306 : Arpeggio::~Arpeggio()
      61              : {
      62              :     // Remove reference to this arpeggio in any chords it may have spanned
      63          153 :     Chord* _chord = chord();
      64          153 :     if (!_chord || !_chord->segment()) {
      65            0 :         return;
      66              :     }
      67          558 :     for (track_idx_t _track = track(); _track <= track() + m_span; _track++) {
      68          405 :         EngravingItem* e = _chord->segment()->element(_track);
      69          405 :         if (e && e->isChord() && toChord(e)->spanArpeggio() == this) {
      70          210 :             toChord(e)->setSpanArpeggio(nullptr);
      71              :         }
      72              :     }
      73          306 : }
      74              : 
      75            1 : const muse::TranslatableString& Arpeggio::arpeggioTypeName() const
      76              : {
      77            1 :     return TConv::userName(m_arpeggioType);
      78              : }
      79              : 
      80          233 : void Arpeggio::findAndAttachToChords()
      81              : {
      82          233 :     const track_idx_t strack = track();
      83          233 :     const Chord* _chord = chord();
      84          233 :     const Part* part = _chord->part();
      85          233 :     const track_idx_t btrack = part->trackRange().endTrack;
      86              : 
      87          233 :     if (strack + m_span > btrack) {
      88            0 :         rebaseEndAnchor(AnchorRebaseDirection::UP);
      89              :     }
      90              : 
      91          233 :     const track_idx_t etrack = strack + (m_span - 1);
      92          233 :     track_idx_t lastTrack = strack;
      93          614 :     for (track_idx_t track = strack; track <= etrack; track++) {
      94          381 :         EngravingItem* e = _chord->segment()->element(track);
      95          381 :         if (e && e->isChord()) {
      96          311 :             toChord(e)->undoChangeSpanArpeggio(this);
      97          311 :             lastTrack = track;
      98              :         }
      99              :     }
     100              : 
     101          233 :     if (lastTrack != etrack) {
     102            0 :         track_idx_t newSpan = lastTrack - strack + 1;
     103            0 :         undoChangeProperty(Pid::ARPEGGIO_SPAN, newSpan);
     104              :     }
     105          233 : }
     106              : 
     107            0 : void Arpeggio::detachFromChords(track_idx_t strack, track_idx_t etrack)
     108              : {
     109            0 :     Chord* _chord = chord();
     110            0 :     if (!_chord) {
     111            0 :         return;
     112              :     }
     113            0 :     for (track_idx_t track = strack; track <= etrack; track++) {
     114            0 :         EngravingItem* e = _chord->segment()->element(track);
     115            0 :         if (e && e->isChord() && toChord(e)->spanArpeggio() == this) {
     116            0 :             toChord(e)->undoChangeSpanArpeggio(nullptr);
     117              :         }
     118              :     }
     119              : }
     120              : 
     121            1 : void Arpeggio::rebaseStartAnchor(AnchorRebaseDirection direction)
     122              : {
     123            1 :     if (direction == AnchorRebaseDirection::UP) {
     124              :         // Move arpeggio to chord above
     125            0 :         Staff* s = staff();
     126            0 :         Part* part = s->part();
     127            0 :         track_idx_t topTrack = part->trackRange().startTrack;
     128            0 :         if (track() > topTrack) {
     129              :             // Loop through voices til we find a chord
     130            0 :             for (int curTrack = static_cast<int>(track()) - 1; curTrack >= static_cast<int>(topTrack); curTrack--) {
     131            0 :                 EngravingItem* e = chord()->segment()->element(curTrack);
     132            0 :                 if (e && e->isChord()) {
     133            0 :                     track_idx_t newSpan = m_span + track() - curTrack;
     134            0 :                     if (newSpan != 0) {
     135            0 :                         undoChangeProperty(Pid::ARPEGGIO_SPAN, newSpan);
     136            0 :                         score()->undoChangeParent(this, e, e->staffIdx());
     137            0 :                         break;
     138              :                     }
     139              :                 }
     140              :             }
     141              :         }
     142            1 :     } else if (direction == AnchorRebaseDirection::DOWN) {
     143              :         // Move arpeggio to chord below
     144            1 :         for (track_idx_t curTrack = track() + 1; curTrack <= track() + m_span - 1; curTrack++) {
     145            0 :             EngravingItem* e = chord()->segment()->element(curTrack);
     146            0 :             if (e && e->isChord()) {
     147            0 :                 track_idx_t newSpan = m_span + track() - curTrack;
     148            0 :                 if (newSpan != 0) {
     149            0 :                     chord()->undoChangeSpanArpeggio(nullptr);
     150            0 :                     undoChangeProperty(Pid::ARPEGGIO_SPAN, newSpan);
     151            0 :                     score()->undoChangeParent(this, e, e->staffIdx());
     152            0 :                     break;
     153              :                 }
     154              :             }
     155              :         }
     156              :     }
     157            1 : }
     158              : 
     159            0 : void Arpeggio::rebaseEndAnchor(AnchorRebaseDirection direction)
     160              : {
     161            0 :     if (direction == AnchorRebaseDirection::UP) {
     162              :         // Move end to chord above
     163            0 :         for (int curTrack = static_cast<int>(track()) + m_span - 2; curTrack >= static_cast<int>(track()); curTrack--) {
     164            0 :             EngravingItem* e = chord()->segment()->element(curTrack);
     165            0 :             if (e && e->isChord()) {
     166            0 :                 track_idx_t newSpan = curTrack - track() + 1;
     167            0 :                 if (newSpan != 0) {
     168            0 :                     EngravingItem* oldEndChord = chord()->segment()->element(endTrack());
     169            0 :                     if (oldEndChord && oldEndChord->isChord()) {
     170            0 :                         toChord(oldEndChord)->undoChangeSpanArpeggio(nullptr);
     171              :                     }
     172            0 :                     undoChangeProperty(Pid::ARPEGGIO_SPAN, newSpan);
     173              :                 }
     174            0 :                 break;
     175              :             }
     176              :         }
     177            0 :     } else if (direction == AnchorRebaseDirection::DOWN) {
     178              :         // Move end to chord below
     179            0 :         Staff* s = staff();
     180            0 :         Part* part = s->part();
     181            0 :         track_idx_t btrack = part->trackRange().endTrack;
     182            0 :         if (track() + m_span < btrack) {
     183              :             // Loop through voices til we find a chord
     184            0 :             for (track_idx_t curTrack = track() + m_span; curTrack < btrack; curTrack++) {
     185            0 :                 EngravingItem* e = chord()->segment()->element(curTrack);
     186            0 :                 if (e && e->isChord()) {
     187            0 :                     track_idx_t newSpan = curTrack - track() + 1;
     188            0 :                     toChord(e)->undoChangeSpanArpeggio(this);
     189            0 :                     undoChangeProperty(Pid::ARPEGGIO_SPAN, newSpan);
     190            0 :                     break;
     191              :                 }
     192              :             }
     193              :         }
     194              :     }
     195            0 : }
     196              : 
     197              : //---------------------------------------------------------
     198              : //   gripsPositions
     199              : //---------------------------------------------------------
     200              : 
     201            0 : std::vector<PointF> Arpeggio::gripsPositions(const EditData&) const
     202              : {
     203            0 :     const LayoutData* ldata = this->ldata();
     204            0 :     const PointF pp(pagePos());
     205            0 :     PointF p1(ldata->bbox().width() / 2, ldata->bbox().top());
     206            0 :     PointF p2(ldata->bbox().width() / 2, ldata->bbox().bottom());
     207            0 :     PointF p3(ldata->bbox().center());
     208            0 :     return { p1 + pp, p2 + pp, p3 + pp };
     209              : }
     210              : 
     211              : //---------------------------------------------------------
     212              : //   dragGrip
     213              : //---------------------------------------------------------
     214              : 
     215            0 : void Arpeggio::dragGrip(EditData& ed)
     216              : {
     217            0 :     if (ed.curGrip == Grip::MIDDLE) {
     218            0 :         setOffset(offset() + ed.delta);
     219            0 :         triggerLayout();
     220            0 :         return;
     221              :     }
     222              : 
     223            0 :     PointF pos = PointF(chord()->canvasPos().x(), ed.pos.y());
     224            0 :     EngravingItem* e = ed.view()->elementNear(pos);
     225              : 
     226            0 :     if (ed.curGrip == Grip::START) {
     227            0 :         m_userLen1 -= ed.delta.y();
     228            0 :         if (e && e->isNote() && e->part() == part()) {
     229            0 :             Chord* c = toNote(e)->chord();
     230            0 :             int newSpan = std::max(1, m_span + int(track()) - int(c->track()));
     231            0 :             if (track() != c->track()) {
     232              :                 // if new track is greater than old we have chords to unmark
     233            0 :                 if (track() < c->track()) {
     234            0 :                     detachFromChords(track(), c->track() - 1);
     235              :                 }
     236            0 :                 undoChangeProperty(Pid::ARPEGGIO_SPAN, newSpan);
     237            0 :                 score()->undoChangeParent(this, c, c->staffIdx());
     238            0 :                 m_userLen1 = 0.0;
     239              :             }
     240              :         }
     241            0 :     } else if (ed.curGrip == Grip::END) {
     242            0 :         m_userLen2 += ed.delta.y();
     243              :         // Increase span
     244            0 :         if (e && e->isNote() && e->part() == part()) {
     245            0 :             int newSpan = std::max(1, int(e->track()) - int(track()) + 1);
     246            0 :             if (e->track() != endTrack()) {
     247              :                 // if new endTrack is less than old we have chords to unmark
     248            0 :                 if (e->track() < endTrack()) {
     249            0 :                     detachFromChords(e->track() + 1, endTrack());
     250              :                 }
     251            0 :                 undoChangeProperty(Pid::ARPEGGIO_SPAN, newSpan);
     252            0 :                 m_userLen2 = 0.0;
     253              :             }
     254              :         }
     255              :     } else {
     256            0 :         UNREACHABLE;
     257              :         return;
     258              :     }
     259              : 
     260            0 :     triggerLayout();
     261              : }
     262              : 
     263              : //---------------------------------------------------------
     264              : //   dragAnchorLines
     265              : //---------------------------------------------------------
     266              : 
     267            0 : std::vector<LineF> Arpeggio::dragAnchorLines() const
     268              : {
     269            0 :     std::vector<LineF> result;
     270              : 
     271            0 :     Chord* c = chord();
     272            0 :     if (c) {
     273            0 :         result.push_back(LineF(canvasPos(), c->upNote()->canvasPos()));
     274              :     }
     275            0 :     return std::vector<LineF>();
     276            0 : }
     277              : 
     278              : //---------------------------------------------------------
     279              : //   gripAnchorLines
     280              : //---------------------------------------------------------
     281              : 
     282            0 : std::vector<LineF> Arpeggio::gripAnchorLines(Grip grip) const
     283              : {
     284            0 :     std::vector<LineF> result;
     285              : 
     286            0 :     const int gripIndex = static_cast<int>(grip);
     287            0 :     if (gripIndex >= gripsCount() || grip == Grip::MIDDLE) {
     288            0 :         return result;
     289              :     }
     290              : 
     291            0 :     Chord* _chord = chord();
     292            0 :     if (!_chord) {
     293            0 :         return result;
     294              :     }
     295              : 
     296            0 :     const Page* p = toPage(findAncestor(ElementType::PAGE));
     297            0 :     const PointF pageOffset = p ? p->pos() : PointF();
     298            0 :     const PointF gripCanvasPos = gripsPositions().at(gripIndex) + pageOffset;
     299              : 
     300            0 :     if (grip == Grip::START) {
     301            0 :         Note* upNote = _chord->upNote();
     302            0 :         EngravingItem* e = _chord->segment()->element(track());
     303            0 :         if (e && e->isChord()) {
     304            0 :             upNote = toChord(e)->upNote();
     305              :         }
     306            0 :         result.push_back(LineF(upNote->canvasPos(), gripCanvasPos));
     307            0 :     } else if (grip == Grip::END) {
     308            0 :         Note* downNote = _chord->downNote();
     309            0 :         EngravingItem* e = _chord->segment()->element(track() + m_span - 1);
     310            0 :         if (e && e->isChord()) {
     311            0 :             downNote = toChord(e)->downNote();
     312              :         }
     313              : 
     314            0 :         result.push_back(LineF(downNote->canvasPos(), gripCanvasPos));
     315              :     }
     316            0 :     return result;
     317            0 : }
     318              : 
     319              : //---------------------------------------------------------
     320              : //   startEdit
     321              : //---------------------------------------------------------
     322              : 
     323            0 : void Arpeggio::startEdit(EditData& ed)
     324              : {
     325            0 :     EngravingItem::startEdit(ed);
     326            0 :     ElementEditDataPtr eed = ed.getData(this);
     327            0 :     eed->pushProperty(Pid::ARP_USER_LEN1);
     328            0 :     eed->pushProperty(Pid::ARP_USER_LEN2);
     329            0 : }
     330              : 
     331            0 : void Arpeggio::startDragGrip(EditData& ed)
     332              : {
     333            0 :     EngravingItem::startDragGrip(ed);
     334            0 :     ElementEditDataPtr eed = ed.getData(this);
     335            0 :     if (!eed) {
     336            0 :         return;
     337              :     }
     338            0 :     eed->pushProperty(Pid::ARP_USER_LEN1);
     339            0 :     eed->pushProperty(Pid::ARP_USER_LEN2);
     340            0 : }
     341              : 
     342            0 : bool Arpeggio::isEditAllowed(EditData& ed) const
     343              : {
     344            0 :     if ((ed.curGrip != Grip::END && ed.curGrip != Grip::START) || !(ed.modifiers & ShiftModifier)) {
     345            0 :         return false;
     346              :     }
     347              : 
     348            0 :     return ed.key == Key_Down || ed.key == Key_Up;
     349              : }
     350              : 
     351              : //---------------------------------------------------------
     352              : //   edit
     353              : //---------------------------------------------------------
     354              : 
     355            0 : bool Arpeggio::edit(EditData& ed)
     356              : {
     357            0 :     if (!isEditAllowed(ed)) {
     358            0 :         return false;
     359              :     }
     360              : 
     361            0 :     if (ed.curGrip == Grip::START) {
     362            0 :         if (ed.key == Key_Down) {
     363            0 :             rebaseStartAnchor(AnchorRebaseDirection::DOWN);
     364            0 :         } else if (ed.key == Key_Up) {
     365            0 :             rebaseStartAnchor(AnchorRebaseDirection::UP);
     366              :         }
     367              :     }
     368              : 
     369            0 :     if (ed.curGrip == Grip::END) {
     370            0 :         if (ed.key == Key_Down) {
     371            0 :             rebaseEndAnchor(AnchorRebaseDirection::DOWN);
     372            0 :         } else if (ed.key == Key_Up) {
     373            0 :             if (m_span > 1) {
     374            0 :                 rebaseEndAnchor(AnchorRebaseDirection::UP);
     375              :             }
     376              :         }
     377              :     }
     378              : 
     379            0 :     triggerLayout();
     380              : 
     381            0 :     return true;
     382              : }
     383              : 
     384              : //---------------------------------------------------------
     385              : //   spatiumChanged
     386              : //---------------------------------------------------------
     387              : 
     388            0 : void Arpeggio::spatiumChanged(double oldValue, double newValue)
     389              : {
     390            0 :     m_userLen1 *= (newValue / oldValue);
     391            0 :     m_userLen2 *= (newValue / oldValue);
     392            0 : }
     393              : 
     394              : //---------------------------------------------------------
     395              : //   acceptDrop
     396              : //---------------------------------------------------------
     397              : 
     398            0 : bool Arpeggio::acceptDrop(EditData& data) const
     399              : {
     400            0 :     return data.dropElement->type() == ElementType::ARPEGGIO || data.dropElement->type() == ElementType::CHORD_BRACKET;
     401              : }
     402              : 
     403              : //---------------------------------------------------------
     404              : //   drop
     405              : //---------------------------------------------------------
     406              : 
     407            0 : EngravingItem* Arpeggio::drop(Transaction&, EditData& data)
     408              : {
     409            0 :     EngravingItem* e = data.dropElement;
     410            0 :     switch (e->type()) {
     411            0 :     case ElementType::ARPEGGIO:
     412              :     case ElementType::CHORD_BRACKET:
     413              :     {
     414            0 :         Arpeggio* a = toArpeggio(e);
     415            0 :         if (ownershipParent()) {
     416            0 :             score()->undoRemoveElement(this);
     417              :         }
     418            0 :         a->setTrack(track());
     419            0 :         a->setOwnershipParent(ownershipParent());
     420            0 :         score()->undoAddElement(a);
     421              :     }
     422            0 :         return e;
     423            0 :     default:
     424            0 :         delete e;
     425            0 :         break;
     426              :     }
     427            0 :     return 0;
     428              : }
     429              : 
     430              : //---------------------------------------------------------
     431              : //   reset
     432              : //---------------------------------------------------------
     433              : 
     434            0 : void Arpeggio::reset()
     435              : {
     436            0 :     undoChangeProperty(Pid::ARP_USER_LEN1, 0.0);
     437            0 :     undoChangeProperty(Pid::ARP_USER_LEN2, 0.0);
     438            0 :     EngravingItem::reset();
     439            0 : }
     440              : 
     441          431 : bool Arpeggio::crossStaff() const
     442              : {
     443          431 :     return (track() + span() - 1) / VOICES != vStaffIdx();
     444              : }
     445              : 
     446         2390 : staff_idx_t Arpeggio::vStaffIdx() const
     447              : {
     448         2390 :     return chord() ? chord()->vStaffIdx() : EngravingItem::vStaffIdx();
     449              : }
     450              : 
     451              : //---------------------------------------------------------
     452              : //   getProperty
     453              : //---------------------------------------------------------
     454              : 
     455         1369 : engraving::PropertyValue Arpeggio::getProperty(Pid propertyId) const
     456              : {
     457         1369 :     switch (propertyId) {
     458          114 :     case Pid::ARPEGGIO_TYPE:
     459          114 :         return int(m_arpeggioType);
     460          114 :     case Pid::TIME_STRETCH:
     461          114 :         return Stretch();
     462            0 :     case Pid::ARP_USER_LEN1:
     463            0 :         return userLen1();
     464            0 :     case Pid::ARP_USER_LEN2:
     465            0 :         return userLen2();
     466          114 :     case Pid::PLAY:
     467          114 :         return m_playArpeggio;
     468            0 :     case Pid::ARPEGGIO_SPAN:
     469            0 :         return m_span;
     470         1027 :     default:
     471         1027 :         break;
     472              :     }
     473         1027 :     return EngravingItem::getProperty(propertyId);
     474              : }
     475              : 
     476              : //---------------------------------------------------------
     477              : //   setProperty
     478              : //---------------------------------------------------------
     479              : 
     480            1 : bool Arpeggio::setProperty(Pid propertyId, const engraving::PropertyValue& val)
     481              : {
     482            1 :     switch (propertyId) {
     483            0 :     case Pid::ARPEGGIO_TYPE:
     484            0 :         setArpeggioType(ArpeggioType(val.toInt()));
     485            0 :         break;
     486            0 :     case Pid::TIME_STRETCH:
     487            0 :         setStretch(val.toDouble());
     488            0 :         break;
     489            0 :     case Pid::ARP_USER_LEN1:
     490            0 :         setUserLen1(val.toDouble());
     491            0 :         break;
     492            0 :     case Pid::ARP_USER_LEN2:
     493            0 :         setUserLen2(val.toDouble());
     494            0 :         break;
     495            0 :     case Pid::PLAY:
     496            0 :         setPlayArpeggio(val.toBool());
     497            0 :         break;
     498            0 :     case Pid::ARPEGGIO_SPAN:
     499            0 :         setSpan(val.toInt());
     500            0 :         break;
     501            1 :     default:
     502            1 :         if (!EngravingItem::setProperty(propertyId, val)) {
     503            0 :             return false;
     504              :         }
     505            1 :         break;
     506              :     }
     507            1 :     triggerLayout();
     508            1 :     return true;
     509              : }
     510              : 
     511              : //---------------------------------------------------------
     512              : //   propertyDefault
     513              : //---------------------------------------------------------
     514              : 
     515         1368 : engraving::PropertyValue Arpeggio::propertyDefault(Pid propertyId) const
     516              : {
     517         1368 :     switch (propertyId) {
     518            0 :     case Pid::ARP_USER_LEN1:
     519            0 :         return 0.0;
     520            0 :     case Pid::ARP_USER_LEN2:
     521            0 :         return 0.0;
     522          114 :     case Pid::TIME_STRETCH:
     523          114 :         return 1.0;
     524          114 :     case Pid::PLAY:
     525          114 :         return true;
     526            0 :     case Pid::ARPEGGIO_SPAN:
     527            0 :         return 1;
     528         1140 :     default:
     529         1140 :         break;
     530              :     }
     531         1140 :     return EngravingItem::propertyDefault(propertyId);
     532              : }
     533              : 
     534              : //---------------------------------------------------------
     535              : //   subtypeUserName
     536              : //---------------------------------------------------------
     537              : 
     538            1 : muse::TranslatableString Arpeggio::subtypeUserName() const
     539              : {
     540            1 :     return arpeggioTypeName();
     541              : }
        

Generated by: LCOV version 2.0-1