LCOV - code coverage report
Current view: top level - src/engraving/dom - ornament.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 70.3 % 296 208
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 "ornament.h"
      24              : 
      25              : #include "accidental.h"
      26              : #include "chord.h"
      27              : #include "engravingitem.h"
      28              : #include "factory.h"
      29              : #include "key.h"
      30              : #include "note.h"
      31              : #include "score.h"
      32              : #include "shape.h"
      33              : #include "staff.h"
      34              : #include "utils.h"
      35              : 
      36              : #include "editing/editchord.h"
      37              : #include "editing/editparentheses.h"
      38              : #include "editing/transaction/transaction.h"
      39              : #include "editing/transpose.h"
      40              : 
      41              : using namespace mu::engraving;
      42              : 
      43          281 : Ornament::Ornament(DummyParentOr<ChordRest> parent)
      44          281 :     : Articulation(parent, ElementType::ORNAMENT)
      45              : {
      46          281 :     _intervalAbove = DEFAULT_ORNAMENT_INTERVAL;
      47          281 :     _intervalBelow = DEFAULT_ORNAMENT_INTERVAL;
      48          281 :     _showAccidental = OrnamentShowAccidental::DEFAULT;
      49          281 :     _startOnUpperNote = false;
      50          281 : }
      51              : 
      52            4 : Ornament::Ornament(const Ornament& o)
      53            4 :     : Articulation(o)
      54              : {
      55            4 :     _intervalAbove = o._intervalAbove;
      56            4 :     _intervalBelow = o._intervalBelow;
      57            4 :     _showAccidental = o._showAccidental;
      58            4 :     _startOnUpperNote = o._startOnUpperNote;
      59            4 :     m_showCueNote = o.m_showCueNote;
      60              : 
      61            4 :     if (o.m_cueNoteChord) {
      62            4 :         m_cueNoteChord = o.m_cueNoteChord->clone();
      63              :     }
      64              : 
      65           24 :     for (size_t i = 0; i < m_accidentalsAboveAndBelow.size(); ++i) {
      66            8 :         Accidental* oldAccidental = o.m_accidentalsAboveAndBelow[i];
      67            8 :         if (!oldAccidental) {
      68            8 :             continue;
      69              :         }
      70            0 :         Accidental* newAccidental = oldAccidental->clone();
      71            0 :         newAccidental->setOwnershipParent(this);
      72            0 :         m_accidentalsAboveAndBelow[i] = newAccidental;
      73              :     }
      74            4 : }
      75              : 
      76          538 : Ornament::~Ornament()
      77              : {
      78          807 :     std::fill(std::begin(m_notesAboveAndBelow), std::end(m_notesAboveAndBelow), nullptr);
      79          807 :     std::fill(std::begin(m_accidentalsAboveAndBelow), std::end(m_accidentalsAboveAndBelow), nullptr);
      80              : 
      81          269 :     if (m_cueNoteChord && m_cueNoteChord->notes().size()) {
      82            7 :         m_cueNoteChord->notes().clear();
      83              :     }
      84              : 
      85          269 :     m_cueNoteChord = nullptr;
      86          538 : }
      87              : 
      88            0 : void Ornament::remove(EngravingItem* e)
      89              : {
      90            0 :     if (e->isAccidental()) {
      91            0 :         for (Accidental*& acc : m_accidentalsAboveAndBelow) {
      92            0 :             if (e == acc) {
      93            0 :                 acc = nullptr;
      94            0 :                 e->removed();
      95              :             }
      96              :         }
      97              :     }
      98            0 : }
      99              : 
     100            0 : muse::TranslatableString Ornament::typeUserName() const
     101              : {
     102            0 :     if (textType() != ArticulationTextType::NO_TEXT) {
     103            0 :         return TranslatableString("engraving", "Ornament text");
     104              :     }
     105              : 
     106            0 :     return TranslatableString("engraving", "Ornament");
     107              : }
     108              : 
     109          518 : void Ornament::setTrack(track_idx_t val)
     110              : {
     111         1554 :     for (Note* note : m_notesAboveAndBelow) {
     112         1036 :         if (note) {
     113           12 :             note->setTrack(val);
     114              :         }
     115              :     }
     116          518 :     if (m_cueNoteChord) {
     117           14 :         m_cueNoteChord->setTrack(val);
     118              :     }
     119          518 :     m_track = val;
     120          518 : }
     121              : 
     122           12 : void Ornament::scanElements(std::function<void(EngravingItem*)> func)
     123              : {
     124           12 :     func(this);
     125           36 :     for (Accidental* accidental : m_accidentalsAboveAndBelow) {
     126           24 :         if (accidental) {
     127            2 :             func(accidental);
     128              :         }
     129              :     }
     130           12 :     if (m_cueNoteChord) {
     131            0 :         m_cueNoteChord->scanElements(func);
     132              :     }
     133           12 : }
     134              : 
     135         2502 : PropertyValue Ornament::getProperty(Pid propertyId) const
     136              : {
     137         2502 :     switch (propertyId) {
     138          146 :     case Pid::INTERVAL_ABOVE:
     139          146 :         return _intervalAbove;
     140          146 :     case Pid::INTERVAL_BELOW:
     141          146 :         return _intervalBelow;
     142          146 :     case Pid::ORNAMENT_SHOW_ACCIDENTAL:
     143          146 :         return _showAccidental;
     144          146 :     case Pid::ORNAMENT_SHOW_CUE_NOTE:
     145          146 :         return m_showCueNote;
     146          146 :     case Pid::START_ON_UPPER_NOTE:
     147          146 :         return _startOnUpperNote;
     148         1772 :     default:
     149         1772 :         return Articulation::getProperty(propertyId);
     150              :     }
     151              : }
     152              : 
     153          152 : Sid Ornament::getPropertyStyle(Pid propertyId) const
     154              : {
     155          152 :     switch (propertyId) {
     156            3 :     case Pid::ARTICULATION_ANCHOR:
     157            3 :         return Sid::articulationAnchorDefault;
     158          149 :     default:
     159          149 :         return Articulation::getPropertyStyle(propertyId);
     160              :     }
     161              : }
     162              : 
     163         2789 : PropertyValue Ornament::propertyDefault(Pid id) const
     164              : {
     165         2789 :     switch (id) {
     166          292 :     case Pid::INTERVAL_ABOVE:
     167              :     case Pid::INTERVAL_BELOW:
     168          292 :         return DEFAULT_ORNAMENT_INTERVAL;
     169          146 :     case Pid::ORNAMENT_SHOW_ACCIDENTAL:
     170          292 :         return OrnamentShowAccidental::DEFAULT;
     171          146 :     case Pid::ORNAMENT_SHOW_CUE_NOTE:
     172          292 :         return AutoOnOff::AUTO;
     173          146 :     case Pid::START_ON_UPPER_NOTE:
     174          146 :         return false;
     175          307 :     case Pid::ARTICULATION_ANCHOR:
     176          307 :         return static_cast<int>(ArticulationAnchor::AUTO);
     177         1752 :     default:
     178         1752 :         return Articulation::propertyDefault(id);
     179              :     }
     180              : }
     181              : 
     182          153 : bool Ornament::setProperty(Pid propertyId, const PropertyValue& v)
     183              : {
     184          153 :     switch (propertyId) {
     185           19 :     case Pid::INTERVAL_ABOVE:
     186           19 :         setIntervalAbove(v.value<OrnamentInterval>());
     187           19 :         break;
     188            8 :     case Pid::INTERVAL_BELOW:
     189            8 :         setIntervalBelow(v.value<OrnamentInterval>());
     190            8 :         break;
     191            0 :     case Pid::ORNAMENT_SHOW_ACCIDENTAL:
     192            0 :         setShowAccidental(v.value<OrnamentShowAccidental>());
     193            0 :         break;
     194            0 :     case Pid::ORNAMENT_SHOW_CUE_NOTE:
     195            0 :         setShowCueNote(v.value<AutoOnOff>());
     196            0 :         break;
     197            0 :     case Pid::START_ON_UPPER_NOTE:
     198            0 :         setStartOnUpperNote(v.toBool());
     199            0 :         break;
     200          126 :     default:
     201          126 :         return Articulation::setProperty(propertyId, v);
     202              :     }
     203           27 :     triggerLayout();
     204           27 :     return true;
     205              : }
     206              : 
     207          916 : bool Ornament::hasIntervalAbove() const
     208              : {
     209          916 :     SymId id = symId();
     210              :     return id == SymId::ornamentTurnInverted
     211          846 :            || id == SymId::ornamentTrill
     212          554 :            || id == SymId::ornamentTurnSlash
     213          544 :            || id == SymId::ornamentTurn
     214          452 :            || id == SymId::ornamentShortTrill
     215          382 :            || id == SymId::ornamentTremblement
     216          352 :            || id == SymId::ornamentPrallMordent
     217          322 :            || id == SymId::ornamentUpPrall
     218          292 :            || id == SymId::ornamentPrecompMordentUpperPrefix
     219          262 :            || id == SymId::ornamentUpMordent
     220         1762 :            || id == SymId::ornamentPrallUp;
     221              : }
     222              : 
     223          916 : bool Ornament::hasIntervalBelow() const
     224              : {
     225          916 :     SymId id = symId();
     226              :     return id == SymId::ornamentTurnInverted
     227          846 :            || id == SymId::ornamentMordent
     228          766 :            || id == SymId::ornamentTurnSlash
     229          756 :            || id == SymId::ornamentTurn
     230          664 :            || id == SymId::ornamentTremblement
     231          634 :            || id == SymId::ornamentPrallMordent
     232          604 :            || id == SymId::ornamentDownMordent
     233          574 :            || id == SymId::ornamentPrallDown
     234          554 :            || id == SymId::ornamentPrallMordent
     235         1762 :            || id == SymId::ornamentPrallMordent;
     236              : }
     237              : 
     238         1648 : bool Ornament::hasFullIntervalChoice() const
     239              : {
     240         1648 :     SymId id = symId();
     241         1648 :     return id == SymId::ornamentTrill;
     242              : }
     243              : 
     244         1648 : bool Ornament::showCueNote()
     245              : {
     246         1648 :     if (m_showCueNote == AutoOnOff::AUTO) {
     247         1648 :         return (hasFullIntervalChoice() && style().styleB(Sid::trillAlwaysShowCueNote)) || _intervalAbove.step != IntervalStep::SECOND;
     248              :     }
     249              : 
     250            0 :     return m_showCueNote == AutoOnOff::ON;
     251              : }
     252              : 
     253          421 : void Ornament::computeNotesAboveAndBelow(AccidentalState* accState)
     254              : {
     255          421 :     ChordRest* parentChordRest = chordRest();
     256          421 :     Chord* parentChord = parentChordRest && parentChordRest->isChord() ? toChord(parentChordRest) : nullptr;
     257              : 
     258          421 :     const Note* mainNote = parentChord ? parentChord->upNote() : nullptr;
     259              : 
     260          421 :     if (!mainNote) {
     261            0 :         return;
     262              :     }
     263              : 
     264          421 :     if (m_cueNoteChord && !m_cueNoteChord->ownershipParent()) {
     265            7 :         m_cueNoteChord->setOwnershipParent(parentChord->segment());
     266              :     }
     267              : 
     268         2526 :     for (size_t i = 0; i < m_notesAboveAndBelow.size(); ++i) {
     269          842 :         bool above = (i == 0);
     270          842 :         bool hasIntAbove = hasIntervalAbove();
     271          842 :         bool hasIntBelow = hasIntervalBelow();
     272              : 
     273          842 :         if ((above && !hasIntAbove) || (!above && !hasIntBelow)) {
     274          356 :             continue;
     275              :         }
     276              : 
     277          486 :         Note*& note = m_notesAboveAndBelow.at(i);
     278          486 :         if (!note && above && m_cueNoteChord) {
     279            1 :             note = m_cueNoteChord->upNote();
     280              :         }
     281              : 
     282          486 :         if (!note) {
     283          308 :             note = mainNote->clone();
     284          308 :             Tie* tie = note->tieFor();
     285          308 :             if (tie) {
     286            6 :                 score()->undoRemoveElement((EngravingItem*)tie);
     287              :             }
     288              :         } else {
     289          178 :             note->setTpc1(mainNote->tpc1());
     290          178 :             note->setTpc2(mainNote->tpc2());
     291          178 :             note->setPitch(mainNote->pitch());
     292              :         }
     293          486 :         note->setTrack(track());
     294              : 
     295          486 :         if (Accidental::isMicrotonal(note->accidentalType())) {
     296              :             // If mainNote has microtonal accidental, don't clone it to the ornament note because microtonal intervals are not supported.
     297            0 :             note->setAccidentalType(Accidental::value2subtype(tpc2alter(note->tpc())));
     298              :         }
     299              : 
     300          486 :         bool autoMode = (above && _intervalAbove.type == IntervalType::AUTO) || (!above && _intervalBelow.type == IntervalType::AUTO);
     301          486 :         if (autoMode) {
     302              :             // NOTE: In AUTO mode, the ornament note should match not only any alteration from the
     303              :             // key signature, but also any alteration present in the measure before this point.
     304          430 :             int intervalSteps = above ? static_cast<int>(_intervalAbove.step) : -static_cast<int>(_intervalBelow.step);
     305          430 :             note->transposeDiatonic(intervalSteps, false, true);
     306          430 :             if (m_trillOldCompatAccidental) {
     307            0 :                 mapOldTrillAccidental(note, mainNote);
     308            0 :                 m_trillOldCompatAccidental = nullptr;
     309              :             } else {
     310          430 :                 int pitchLine = absStep(note->tpc(), note->epitch());
     311          430 :                 AccidentalVal accidentalVal = accState->accidentalVal(pitchLine);
     312          430 :                 AccidentalVal noteAccidentalVal = tpc2alter(note->tpc());
     313          430 :                 int accidentalDiff = static_cast<int>(accidentalVal) - static_cast<int>(noteAccidentalVal);
     314          430 :                 note->transpose(Interval(0, accidentalDiff), true);
     315              :             }
     316              :         } else {
     317           56 :             Interval interval = Interval::fromOrnamentInterval(above ? _intervalAbove : _intervalBelow);
     318           56 :             if (!above) {
     319           15 :                 interval.flip();
     320              :             }
     321           56 :             note->transpose(interval, true);
     322              :         }
     323              : 
     324          486 :         AccidentalState copyOfAccState = *accState;
     325          486 :         note->updateAccidental(&copyOfAccState);
     326          486 :         if (note->accidental()) {
     327           55 :             int pitchLine = absStep(note->tpc(), note->epitch());
     328           55 :             accState->setForceRestateAccidental(pitchLine, true);
     329              :         }
     330              : 
     331          486 :         manageAccidentalVisibilityRules(note);
     332              :     }
     333              : 
     334          421 :     updateAccidentalsAboveAndBelow();
     335          421 :     updateCueNote();
     336              : }
     337              : 
     338          486 : void Ornament::manageAccidentalVisibilityRules(Note* note)
     339              : {
     340          486 :     if (_showAccidental == OrnamentShowAccidental::DEFAULT) {
     341          486 :         return;
     342              :     }
     343              : 
     344            0 :     AccidentalVal accidentalValue = tpc2alter(note->tpc());
     345            0 :     AccidentalType accidentalType = Accidental::value2subtype(accidentalValue);
     346            0 :     if (accidentalType == AccidentalType::NONE) {
     347            0 :         accidentalType = AccidentalType::NATURAL;
     348              :     }
     349            0 :     if (note->accidentalType() == accidentalType) {
     350            0 :         return;
     351              :     }
     352              : 
     353            0 :     bool show = false;
     354            0 :     if (_showAccidental == OrnamentShowAccidental::ALWAYS) {
     355            0 :         show = true;
     356            0 :     } else if (_showAccidental == OrnamentShowAccidental::ANY_ALTERATION) {
     357            0 :         Key key = staff()->key(tick());
     358            0 :         AccidentalState unalteredState;
     359            0 :         unalteredState.init(key);
     360            0 :         int pitchLine = absStep(note->tpc(), note->epitch() + note->ottaveCapoFret());
     361            0 :         AccidentalVal unalteredAccidentalVal = unalteredState.accidentalVal(pitchLine);
     362            0 :         show = accidentalValue != unalteredAccidentalVal;
     363              :     }
     364              : 
     365            0 :     if (show) {
     366            0 :         note->setAccidentalType(accidentalType);
     367            0 :         note->accidental()->setRole(AccidentalRole::AUTO);
     368              :     }
     369              : }
     370              : 
     371          421 : void Ornament::updateAccidentalsAboveAndBelow()
     372              : {
     373         2526 :     for (size_t i = 0; i < m_notesAboveAndBelow.size(); ++i) {
     374          842 :         Note* note = m_notesAboveAndBelow[i];
     375          842 :         Accidental* accidental = note ? note->accidental() : nullptr;
     376          842 :         Accidental*& curAccidental = m_accidentalsAboveAndBelow[i];
     377          842 :         if (showCueNote() || !accidental) {
     378          803 :             if (curAccidental) {
     379            0 :                 delete curAccidental;
     380            0 :                 curAccidental = nullptr;
     381              :             }
     382          803 :             continue;
     383              :         }
     384           39 :         if (accidental) {
     385           39 :             if (!curAccidental) {
     386            0 :                 curAccidental = accidental->clone();
     387            0 :                 curAccidental->setOwnershipParent(this);
     388            0 :                 curAccidental->setPlacement(i == 0 ? PlacementV::ABOVE : PlacementV::BELOW);
     389              :             } else {
     390           39 :                 curAccidental->setAccidentalType(accidental->accidentalType());
     391           39 :                 curAccidental->setRole(accidental->role());
     392              :             }
     393              :         }
     394              :     }
     395          421 : }
     396              : 
     397          421 : void Ornament::updateCueNote()
     398              : {
     399          421 :     ChordRest* parentChordRest = chordRest();
     400          421 :     Chord* parentChord = parentChordRest && parentChordRest->isChord() ? toChord(parentChordRest) : nullptr;
     401              : 
     402          421 :     if (!showCueNote()) {
     403          405 :         if (noteAbove() && ownershipParent()) {
     404          304 :             noteAbove()->setOwnershipParent(parentChord);
     405              :         }
     406          405 :         if (m_cueNoteChord) {
     407            0 :             m_cueNoteChord->notes().clear();
     408            0 :             delete m_cueNoteChord;
     409            0 :             m_cueNoteChord = nullptr;
     410              :         }
     411          405 :         return;
     412              :     }
     413              : 
     414           16 :     if (!parentChord) {
     415            0 :         return;
     416              :     }
     417              : 
     418           16 :     Note* cueNote = noteAbove();
     419              :     // If needed, create cue note
     420           16 :     if (!m_cueNoteChord) {
     421            0 :         m_cueNoteChord = Factory::createChord(parentChord->segment());
     422            0 :         m_cueNoteChord->setSmall(true);
     423            0 :         cueNote->setHeadType(NoteHeadType::HEAD_QUARTER);
     424            0 :         m_cueNoteChord->add(cueNote);
     425            0 :         cueNote->setOwnershipParent(m_cueNoteChord);
     426              : 
     427            0 :         Transaction& tx = score()->transactionManager()->currentOrDummyTransaction();
     428            0 :         EditParentheses::addParenthesesToNotes(tx, { cueNote });
     429              :     }
     430           16 :     m_cueNoteChord->setTrack(track());
     431           16 :     m_cueNoteChord->setOwnershipParent(parentChord->segment());
     432           16 :     m_cueNoteChord->setStaffMove(parentChord->staffMove());
     433           16 :     cueNote->updateLine();
     434           16 :     cueNote->setIsTrillCueNote(true);
     435              : }
     436              : 
     437           67 : SymId Ornament::fromTrillType(TrillType trillType)
     438              : {
     439           67 :     switch (trillType) {
     440           54 :     case TrillType::TRILL_LINE:
     441           54 :         return SymId::ornamentTrill;
     442            4 :     case TrillType::UPPRALL_LINE:
     443            4 :         return SymId::ornamentUpPrall;
     444            4 :     case TrillType::DOWNPRALL_LINE:
     445            4 :         return SymId::ornamentDownMordent;
     446            5 :     case TrillType::PRALLPRALL_LINE:
     447            5 :         return SymId::ornamentTrill;
     448            0 :     default:
     449            0 :         return SymId::noSym;
     450              :     }
     451              : }
     452              : 
     453            0 : void Ornament::mapOldTrillAccidental(Note* note, const Note* mainNote)
     454              : {
     455              :     // Compatibility with trills pre-4.1
     456            0 :     AccidentalVal oldCompatValue = Accidental::subtype2value(m_trillOldCompatAccidental->accidentalType());
     457            0 :     AccidentalVal noteAccidentalVal = tpc2alter(note->tpc());
     458            0 :     int accidentalDiff = static_cast<int>(oldCompatValue) - static_cast<int>(noteAccidentalVal);
     459            0 :     note->transpose(Interval(0, accidentalDiff), true);
     460            0 :     int semitones = note->pitch() - mainNote->pitch();
     461            0 :     switch (semitones) {
     462            0 :     case 0:
     463            0 :         _intervalAbove.type = IntervalType::DIMINISHED;
     464            0 :         break;
     465            0 :     case 1:
     466            0 :         _intervalAbove.type = IntervalType::MINOR;
     467            0 :         break;
     468            0 :     case 2:
     469            0 :         _intervalAbove.type = IntervalType::MAJOR;
     470            0 :         break;
     471            0 :     case 3:
     472            0 :         _intervalAbove.type = IntervalType::AUGMENTED;
     473            0 :         break;
     474            0 :     default:
     475            0 :         break;
     476              :     }
     477            0 : }
        

Generated by: LCOV version 2.0-1