LCOV - code coverage report
Current view: top level - src/engraving/dom - shadownote.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 4.1 % 145 6
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 "shadownote.h"
      24              : 
      25              : #include "draw/types/pen.h"
      26              : 
      27              : #include "accidental.h"
      28              : #include "articulation.h"
      29              : #include "hook.h"
      30              : #include "measure.h"
      31              : #include "rest.h"
      32              : #include "score.h"
      33              : #include "stafftype.h"
      34              : 
      35              : using namespace mu;
      36              : using namespace muse::draw;
      37              : 
      38              : namespace mu::engraving {
      39              : //---------------------------------------------------------
      40              : //   ShadowNote
      41              : //---------------------------------------------------------
      42              : 
      43         2334 : ShadowNote::ShadowNote(Score* s)
      44         2334 :     : EngravingItem(ElementType::SHADOW_NOTE, s), m_noteheadSymbol(SymId::noSym)
      45              : {
      46         2334 :     m_lineIndex = 1000;
      47         2334 :     m_duration = TDuration(DurationType::V_INVALID);
      48         2334 :     m_isRest = false;
      49         2334 : }
      50              : 
      51            0 : bool ShadowNote::isValid() const
      52              : {
      53            0 :     return m_noteheadSymbol != SymId::noSym;
      54              : }
      55              : 
      56            0 : void ShadowNote::setState(SymId noteSymbol, TDuration duration, bool rest, bool beyondScore, AccidentalType accidentalType,
      57              :                           const std::set<SymId>& articulationIds)
      58              : {
      59            0 :     m_noteheadSymbol = noteSymbol;
      60            0 :     m_duration = duration;
      61            0 :     m_isRest = rest;
      62            0 :     m_beyondScore = beyondScore;
      63            0 :     m_accidentalType = accidentalType;
      64            0 :     m_articulationIds = articulationIds;
      65            0 : }
      66              : 
      67            0 : bool ShadowNote::hasStem() const
      68              : {
      69            0 :     return !m_isRest && m_duration.hasStem();
      70              : }
      71              : 
      72            0 : bool ShadowNote::hasFlag() const
      73              : {
      74            0 :     return hasStem() && m_duration.hooks() > 0;
      75              : }
      76              : 
      77            0 : SymId ShadowNote::flagSym() const
      78              : {
      79            0 :     if (!hasStem()) {
      80            0 :         return SymId::noSym;
      81              :     }
      82              : 
      83            0 :     bool straight = style().styleB(Sid::useStraightNoteFlags);
      84            0 :     int hooks = computeUp() ? m_duration.hooks() : -m_duration.hooks();
      85            0 :     return Hook::symIdForHookIndex(hooks, straight);
      86              : }
      87              : 
      88            0 : AccidentalType ShadowNote::accidentalType() const
      89              : {
      90            0 :     return m_accidentalType;
      91              : }
      92              : 
      93            0 : const std::set<SymId>& ShadowNote::articulationIds() const
      94              : {
      95            0 :     return m_articulationIds;
      96              : }
      97              : 
      98            0 : bool ShadowNote::ledgerLinesVisible() const
      99              : {
     100            0 :     return !m_isRest && m_lineIndex < 100 && m_lineIndex > -100;
     101              : }
     102              : 
     103              : //---------------------------------------------------------
     104              : //   computeUp
     105              : //---------------------------------------------------------
     106              : 
     107            0 : bool ShadowNote::computeUp() const
     108              : {
     109            0 :     const StaffType* staffType = this->staffType();
     110            0 :     bool tabStaff = staffType && staffType->isTabStaff();
     111              : 
     112            0 :     if (tabStaff && (staffType->stemless() || !staffType->stemThrough())) {
     113              :         // if TAB staff with no stems or stem beside staff
     114            0 :         return true;
     115              :     }
     116              : 
     117            0 :     if (score()->tick2measure(m_tick)->hasVoices(staffIdx()) || voice() != 0) {
     118            0 :         return !(voice() & 1);
     119              :     } else {
     120            0 :         return m_lineIndex > (staffType ? staffType->middleLine() : 4);
     121              :     }
     122              : }
     123              : 
     124              : // draw articulations, laying them out in a fashion similar to
     125              : // ChordLayout::layoutArticulations
     126            0 : void ShadowNote::drawArticulations(Painter* painter) const
     127              : {
     128              :     // Approximate bounding box of note, including stem, relative to notehead
     129            0 :     double noteheadWidth = symWidth(m_noteheadSymbol);
     130            0 :     double noteheadHeight = symHeight(m_noteheadSymbol);
     131            0 :     double ms = spatium();
     132            0 :     double x1 = noteheadWidth * .5 - (ms * mag());
     133            0 :     double x2 = x1 + 2 * ms * mag();
     134            0 :     bool up = !computeUp();
     135            0 :     double y01 = -noteheadHeight * .5;
     136            0 :     double y02 = -y01;
     137            0 :     if (hasStem()) {
     138              :         // Stem set at 3.5 spatium in SingleDraw::draw(const ShadowNote* item ...
     139            0 :         if (up) {
     140            0 :             y02 = ms * 3.5;
     141              :         } else {
     142            0 :             y01 = -ms * 3.5;
     143              :         }
     144              :     }
     145            0 :     RectF boundRect = RectF(PointF(x1, y01), PointF(x2, y02));
     146              : 
     147              :     // Place articulations kept close to the note first, near the notehead
     148              :     // Articulations close to the note are defined in Articulation::layoutCloseToNote
     149            0 :     bool wasStaccato = false; // keep track for special accent-staccato kerning
     150            0 :     SymId prevArticulation = mu::engraving::SymId::noSym;
     151            0 :     for (const SymId& artic: m_articulationIds) {
     152            0 :         String articName = Articulation::symId2ArticulationName(artic);
     153            0 :         if (articName == u"staccato") {
     154            0 :             wasStaccato = true;
     155            0 :             drawCloseArticulation(painter, artic, boundRect, up, prevArticulation);
     156            0 :             prevArticulation = artic;
     157              :         } else {
     158            0 :             if (articName == u"tenuto") {
     159            0 :                 drawCloseArticulation(painter, artic, boundRect, up, prevArticulation);
     160            0 :                 prevArticulation = artic;
     161              :             }
     162            0 :             wasStaccato = false;
     163              :         }
     164            0 :     }
     165              : 
     166              :     // Adjust bounding box to include the whole of the staff segment,
     167              :     // then place articulations not closeToNote
     168            0 :     ms *= .5;
     169            0 :     double y1 = -m_lineIndex * ms;
     170            0 :     double y2 = (this->staffType()->bottomLine() - m_lineIndex) * ms;
     171            0 :     if (boundRect.y() > y1) {
     172            0 :         boundRect.setTop(y1);
     173            0 :         if (up) {
     174            0 :             wasStaccato = false;
     175              :         }
     176              :     }
     177            0 :     if (boundRect.bottom() < y2) {
     178            0 :         boundRect.setBottom(y2);
     179            0 :         if (!up) {
     180            0 :             wasStaccato = false;
     181              :         }
     182              :     }
     183              : 
     184            0 :     for (const SymId& artic: m_articulationIds) {
     185            0 :         String articName = Articulation::symId2ArticulationName(artic);
     186            0 :         if (articName == u"staccato" || articName == u"tenuto") {
     187            0 :             continue;
     188              :         } else {
     189            0 :             if (articName.contains(u"marcato")) {
     190              :                 // Marcato always above staff
     191            0 :                 drawFarArticulation(painter, artic, boundRect, true, false);
     192            0 :             } else if (articName == u"accent") {
     193              :                 // Accents get special kerning if adjacent to staccato
     194            0 :                 drawFarArticulation(painter, artic, boundRect, up, wasStaccato);
     195              :             } else {
     196              :                 // Other articulations (e.g., laissez-vibrer)
     197            0 :                 drawFarArticulation(painter, artic, boundRect,  up, false);
     198              :             }
     199            0 :             wasStaccato = false;
     200              :         }
     201            0 :     }
     202            0 : }
     203              : 
     204            0 : void ShadowNote::drawCloseArticulation(muse::draw::Painter* painter, const SymId& artic, RectF& boundRect, bool up,
     205              :                                        const SymId& prevArticulation) const
     206              : {
     207            0 :     PointF coord;
     208            0 :     double spacing = spatium();
     209            0 :     SymId articSym = artic;
     210            0 :     double articHeight = symHeight(articSym);
     211              : 
     212              :     // Center the articulation on the note-head
     213            0 :     coord.rx() = boundRect.x() + (boundRect.width() - symWidth(artic)) / 2;
     214              : 
     215              :     // Place articulation above or below boundRect and adjust boundRect accordingly
     216            0 :     double minDist = 0.4 * spacing;
     217            0 :     if (up) {
     218              :         // If operating inside the staff, center articulation between lines
     219            0 :         if (m_lineIndex > 1) {
     220            0 :             coord.ry() = (articHeight - spacing * (3 - (m_lineIndex & 1))) / 2;
     221            0 :             if (prevArticulation != mu::engraving::SymId::noSym) {
     222            0 :                 if (m_lineIndex > 2) {
     223            0 :                     coord.ry() -= spacing;
     224              :                 } else {
     225            0 :                     coord.ry() -= minDist + symHeight(prevArticulation);
     226              :                 }
     227              :             }
     228              :         } else {
     229            0 :             double topY = boundRect.y();
     230            0 :             coord.ry() = topY - minDist;
     231              :         }
     232            0 :         boundRect.setTop(coord.ry() - articHeight);
     233              :     } else {
     234            0 :         if (m_lineIndex < this->staffType()->bottomLine() - 1) {
     235            0 :             coord.ry() = (articHeight + spacing * (3 - (m_lineIndex & 1))) / 2;
     236            0 :             if (prevArticulation != mu::engraving::SymId::noSym) {
     237            0 :                 if (m_lineIndex < this->staffType()->bottomLine() - 2) {
     238            0 :                     coord.ry() += spacing;
     239              :                 } else {
     240            0 :                     coord.ry() += minDist + symHeight(prevArticulation);
     241              :                 }
     242              :             }
     243              :         } else {
     244            0 :             double bottomY = boundRect.bottom();
     245            0 :             coord.ry() = bottomY + minDist + articHeight;
     246              :         }
     247            0 :         boundRect.setBottom(coord.ry());
     248              :     }
     249              : 
     250            0 :     drawSymbol(articSym, painter, coord);
     251            0 : }
     252              : 
     253            0 : void ShadowNote::drawFarArticulation(muse::draw::Painter* painter, const SymId& artic, RectF& boundRect, bool up,
     254              :                                      bool accentStaccatoKern) const
     255              : {
     256            0 :     PointF coord;
     257            0 :     double spacing = spatium();
     258            0 :     SymId articSym = artic;
     259            0 :     double articHeight = symHeight(articSym);
     260              : 
     261              :     // If a LaissezVibrer, place beside the note and widen the bounding box
     262            0 :     if (articSym == SymId::articLaissezVibrerAbove || articSym == SymId::articLaissezVibrerBelow) {
     263            0 :         articSym = up ? SymId::articLaissezVibrerAbove : SymId::articLaissezVibrerBelow;
     264            0 :         const int upDir = up ? -1 : 1;
     265            0 :         const double noteHeight = symHeight(m_noteheadSymbol);
     266              : 
     267            0 :         double center = 0.5 * symWidth(m_noteheadSymbol);
     268            0 :         double visualInset = 0.1 * spacing;
     269              : 
     270            0 :         coord.rx() = center + visualInset - symBbox(articSym).left();
     271            0 :         coord.ry() = (noteHeight / 2 + 0.2 * spacing) * upDir;
     272            0 :         boundRect.setWidth(boundRect.width() + symWidth(articSym));
     273            0 :     } else {
     274              :         // If on the stem side of a note (e.g., marcato on a low note), move artic over for stem,
     275              :         // else, center it on the notehead;
     276            0 :         coord.rx() = boundRect.x() + (boundRect.width() - symWidth(artic)) / 2;
     277            0 :         if (up == computeUp() && hasStem()) {
     278            0 :             coord.rx() += 0.25 * symWidth(m_noteheadSymbol);
     279              :         }
     280              : 
     281              :         // Place articulation above or below boundRect and adjust boundRect accordingly
     282            0 :         if (up) {
     283            0 :             double topY = boundRect.y();
     284            0 :             coord.ry() = topY - (accentStaccatoKern ? 0.2 : 0.4) * spacing;
     285            0 :             boundRect.setTop(coord.ry() - articHeight);
     286              :         } else {
     287            0 :             double bottomY = boundRect.bottom();
     288            0 :             coord.ry() = bottomY + (accentStaccatoKern ? 0.2 : 0.4) * spacing + articHeight;
     289            0 :             boundRect.setBottom(coord.ry());
     290              :         }
     291              :     }
     292              : 
     293            0 :     drawSymbol(articSym, painter, coord);
     294            0 : }
     295              : }
        

Generated by: LCOV version 2.0-1