LCOV - code coverage report
Current view: top level - src/engraving/dom - glissando.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 63.3 % 226 143
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              : /* TO DO:
      24              : - XML export
      25              : 
      26              : NICE-TO-HAVE TODO:
      27              : - draggable handles of glissando segments
      28              : - re-attachable glissando extrema (with [Shift]+arrows, use SlurSegment::edit()
      29              :       and SlurSegment::changeAnchor() in slur.cpp as models)
      30              : */
      31              : 
      32              : #include "glissando.h"
      33              : 
      34              : #include <cmath>
      35              : #include <algorithm>
      36              : 
      37              : #include "types/typesconv.h"
      38              : 
      39              : #include "chord.h"
      40              : #include "harppedaldiagram.h"
      41              : #include "measure.h"
      42              : #include "note.h"
      43              : #include "part.h"
      44              : #include "score.h"
      45              : #include "segment.h"
      46              : #include "staff.h"
      47              : #include "system.h"
      48              : #include "utils.h"
      49              : 
      50              : #include "log.h"
      51              : 
      52              : using namespace mu;
      53              : using namespace mu::engraving;
      54              : 
      55              : namespace mu::engraving {
      56              : static const ElementStyle glissandoElementStyle {
      57              :     { Sid::glissandoFontFace,    Pid::FONT_FACE },
      58              :     { Sid::glissandoFontSize,    Pid::FONT_SIZE },
      59              :     { Sid::glissandoFontStyle,   Pid::FONT_STYLE },
      60              :     { Sid::glissandoLineWidth,   Pid::LINE_WIDTH },
      61              :     { Sid::glissandoShowText,    Pid::GLISS_SHOW_TEXT },
      62              :     { Sid::glissandoText,        Pid::GLISS_TEXT },
      63              :     { Sid::glissandoStyle,       Pid::GLISS_STYLE },
      64              :     { Sid::glissandoLineStyle,   Pid::LINE_STYLE },
      65              :     { Sid::glissandoDashLineLen, Pid::DASH_LINE_LEN },
      66              :     { Sid::glissandoDashGapLen,  Pid::DASH_GAP_LEN },
      67              :     { Sid::glissandoType,        Pid::GLISS_TYPE }
      68              : };
      69              : 
      70              : //=========================================================
      71              : //   GlissandoSegment
      72              : //=========================================================
      73              : 
      74          160 : GlissandoSegment::GlissandoSegment(Glissando* sp)
      75          160 :     : LineSegment(ElementType::GLISSANDO_SEGMENT, sp, ElementFlag::MOVABLE)
      76              : {
      77          160 : }
      78              : 
      79              : //---------------------------------------------------------
      80              : //   propertyDelegate
      81              : //---------------------------------------------------------
      82              : 
      83          262 : EngravingObject* GlissandoSegment::propertyDelegate(Pid pid) const
      84              : {
      85          262 :     switch (pid) {
      86            0 :     case Pid::GLISS_TYPE:
      87              :     case Pid::GLISS_TEXT:
      88              :     case Pid::GLISS_SHOW_TEXT:
      89              :     case Pid::GLISS_STYLE:
      90              :     case Pid::GLISS_SHIFT:
      91              :     case Pid::GLISS_EASEIN:
      92              :     case Pid::GLISS_EASEOUT:
      93              :     case Pid::FONT_FACE:
      94              :     case Pid::FONT_SIZE:
      95              :     case Pid::FONT_STYLE:
      96              :     case Pid::LINE_WIDTH:
      97            0 :         return glissando();
      98          262 :     default:
      99          262 :         return LineSegment::propertyDelegate(pid);
     100              :     }
     101              : }
     102              : 
     103              : //=========================================================
     104              : //   Glissando
     105              : //=========================================================
     106              : 
     107          171 : Glissando::Glissando(DummyParentOr<EngravingItem> parent)
     108          171 :     : SLine(ElementType::GLISSANDO, parent, ElementFlag::MOVABLE)
     109              : {
     110          171 :     initElementStyle(&glissandoElementStyle);
     111              : 
     112              :     static const std::array<Pid, 4> propertiesToInitialise {
     113              :         Pid::GLISS_SHIFT,
     114              :         Pid::GLISS_EASEIN,
     115              :         Pid::GLISS_EASEOUT,
     116              :         Pid::DIAGONAL
     117              :     };
     118              : 
     119          855 :     for (const Pid& pid : propertiesToInitialise) {
     120          684 :         resetProperty(pid);
     121              :     }
     122          171 : }
     123              : 
     124            8 : Glissando::Glissando(const Glissando& g)
     125            8 :     : SLine(g)
     126              : {
     127            8 :     _text           = g._text;
     128            8 :     _fontFace       = g._fontFace;
     129            8 :     _fontSize       = g._fontSize;
     130            8 :     _glissandoType  = g._glissandoType;
     131            8 :     _glissandoStyle = g._glissandoStyle;
     132            8 :     _glissandoShift = g._glissandoShift;
     133            8 :     _easeIn         = g._easeIn;
     134            8 :     _easeOut        = g._easeOut;
     135            8 :     _showText       = g._showText;
     136            8 :     _fontStyle      = g._fontStyle;
     137            8 :     m_isHarpGliss   = g.m_isHarpGliss;
     138            8 : }
     139              : 
     140              : //---------------------------------------------------------
     141              : //   createLineSegment
     142              : //---------------------------------------------------------
     143              : 
     144          160 : LineSegment* Glissando::createLineSegment()
     145              : {
     146          160 :     GlissandoSegment* seg = new GlissandoSegment(this);
     147          160 :     seg->setTrack(track());
     148          160 :     seg->setColor(color());
     149          160 :     return seg;
     150              : }
     151              : 
     152         2104 : Sid Glissando::getPropertyStyle(Pid id) const
     153              : {
     154         2104 :     if (id == Pid::GLISS_STYLE) {
     155          279 :         return isHarpGliss().value_or(false) ? Sid::glissandoStyleHarp : Sid::glissandoStyle;
     156              :     }
     157              : 
     158         1825 :     return SLine::getPropertyStyle(id);
     159              : }
     160              : 
     161           10 : bool Glissando::pitchSteps(const Spanner* spanner, std::vector<int>& pitchOffsets)
     162              : {
     163           10 :     if (!spanner->endElement()->isNote()) {
     164            0 :         return false;
     165              :     }
     166           10 :     const Glissando* glissando = toGlissando(spanner);
     167           10 :     if (!glissando->playSpanner()) {
     168            0 :         return false;
     169              :     }
     170           10 :     GlissandoStyle glissandoStyle = glissando->glissandoStyle();
     171           10 :     if (glissandoStyle == GlissandoStyle::PORTAMENTO) {
     172            0 :         return false;
     173              :     }
     174              :     // only consider glissando connected to NOTE.
     175           10 :     const Note* noteStart = toNote(spanner->startElement());
     176           10 :     const Note* noteEnd = toNote(spanner->endElement());
     177           10 :     int pitchStart = noteStart->ppitch();
     178           10 :     int pitchEnd = noteEnd->ppitch();
     179           10 :     if (pitchEnd == pitchStart) {
     180            0 :         return false;
     181              :     }
     182           10 :     int direction = pitchEnd > pitchStart ? 1 : -1;
     183           10 :     pitchOffsets.clear();
     184           10 :     if (glissandoStyle == GlissandoStyle::DIATONIC) {
     185              :         // Obey harp pedal diagrams if on a harp staff
     186            2 :         if (glissando->isHarpGliss().value_or(false)) {
     187            2 :             HarpPedalDiagram* hd = spanner->originPart()->currentHarpDiagram(spanner->tick());
     188            2 :             std::set<int> playableTpcs = hd ? hd->playableTpcs() : std::set<int>({ 14, 16, 18, 13, 15, 17, 19 });
     189            2 :             std::vector<int> playablePitches;
     190            2 :             playablePitches.reserve(playableTpcs.size());
     191           16 :             for (int t : playableTpcs) {
     192           14 :                 playablePitches.push_back(tpc2pitch(t) % PITCH_DELTA_OCTAVE);
     193              :             }
     194              : 
     195              :             // Push starting note, then check for enharmonic on the next string.  If there is an enharmonic, 0 will be pushed back twice
     196            2 :             pitchOffsets.push_back(0);
     197            2 :             int en = noteStart->tpc() + TPC_DELTA_ENHARMONIC * -direction;
     198              :             // Harp pedalling will only have 1 flat or sharp
     199            2 :             if (en >= TPC_F_B && en <= TPC_B_S && playableTpcs.find(en) != playableTpcs.end()) {
     200            1 :                 pitchOffsets.push_back(0);
     201              :             }
     202              : 
     203           18 :             for (int p = pitchStart + direction; p != pitchEnd; p += direction) {
     204              :                 // Count times pitch occurs in harp pedalling - this accounts for enharmonics
     205           16 :                 int pitchOccurrences = std::count(playablePitches.begin(), playablePitches.end(), p % PITCH_DELTA_OCTAVE);
     206           16 :                 if (pitchOccurrences > 0) {
     207            7 :                     pitchOffsets.insert(pitchOffsets.end(), pitchOccurrences, p - pitchStart);
     208              :                 }
     209              :             }
     210              : 
     211              :             // Check for enharmonic at end, in correct direction
     212            2 :             en = noteEnd->tpc() + TPC_DELTA_ENHARMONIC * direction;
     213            2 :             if (en >= TPC_F_B && en <= TPC_B_S && playableTpcs.find(en) != playableTpcs.end()) {
     214            2 :                 pitchOffsets.push_back(pitchEnd - pitchStart);
     215              :             }
     216              : 
     217            2 :             return pitchOffsets.size() > 0;
     218            2 :         }
     219              : 
     220              :         // Regular diatonic mode
     221            0 :         int lineStart = noteStart->line();
     222              :         // scale obeying accidentals
     223            0 :         for (int line = lineStart, pitch = pitchStart; (direction == 1) ? (pitch < pitchEnd) : (pitch > pitchEnd); line -= direction) {
     224            0 :             int halfSteps = chromaticPitchSteps(noteStart, noteEnd, lineStart - line);
     225            0 :             pitch = pitchStart + halfSteps;
     226            0 :             if ((direction == 1) ? (pitch < pitchEnd) : (pitch > pitchEnd)) {
     227            0 :                 pitchOffsets.push_back(halfSteps);
     228              :             }
     229              :         }
     230            0 :         return pitchOffsets.size() > 0;
     231              :     }
     232            8 :     if (glissandoStyle == GlissandoStyle::CHROMATIC) {
     233           41 :         for (int pitch = pitchStart; pitch != pitchEnd; pitch += direction) {
     234           33 :             pitchOffsets.push_back(pitch - pitchStart);
     235              :         }
     236            8 :         return true;
     237              :     }
     238            0 :     static const std::vector<bool> whiteNotes = { true, false, true, false, true, true, false, true, false, true, false, true };
     239            0 :     int Cnote = 60;   // pitch of middle C
     240            0 :     bool notePick = glissandoStyle == GlissandoStyle::WHITE_KEYS;
     241            0 :     for (int pitch = pitchStart; pitch != pitchEnd; pitch += direction) {
     242            0 :         int idx = ((pitch - Cnote) + 1200) % 12;
     243            0 :         if (whiteNotes[idx] == notePick) {
     244            0 :             pitchOffsets.push_back(pitch - pitchStart);
     245              :         }
     246              :     }
     247            0 :     return true;
     248              : }
     249              : 
     250              : //---------------------------------------------------------
     251              : //   STATIC FUNCTIONS: guessInitialNote
     252              : //
     253              : //    Used while reading old scores (either 1.x or transitional 2.0) to determine (guess!)
     254              : //    the glissando initial note from its final chord. Returns the top note of previous chord
     255              : //    of the same instrument, preferring the chord in the same track as chord, if it exists.
     256              : //
     257              : //    CANNOT be called if the final chord and/or its segment do not exist yet in the score
     258              : //
     259              : //    Parameter:  chord: the chord this glissando ends into
     260              : //    Returns:    the top note in a suitable previous chord or nullptr if none found.
     261              : //---------------------------------------------------------
     262              : 
     263            0 : Note* Glissando::guessInitialNote(Chord* chord)
     264              : {
     265            0 :     switch (chord->noteType()) {
     266              : //            case NoteType::INVALID:
     267              : //                  return 0;
     268              :     // for grace notes before, previous chord is previous chord of parent chord
     269            0 :     case NoteType::ACCIACCATURA:
     270              :     case NoteType::APPOGGIATURA:
     271              :     case NoteType::GRACE4:
     272              :     case NoteType::GRACE16:
     273              :     case NoteType::GRACE32:
     274              :         // move unto parent chord and proceed to standard case
     275            0 :         if (chord->ownershipParent() && chord->ownershipParent()->isChord()) {
     276            0 :             chord = toChord(chord->ownershipParent());
     277              :         } else {
     278            0 :             return 0;
     279              :         }
     280            0 :         break;
     281              :     // for grace notes after, return top note of parent chord
     282            0 :     case NoteType::GRACE8_AFTER:
     283              :     case NoteType::GRACE16_AFTER:
     284              :     case NoteType::GRACE32_AFTER:
     285            0 :         if (chord->ownershipParent() && chord->ownershipParent()->isChord()) {
     286            0 :             return toChord(chord->ownershipParent())->upNote();
     287              :         } else {                                // no parent or parent is not a chord?
     288            0 :             return nullptr;
     289              :         }
     290            0 :     case NoteType::NORMAL:
     291              :     {
     292              :         // if chord has grace notes before, the last one is the previous note
     293            0 :         std::vector<Chord*> graces = chord->graceNotesBefore();
     294            0 :         if (graces.size() > 0) {
     295            0 :             return graces.back()->upNote();
     296              :         }
     297            0 :     }
     298            0 :     break;                                      // else process to standard case
     299            0 :     default:
     300            0 :         break;
     301              :     }
     302              : 
     303              :     // standard case (NORMAL or grace before chord)
     304              : 
     305              :     // if parent not a segment, can't locate a target note
     306            0 :     if (!chord->ownershipParent()->isSegment()) {
     307            0 :         return 0;
     308              :     }
     309              : 
     310            0 :     track_idx_t chordTrack = chord->track();
     311            0 :     Segment* segm = chord->segment();
     312            0 :     Part* part = chord->part();
     313            0 :     if (segm != nullptr) {
     314            0 :         segm = segm->prev1();
     315              :     }
     316            0 :     while (segm) {
     317              :         // if previous segment is a ChordRest segment
     318            0 :         if (segm->segmentType() == SegmentType::ChordRest) {
     319            0 :             Chord* target = nullptr;
     320              :             // look for a Chord in the same track
     321            0 :             if (segm->element(chordTrack) && segm->element(chordTrack)->isChord()) {
     322            0 :                 target = toChord(segm->element(chordTrack));
     323              :             } else {                 // if no same track, look for other chords in the same instrument
     324            0 :                 for (EngravingItem* currChord : segm->elist()) {
     325            0 :                     if (currChord && currChord->isChord() && toChord(currChord)->part() == part) {
     326            0 :                         target = toChord(currChord);
     327            0 :                         break;
     328              :                     }
     329              :                 }
     330              :             }
     331              :             // if we found a target previous chord
     332            0 :             if (target) {
     333              :                 // if chord has grace notes after, the last one is the previous note
     334            0 :                 std::vector<Chord*> graces = target->graceNotesAfter();
     335            0 :                 if (graces.size() > 0) {
     336            0 :                     return graces.back()->upNote();
     337              :                 }
     338            0 :                 return target->upNote();              // if no grace after, return top note
     339            0 :             }
     340              :         }
     341            0 :         segm = segm->prev1();
     342              :     }
     343            0 :     LOGD("no first note for glissando found");
     344            0 :     return 0;
     345              : }
     346              : 
     347              : //---------------------------------------------------------
     348              : //   getProperty
     349              : //---------------------------------------------------------
     350              : 
     351         1015 : PropertyValue Glissando::getProperty(Pid propertyId) const
     352              : {
     353         1015 :     switch (propertyId) {
     354            0 :     case Pid::GLISS_TYPE:
     355            0 :         return glissandoType();
     356           13 :     case Pid::GLISS_TEXT:
     357           13 :         return text();
     358            0 :     case Pid::GLISS_SHOW_TEXT:
     359            0 :         return showText();
     360            1 :     case Pid::GLISS_STYLE:
     361            1 :         return glissandoStyle();
     362           77 :     case Pid::GLISS_SHIFT:
     363           77 :         return glissandoShift();
     364           77 :     case Pid::GLISS_EASEIN:
     365           77 :         return easeIn();
     366           77 :     case Pid::GLISS_EASEOUT:
     367           77 :         return easeOut();
     368            0 :     case Pid::FONT_FACE:
     369            0 :         return _fontFace;
     370            0 :     case Pid::FONT_SIZE:
     371            0 :         return _fontSize;
     372            0 :     case Pid::FONT_STYLE:
     373            0 :         return int(_fontStyle);
     374          770 :     default:
     375          770 :         break;
     376              :     }
     377          770 :     return SLine::getProperty(propertyId);
     378              : }
     379              : 
     380              : //---------------------------------------------------------
     381              : //   setProperty
     382              : //---------------------------------------------------------
     383              : 
     384         2875 : bool Glissando::setProperty(Pid propertyId, const PropertyValue& v)
     385              : {
     386         2875 :     switch (propertyId) {
     387          177 :     case Pid::GLISS_TYPE:
     388          177 :         setGlissandoType(v.value<GlissandoType>());
     389          177 :         break;
     390          248 :     case Pid::GLISS_TEXT:
     391          248 :         setText(v.value<String>());
     392          248 :         break;
     393          175 :     case Pid::GLISS_SHOW_TEXT:
     394          175 :         setShowText(v.toBool());
     395          175 :         break;
     396          271 :     case Pid::GLISS_STYLE:
     397              :     {
     398              :         // Make sure harp glisses can only be diatonic and chromatic
     399          271 :         GlissandoStyle glissStyle = v.value<GlissandoStyle>();
     400          271 :         if (isHarpGliss().value_or(false) && (glissStyle != GlissandoStyle::DIATONIC && glissStyle != GlissandoStyle::CHROMATIC)) {
     401            0 :             glissStyle = GlissandoStyle::DIATONIC;
     402              :         }
     403          271 :         setGlissandoStyle(glissStyle);
     404          271 :         break;
     405              :     }
     406          171 :     case Pid::GLISS_SHIFT:
     407          171 :         setGlissandoShift(v.toBool());
     408          171 :         break;
     409          171 :     case Pid::GLISS_EASEIN:
     410          171 :         setEaseIn(v.toInt());
     411          171 :         break;
     412          171 :     case Pid::GLISS_EASEOUT:
     413          171 :         setEaseOut(v.toInt());
     414          171 :         break;
     415          175 :     case Pid::FONT_FACE:
     416          175 :         setFontFace(v.value<String>());
     417          175 :         break;
     418          175 :     case Pid::FONT_SIZE:
     419          175 :         setFontSize(v.toReal());
     420          175 :         break;
     421          175 :     case Pid::FONT_STYLE:
     422          175 :         setFontStyle(FontStyle(v.toInt()));
     423          175 :         break;
     424          966 :     default:
     425          966 :         if (!SLine::setProperty(propertyId, v)) {
     426            0 :             return false;
     427              :         }
     428          966 :         break;
     429              :     }
     430         2875 :     triggerLayout();
     431         2875 :     return true;
     432              : }
     433              : 
     434              : //---------------------------------------------------------
     435              : //   propertyDefault
     436              : //---------------------------------------------------------
     437              : 
     438         1868 : PropertyValue Glissando::propertyDefault(Pid propertyId) const
     439              : {
     440         1868 :     switch (propertyId) {
     441            0 :     case Pid::GLISS_TYPE:
     442            0 :         return style().styleV(Sid::glissandoType);
     443            0 :     case Pid::GLISS_SHOW_TEXT:
     444            0 :         return true;
     445           93 :     case Pid::GLISS_STYLE:
     446           93 :         return style().styleV(getPropertyStyle(propertyId));
     447          248 :     case Pid::GLISS_SHIFT:
     448          248 :         return false;
     449          496 :     case Pid::GLISS_EASEIN:
     450              :     case Pid::GLISS_EASEOUT:
     451          496 :         return 0;
     452           90 :     case Pid::GLISS_TEXT:
     453           90 :         return style().styleV(Sid::glissandoText);
     454          171 :     case Pid::DIAGONAL:
     455          171 :         return true;
     456          770 :     default:
     457          770 :         break;
     458              :     }
     459          770 :     return SLine::propertyDefault(propertyId);
     460              : }
     461              : 
     462            0 : TranslatableString Glissando::subtypeUserName() const
     463              : {
     464            0 :     return TConv::userName(glissandoType());
     465              : }
     466              : }
        

Generated by: LCOV version 2.0-1